canyoucrackit riddle

December 4, 2011

Вчера на ночь глядя получил от Антоши ссылку на загадку. Обычно я таким даже не пытаюсь заниматься, ибо сложные :].

Но в этот раз я проявил настойчивость и хакнул его. Попробуйте разломать это чудо сами. Даже я осилил!.

Дальше идёт один большой спойлер :].

Первая стадия.

Всё начинается с PNG файла на главной странице. eb, e8 и 90 подсказали, что это шестнадцатиричный дамп 32-битного или 16-битного варианта IA-32 инструкций.

Самый простой вариант это дело проверить - вбить первых пару байт в любимый редактор с дизассемблером. Я кроме hteditor ничего не знаю, так что юзаем его :].

00000000 eb04                           jmp         0x6
00000002 af                             scasd
00000003 c2bfa3                         ret         a3bf
00000006 81ec00010000                   sub         esp, 0x100
0000000c 31c9                           xor         ecx, ecx
0000000e 880c0c                         mov         [esp+ecx], cl
00000011 fec1                           inc         cl
00000013 75f9                           jnz         0xe
00000015 31c0                           xor         eax, eax
00000017 baefbeadde                     mov         edx, deadbeef

Первый прыжок пропускает что-то странное, а дальше идёт (немножко понятный!) код.

Я понял, что забивать все 160 символов вручную мне будет лень и я загуглил первых 10 байт. Я тут-же получил текстовую форму представления на картинке и написал прожку на “C”, чтобы ее можно было изучать и патчить в hteditor.

// gcc -m32 -O2 -fomit-frame-pointer -nostdlib stage1.c -o stage1
static char pseudostack[1024 + 256];
int _start()
{
    void * stack_bottom = (void*)&pseudostack[sizeof (pseudostack)];
    asm volatile("movl %0, %%esp" : : "r"(stack_bottom) : "esp", "memory");
    asm volatile(".byte 0xeb, 0x04, 0xaf, 0xc2, 0xbf, 0xa3, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00, 0x31, 0xc9, 0x88, 0x0c\n");
    asm volatile(".byte 0x0c, 0xfe, 0xc1, 0x75, 0xf9, 0x31, 0xc0, 0xba, 0xef, 0xbe, 0xad, 0xde, 0x02, 0x04, 0x0c, 0x00\n");
    asm volatile(".byte 0xd0, 0xc1, 0xca, 0x08, 0x8a, 0x1c, 0x0c, 0x8a, 0x3c, 0x04, 0x88, 0x1c, 0x04, 0x88, 0x3c, 0x0c\n");
    asm volatile(".byte 0xfe, 0xc1, 0x75, 0xe8, 0xe9, 0x5c, 0x00, 0x00, 0x00, 0x89, 0xe3, 0x81, 0xc3, 0x04, 0x00, 0x00\n");
    asm volatile(".byte 0x00, 0x5c, 0x58, 0x3d, 0x41, 0x41, 0x41, 0x41, 0x75, 0x43, 0x58, 0x3d, 0x42, 0x42, 0x42, 0x42\n");
    asm volatile(".byte 0x75, 0x3b, 0x5a, 0x89, 0xd1, 0x89, 0xe6, 0x89, 0xdf, 0x29, 0xcf, 0xf3, 0xa4, 0x89, 0xde, 0x89\n");
    asm volatile(".byte 0xd1, 0x89, 0xdf, 0x29, 0xcf, 0x31, 0xc0, 0x31, 0xdb, 0x31, 0xd2, 0xfe, 0xc0, 0x02, 0x1c, 0x06\n");
    asm volatile(".byte 0x8a, 0x14, 0x06, 0x8a, 0x34, 0x1e, 0x88, 0x34, 0x06, 0x88, 0x14, 0x1e, 0x00, 0xf2, 0x30, 0xf6\n");
    asm volatile(".byte 0x8a, 0x1c, 0x16, 0x8a, 0x17, 0x30, 0xda, 0x88, 0x17, 0x47, 0x49, 0x75, 0xde, 0x31, 0xdb, 0x89\n");
    asm volatile(".byte 0xd8, 0xfe, 0xc0, 0xcd, 0x80, 0x90, 0x90, 0xe8, 0x9d, 0xff, 0xff, 0xff, 0x41, 0x41, 0x41, 0x41\n");
    return 0;
}

