CTFzone write-ups – Shall I reverse it too?
Друзья, едва мы опубликовали решения на четыре задания из категории Reverse, как тут же от вас начали поступать вопросы насчет того, где же задание на 1000. Мы решили вас долго не томить и готовы представить свежий райтап :)
Как уже было сказано, таск на 1000 был одним из самых сложных заданий CTFzone, и он так и остался нерешенным. Поэтому, если вы не решили какие-то задания из этой ветки, рекомендуем для начала разобраться с ними вот тут, а уже потом приступать к заданию на максимальное количество очков. Удачи!
Reverse_1000. Black hole
A.U. R.O.R. A.: Lieutenant, look! Our pilot is downstairs, you just have to come down. There are three doors — with Earth, Moon and Sun — and one of them leads to the stairs. Choose and go, but be careful — two other doors lead to the black hole which are impossible to climb out of.
Решение:
Итак, в этом задании участникам необходимо было сделать выбор — найти верную дверь, чтобы добраться до пилота. Однако, цена ошибки велика — можно провалиться в черную дыру, так что нам стоит быть предельно осторожными.
Для начала запустим сам файл «reverse1000.exe» на исполнение и попробуем что-нибудь ввести:
В ответ на введенную строку »123456» выводится окно с сообщением «Access denied». Что же делать?
Попробуем посмотреть файл в программе Ida — Interactive Disassembler и поищем, где встречается вызов окна с сообщением «Access denied»:
Как видно на скриншоте, после запуска программы происходит вызов диалоговой функции DialogBoxParamW, куда в качестве аргумента lpDialogFunc передается указатель на функцию DialogFunc; в ней происходит получение и проверка ввода:
Итак, при проверке ввода вызывается функция sub_41a980 — назовем ее check_input.
Попробуем разобраться, что происходит внутри функции check_input.
Итак, мы видим, что происходит работа с некой переменной dword_45E124, похожей на некоторый глобальный контекст, с которым выполняется дальнейшая работа. Назовем эту переменную cntx.
Также переименуем переменную this в InputString. Видно, что выполняется конвертация входной строки, получение ее длины и передача в функцию sub_412100. Прежде чем идти дальше, проверим, где еще может использоваться cntx:
Оказывается, cntx также используется в функции WinMain. Посмотрим, что там:
Действительно, выполняется инициализация глобального контекста. Внимание может привлечь переменная unk_44E11D, указывающая на массив смещений к различным строкам:
Поищем в Google, может быть используется какая-нибудь стандартная библиотека. Найденные строки «getmetatable», «getupvalue», «setmetatable» приводят к документации по языку «Lua», на это же указывают многие другие строки в исполняемом файле программы. Есть основания подозревать, что для проверки введенной строки используется «Lua — интерпретатор». Однако при просмотре в HEX — редакторе исполняемого файла программы исходного кода на «Lua» на первый взгляд не видно. Вероятно, он зашифрован или закодирован.
В документации по «Lua», сказано, что для исполнения кода в интерпретаторе Lua необходимо:
- Инициализировать Lua с помощью luaL_newstate (), возвращающей контекст Lua:
LUALIB_API lua_State *luaL_newstate (void) { lua_State *L = lua_newstate(l_alloc, NULL); if (L) lua_atpanic(L, &panic); return L; }
- Использовать функции luaL_load () или luaL_loadbuffer (), загружающие исходный код или скомпилированные куски кода Lua.
- Вызвать функцию luaL_openlibs () для инициализации встроенных библиотек Lua.
- После этого можно вызывать Lua — код с помощью функции lua_pcall ().
Вот тут можно более подробно ознакомиться с языком Lua.
Далее, исследовав функцию WinMain, мы видим вызов luaL_load (), принимающей на вход статический буфер содержащий код (переменная compressed_code):
Однако, если перейти по адресу 0044E778, то никакого Lua — кода (даже скомпилированного), там нет. Судя по всему, функция luaL_load () изменена для того, чтобы загружать сжатый или зашифрованный Lua — код. В этом случае есть два варианта: мы можем попытаться получить этот буфер после распаковки в отладчике или попробовать выяснить, чего означает сигнатура в начале буфера:
Выясниться, что 5D 00 00 10 — это заголовок архивов LZMA. Воспользуемся программой 7 — Zip, чтобы распаковать этот блок данных:
Действительно, теперь этот блок данных похож на скомпилированный код на Lua. Попробуем использовать декомпилятор, например, luadec. Однако при попытке декомпиляции декомпилятор выдает ошибку. Приглядимся повнимательнее: заголовок файла начинается с CA FE BA BE, в то время как заголовок скомпилированного Lua байткода начинается с 1B 4C 75 61. Поменяем заголовок файла в HEX — редакторе:
И еще раз запустим декомпилятор:
Ура! Файл успешно декомпилирован. В результате видно, что в функции «a» производится вызов некоторой функции «string.a», из встроенной библиотеки Lua String Library, и проверяется возвращаемое ей значение. Если оно равно строке некоторой константной строке, то возвращается значение 1, иначе возвращается значение 0.
Теперь нам нужно найти функцию «a» в библиотеке Lua String. Эта функция должна находится где-то рядом с именами и указателями на остальные функции Lua String Library, о которых можно узнать в документации.
Поищем в IDA строку «gfind» (так называется одна из стандартных функций Lua String Library, благодаря имени ее несложно найти):
Вот она! И все остальные имена функций для работы со строками здесь же. Перейдем теперь к строке почти в самом низу списка, по адресу 0045D03, с именем «a» — искомой функции. Определим перекрестные ссылки:
Перейдем к массиву смещений на названия функций и сами функции:
После этого перейдем к телу функции sub_4124F0. Как видно, здесь происходит шифрование введенной строки на ключе «starforcestrikesback». Зашифрованная таким образом строка затем сравнивается с шифрованной строкой, находящейся внутри Lua — скрипта:
Зная алгоритм, мы можем расшифровать строку из Lua — кода:
Пример программы на языке C, предназначенной для расшифровки искомой строки:
#include
#include
#include
int main(int argc, char *argv[]) {
unsigned char ciphertext[36] = {
0x10, 0x63, 0x73, 0x6E, 0x73, 0x6E, 0x79, 0x7D, 0x2E, 0x33, 0x37, 0x2F,
0x58, 0x03, 0x3A, 0x28, 0x0E, 0x6E, 0x03, 0x3F, 0x48, 0x49, 0x37, 0x3F,
0x40, 0x54, 0x26, 0x63, 0x27, 0x34, 0x1E, 0x7E, 0x0B, 0x28, 0x26, 0x3F
};
int len = sizeof(ciphertext);
unsigned char key[] = "starforcestrikesback";
int keylen = strlen(key);
char *plaintext = malloc(len + 1);
memset(plaintext, 0, len + 1);
plaintext[0] = ciphertext[0] ^ key[0];
for (int i = 1; i < len; i++) {
plaintext[i] = ciphertext[i] ^ plaintext[i - 1] ^ key[i % keylen];
}
for(int i = 0; i < len; i++)
printf("%c", plaintext[i]);
puts("\n");
return 0;
}
Скомпилировав указанную выше программу и запустив ее на исполнение, получим искомую строку: ctfzone{0p3n_7h3_P0d_b4y_d00r5_S1r1}.
Теперь введем эту строку в поле ввода программы и нажав «Ok», увидим подтверждение того, что введенная строка верна:
Вот и все!
Ответ: ctfzone{0p3n_7h3_P0d_b4y_d00r5_S1r1}
В этом задании используется довольно редкий язык, но в остальном все вполне реально, не правда ли? Если у вас остались вопросы, оставляйте комментарии и добавляйтесь в наш чат в Telegram.
Также не забывайте про задания на нашем портале. Проявите себя до 15.12, и, возможно, на следующем CTFzone вы будете уже не игроком ;).