Это перевод второй части публикации «Меня попросили взломать программу на собеседовании». Оригинальный текст можно найти здесь.
Предисловие
Привет, ребята. Если вы не знаете, что означает «Часть 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 (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
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, пока не доберетесь до адреса: 0x80495ed. Или же можете просто ввести команду: b * 0x80495ed
и нажать с
, чтобы добраться до необходимого адреса.
В любом случае, теперь мы на месте:
0x80495ed <main+195>:
Здесь мы можем увидеть операцию сравнения:
0x80495ed <main+195>: cmp DWORD PTR [esp+0x1c],0x0
В gdb вы можете ввести: p/x $esp
для просмотра содержимого $esp.
Также вы можете провести некоторые вычисления с использованием регистров и адресов: p/x $esp+0x1c
. Или же просмотреть содержимое адреса после разименования: p/x *0xff811bac
.
Здесь вы можете ввести si, что приведет нас к моменту, когда crackme получил наши 16 символов и ожидает символ окончания строки n.
Советую вам поставить брейкпоинт по адресу 0x804962d : b * 0x804962d
, если вы не хотите долго и мучительно ждать.
А вот теперь начинается веселье:
=> 0x804962d <main+259>: push eax
0x804962e <main+260>: push ebx
0x804962f <main+261>: rdtsc
0x8049631 <main+263>: and eax,0xfffff
0x8049636 <main+268>: test eax,eax
0x8049638 <main+270>: je 0x8049646 <g99>
0x804963a <main+272>: xor ebx,0xe
0x804963d <main+275>: add ebx,0xe
0x8049640 <main+278>: sub ebx,0xe
0x8049643 <main+281>: dec eax
Слышали ли вы когда-нибудь об инструкции rdtsc? Её основная задача — подсчет количества циклов процессора. После вызова rdtsc счётчик TSC будет помещен в регистры edx и eax:
0x8049636 <main+268>: test eax,eax
0x8049638 <main+270>: je 0x8049646 <g99>
То же самое на С выглядело бы примерно так:
if(eax == 0)
{
goto 0x8049646
}
Поскольку eax не равен 0, мы продолжим копать. Как вы вероятнее всего заметили, нижеприведенный код — полный треш: мы добавляем 0xe к ebx, а затем вычитаем его. Похоже, нас пытаются запутать.
xor ebx,0xe
add ebx,0xe
sub ebx,0xe
dec eax
Пока eax равен 0, продолжаем данный цикл.
Поставьте брейкпоинт: b * 0x8049646
и нажмите c.
Окей, ничего интересного — идем дальше.
=> 0x80494db <nkc1qpE2L6f6AyqaendA>: push ebp
0x80494dc <nkc1qpE2L6f6AyqaendA+1>: mov ebp,esp
0x80494de <nkc1qpE2L6f6AyqaendA+3>: sub esp,0x14
0x80494e1 <nkc1qpE2L6f6AyqaendA+6>: mov DWORD PTR [ebp-0x4],0x0
0x80494e8 <nkc1qpE2L6f6AyqaendA+13>: mov DWORD PTR [esp],0x0
0x80494ef <nkc1qpE2L6f6AyqaendA+20>: call 0x804944b <qEWL8Jl0zdpmTbwhziDv>
0x80494f4 <nkc1qpE2L6f6AyqaendA+25>: mov eax,DWORD PTR [ebp+0x8]
0x80494f7 <nkc1qpE2L6f6AyqaendA+28>: mov DWORD PTR [esp],eax
0x80494fa <nkc1qpE2L6f6AyqaendA+31>: call 0x8048604 <fjDKIzPtGuE8ZdfSL8vq>
0x80494ff <nkc1qpE2L6f6AyqaendA+36>: mov DWORD PTR [esp],0x2
0x8049506 <nkc1qpE2L6f6AyqaendA+43>: call 0x804944b <qEWL8Jl0zdpmTbwhziDv>
0x804950b <nkc1qpE2L6f6AyqaendA+48>: mov eax,DWORD PTR [ebp+0x8]
0x804950e <nkc1qpE2L6f6AyqaendA+51>: mov DWORD PTR [esp],eax
0x8049511 <nkc1qpE2L6f6AyqaendA+54>: call 0x8048ab1 <W0ElBw5Smo9TPiWOeK8c>
0x8049516 <nkc1qpE2L6f6AyqaendA+59>: mov DWORD PTR [ebp-0x4],eax
0x8049519 <nkc1qpE2L6f6AyqaendA+62>: mov DWORD PTR [esp],0x1
0x8049520 <nkc1qpE2L6f6AyqaendA+69>: call 0x804944b <qEWL8Jl0zdpmTbwhziDv>
0x8049525 <nkc1qpE2L6f6AyqaendA+74>: mov eax,DWORD PTR [ebp-0x4]
0x8049528 <nkc1qpE2L6f6AyqaendA+77>: leave
0x8049529 <nkc1qpE2L6f6AyqaendA+78>: ret
nkc1qpE2L6f6AyqaendA
— эта функция и есть основой всего процесса.
Давайте попробуем исследовать все функции, к которым обращается nkc1qpE2L6f6AyqaendA: qEWL8Jl0zdpmTbwhziDv , fjDKIzPtGuE8ZdfSL8vq и W0ElBw5Smo9TPiWOeK8c:
(gdb) x/10i qEWL8Jl0zdpmTbwhziDv
0x804944b <qEWL8Jl0zdpmTbwhziDv>: push ebp
0x804944c <qEWL8Jl0zdpmTbwhziDv+1>: mov ebp,esp
0x804944e <qEWL8Jl0zdpmTbwhziDv+3>: mov eax,DWORD PTR [ebp+0x8]
0x8049451 <qEWL8Jl0zdpmTbwhziDv+6>: cmp eax,0x0
0x8049454 <qEWL8Jl0zdpmTbwhziDv+9>: je 0x80494b9 <hzdhp>
0x8049456 <qEWL8Jl0zdpmTbwhziDv+11>: cmp eax,0x1
0x8049459 <qEWL8Jl0zdpmTbwhziDv+14>: je 0x8049499 <qEWL8Jl0zdpmTbwhziDv+78>
0x804945b <qEWL8Jl0zdpmTbwhziDv+16>: call 0x8047b71
0x8049460 <qEWL8Jl0zdpmTbwhziDv+21>: add DWORD PTR [eax+0x48604bf],0x5eb9008
0x804946a <qEWL8Jl0zdpmTbwhziDv+31>: add DWORD PTR [eax-0x4608ea13],0x8048ab1
(gdb) x/10i fjDKIzPtGuE8ZdfSL8vq
0x8048604 <fjDKIzPtGuE8ZdfSL8vq>: call 0xb027:0xaf72c78c
0x804860b <fjDKIzPtGuE8ZdfSL8vq+7>: cmp esi,DWORD PTR ds:0xe4dfbbf1
0x8048611 <fjDKIzPtGuE8ZdfSL8vq+13>: (bad)
0x8048612 <fjDKIzPtGuE8ZdfSL8vq+14>: and al,BYTE PTR [ebp+edi*2-0x8]
0x8048616 <fjDKIzPtGuE8ZdfSL8vq+18>: push ebx
0x8048617 <fjDKIzPtGuE8ZdfSL8vq+19>: push esi
0x8048618 <fjDKIzPtGuE8ZdfSL8vq+20>: inc edx
0x8048619 <fjDKIzPtGuE8ZdfSL8vq+21>: mov WORD PTR [ebp+0x76],ss
0x804861c <fjDKIzPtGuE8ZdfSL8vq+24>: xchg edx,eax
0x804861d <fjDKIzPtGuE8ZdfSL8vq+25>: mov al,ds:0x45fd3fbb
(gd
(gdb) x/10i W0ElBw5Smo9TPiWOeK8c
0x8048ab1 <W0ElBw5Smo9TPiWOeK8c>: call 0xb023:0x1c72c78c
0x8048ab8 <W0ElBw5Smo9TPiWOeK8c+7>: cmp esi,DWORD PTR ds:0xe4dfbbf1
0x8048abe <W0ElBw5Smo9TPiWOeK8c+13>: jmp 0xf86e358
0x8048ac3 <W0ElBw5Smo9TPiWOeK8c+18>: xchg ax,ax
0x8048ac5 <W0ElBw5Smo9TPiWOeK8c+20>: out dx,eax
0x8048ac6 <W0ElBw5Smo9TPiWOeK8c+21>: dec ebp
0x8048ac7 <W0ElBw5Smo9TPiWOeK8c+22>: xchg edi,eax
0x8048ac8 <W0ElBw5Smo9TPiWOeK8c+23>: popa
0x8048ac9 <W0ElBw5Smo9TPiWOeK8c+24>: test DWORD PTR [ecx-0x7e],esp
0x8048acc <W0ElBw5Smo9TPiWOeK8c+27>: test DWORD PTR [edi],esi
Как мы можем увидеть, основной алгоритм находится внутри функции nkc1qpE2L6f6AyqaendA , а цепочка вызовов выглядит следующим образом: qEWL8Jl0zdpmTbwhziDv -> fjDKIzPtGuE8ZdfSL8vq -> qEWL8Jl0zdpmTbwhziDv -> W0ElBw5Smo9TPiWOeK8c -> qEWL8Jl0zdpmTbwhziDv
.
Просмотрев первые 10 строк каждой функции, смогли ли вы найти нечто необычное? Посмотрите внимательно на первые строки fjDKIzPtGuE8ZdfSL8vq и W0ElBw5Smo9TPiWOeK8c , они абсолютно бессмысленны.
Я ни разу в жизни (от переводчика: в оригинале: life:) — вероятнее всего отсыл к небезизвестному мобильному оператору) не встречался с чем-либо подобным: call 0xb023:0x1c72c78c
. А все дело в том, что обе эти функции зашифрованы и gdb попытался их дизассемблить.
Итак, qEWL8Jl0zdpmTbwhziDv занимается расшифровкой функций (поэтому её вызов и стоит перед ними).
Я попробую поменять алгоритм выполнения программы, заменив зашифрованные функции их расшифрованными соответствиями и уберу вызов qEWL8Jl0zdpmTbwhziDv.
Исходя из этого, новый алгоритм будет выглядеть следующим образом: fjDKIzPtGuE8ZdfSL8vq -> W0ElBw5Smo9TPiWOeK8c
— и всё.
Тупик 1. Начало
Работая над этим crackme, я попытался отключить TimeStampCounter или как-нибудь его контролировать. В данном случае rdtsc используется для проверки интервала времени между выполнениями инструкций. Соответственно, если вы попытаетесь прогнать программу через gdb, то данный интервал будет намного больше такого же, но при нормальной работе кода. Поэтому я попытался найти способ управления счетчиком tsc, но, к сожалению, он управляется процессором — и поэтому я ничего не могу сделать из-под ОС.
Но все же я попытался написать модуль для ядра, который бы сбивал счетчик, устанавливая его значение равным 0:
#include <linux/module.h> // included for all kernel modules
#include <linux/kernel.h> // included for KERN_INFO
#include <linux/init.h> // included for __init and __exit macros
#include <linux/kthread.h> // for threads
#include <linux/sched.h> // for task_struct
#include <linux/time.h> // for using jiffies
#include <linux/timer.h>
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"(0x10),"a"(lo),"d"(hi));
while(1){
if(time_before(jiffies,j1))
schedule();
else
{
j1 = jiffies + delay;
asm volatile("wrmsr"::"c"(0x10),"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. Конец
У меня появилась идея остановить программу в тот момент, когда обе функции будут в расшифрованном состоянии. Например, 0x8048ab0 — очень хорошее место, поскольку это конец функции fjDKIzPtGuE8ZdfSL8vq.
Давайте откроем .gdbinit и запишем:
set disassembly-flavor intel
set disassemble-next-line on
handle SIGTRAP noprint pass nostop
b * 0x8048ab0
Перезапускаем crackme и снова цепляем gdb. Вводим 16 символов и жмем c.
=> 0xf7706430 <__kernel_vsyscall+16>: 5d pop ebp
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x08048ab1 in W0ElBw5Smo9TPiWOeK8c ()
=> 0x08048ab1 <W0ElBw5Smo9TPiWOeK8c+0>: 9a 8c c7 72 1c 23 b0 call 0xb023:0x1c72c78c
(gdb) x/10i fjDKIzPtGuE8ZdfSL8vq
0x8048604 <fjDKIzPtGuE8ZdfSL8vq>: push ebp
0x8048605 <fjDKIzPtGuE8ZdfSL8vq+1>: mov ebp,esp
0x8048607 <fjDKIzPtGuE8ZdfSL8vq+3>: call 0x8047b08
0x804860c <fjDKIzPtGuE8ZdfSL8vq+8>: xor eax,0x20ec8390
0x8048611 <fjDKIzPtGuE8ZdfSL8vq+13>: call 0x8047b08
0x8048616 <fjDKIzPtGuE8ZdfSL8vq+18>: xor eax,0x32ff45c6
0x804861b <fjDKIzPtGuE8ZdfSL8vq+23>: call 0x8047b08
0x8048620 <fjDKIzPtGuE8ZdfSL8vq+28>: xor eax,0xdafe45c6
0x8048625 <fjDKIzPtGuE8ZdfSL8vq+33>: call 0x8047b08
0x804862a <fjDKIzPtGuE8ZdfSL8vq+38>: xor eax,0xdbfd45c6
(gdb)
Вуаля. Теперь у нас есть чистая функция fjDKIzPtGuE8ZdfSL8vq . Но у нас все еще есть проблема с gdb — false assembly (доступно для прочтения на английском тут).
Давайте сохраним нашу функцию во временный файл (параметры: имя_файла, начальный_адрес и конечный_адрес):
dump ihex memory fjDKIzPtGuE8ZdfSL8vq_dump 0x8048604 0x8048ab0
Теперь сделаем то же самое для второй функции — ставим брейкпоинт по адресу: 0x08048e14 и создаем дамп:
dump ihex memory W0ElBw5Smo9TPiWOeK8c_dump W0ElBw5Smo9TPiWOeK8c g999+3
Теперь, когда у нас есть обе функции, давайте попробуем поменять алгоритм выполнения программы. Для этого очищаем файл .gdbinit и ставим брейкпоинт: 0x80494db:
set disassembly-flavor intel
set disassemble-next-line on
break * 0x80494ef
commands
set($eip) = 0x80494f4
continue
end
break * 0x80494fa
commands
restore fjDKIzPtGuE8ZdfSL8vq_dump
restore W0ElBw5Smo9TPiWOeK8c_dump
continue
end
break * 0x08049506
commands
set($eip) = 0x804950b
continue
end
break * 0x8049520
commands
set($eip) = 0x8049525
continue
end
Ну а теперь, когда мы изменили алгоритм — все достаточно просто. Следуем инструкциям, описанным в первой части данной статьи.
Введенные нами символы ксорятся (XOR) c некими константами, после чего результат проверяется на правильность: Inputs ^ FirstConstants == SecondConstants
, соответственно: Inputs = SecondConstants ^ FirstConstants
А вот и наш генератор ключа:
#!/usr/bin/python
firstConst = [0x32,0xda,0xdb,0x1,0xf3,0x77,0x4c,0x57,0xbe,0x49,0xec,0x5f,0xab,0x7f,0xed,0x9f]
secondConst = [0x0d,0xef,0xf1,0x4d,0xb6,0x4c,0x69,0x20,0xf9,0x20,0xdd,0x7c,0xda,0x3b,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…
Но теперь я живу в Барселоне и у меня прекрасная жизнь.
Автор: WhiteAngel