Немного черной магии для установки esp в статическую область памяти (чтобы было прощее ее исследовать). Дальше до запуска опасного бинаря я решил изучить всю программу целиком (а вдруг там троян? :]). Дальше я буду приводить код с адресами в ELF файле (я не осилил заставить hteditor переименовывать метки в обычных бинарных данных).

Прога состоит из нескольких частей:

80480c7 ! eb04                             jmp         skip_something
80480c9   af                               scasd
80480ca   c2bfa3                           ret         0a3bfh

Пропуск какого-то подозрительного мусора.

....... ! skip_something:                 ;xref j80480c7
....... ! 81ec00010000                     sub         esp, 100h
80480d3 ! 31c9                             xor         ecx, ecx
80480d5 !
....... ! init_table:                     ;xref j80480da
....... ! 880c0c                           mov         [esp+ecx], cl
80480d8 ! fec1                             inc         cl
80480da ! 75f9                             jnz         init_table

Выделение на стеке 256 байт и инициализация их значениями 0x00-0xFF.

80480dc ! 31c0                             xor         eax, eax
80480de ! baefbeadde                       mov         edx, 0deadbeefh
80480e3 !
....... ! permutate_table:                ;xref j80480f9
....... ! 02040c                           add         al, [esp+ecx]
80480e6 ! 00d0                             add         al, dl
80480e8 ! c1ca08                           ror         edx, 8
80480eb ! 8a1c0c                           mov         bl, [esp+ecx]
80480ee ! 8a3c04                           mov         bh, [esp+eax]
80480f1 ! 881c04                           mov         [esp+eax], bl
80480f4 ! 883c0c                           mov         [esp+ecx], bh
80480f7 ! fec1                             inc         cl
80480f9 ! 75e8                             jnz         permutate_table
80480fb ! e95c000000                       jmp         trampoline

Перестановка некоторых байт местами. Первый байт - cl, второй выбирается по ключу 0xdeadbeef и add/ror. Главное, что код пока пасётся в своих 256 байтах, никуда не вылзит.

Последняя инструкция уводит нас в конец кода (я назвал его trampoline). Рассмотрим сначала его:

....... ! trampoline:                     ;xref j80480fb
....... ! 90                               nop
804815d ! 90                               nop
804815e ! e89dffffff                       call        body
8048163 ! 41414141                         dd          41414141h

Из необычного: впервые используется call (и только однажды!). Вернуться ему пока некуда (дальше только конец программы).

Первая часть body - очень интересная.

....... ! body:                           ;xref c804815e
....... ! 89e3                             mov         ebx, esp
8048102 ! 81c304000000                     add         ebx, 4
8048108 ! 5c                               pop         esp
8048109 ! 58                               pop         eax ; 1
804810a ! 3d41414141                       cmp         eax, 41414141h
804810f ! 7543                             jnz         bad_signature
8048111 ! 58                               pop         eax ; 2
8048112 ! 3d42424242                       cmp         eax, 42424242h
8048117 ! 753b                             jnz         bad_signature
8048119 ! 5a                               pop         edx
804811a ! 89d1                             mov         ecx, edx
804811c ! 89e6                             mov         esi, esp
804811e ! 89df                             mov         edi, ebx
8048120 ! 29cf                             sub         edi, ecx
8048122 ! f3a4                             repz movsb
8048124 ! 89de                             mov         esi, ebx
8048126 ! 89d1                             mov         ecx, edx
8048128 ! 89df                             mov         edi, ebx
804812a ! 29cf                             sub         edi, ecx
804812c ! 31c0                             xor         eax, eax
804812e ! 31db                             xor         ebx, ebx
8048130 ! 31d2                             xor         edx, edx

Здесь со стека восстанавливается адрес возврата (который указывает на 0x41414141) и сохраняется в esp. По этому адресу последовательно считываются:

Уже весело! Код привязался к данным в сегменте кода, которых нет на картинке. Посмотрим, что делает остальной код.

....... ! decrypt_input:                  ;xref j8048152
....... ! fec0                             inc         al
8048134 ! 021c06                           add         bl, [esi+eax]
8048137 ! 8a1406                           mov         dl, [esi+eax]
804813a ! 8a341e                           mov         dh, [esi+ebx]
804813d ! 883406                           mov         [esi+eax], dh
8048140 ! 88141e                           mov         [esi+ebx], dl
8048143 ! 00f2                             add         dl, dh
8048145 ! 30f6                             xor         dh, dh
8048147 ! 8a1c16                           mov         bl, [esi+edx]
804814a ! 8a17                             mov         dl, [edi]
804814c ! 30da                             xor         dl, bl
804814e ! 8817                             mov         [edi], dl
8048150 ! 47                               inc         edi
8048151 ! 49                               dec         ecx
8048152 ! 75de                             jnz         decrypt_input

