[Перевод] Меня попросили взломать программу на собеседовании

TL; DR Меня попросили взломать программу на собеседовании. И я получил работу.Всем привет,

Я собеседовался на позицию инженера программной безопасности, они спрашивали в основном разные низкоуровневые вещи. Некоторые ответы я знал, некоторые нет.Потом они прислали email с защищённым и зашифрованным бинарником, который нужно было взломать.Когда я добрался до дома, я скачал его и увидел, что он спрашивает пароль. Они хотели, чтобы я нашёл этот пароль.Вот что я увидел при первом запуске:

root@lisa:~# ./CrackTheDoor

*** DOOR CONTROL SYSTEM ***

PASSWORD:

Я напечатал три раза какие-то глупые слова, и программа закрылась.У меня ещё есть инструменты в запасе для анализа. Давайте соберём побольше информации о файле. root@lisa:~# file CrackTheDoorCrackTheDoor: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=0×9927be2fe310bea01d412164103b9c8b2d7567ea, not strippedroot@lisa:~#

Окей, теперь мы знаем немного больше о бинарнике. root@lisa:~# ldd CrackTheDoorlinux-gate.so.1 => (0xf777b000)libc.so.6 => /lib32/libc.so.6 (0xf760c000)/lib/ld-linux.so.2 (0xf777c000)root@lisa:~#

Ничего необычного. Объясню немного. linux-gate.so вы можете не найти в своей файловой системе. Но ldd показывает его как общую библиотеку. Это Virtual DSO.Итак, вы знаете о linux-gate.so.1.Возможно вы также знаете, что libc.so.6 это основная C библиотека для GNU систем.ld-linux.so это загрузчик динамических библиотек в linux.

Запустим программу в отладчике и посмотрим что произойдёт.

root@lisa:~# gdb CrackTheDoorGNU gdb (GDB) 7.4.1-debianCopyright © 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: …Reading symbols from /root/CrackTheDoor…(no debugging symbols found)…done.(gdb) rStarting program: /root/CrackTheDoor

Program received signal SIGSEGV, Segmentation fault.0×080484fb in __do_global_dtors_aux ()(gdb)

Программа упала. Это значит, что в ней должны быть какие-то антиотладочные трюки. Окей…Запустим её снова и посмотрим точку входа программы.

root@lisa:~# gdb CrackTheDoorGNU gdb (GDB) 7.4.1-debianCopyright © 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: …Reading symbols from /root/CrackTheDoor…(no debugging symbols found)…done.(gdb) info fileSymbols from »/root/CrackTheDoor».Local exec file:`/root/CrackTheDoor', file type elf32-i386.Entry point: 0×804762c……

Поставим breakpoint на точку входа и начнём отладку: b * 0×804762c

Затем нажмём «r» для запуска. Окажемся на первой строке точки входа программы:

gdb) x/30i $pc=> 0×804762c: pusha0×804762d: mov $0xaa,%dl0×804762f: mov $0×8048480,%edi0×8047634: mov $0×8048cbc,%ecx0×8047639: mov %edi,0×80476f30×804763f: mov %ecx,0×80476f70×8047645: sub %edi,%ecx0×8047647: mov $0×804762f,%esi0×804764c: push $0×80476c10×8047651: pusha0×8047652: mov $0×55,%al0×8047654: xor $0×99,%al0×8047656: mov $0×8047656,%edi0×804765b: mov $0×80476e5,%ecx0×8047660: sub $0×8047656,%ecx0×8047666: repnz scas %es:(%edi),%al0×8047668: je 0×804770a0×804766e: mov %edi,0×80476eb0×8047674: popa0×8047675: add 0×80476eb,%edx0×804767b: ret

Должно выглядеть как-то так. Можно выбрать синтаксис между AT&T и Intel версиями. Мне больше нравится Intel.По адресу 0×8047654, мы присваиваем 0×55 al регистру, а затем XOR’им его с 0×99 получая 0xCC.0xCC это очень важно, так как это означает остановку процесса. Когда отладчик хочет прервать выполнение программы, он заменяет байты на 0xCC в том месте, где хочет её остановить.По адресу 0×8047666, мы видим repnz scas; это означает поиск участка памяти, ограниченного es и edi, на предмет значения в al (0xCC).Итак, эти строчки просто сканируют память, и если там есть 0xCC, программа прекращает выполнение.Не хочу тратить слишком много времени тут. Запустим strace:

