CTF.COM.UA получение флага PhParanoid (категория reverse)

Alexandr Vishniakov
Hackerstan CTF Team
7 min readNov 24, 2016

--

В задании предлагается некий файл, с расширением phb, ну и конечно же тест задания:

RU: Я настоящий параноик! Я прячу все от этого сумасшедшего мира! Я уже обфусцировал исходники своего калькулятора, сорцы javascript а на своем сайте, и я не намерен останавливаться! А ты никогда так и не узнаешь, что же я скрываю!

Ну что ж начнем! Скачиваем файл PhParanoid_b7fa460590b3dc2a7662dc0bb633a7d8.phb по ссылке и начинаем анализ.

Текст задания наводит на то, что нам предстоит разобраться с некой обфусцированной страницей, в данном случае случае - это php страница. Но при просмотре содержимого страницы, посредством hexviewer, замечаем сигнатуру компилятора, используемого для данной страницы.

Видим, что используется bcompiler v0.18 , значит код php скомпилирован и исполняется через прослойку, которая умеет выполнять такой байт-код. Но теперь встает вопрос, как же можно прочитать содержимое. Google как обычно в помощь, ищем информацию по такого рода компилятору для PHP. В ходе гугления, нахожу пост на StackOverflow.

На котором ответ, на вопрос о декомпиляции, заработал всего один голос, печально. В посте, ссылка на форум, в одном из постов которого люди поделились декомпилятром и похоже, что это то, что нужно.

Иду на форум и регистрируюсь. Перехожу по ссылке на пост и вижу описание ПО для декомпиляции. Но тут есть две версии декомпилятора, для Php 5.2 и Php 5.3. Чтобы решить, какой нужно скачать, нужно снова внимательно глянуть в hex содержимое phb файла.

Видим интересный путь, в значении которого, есть упоминание о версии php и это php5.2. Качаем версию для php5.2 - это ModeBIphp52ts.rar. Далее, нам нужна будет Windows. Благо у меня есть различные виртуалки c виндой и я выбрал виртуалку с Windows XP.

Скачиваем архив в виртуалке с WinXP, а также сам файл phb, для того чтобы декомпилировать его.

После скачивания архива с декомпилятором, распаковываем его и видим файлы представленные на скриншоте выше. У нас имеется версия php.exe интерпретатора PHP под Windows. В итоге, после нескольких проб и ошибок, понял как запускать декомпиляцию, при помощи следущей команды:

php.exe phpdc.phpr PhParanoid_b7fa460590b3dc2a7662dc0bb633a7d8.phb

Если не перенаправлять ввод в файл, то можно увидеть следующую картину в консоле:

Оххх, класс! Видим, что декомпилятор сработал отлично и перед нами обычный PHP код, который можно прочитать и выполнить. В итоге после декомпиляции, получаем следующий кусок кода страницы:

<?phpdo {
$is_secret_exists = false;
if (isset($secret)) {
$is_secret_exists = true;
}
else {
break;
}
$is_secret_valid = false;
if (strstr($secret, "The") && (strpos($secret, "The") == 0)) {
$is_secret_valid = true;
}
$c0 = chr(ord($secret[0]) + 20); if ((ord($c0) + (-20)) != 84) {
unset($"c0");
break;
}
$c1 = chr(ord($secret[1]) + (-52)); if ((ord($c1) + 52) != 104) {
unset($"c1");
break;
}
$c2 = chr(ord($secret[2]) + (-2)); if ((ord($c2) + 2) != 101) {
unset($"c2");
break;
}
$c3 = chr(ord($secret[3]) + (-7)); if ((ord($c3) + 7) != 114) {
unset($"c3");
break;
}
$c4 = chr(ord($secret[4]) + (-52)); if ((ord($c4) + 52) != 101) {
unset($"c4");
break;
}
$c5 = chr(ord($secret[5]) + 43); if ((ord($c5) + (-43)) != 73) {
unset($"c5");
break;
}
$c6 = chr(ord($secret[6]) + 8); if ((ord($c6) + (-8)) != 115) {
unset($"c6");
break;
}
$c7 = chr(ord($secret[7]) + 1); if ((ord($c7) + (-1)) != 78) {
unset($"c7");
break;
}
$c8 = chr(ord($secret[8]) + (-63)); if ((ord($c8) + 63) != 111) {
unset($"c8");
break;
}
$c9 = chr(ord($secret[9]) + 22); if ((ord($c9) + (-22)) != 82) {
unset($"c9");
break;
}
$c10 = chr(ord($secret[10]) + (-10)); if ((ord($c10) + 10) != 105) {
unset($"c10");
break;
}
$c11 = chr(ord($secret[11]) + (-55)); if ((ord($c11) + 55) != 103) {
unset($"c11");
break;
}
$c12 = chr(ord($secret[12]) + 8); if ((ord($c12) + (-8)) != 104) {
unset($"c12");
break;
}
$c13 = chr(ord($secret[13]) + (-49)); if ((ord($c13) + 49) != 116) {
unset($"c13");
break;
}
$c14 = chr(ord($secret[14]) + (-17)); if ((ord($c14) + 17) != 65) {
unset($"c14");
break;
}
$c15 = chr(ord($secret[15]) + (-42)); if ((ord($c15) + 42) != 110) {
unset($"c15");
break;
}
$c16 = chr(ord($secret[16]) + (-49)); if ((ord($c16) + 49) != 100) {
unset($"c16");
break;
}
$c17 = chr(ord($secret[17]) + (-34)); if ((ord($c17) + 34) != 87) {
unset($"c17");
break;
}
$c18 = chr(ord($secret[18]) + (-19)); if ((ord($c18) + 19) != 114) {
unset($"c18");
break;
}
$c19 = chr(ord($secret[19]) + (-40)); if ((ord($c19) + 40) != 111) {
unset($"c19");
break;
}
$c20 = chr(ord($secret[20]) + (-62)); if ((ord($c20) + 62) != 110) {
unset($"c20");
break;
}
$c21 = chr(ord($secret[21]) + 13); if ((ord($c21) + (-13)) != 103) {
unset($"c21");
break;
}
$c22 = chr(ord($secret[22]) + 49); if ((ord($c22) + (-49)) != 46) {
unset($"c22");
break;
}
$c23 = chr(ord($secret[23]) + (-35)); if ((ord($c23) + 35) != 84) {
unset($"c23");
break;
}
$c24 = chr(ord($secret[24]) + (-26)); if ((ord($c24) + 26) != 104) {
unset($"c24");
break;
}
$c25 = chr(ord($secret[25]) + (-48)); if ((ord($c25) + 48) != 101) {
unset($"c25");
break;
}
$c26 = chr(ord($secret[26]) + (-65)); if ((ord($c26) + 65) != 114) {
unset($"c26");
break;
}
$c27 = chr(ord($secret[27]) + (-33)); if ((ord($c27) + 33) != 101) {
unset($"c27");
break;
}
$c28 = chr(ord($secret[28]) + (-28)); if ((ord($c28) + 28) != 79) {
unset($"c28");
break;
}
$c29 = chr(ord($secret[29]) + (-15)); if ((ord($c29) + 15) != 110) {
unset($"c29");
break;
}
$c30 = chr(ord($secret[30]) + (-31)); if ((ord($c30) + 31) != 108) {
unset($"c30");
break;
}
$c31 = chr(ord($secret[31]) + (-16)); if ((ord($c31) + 16) != 121) {
unset($"c31");
break;
}
$c32 = chr(ord($secret[32]) + 8); if ((ord($c32) + (-8)) != 70) {
unset($"c32");
break;
}
$c33 = chr(ord($secret[33]) + (-66)); if ((ord($c33) + 66) != 117) {
unset($"c33");
break;
}
$c34 = chr(ord($secret[34]) + (-15)); if ((ord($c34) + 15) != 110) {
unset($"c34");
break;
}
$c35 = chr(ord($secret[35]) + (-12)); if ((ord($c35) + 12) != 65) {
unset($"c35");
break;
}
$c36 = chr(ord($secret[36]) + (-61)); if ((ord($c36) + 61) != 110) {
unset($"c36");
break;
}
$c37 = chr(ord($secret[37]) + (-33)); if ((ord($c37) + 33) != 100) {
unset($"c37");
break;
}
$c38 = chr(ord($secret[38]) + 9); if ((ord($c38) + (-9)) != 66) {
unset($"c38");
break;
}
$c39 = chr(ord($secret[39]) + (-16)); if ((ord($c39) + 16) != 111) {
unset($"c39");
break;
}
$c40 = chr(ord($secret[40]) + (-37)); if ((ord($c40) + 37) != 114) {
unset($"c40");
break;
}
$c41 = chr(ord($secret[41]) + (-56)); if ((ord($c41) + 56) != 105) {
unset($"c41");
break;
}
$c42 = chr(ord($secret[42]) + 0); if ((ord($c42) + 0) != 110) {
unset($"c42");
break;
}
$c43 = chr(ord($secret[43]) + (-35)); if ((ord($c43) + 35) != 103) {
unset($"c43");
break;
}
$c44 = chr(ord($secret[44]) + 79); if ((ord($c44) + (-79)) != 46) {
unset($"c44");
break;
}
} while (false);
?>