Опять видим xor и перестановку байт свежевычитанного блока с нашей таблицей.

....... ! bad_signature:                  ;xref j804810f j8048117
....... ! 31db                             xor         ebx, ebx
8048156 ! 89d8                             mov         eax, ebx
8048158 ! fec0                             inc         al
804815a ! cd80                             int         80h

eax = 1; int 0x80 - это системный вызов exit в linux.

Выводы:

Тут я пошел спать, так как не знал где взять данные на расшифровку. Утром меня осенило: неспроста код выложен на картинке (а не в текстовом или бинарном виде). Заглянув в нее текстовым редактором в глаза бросается base64 строка в секции коментариев:

QkJCQjIAAACR2PFtcCA6q2eaC8SR+8dmD/zNzLQC+td3tFQ4qx8O447TDeuZw5P+0SsbEcYR78jKLw==
$ printf "QkJCQjIAAACR2PFtcCA6q2eaC8SR+8dmD/zNzLQC+td3tFQ4qx8O447TDeuZw5P+0SsbEcYR78jKLw==" | base64 -d > stage1.data
$ hexdump -C stage1.sata
00000000  42 42 42 42 32 00 00 00  91 d8 f1 6d 70 20 3a ab  |BBBB2......mp :.|
00000010  67 9a 0b c4 91 fb c7 66  0f fc cd cc b4 02 fa d7  |g......f........|
00000020  77 b4 54 38 ab 1f 0e e3  8e d3 0d eb 99 c3 93 fe  |w.T8............|
00000030  d1 2b 1b 11 c6 11 ef c8  ca 2f                    |.+......./|
0000003a

Видим магическую сигнатуру 0x42424242 и судя по всему размер блока - 0x32 байта.

Дописываем этот блок в конец функции trampoline сразу после 0x41414141 и заменяем int 80 на int 3, чтобы в core dump увидеть чего там нарасшифровалось :]

// gcc -m32 -O2 -fomit-frame-pointer -nostdlib stage1.c -o stage1
static char pseudostack[1024 + 256];
int _start()
{
    void * stack_bottom = (void*)&pseudostack[sizeof (pseudostack)];
    asm volatile("movl %0, %%esp" : : "r"(stack_bottom) : "esp", "memory");
    asm volatile(".byte 0xeb, 0x04, 0xaf, 0xc2, 0xbf, 0xa3, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00, 0x31, 0xc9, 0x88, 0x0c\n");
    asm volatile(".byte 0x0c, 0xfe, 0xc1, 0x75, 0xf9, 0x31, 0xc0, 0xba, 0xef, 0xbe, 0xad, 0xde, 0x02, 0x04, 0x0c, 0x00\n");
    asm volatile(".byte 0xd0, 0xc1, 0xca, 0x08, 0x8a, 0x1c, 0x0c, 0x8a, 0x3c, 0x04, 0x88, 0x1c, 0x04, 0x88, 0x3c, 0x0c\n");
    asm volatile(".byte 0xfe, 0xc1, 0x75, 0xe8, 0xe9, 0x5c, 0x00, 0x00, 0x00, 0x89, 0xe3, 0x81, 0xc3, 0x04, 0x00, 0x00\n");
    asm volatile(".byte 0x00, 0x5c, 0x58, 0x3d, 0x41, 0x41, 0x41, 0x41, 0x75, 0x43, 0x58, 0x3d, 0x42, 0x42, 0x42, 0x42\n");
    asm volatile(".byte 0x75, 0x3b, 0x5a, 0x89, 0xd1, 0x89, 0xe6, 0x89, 0xdf, 0x29, 0xcf, 0xf3, 0xa4, 0x89, 0xde, 0x89\n");
    asm volatile(".byte 0xd1, 0x89, 0xdf, 0x29, 0xcf, 0x31, 0xc0, 0x31, 0xdb, 0x31, 0xd2, 0xfe, 0xc0, 0x02, 0x1c, 0x06\n");
    asm volatile(".byte 0x8a, 0x14, 0x06, 0x8a, 0x34, 0x1e, 0x88, 0x34, 0x06, 0x88, 0x14, 0x1e, 0x00, 0xf2, 0x30, 0xf6\n");
    asm volatile(".byte 0x8a, 0x1c, 0x16, 0x8a, 0x17, 0x30, 0xda, 0x88, 0x17, 0x47, 0x49, 0x75, 0xde, 0x31, 0xdb, 0x89\n");
    //                                          v - here it was 0x80
    asm volatile(".byte 0xd8, 0xfe, 0xc0, 0xcd, 0x03, 0x90, 0x90, 0xe8, 0x9d, 0xff, 0xff, 0xff, 0x41, 0x41, 0x41, 0x41\n");
    // base64-decoded from cyber.png
    asm volatile(".byte  0x42, 0x42, 0x42, 0x42, 0x32, 0x00, 0x00, 0x00, 0x91, 0xd8, 0xf1, 0x6d, 0x70, 0x20, 0x3a, 0xab\n");
    asm volatile(".byte  0x67, 0x9a, 0x0b, 0xc4, 0x91, 0xfb, 0xc7, 0x66, 0x0f, 0xfc, 0xcd, 0xcc, 0xb4, 0x02, 0xfa, 0xd7\n");
    asm volatile(".byte  0x77, 0xb4, 0x54, 0x38, 0xab, 0x1f, 0x0e, 0xe3, 0x8e, 0xd3, 0x0d, 0xeb, 0x99, 0xc3, 0x93, 0xfe\n");
    asm volatile(".byte  0xd1, 0x2b, 0x1b, 0x11, 0xc6, 0x11, 0xef, 0xc8, 0xca, 0x2f\n");
    return 0;
}

