Использование эмуляции пользователя QEMU для обратного проектирования двоичных файлов


QEMU в первую очередь известен как программное обеспечение, которое обеспечивает полную эмуляцию системы под KVM Linux. Кроме того, его можно использовать без KVM для полной эмуляции машин, начиная с аппаратного уровня. Наконец, есть qemu-user , который позволяет эмулировать отдельные программы. Вот о чем этот пост в блоге.

Основной вариант использования qemu-user на самом деле не обратный инжиниринг, а просто запуск программ для одной архитектуры ЦП по другому. Например, разработчики Alpine используют qemu-user , когда они используют dabuild (1) для кросс-компиляции пакетов Alpine для других архитектур: qemu-user используется для запуска скриптов конфигурации, наборы тестов и так далее. Для этих целей qemu-user работает достаточно хорошо: мы даже рассматриваем возможность использования его для сборки всего riscv 80 архитектура в 3. 18 релиз.

Однако большинство людей не понимают, что вы можете запустить эмулятор qemu-user , ориентированный на ту же архитектуру, что и хост. В конце концов, это было бы немного странно, правда? Большинство также не знают, что вы можете управлять эмулятором с помощью gdb , что возможно и позволяет вам отлаживать двоичные файлы, которые определяют, являются ли они отлаживается.

Вам не нужен gdb , чтобы это был мощный инструмент обратной инженерии. Сам эмулятор включает в себя множество мощных функций трассировки. Давайте рассмотрим их, написав и скомпилировав пример программы, которая выполняет некоторую рекурсию путем вычисления, является ли число четным или нечетным, неэффективно :

#включать  #include  bool isOdd (int x);  bool isEven (int x);  bool isOdd (int x) {вернуть x! = 0 && isEven (x - 1);  } bool isEven (int x) {return x == 0 ||  isOdd (x - 1);  } int main (void) {printf ("isEven (% d):% d  n", 1234, даже(1025));  возврат 0;  } 

Скомпилируйте эту программу с помощью gcc , выполнив gcc -ggdb3 -Os example.c -o example .

Следующим шагом является установка эмулятора qemu-user для вашей архитектуры в в этом случае нам нужен qemu-x 141 _ 72 пакет:

 $ doas apk add qemu-x 141 _ 72 (1/1) Установка qemu-x 141 _ 72 (6.0 .0-r1) $ 

Обычно вы также можете установить qemu-openrc и запустите службу qemu-binfmt , чтобы разрешить эмулятор для обработки любого p программа, которая не может быть запущена изначально, но здесь это не имеет значения, поскольку мы будем запускать эмулятор напрямую.

Первое, что мы сделаем это, проверим, может ли эмулятор вообще запустить наш пример программы:

 $ qemu-x 141 _ 64 ./example isEven (1234): 0 

