voroshil's Blog

Здесь должно быть что-то умное

Использование Gdb для сравнения работы дизассемблированной программы с оригиналом

В процессе обратного инжиниринга (Reverse Engineering) может возникнуть ситуация, когда требуется проверить эквивалентность работы восстановленного кода с работой исходного бинарного файла.

Далее я постараюсь описать как можно немного автоматизировать этот процесс

Как мне кажется, проблему проверки эквивалентности работы бинарного кода можно свести к проверке идентичности значений регистров процессора или значений ячеек памяти в ключевых точках анализируемой программы (поправьте меня, если это не так).

Далее я буду рассматривать задачу сравнения значения параметра передаваемого в функцию через стек. В качестве рабочей ОС языка будет использоваться Linux (дистрибутив особой роли не играет, т.к. набор необходимых программ есть практически в каждом из них), язык программирования - C, отладчик - GDB, дизассемблер - objdump из комплекта binutils.

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

objdump -d -M intel foo > foo.asm

Получаем программу на Ассемблере (для краткости я пропускаю процесс анализа полученного файла, поиск процедур и т.п.) с процедурой foo, значение параметра которой мы будет анализировать:

08048344 <bar>:
 8048344:    55                       push   ebp
 8048345:    89 e5                    mov    ebp,esp
 8048347:    8b 45 08                 mov    eax,DWORD PTR [ebp+0x8]
 804834a:    83 c0 01                 add    eax,0x1
 804834d:    5d                       pop    ebp
 804834e:    c3                       ret

0804834f <main>:
 804834f: 8d 4c 24 04             lea    ecx,[esp+0x4]
 8048353: 83 e4 f0                and    esp,0xfffffff0
 8048356: ff 71 fc                push   DWORD PTR [ecx-0x4]
 8048359: 55                      push   ebp

Если надо сверить результат всего лишь несколько раз, то достаточно установить точку отладки в определенном месте и сверить значение регистров/участков памяти. Например, следующим образом:

$gdb foo

GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &lt;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 "i486-linux-gnu"...
(gdb) break bar
Breakpoint 1 at 0x8048347
(gdb) run
Starting program: foo

Breakpoint 1, 0x08048347 in bar ()
Current language:  auto; currently asm
(gdb) x/d $ebp+8
0xbf9b1ef0:    1
(gdb) cont
Continuing.

Breakpoint 1, 0x08048347 in bar ()
(gdb) x/d $ebp+8
0xbf9b1ef0:    2
(gdb) quit
The program is running.  Exit anyway? (y or n)

Чтобы не вводить команды каждый раз, можно воспользоваться возможностью GDB исполнять скрипты. Достаточно записать последовательность команд в текстовый файл и указать его GDB при помощи ключа "-x":

$cat foo.gdb
file foo
break bar
run
x/d ($ebp+8)
cont
x/d ($ebp+8)
quit

$gdb -x foo.gdb

GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &lt;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 "i486-linux-gnu".
Breakpoint 1 at 0x8048347

Breakpoint 1, 0x08048347 in bar ()
Current language:  auto; currently asm
0xbfef4410:   1

Breakpoint 1, 0x08048347 in bar ()
0xbfef4410:   2
The program is running.  Exit anyway? (y or n) [answered Y; input not from terminal]

Имя исполняемого файла в командной строке можно не указывать, т.к. это сделает первая выполняемая команда - "file foo". Оператор "x/d address" выводит в десятичном формате содержимое памяти по адресу "address".

Указанный способ хорош, когда количество итераций стравнитеьно невелико, но что делать, если необходимо проверить значение переменной в нескольких сотнях итераций? В этом случае поможет оператор "command". Он позволяет указать какая последовательность команд должна автоматически выполняться при достижении точки останова. Изменим скрипт foo.gdb следующим образом:

file foo

break bar
commands
  x/d ($ebp+8)
  cont
end

run
quit

Операторы между "commands" и "end" будут последовательно выполнены при каждом достижении точки останова установленной предшествующим им оператором "break foo":

$gdb -x foo.bar

GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &lt;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 "i486-linux-gnu".
Breakpoint 1 at 0x8048347

Breakpoint 1, 0x08048347 in bar ()
Current language:  auto; currently asm
0xbff3a460:   1

Breakpoint 1, 0x08048347 in bar ()
0xbff3a460:   2

Breakpoint 1, 0x08048347 in bar ()
0xbff3a460:   3

Program exited normally.

Итак. Пишем код на С, который (как мы считаем) выполняет ту же работу, что и исходная программа. Добавляем в него отладочный оператор printf:

#include <stdio.h>
int my_bar(int param)
{
    printf("param: %f\n", param);
    return param+1;
}

int main(void)
{
    int i=1;

    i = my_bar(i);
    i = my_bar(i);
    i = my_bar(i);

    return 0;
}

Можно на этом остановиться и вручную сравнивать выводимые значения, но, к счастью, в GDB тоже имеется оператор printf, синтаксис которого идентичен. Изменим скрипт foo.gdb и заменим в нем оператор "x/d" на printf:

file foo

break bar
commands
  printf "param: %d\n", *(int*)($ebp+8)
  cont
end

run
quit

$gdb goo.gdb | grep "param:"
param: 1
param: 2
param: 3

Теперь вывод GDB аналогичен выводу нашей программы и можно воспользоваться утилитой diff для сравнения вывода GDB и вывода нашей программы:

$gdb goo.gdb | grep "param:" > foo.log
$./my_foo > my_foo.log
$diff foo.log my_foo.log

Если вывод обеих программ совпадает, то diff не должна ничего вывести на консоль.

Таким, образом (правильно расставляя точки останова и добавляя операторы printf) можно сравнивать работу большинства программ. Если же потребуется более сложный анализ (например сравнение массивов), то существуют операторы for, if, а также базовая поддержка процедур, реализуемая оператором define. Возможно, я расскажу о них в другой раз.