[Из песочницы] [Перевод] Меня попросили взломать программу на собеседовании. Часть 2

Это перевод второй части публикации «Меня попросили взломать программу на собеседовании». Оригинальный текст можно найти здесь.ПредисловиеПривет, ребята. Если вы не знаете, что означает «Часть 2», пожалуйста прочитайте Часть 1.Для начала я хотел бы поблагодарить всех прочитавших первую часть, поскольку в итоге я получил массу отличных отзыв.Так же я бы хотел устранить некоторые недопонимания:

Я более не работаю на данную компанию, я переехал в Барселону; Я проходил данное интервью почти год назад; Программы я взламывал в облаке ($5 тариф, да, вы угадали компанию), поэтому я не считаю, что использование root@'a является проблемой — я могу пересоздать новую среду за пару секунд. В итоге я все же переключился на пользователя eren@, так как gdb не принимал рутовые инит файлы. Не забудьте прочитать окончание статьи — вам обязательно понравится! Поехали На этот раз мы будем работать не с дверью, а с ядерной ракетой. eren@lisa:~$ ./CrackTheNuke

*** NUKE CONTROL SYSTEM ***

PASSWORD: giveMeNuke

*** ACCESS DENIED ***

PASSWORD: iwantanexplosion

*** ACCESS DENIED ***

PASSWORD: knockknockitsme

*** ACCESS DENIED ***

*** SYSTEM LOCKED ***

*** SHUTTING DOWN ***

eren@lisa:~$ Я создам дамп всего бинарника с intel asm синтаксисом, как образец: eren@lisa:~$ objdump -M intel -D CrackTheNuke > staticDis eren@lisa:~$ Этот файл нам понадобится позже. Если вы загляните в файл staticDis, вы сможете найти полный дамп с intel’овским синтаксисом.

Давайте на этот раз попробуем кое-что другое: для начала я запущу процесс, а после подцеплю на него дебаггер.

eren@lisa:~$ ./CrackTheNuke

*** NUKE CONTROL SYSTEM ***

PASSWORD: Теперь мы можем переключиться в другой шелл и запустить из него отладчик:

eren@lisa:~$ ps aux | grep Crack eren 4741 0.0 0.0 1724 252 pts/0 S+ 14:54 0:00 ./CrackTheNuke eren 4845 0.0 0.1 7832 832 pts/1 S+ 14:56 0:00 grep Crack eren@lisa:~$ gdb --pid 4741 GNU gdb (GDB) 7.4.1-debian Copyright © 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type «show copying» and «show warranty» for details. This GDB was configured as «x86_64-linux-gnu». For bug reporting instructions, please see: . Catchpoint 1 (syscall 'ptrace' [26]) Attaching to process 4741 Reading symbols from /home/eren/CrackTheNuke…(no debugging symbols found)…done. Reading symbols from /lib32/libc.so.6…(no debugging symbols found)…done. Loaded symbols for /lib32/libc.so.6 Reading symbols from /lib/ld-linux.so.2…(no debugging symbols found)…done. Loaded symbols for /lib/ld-linux.so.2 0xf7726430 in __kernel_vsyscall () => 0xf7726430 <__kernel_vsyscall+16>: 5d pop ebp (gdb) Теперь вы можете ввести любые 16 символов в предыдущем окне и вернуться сюда. Сейчас мы находимся в функции scanf, которая покоится в библиотеке glibc (crackme будет вызывать scanf 16 раз, но мы сэкономим здесь немного времени).

В gdb вы можете набрать si (аббр. от single step). Вводите si, пока не доберетесь до адреса: 0×80495ed. Или же можете просто ввести команду: b * 0×80495ed и нажать с, чтобы добраться до необходимого адреса.

В любом случае, теперь мы на месте:

0×80495ed : Здесь мы можем увидеть операцию сравнения:

0×80495ed : cmp DWORD PTR [esp+0×1c],0×0 В gdb вы можете ввести: p/x $esp для просмотра содержимого $esp.Также вы можете провести некоторые вычисления с использованием регистров и адресов: p/x $esp+0×1c. Или же просмотреть содержимое адреса после разименования: p/x *0xff811bac.