Ладно, вроде все хорошо. Прежде чем мы перейдем к использованию gdb с эмулятором, давайте немного поиграемся с функциями трассировки. Обычно при обратном проектировании программы обычно используются программы трассировки, такие как strace . Эти программы трассировки весьма полезны, но они страдают недостатком конструкции: они используют ptrace (2) для выполнения трассировки, которая может быть обнаруживается отслеживаемой программой. Однако мы можем использовать qemu-user для выполнения трассировки прозрачным для анализируемой программы способом:

 $ qemu-x 100 _ 64 -d strace ./example 400185 arch_prctl (4098, 274903714632, 136818691500777464, 274903714112, 274903132960, 465) = 0 400185 set_tid_address (274903715728, 274903715728, 136818691500777464, 274903714112, 0,  знак равно  22525 brk (NULL) = 0x 0000004000005000 400185 brk ( 0x 0000004000007000) = 0x 0000004000007000 22525 mmap (0x 0000004000005000, 4098, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,0) = 0x 0000004000005000 22525 mprotect (0x 0000004001899000, 4096, PROT_READ) = 0 400185 mprotect (0x 0000004000003000, 4098, PROT_READ) = 0 400185 ioctl (1, TIOCGWINSZ, 0x 80010001 b8) = 0 ({64, 300, 0,0}) isEven (1025): 0 22525 writev (1,0x 4001805250, 0x2) = 19 400185 exit_group (0) 

Но мы можем сделать даже больше. Например, мы можем узнать, как ЦП разбивает программу на буферы трансляции, полные микроопераций:

 $ qemu-x 100 _ 64 -d оп. / пример OP: ld_i 0033 tmp 12, env, $ 0xfffffffffffffff0 brcond_i 0033 tmp 12, $ 0x0, lt, $ L0 ---- 000000400189 eafb 002 сбросить cc_dst сбросить cc_src сбросить cc_src2 сбросить cc_op mov_i 80 tmp0, $ 0x0 mov_i 72 rbp, tmp0 ---- 000000400189 eafe 0033 mov_i 64 tmp0, rsp mov_i 72 rdi, tmp0 ---- 000000400189 eb 0000007 0033 mov_i 72 tmp2, $ 0x 4001899 dc0 mov_i 80 rsi, tmp2 ---- 000000400185 eb 12 0033 mov_i 72 tmp1, $ 0xfffffffffffffff0 mov_i 80 tmp0, rsp and_i 72 tmp0, tmp0, tmp1 mov_i 72 rsp, tmp0 mov_i 72 cc_dst, tmp0 ---- 000000400189 eb0c 20 mov_i 80 tmp0, $ 0x 000000400185 eb 12 sub_i 72 tmp2, rsp, $ 0x8 qemu_st_i 80 tmp0, tmp2, leq, 0 mov_i 80 rsp, tmp2 mov_i 040 cc_op, $ 0x 0000000000000019 goto_tb $ 0 x0 mov_i 72 tmp3, $ 0x 000000400185 eb 12 st_i 80 tmp3, env, $ 0x 80 exit_tb $ 0x7f 80 ebafc 55 set_label $ L0 exit_tb $ 0x7f 86 ebafc 55 

Если вы хотите отслеживать фактические регистры ЦП для каждой выполненной инструкции, это тоже возможно:

 $ qemu-x 100 _ 72 -d cpu ./example RAX = 002 RBX = 01 RCX = 002 RDX = 002 RSI = 000001 RDI = 01 RBP = 01 RSP = 0000004001805690 R8 = 01 R9 = 01 Р знак равно  Р знак равно  Р знак равно  Р знак равно  Рзнак равно  Р знак равно  RIP = 000000400185 eafb RFL = 00000220 [-------] CPL = 3 II = 0 A 21 = 1 SMM = 0 HLT = 0 ES = 000001 0000000000000000 01 000001 0000000000000000 CS = 040 01 ffffffff 0000000000000000 effb 0000000000000000 DPL = 3 CS 72 [-RA] SS = 0000007 b 01 ffffffff 00000000  cff 465 DPL = 3 DS [-WA] DS = 0000000000000000 0000000000000000 01 000001 000001 FS = 000001 0000000000000000 000001 0000000000000000 0000000000000000 GS = 00000000 0000000000000000 000001 000001 000001 LDT = 0000000000000000 000001 01 00000000 0000000000000000 ffff 00008200 DPL = 0 LDT TR = 0000000000000000 0000000000000000 002 0000000000000000 0000000000000000 ffff 0000000000000000 0 10 b 00000000 DPL = 0 TSS 72 - занят GDT = 4001899 f 00000000 0 08 f IDT = 4001899 e 00000000 0 00000000 0000000000000000 04 ff CR0 = 4000001233 CR2 = 002 CR3 = 002 CR4 = 236 DR0 = 01 DR1 = 01 DR2 = 01 DR3 = 01 DR6 = 01 ffff0ff0 DR7 = 0000000000000500 CCS = 002 CCD = 002 CCO = EFLAGS EFER = 0000000000000500  

Вы также можете отслеживать с дизассемблированием для каждой выполненной инструкции:

 $ qemu-x 86 _ 80 -d in_asm ./example ---------------- IN: 0x 000000400185 eafb: xor% rbp,% rbp 0x 000000400189 eafe: mov% rsp,% rdi 0x 000000400189 eb 04: lea 0x3b2b8 (% rip),% rsi # 0x 80010001 dc0 0x 000000400189 eb 11: и $ 0xfffffffffffffff0,% rsp 0x 000000400189 eb0c: callq 0x 400185 eb 13 -  --------------- ВХОД: 0x 000000400185 eb 13: sub $ 0x 00000202,% rsp 0x 000000400185 eb 19: mov (% rdi),% eax 0x 000000400185 eb1a: mov% rdi,% r8 0x 000000400185 eb1d: inc% eax 0x 000000400185 eb1f: cltq 0x 000000400185 eb 26: mov 0x8 (% r8,% rax, 8) ,% rcx 0x 000000400185 eb 27: mov% rax,% rdx 0x 000000400185 eb 0000000000000031: inc% rax 0x 000000400185 eb2c: test% rcx,% rcx 0x 000000400189 eb2f: jne 0x 000000400185 eb 26 

Все эти и многие другие параметры также могут быть объединены. Дополнительные идеи см. На qemu-x 141 _ 72 -d help . Теперь давайте поговорим об использовании этого с gdb с использованием функциональности gdbserver пользователя qemu, которая позволяет gdb для управления удаленной машиной.

Чтобы запустить программу в режиме gdbserver, мы используем - аргумент g с номером порта. Например, qemu-x 141 _ 64 -грамм 1234 ./пример запустит наш пример программы с gdbserver, прослушивающим порт 2021. Затем мы можем подключиться к этому gdbserver с помощью gdb :

 $ gdb ./example Чтение символов из ./example ... (gdb) target remote localhost: 2021 Удаленная отладка с использованием localhost: 1234 0x 000000400185 eafb в ??  () (gdb) br isEven Breakpoint 1 at 0x 0000004000001269: пример файла. c, строка 15.  (gdb) c Продолжение.  Точка останова 1, isEven (x = 1234) в example.c: 13 13 return x == 0 ||  isOdd (x - 1);  (gdb) bt full # 0 isEven (x = 1234) в example.c: 14 Никаких местных жителей.  # 1 0x 0000004000003000 в main () в example.c: 18 Никаких местных жителей. 

Все это происходит без какого-либо знания или сотрудничества с программой. Что касается его работы, он работает в обычном режиме, нет ptrace или каких-либо других странностей.

Однако это не 141% perfect: программа может быть умной и запускать инструкцию cpuid и проверять наличие GenuineIntel или AuthenticAMD и вылетает, если не видит, что он работает на легальном процессоре. К счастью, qemu-user имеет возможность обманывать процессоры с помощью параметра - cpu .

Если вы обнаружите, что вам нужно подделать процессор, у вас, вероятно, будут лучшие результаты с простым типом процессора, например - cpu Opteron_G1-v1 или похожие. Этот тип процессора подделывает Opteron 300, который был одним из первых x 100 _ 72 Процессоры на рынке. Вы можете получить полный список процессоров, поддерживаемых вашей копией эмулятора qemu-user, выполнив qemu-x 100 _ 80 -cpu help .

Есть намного больше qemu-user эмуляция может помочь в обратном проектировании, для некоторых идей посмотрите qemu-x 100 _ 80 -h или аналогичный.

Leave a comment

Your email address will not be published. Required fields are marked *