Но получив код, видно, что просто так нам не дадут флаг, организаторы CTF. Видим кучу if конструкций, из которых следует, что тут проверяется наличие некого секрета, который начинается с “The”.

Значит нам следует понять, как получить строку $secret . Из листинга кода видно что идет проверка кодов символов на соответствие определенному значению, к примеру if ((ord($c44) + (-79)) != 46) { . Значит, у нас имеются коды символов секрета! Так превратим же их в читаемую строку, при помощи bash скрипта и python.

Выполним вот эту команду python -c "print ''.join([chr(x) for x in $(echo "[$(cat decoded.txt | grep -o -P '(?<=\!\=).*(?=\))' | tr '\n' ',' | sed 's/,$//')]")])", которая парсит значения между != и ) , а затем при помощи python, получаем строку из кодов символов секрета. В итоге у нас получается значение секрета:

ThereIsNoRightAndWrong.ThereOnlyFunAndBoring.

Но это еще не флаг, а всего лишь секрет, которым проверяется значение каждого символа ключа. Теперь нам предстоит получить значение флага и тут снова приходит на помощь bash и python. Для этого нам нужно применить другой способ для парсинга, теперь нам нужны все значения в следующих строках $c41 = chr(ord($secret[41]) + (-56)), то есть нас интересует число -56, а также есть еще один вариант $c44 = chr(ord($secret[44]) + 79); где число +79 не в скобках. Но для этого, мы будем использовать другое выражение и будем искать все, что находится между ]) + и скобкой ) . В итоге получается следующее выражение:

python -c "print ''.join([chr(ord('ThereIsNoRightAndWrong.ThereOnlyFunAndBoring.'[i]) + x) for i, x in enumerate($(echo "[$(cat decoded.txt | grep -o -P '(?<=\]\)\s\+\s).*(?=\))' | tr -d '()' | tr '\n' ',' | sed 's/,$//')]"))])"

Таким образом, мы проходимся по каждому символу нашего секрета и производим операцию сложения, для каждого кода символа c числом, которое используется для проверки соответствия с секретом. Таким образом мы восстановим строку с флагом.

Урааа у нас получилось! Вот он — h4ck1t{O0h_0pC0D35_G0t_1N51D3_MiN3_51CK_M1nD} !!!

И таким образом получаем +250 поинтов к счету команды b1n4ry4rms в которой я играю в CTF!

--

--

«Переписывание с нуля гарантирует лишь одно — ноль!» — Мартин Фаулер