В блоке pseudostack видим заветную строку:

"GET /15b436de1f9107f3778aad525e5d0b20.js HTTP/1.1"

Вторая стадия.

Идём по ссылке и попадаем на второе задание.

Узнаем, что это

// stage 2 of 3

Эта стадия очень простая. Она напомнила мне о ICFPC 2006, где используется чуть более сложная виртуальная машина.

Сначала я реализовал всё, как написано и удивлялся почему же оно падает сразу после цикла расшифровки. Потом дошло, что спека немного глючная:

--- stage2.js   2011-09-26 11:47:24.000000000 +0300
+++ stage2_fixed.js     2011-12-04 00:30:54.037239149 +0300
@@ -118,13 +118,13 @@
     // 
     // opcode | instruction | operands (mod 0) | operands (mod 1)
     // -------+-------------+------------------+-----------------
-    // 0x00   | jmp         | r1               | r2:r1
-    // 0x01   | movr        | r1, r2           | rx,   imm
+    // 0x00   | jmp         | r1               | imm:r1
+    // 0x01   | movr        | r1, r2           | r1,   imm
     // 0x02   | movm        | r1, [ds:r2]      | [ds:r1], r2
     // 0x03   | add         | r1, r2           | r1,   imm
     // 0x04   | xor         | r1, r2           | r1,   imm
     // 0x05   | cmp         | r1, r2           | r1,   imm
-    // 0x06   | jmpe        | r1               | r2:r1
+    // 0x06   | jmpe        | r1               | imm:r1
     // 0x07   | hlt         | N/A              | N/A
     //
     // flags

Второй операнд должен трактоваться как новое значение сегмента кода, а не номер регистра, в котором он хранится.

Я написал эмулятор на С. С дебагом исходник весит 11KB. Интерфейсов общения со внешним миром у виртуальной машины тоже нет - это видно из ее набора инструкций:

// opcode | instruction | operands (mod 0) | operands (mod 1)
// -------+-------------+------------------+-----------------
// 0x00   | jmp         | r1               | imm:r1
// 0x01   | movr        | r1, r2           | r1,   imm
// 0x02   | movm        | r1, [ds:r2]      | [ds:r1], r2
// 0x03   | add         | r1, r2           | r1,   imm
// 0x04   | xor         | r1, r2           | r1,   imm
// 0x05   | cmp         | r1, r2           | r1,   imm
// 0x06   | jmpe        | r1               | imm:r1
// 0x07   | hlt         | N/A              | N/A

Выводы:

Запускаем эмулятор и дампим память. Видим заветную строку:

"GET /da75370fe15c4148bd4ceec861fbdaa5.exe HTTP/1.0"

Третья стадия.

Идём по ссылке и попадаем на последнее задание.

Это исполняемый PE файл, у которого в зависимостях библиотеки cygwin: cygwin1.dll и cygcrypt-0.dll. Я поставил cygwin с оффсайта и в опциях выбрал crypt к дополнительной установке.

