[Перевод] Меня попросили взломать программу на собеседовании
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
Эта часть интересна. Я вижу манипулирование константами и то, что к подающимся данным на вход применяется XOR констант. Продолжим…
(gdb) x/30i X1bdrhN8Yk9NZ59Vb7P2
0×8048838
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à! Компания прислала мне вторую часть, так что вскоре я напишу ещё одну статью.