Здесь вы можете ввести si, что приведет нас к моменту, когда crackme получил наши 16 символов и ожидает символ окончания строки \n.

Советую вам поставить брейкпоинт по адресу 0×804962d: b * 0×804962d, если вы не хотите долго и мучительно ждать.

А вот теперь начинается веселье:

=> 0×804962d : push eax 0×804962e : push ebx 0×804962f : rdtsc 0×8049631 : and eax,0xfffff 0×8049636 : test eax, eax 0×8049638 : je 0×8049646 0×804963a : xor ebx,0xe 0×804963d : add ebx,0xe 0×8049640 : sub ebx,0xe 0×8049643 : dec eax Слышали ли вы когда-нибудь об инструкции rdtsc? Её основная задача — подсчет количества циклов процессора. После вызова rdtsc счётчик TSC будет помещен в регистры edx и eax:

0×8049636 : test eax, eax 0×8049638 : je 0×8049646 То же самое на С выглядело бы примерно так:

if (eax == 0) { goto 0×8049646 } Поскольку eax не равен 0, мы продолжим копать. Как вы вероятнее всего заметили, нижеприведенный код — полный треш: мы добавляем 0xe к ebx, а затем вычитаем его. Похоже, нас пытаются запутать.

xor ebx,0xe add ebx,0xe sub ebx,0xe dec eax Пока eax равен 0, продолжаем данный цикл.Поставьте брейкпоинт: b * 0×8049646 и нажмите c. Окей, ничего интересного — идем дальше.

=> 0×80494db : push ebp 0×80494dc : mov ebp, esp 0×80494de : sub esp,0×14 0×80494e1 : mov DWORD PTR [ebp-0×4],0×0 0×80494e8 : mov DWORD PTR [esp],0×0 0×80494ef : call 0×804944b 0×80494f4 : mov eax, DWORD PTR [ebp+0×8] 0×80494f7 : mov DWORD PTR [esp], eax 0×80494fa : call 0×8048604 0×80494ff : mov DWORD PTR [esp],0×2 0×8049506 : call 0×804944b 0×804950b : mov eax, DWORD PTR [ebp+0×8] 0×804950e : mov DWORD PTR [esp], eax 0×8049511 : call 0×8048ab1 0×8049516 : mov DWORD PTR [ebp-0×4], eax 0×8049519 : mov DWORD PTR [esp],0×1 0×8049520 : call 0×804944b 0×8049525 : mov eax, DWORD PTR [ebp-0×4] 0×8049528 : leave 0×8049529 : ret nkc1qpE2L6f6AyqaendA — эта функция и есть основой всего процесса.Давайте попробуем исследовать все функции, к которым обращается nkc1qpE2L6f6AyqaendA: qEWL8Jl0zdpmTbwhziDv, fjDKIzPtGuE8ZdfSL8vq и W0ElBw5Smo9TPiWOeK8c:

(gdb) x/10i qEWL8Jl0zdpmTbwhziDv 0×804944b : push ebp 0×804944c : mov ebp, esp 0×804944e : mov eax, DWORD PTR [ebp+0×8] 0×8049451 : cmp eax,0×0 0×8049454 : je 0×80494b9 0×8049456 : cmp eax,0×1 0×8049459 : je 0×8049499 0×804945b : call 0×8047b71 0×8049460 : add DWORD PTR [eax+0×48604bf],0×5eb9008 0×804946a : add DWORD PTR [eax-0×4608ea13],0×8048ab1