Подробное дизассемблирование я приводить не буду. Расскажу только пару моментов:

...... ! entrypoint:
...... !   push        ebp
401001 !   mov         ebp, esp
401003 !   sub         esp, 18h
401006 !   and         esp, 0fffffff0h
401009 !   mov         dword ptr [esp], user_code ; вот он!
401010 !   call        to_crt

Мне DES ломать было лень, точнее я не осилил johntheripper. В теории файла с содержимым:

hello:hqDTK7b8K2rvw

должно было хватить, но, видать - пароль длинноват (позже я выгуглил, что key=”cyberwin”). Я просто пропатчил фигов бинарь там, где он проверяет равенство на strcmp:

401167 !   cmp         dword ptr [ebp-38h], 71686367h
40116e !   jnz         invalid_license
401170 !   mov         eax, [salt_indir]
401175 !   mov         [esp+4], eax
401179 !   lea         eax, [ebp-38h]
40117c !   add         eax, 4
40117f !   mov         [esp], eax
401182 !   call        crypt_wrapper
401187 !   mov         edx, eax
401189 !   mov         eax, [salt_indir]
40118e !   mov         [esp+4], eax
401192 !   mov         [esp], edx
401195 !   call        strcmp_wrapper
40119a !   test        eax, eax
40119c !   jnz         hash_mismatch ; от тут мы его и прищучим
40119e !   mov         dword ptr [ebp-0ch], 1
4011a5 !
...... ! hash_mismatch:                  ;xref j40119c

Меняем jnz на jz и суем в файл мусорок:

printf "gchqhelloworld" > license.txt; wine da75370fe15c4148bd4ceec861fbdaa5.exe canyoucrackit.co.uk
keygen.exe
loading stage1 license key(s)...loading stage2 license key(s)...request:GET /hqDTK7b8K2rvw/646c/0/0/key.txt HTTP/1.0response:HTTP/1.1 404 Not FoundContent-Type: text/html; charset=us-asciiServer: Microsoft-HTTPAPI/2.0Date: Sat, 03 Dec 2011 21:53:17 GMTConnection: closeContent-Length: 315<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd"><HTML><HEAD><TITLE>Not Found</TITLE><META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD><BODY><h2>Not Found</h2><hr><p>HTTP Error 404. The requested resource is not found.</p></BODY></HTML>

Урлик, по кторому нам говорят, что мы уже почти у цели. Но фигурируют 3 магические цифры для форматной строки (хорошо видно в бинаре):

"GET /%s/%x/%x/%x/key.txt HTTP/1.0"

В дизассембледном дампе видно, что после loading stage1 license key(s)… вычитывается одно 32-битное число, а после loading stage2 license key(s)… еще два.

Во второром задании было 2 магических числа - это неиспользуемый firmware: [0xd2ab1f05, 0xda13f110]. В первом задании странный 4-байтный мусор перепрыгивался первой же инструкцией jmp:

00000000 eb04                           jmp         0x6
00000002 af                             scasd
00000003 c2bfa3                         ret         a3bf

Логично предположить, что 0xa3bfc2af - это и есть первый ключ.

Итого наш ключ для патченного бинаря (12345678 можно заменить на любые 8 символов кроме cyberwin :]):

printf "gchq12345678\xaf\xc2\xbf\xa3\x05\x1f\xab\xd2\x10\xf1\x13\xda" > license.txt; wine da75370fe15c4148bd4ceec861fbdaa5.exe canyoucrackit.co.uk

и для непатченного бинаря:

printf "gchqcyberwin\xaf\xc2\xbf\xa3\x05\x1f\xab\xd2\x10\xf1\x13\xda" > license.txt; wine da75370fe15c4148bd4ceec861fbdaa5.exe canyoucrackit.co.uk

Запуск выплёвывает нам заветную строку:

"GET /hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt HTTP/1.0"

Идём по ссылке в браузере (сам бинарь нам нагло врёт и выводит not found):

Pr0t3ct!on#cyber_security@12*12.2011+

Вводим код на главной. Получаем следующий опус:

So you did it. Well done! Now this is where it gets interesting.
Could you use your skills and ingenuity to combat terrorism and
cyber threats? As one of our experts, you'll help protect our
nation's security and the lives of thousands. Every day will bring
new challenges, new solutions to find – and new ways to prove that
you're one of the best.
[Find out more and apply]

Такие пироги :]