root@lisa:~# strace ./CrackTheDoorexecve (»./CrackTheDoor», [»./CrackTheDoor»], [/* 17 vars */]) = 0[ Process PID=31085 runs in 32 bit mode. ]brk (0) = 0×9972000access (»/etc/ld.so.nohwcap», F_OK) = -1 ENOENT (No such file or directory)mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7715000access (»/etc/ld.so.preload», R_OK) = -1 ENOENT (No such file or directory)open (»/etc/ld.so.cache», O_RDONLY) = 3fstat64(3, {st_mode=S_IFREG|0644, st_size=35597, …}) = 0mmap2(NULL, 35597, PROT_READ, MAP_PRIVATE, 3, 0) = 0xfffffffff770c000close (3) = 0access (»/etc/ld.so.nohwcap», F_OK) = -1 ENOENT (No such file or directory)open (»/lib32/libc.so.6», O_RDONLY) = 3read (3,»\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300o\1\0004\0\0\0»…, 512) = 512fstat64(3, {st_mode=S_IFREG|0755, st_size=1441884, …}) = 0mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff770b000mmap2(NULL, 1456504, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xfffffffff75a7000mprotect (0xf7704000, 4096, PROT_NONE) = 0mmap2(0xf7705000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0×15d) = 0xfffffffff7705000mmap2(0xf7708000, 10616, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7708000close (3) = 0mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff75a6000set_thread_area (0xffe4d864) = 0mprotect (0xf7705000, 8192, PROT_READ) = 0mprotect (0×8049000, 4096, PROT_READ) = 0mprotect (0xf7733000, 4096, PROT_READ) = 0munmap (0xf770c000, 35597) = 0ptrace (PTRACE_TRACEME, 0, 0×1, 0) = -1 EPERM (Operation not permitted)ptrace (PTRACE_TRACEME, 0, 0×1, 0) = -1 EPERM (Operation not permitted)

Судя по последним двум строчкам, программа упала опять. Из-за системного вызова ptrace.In Linux, ptrace is an abbreviation for «Process Trace». With ptrace, you can control another process, changing its internal state like debuggers.

Ptrace — это аббревиатура для «Process Trace». При помощи ptrace вы можете контролировать другой процесс меняя его состояние, как это делают отладчики.

Отладчики очень интенсивно используют ptrace. Это их работа.

Код для такого участка может выглядеть примерно так:

int main () { if (ptrace (PTRACE_TRACEME, 0, 1, 0) < 0) { printf("DEBUGGING... Bye\n"); return 1; } printf("Hello\n"); return 0; }

Кстати, вы можете сделать ptrace[PTRACE_TRACEMe] только один раз, так что если отладчик вызвал ptrace на нашей программе до этого, то наш вызов вернёт false и мы поймём, что кто-то контролирует программу извне.Нам нужно обойти защиту на ptrace чтобы программа не думала, что мы пытаемся её отлаживать.Мы заменим результат системного вызова.Мы будем определять когда программа вызывает ptrace и возвращать ноль.В домашней папке я создал новый .gdbinit файл. Следовательно, каждый раз когда я запускаю gdb, конфигурация будет загружаться автоматически. ~/.gdbinitset disassembly-flavor intel # Intel syntax is betterset disassemble-next-line oncatch syscall ptrace #Catch the syscall.commands 1set ($eax) = 0continueend

В eax будет результат системного вызова. И он всегда будет 0, что означает истину в нашем случае.Таким образом мы обошли защиту от отладки и можем вернуться к gdb.

eren@lisa:~$ gdb ./CrackTheDoorGNU gdb (GDB) 7.4.1-debianCopyright © 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])Reading symbols from /home/eren/CrackTheDoor…(no debugging symbols found)…done.(gdb) rStarting program: /home/eren/CrackTheDoor

Catchpoint 1 (call to syscall ptrace), 0×08047698 in? ()=> 0×08047698: 3d 00 f0 ff ff cmp eax,0xfffff000

Catchpoint 1 (returned from syscall ptrace), 0×08047698 in? ()=> 0×08047698: 3d 00 f0 ff ff cmp eax,0xfffff000

*** DOOR CONTROL SYSTEM ***

PASSWORD:

Я поставил breakpoint на PJeGPC4TIVaKFmmy53DJ Breakpoint 2, 0x08048534 in PJeGPC4TIVaKFmmy53DJ () => 0x08048534 : 1e push ds (gdb) x/40i $pc => 0x8048534 : push ds 0x8048535 : mov ebp,esp 0x8048537 : sub esp,0x20 0x804853a : mov BYTE PTR [ebp-0x1],0xe4 0x804853e : mov BYTE PTR [ebp-0x2],0x87 0x8048542 : mov BYTE PTR [ebp-0x3],0xfb 0x8048546 : mov BYTE PTR [ebp-0x4],0xbe 0x804854a : mov BYTE PTR [ebp-0x5],0xc9 0x804854e : mov BYTE PTR [ebp-0x6],0x93 0x8048552 : mov BYTE PTR [ebp-0x7],0x84 0x8048556 : mov BYTE PTR [ebp-0x8],0xfc 0x804855a : mov BYTE PTR [ebp-0x9],0x8d 0x804855e : mov BYTE PTR [ebp-0xa],0xe5 0x8048562 : mov BYTE PTR [ebp-0xb],0xbf 0x8048566 : mov BYTE PTR [ebp-0xc],0x5c 0x804856a : mov BYTE PTR [ebp-0xd],0xe2 0x804856e : mov BYTE PTR [ebp-0xe],0x76 0x8048572 : mov BYTE PTR [ebp-0xf],0x21 0x8048576 : mov BYTE PTR [ebp-0x10],0xb8 0x804857a : mov DWORD PTR [ebp-0x14],0x0 0x8048581 : mov eax,DWORD PTR [ebp-0x14] 0x8048584 : add eax,DWORD PTR [ebp+0x8] 0x8048587 : movzx eax,BYTE PTR [eax] 0x804858a : test al,al 0x804858c : je 0x8048808 0x8048592 : mov eax,DWORD PTR [ebp-0x14] 0x8048595 : add eax,DWORD PTR [ebp+0x8] 0x8048598 : mov edx,DWORD PTR [ebp-0x14] 0x804859b : add edx,DWORD PTR [ebp+0x8] 0x804859e : movzx edx,BYTE PTR [edx] 0x80485a1 : xor dl,BYTE PTR [ebp-0x1] 0x80485a4 : mov BYTE PTR [eax],dl 0x80485a6 : add DWORD PTR [ebp-0x14],0x1 0x80485aa : mov eax,DWORD PTR [ebp-0x14] 0x80485ad : add eax,DWORD PTR [ebp+0x8] 0x80485b0 : movzx eax,BYTE PTR [eax] 0x80485b3 : test al,al 0x80485b5 : je 0x804880b 0x80485bb : mov eax,DWORD PTR [ebp-0x14] 0x80485be : add eax,DWORD PTR [ebp+0x8] 0x80485c1 : mov edx,DWORD PTR [ebp-0x14] 0x80485c4 : add edx,DWORD PTR [ebp+0x8] 0x80485c7 : movzx edx,BYTE PTR [edx] 0x80485ca : xor dl,BYTE PTR [ebp-0x2] Эта часть интересна. Я вижу манипулирование константами и то, что к подающимся данным на вход применяется XOR констант. Продолжим… (gdb) x/30i X1bdrhN8Yk9NZ59Vb7P2 0×8048838 : sbb ecx, DWORD PTR [ecx+0×20ec83e5] 0×804883e : mov DWORD PTR [ebp-0×18],0×0 0×8048845 : mov BYTE PTR [ebp-0×1],0xd9 0×8048849 : mov BYTE PTR [ebp-0×2],0xcd 0×804884d : mov BYTE PTR [ebp-0×3],0xc9 0×8048851 : mov BYTE PTR [ebp-0×4],0xe5 0×8048855 : mov BYTE PTR [ebp-0×5],0×9e 0×8048859 : mov BYTE PTR [ebp-0×6],0xd0 0×804885d : mov BYTE PTR [ebp-0×7],0xe8 0×8048861 : mov BYTE PTR [ebp-0×8],0xa5 0×8048865 : mov BYTE PTR [ebp-0×9],0xaf 0×8048869 : mov BYTE PTR [ebp-0xa],0×87 0×804886d : mov BYTE PTR [ebp-0xb],0xd2 0×8048871 : mov BYTE PTR [ebp-0xc],0×79 0×8048875 : mov BYTE PTR [ebp-0xd],0xa9 0×8048879 : mov BYTE PTR [ebp-0xe],0×5d 0×804887d : mov BYTE PTR [ebp-0xf],0×7 0×8048881 : mov BYTE PTR [ebp-0×10],0×81 0×8048885 : mov DWORD PTR [ebp-0×14],0×0 0×804888c : mov eax, DWORD PTR [ebp-0×14] 0×804888f : add eax, DWORD PTR [ebp+0×8] 0×8048892 : movzx eax, BYTE PTR [eax] 0×8048895 : cmp al, BYTE PTR [ebp-0×1] 0×8048898 : je 0×80488a2 0×804889a : mov eax, DWORD PTR [ebp-0×18] Ещё константы…И оставшаяся часть функции:

0×804889d : jmp 0×8048a20 0×80488a2 : add DWORD PTR [ebp-0×14],0×1 0×80488a6 : mov eax, DWORD PTR [ebp-0×14] 0×80488a9 : add eax, DWORD PTR [ebp+0×8] 0×80488ac : movzx eax, BYTE PTR [eax] 0×80488af : cmp al, BYTE PTR [ebp-0×2] 0×80488b2 : je 0×80488bc 0×80488b4 : mov eax, DWORD PTR [ebp-0×18] 0×80488b7 : jmp 0×8048a20 0×80488bc : add DWORD PTR [ebp-0×14],0×1 0×80488c0 : mov eax, DWORD PTR [ebp-0×14] 0×80488c3 : add eax, DWORD PTR [ebp+0×8] 0×80488c6 : movzx eax, BYTE PTR [eax] 0×80488c9 : cmp al, BYTE PTR [ebp-0×3] 0×80488cc : je 0×80488d6 0×80488ce : mov eax, DWORD PTR [ebp-0×18] 0×80488d1 : jmp 0×8048a20 0×80488d6 : add DWORD PTR [ebp-0×14],0×1 0×80488da : mov eax, DWORD PTR [ebp-0×14] 0×80488dd : add eax, DWORD PTR [ebp+0×8] ---Type to continue, or q to quit--- 0×80488e0 : movzx eax, BYTE PTR [eax] 0×80488e3 : cmp al, BYTE PTR [ebp-0×4] 0×80488e6 : je 0×80488f0 0×80488e8 : mov eax, DWORD PTR [ebp-0×18] 0×80488eb : jmp 0×8048a20 0×80488f0 : add DWORD PTR [ebp-0×14],0×1 0×80488f4 : mov eax, DWORD PTR [ebp-0×14] 0×80488f7 : add eax, DWORD PTR [ebp+0×8] 0×80488fa : movzx eax, BYTE PTR [eax] 0×80488fd : cmp al, BYTE PTR [ebp-0×5] 0×8048900 : je 0×804890a 0×8048902 : mov eax, DWORD PTR [ebp-0×18] 0×8048905 : jmp 0×8048a20 0×804890a : add DWORD PTR [ebp-0×14],0×1 0×804890e : mov eax, DWORD PTR [ebp-0×14]

Видите ли вы те же закономерности, что вижу я? Если нет — не беда.Здесь в программе проверяется мой ввод поксоренный с константами.Теперь ещё раз посмотрим на ввод, первые байты ксорятся с некоторыми константами, и затем вывод сравнивается с другими костантами.Последние две функции должны выглядеть как-то так: void PJeGPC4TIVaKFmmy53DJ (int * p) { int array[] = {0xe4,0×87,0xfb,0xbe,0xc9,0×93,0×84,0xfc,0×8d,0xe5,0xbf,0×5c,0xe2,0×76,0×21,0xb8} for (i=0; i<16;i++) { p[i] = p[i] ^ array[i] } } int X1bdrhN8Yk9NZ59Vb7P2(int * p) { int array = {0xd9,0xcd,0xc9,0xe5,0x9e,0xd0,0xe8,0xa5,0xaf,0x87,0xd2,0x79,0xa9,0x5d,0x7,0x81} for(i=0;i<16;i++) { if(p[i] != array[i]) return false; // fail.. } return true } Я набросал скрипт на Python чтобы применить XOR к этим константам и нашёл ключ: #!/usr/bin/python firstConst = [0xe4,0x87,0xfb,0xbe,0xc9,0x93,0x84,0xfc,0x8d,0xe5,0xbf,0x5c,0xe2,0x76,0x21,0xb8] secondConst = [0xd9,0xcd,0xc9,0xe5,0x9e,0xd0,0xe8,0xa5,0xaf,0x87,0xd2,0x79,0xa9,0x5d,0x7,0x81] ret ="" for x in range(16): ret+=chr(firstConst[x] ^ secondConst[x]) print ret eren@lisa:~$ ./CrackTheDoor

*** DOOR CONTROL SYSTEM ***

PASSWORD: =J2[WClY«bm%K+&9

*** ACCESS GRANTED ***

*** THE DOOR OPENED ***

Voilà! Компания прислала мне вторую часть, так что вскоре я напишу ещё одну статью.

© Habrahabr.ru