(gdb) x/10i fjDKIzPtGuE8ZdfSL8vq 0×8048604 : call 0xb027:0xaf72c78c 0×804860b : cmp esi, DWORD PTR ds:0xe4dfbbf1 0×8048611 : (bad) 0×8048612 : and al, BYTE PTR [ebp+edi*2–0×8] 0×8048616 : push ebx 0×8048617 : push esi 0×8048618 : inc edx 0×8048619 : mov WORD PTR [ebp+0×76], ss 0×804861c : xchg edx, eax 0×804861d : mov al, ds:0×45fd3fbb (gd

(gdb) x/10i W0ElBw5Smo9TPiWOeK8c 0×8048ab1 : call 0xb023:0×1c72c78c 0×8048ab8 : cmp esi, DWORD PTR ds:0xe4dfbbf1 0×8048abe : jmp 0xf86e358 0×8048ac3 : xchg ax, ax 0×8048ac5 : out dx, eax 0×8048ac6 : dec ebp 0×8048ac7 : xchg edi, eax 0×8048ac8 : popa 0×8048ac9 : test DWORD PTR [ecx-0×7e], esp 0×8048acc : test DWORD PTR [edi], esi Как мы можем увидеть, основной алгоритм находится внутри функции nkc1qpE2L6f6AyqaendA, а цепочка вызовов выглядит следующим образом: qEWL8Jl0zdpmTbwhziDv → fjDKIzPtGuE8ZdfSL8vq → qEWL8Jl0zdpmTbwhziDv → W0ElBw5Smo9TPiWOeK8c → qEWL8Jl0zdpmTbwhziDv.

Просмотрев первые 10 строк каждой функции, смогли ли вы найти нечто необычное? Посмотрите внимательно на первые строки fjDKIzPtGuE8ZdfSL8vq и W0ElBw5Smo9TPiWOeK8c, они абсолютно бессмысленны.

Я ни разу в жизни (от переводчика: в оригинале: life:) — вероятнее всего отсыл к небезизвестному мобильному оператору) не встречался с чем-либо подобным: call 0xb023:0×1c72c78c. А все дело в том, что обе эти функции зашифрованы и gdb попытался их дизассемблить.

Итак, qEWL8Jl0zdpmTbwhziDv занимается расшифровкой функций (поэтому её вызов и стоит перед ними).

Я попробую поменять алгоритм выполнения программы, заменив зашифрованные функции их расшифрованными соответствиями и уберу вызов qEWL8Jl0zdpmTbwhziDv.

Исходя из этого, новый алгоритм будет выглядеть следующим образом: fjDKIzPtGuE8ZdfSL8vq → W0ElBw5Smo9TPiWOeK8c — и всё.

Тупик 1. Начало Работая над этим crackme, я попытался отключить TimeStampCounter или как-нибудь его контролировать. В данном случае rdtsc используется для проверки интервала времени между выполнениями инструкций. Соответственно, если вы попытаетесь прогнать программу через gdb, то данный интервал будет намного больше такого же, но при нормальной работе кода. Поэтому я попытался найти способ управления счетчиком tsc, но, к сожалению, он управляется процессором — и поэтому я ничего не могу сделать из-под ОС.Но все же я попытался написать модуль для ядра, который бы сбивал счетчик, устанавливая его значение равным 0: #include // included for all kernel modules #include // included for KERN_INFO #include // included for __init and __exit macros #include // for threads #include // for task_struct #include // for using jiffies #include

MODULE_LICENSE («GPL»); MODULE_AUTHOR («m00dy»); MODULE_DESCRIPTION («A Fake rdtsc emulation»);

static struct task_struct *thread1;

int thread_fn (){

uint32_t hi, lo; unsigned long j0, j1; int delay = HZ / 250; hi=0; lo=0xb; printk (KERN_INFO «In thread1»); j0 = jiffies; j1 = j0 + delay;

asm volatile («wrmsr»:: «c»(0×10), «a»(lo), «d»(hi));

while (1){ if (time_before (jiffies, j1)) schedule (); else { j1 = jiffies + delay; asm volatile («wrmsr»:: «c»(0×10), «a»(lo), «d»(hi)); } }

}

static int __init hello_init (void) {

char our_thread[8]=«thread1»; printk (KERN_INFO «in init»); thread1 = kthread_create (thread_fn, NULL, our_thread); if ((thread1)) { printk (KERN_INFO «in if»); wake_up_process (thread1); }

return 0; }

static void __exit hello_cleanup (void) { printk (KERN_INFO «Fake RDTSC end \n»); }

module_init (hello_init); module_exit (hello_cleanup);= К сожалению, данный метод не сработал, так, как мне было необходимо, и я продолжил поиски.

Тупик 1. Конец У меня появилась идея остановить программу в тот момент, когда обе функции будут в расшифрованном состоянии. Например, 0×8048ab0 — очень хорошее место, поскольку это конец функции fjDKIzPtGuE8ZdfSL8vq.Давайте откроем .gdbinit и запишем:

set disassembly-flavor intel set disassemble-next-line on handle SIGTRAP noprint pass nostop b * 0×8048ab0 Перезапускаем crackme и снова цепляем gdb. Вводим 16 символов и жмем c.

=> 0xf7706430 <__kernel_vsyscall+16>: 5d pop ebp (gdb) c Continuing.

Program received signal SIGSEGV, Segmentation fault. 0×08048ab1 in W0ElBw5Smo9TPiWOeK8c () => 0×08048ab1 : 9a 8c c7 72 1c 23 b0 call 0xb023:0×1c72c78c (gdb) x/10i fjDKIzPtGuE8ZdfSL8vq 0×8048604 : push ebp 0×8048605 : mov ebp, esp 0×8048607 : call 0×8047b08 0×804860c : xor eax,0×20ec8390 0×8048611 : call 0×8047b08 0×8048616 : xor eax,0×32ff45c6 0×804861b : call 0×8047b08 0×8048620 : xor eax,0xdafe45c6 0×8048625 : call 0×8047b08 0×804862a : xor eax,0xdbfd45c6 (gdb) Вуаля. Теперь у нас есть чистая функция fjDKIzPtGuE8ZdfSL8vq. Но у нас все еще есть проблема с gdb — false assembly (доступно для прочтения на английском тут).

Давайте сохраним нашу функцию во временный файл (параметры: имя_файла, начальный_адрес и конечный_адрес):

dump ihex memory fjDKIzPtGuE8ZdfSL8vq_dump 0×8048604 0×8048ab0 Теперь сделаем то же самое для второй функции — ставим брейкпоинт по адресу: 0×08048e14 и создаем дамп:

dump ihex memory W0ElBw5Smo9TPiWOeK8c_dump W0ElBw5Smo9TPiWOeK8c g999+3 Теперь, когда у нас есть обе функции, давайте попробуем поменять алгоритм выполнения программы. Для этого очищаем файл .gdbinit и ставим брейкпоинт: 0×80494db:

set disassembly-flavor intel set disassemble-next-line on

break * 0×80494ef commands set ($eip) = 0×80494f4 continue end

break * 0×80494fa commands restore fjDKIzPtGuE8ZdfSL8vq_dump restore W0ElBw5Smo9TPiWOeK8c_dump continue end

break * 0×08049506 commands set ($eip) = 0×804950b continue end

break * 0×8049520 commands set ($eip) = 0×8049525 continue end Ну, а теперь, когда мы изменили алгоритм — все достаточно просто. Следуем инструкциям, описанным в первой части данной статьи.

Введенные нами символы ксорятся (XOR) c некими константами, после чего результат проверяется на правильность: Inputs ^ FirstConstants == SecondConstants, соответственно: Inputs = SecondConstants ^ FirstConstants

А вот и наш генератор ключа:

#!/usr/bin/python firstConst = [0×32,0xda,0xdb,0×1,0xf3,0×77,0×4c,0×57,0xbe,0×49,0xec,0×5f,0xab,0×7f,0xed,0×9f] secondConst = [0×0d,0xef,0xf1,0×4d,0xb6,0×4c,0×69,0×20,0xf9,0×20,0xdd,0×7c,0xda,0×3b,0xc9,0xaf] ret =» for x in range (16): ret+=chr (firstConst[x] ^ secondConst[x]) print ret Поехали проверять:

eren@lisa:~$ ./CrackTheNuke

*** NUKE CONTROL SYSTEM ***

PASSWORD: ?5*LE;%wGi1#qD$0

*** ACCESS GRANTED ***

*** THE NUKE STOPPED ***

eren@lisa:~$ Все работает.

Заключение Так же хотелось бы вам рассказать, что случилось после того, как меня приняли на работу. В самый же первый день моей новой работы они решили поменять мой департамент (я до сих пор не могу понять, почему эта компания считает себя лучшей из лучших в Турции).После этого я стал J2ee разработчиком. Мне приходилось использовать eclipse, svn и даже операционную систему под названием Windows *. Но, как оказалось в последствии, это было не самое страшное. Позже они заставили меня писать css…

Но теперь я живу в Барселоне и у меня прекрасная жизнь.

© Habrahabr.ru