Одним из важнейших аспектов обратной разработки Reverse Engineering является понимание архитектуры процессора и, в частности, работы стека.
Стек — это структура данных типа LIFO (Last In, First Out), которая используется для хранения временных данных, таких как:
В архитектуре x86:
В архитектуре x64:
Пример простейшего вызова функции:
push ebp ; сохраняем предыдущий кадр стека mov ebp, esp ; создаём новый фрейм стека sub esp, 0x20 ; резервируем 32 байта под локальные переменные ... mov esp, ebp ; восстанавливаем стек pop ebp ; восстанавливаем старый ebp ret ; возвращаем управление вызывающему коду
; вызывающий код push 0x2 ; аргумент 2 push 0x1 ; аргумент 1 call add_numbers ; вызов функции ... add_numbers: push ebp mov ebp, esp mov eax, [ebp+8] ; первый аргумент add eax, [ebp+12] ; второй аргумент pop ebp ret
Разбор:
Когда функция вызывается, стек выглядит так (снизу вверх):
[ebp+12] — второй аргумент [ebp+8] — первый аргумент [ebp+4] — адрес возврата (EIP) [ebp] — сохранённый ebp вызывающей функции [ebp-4] — локальная переменная 1 [ebp-8] — локальная переменная 2 ...
| Команда | Описание |
|---|---|
| `push reg` | Помещает значение регистра в стек, `ESP -= 4` |
| `pop reg` | Извлекает значение из вершины стека в регистр, `ESP += 4` |
| `call addr` | Помещает адрес следующей инструкции (`EIP`) в стек, затем прыгает по адресу |
| `ret` | Извлекает адрес возврата из вершины стека и прыгает на него |
Инструменты позволяют анализировать вызовы функций, структуру стека, и регистры:
Понимание работы стека — фундаментальный навык для любого инженера, занимающегося реверсом. Он позволяет точно определять логику исполнения, отлаживать, выявлять уязвимости и производить полную реконструкцию логики бинарного файла.