[Переклад] Мене попросили зламати програму на співбесіді. Частина 2

Це переклад другій частині публікації «Мене попросили зламати програму на співбесіді». Оригінальний текст можна знайти на тут.

Передмова

Привіт, хлопці. Якщо ви не знаєте, що означає «Частина 2», прочитайте будь ласка Частина 1.
Для початку я хотів би подякувати всіх прочитали першу частину, оскільки в результаті я отримав масу відмінних відгук.

Так само я б хотів усунути деякі непорозуміння:
  1. Я більше не працюю на дану компанію, я переїхав в Барселону;
  2. Я проходив дане інтерв'ю майже рік тому;
  3. Програми я зламував в хмарі ($5 тариф, так, ви вгадали компанію), тому я не вважаю, що використання root@'a є проблемою — я можу створювати нову середу за пару секунд. У підсумку я все ж переключився на користувача eren@, так як gdb не брав рутові инит файли.
  4. Не забудьте прочитати закінчення статті — вам обов'язково сподобається!

Поїхали

На цей раз ми будемо працювати не з дверима, а з ядерної ракетою.

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 версії 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> // for all included kernel modules
#include <linux/kernel.h> // included for KERN_INFO
#include <linux/init.h> // included for __init and __exit macros
#include <linux/kthread.h> // threads for
#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 = j 0 + delay;


asm volatile("wrmsr"::"с"(0x10),"a"(lo),"d"(hi));

while(1){
if(time_before(jiffies,j1))
schedule();
else
{
j1 = jiffies + delay;
asm volatile("wrmsr"::"с"(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 "if in");
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) з певними константами, після чого результат перевіряється на правильність:
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

Але тепер я живу в Барселоні і у мене прекрасна життя.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.