![]() |
Анализ стека
Article: Анализ стека
Author: Great Date: 12.08.2006/3.12.2006 Theme: Coding/Reversing Lang.: C/C++ Base: - Note: Статья рассчитана знающих язык С++, программирующих на Win32 API и знающих основы вызова функций в ассемблере. I. Кручу-верчу, раскрутить хочу Вероятно, ты не раз видел во всяких полезных утилитах вроде отладчика или просмотрщика процессов одну полезную функцию - раскрутка стека. Если нет - поясню, что это такое. При вызове функции командой CALL в стеке сохраняется адрес возврата, который потом снимается оттуда командой RET. Проанализировав стек потока, можно узнать, каким путем и через какие вызовы выполнение добралось до текущей точки. Такая полезная возможность есть, например, в Process Explorer'е от небезызвестного Марка Руссиновича или, например, во встроенном отладчике в MS Visual Studio. Обычно, формат выводимой информации таков: имя_модуля!имя_функции + смещение [аргументы] Насчет аргументов. По идее, в стек запихиваются и переданные в функцию аргументы, поэтому можно при анализе стека еще и показывать аргументы. Правда, нигде не сохраняется информация об их размере, потому что вызываемая программа обычно знает размер своих аргументов. Но так как большая часть всех аргументов имеет размер 32 бита или 4 байта (это, например, int, long, enum, bool, все указатели и еще некоторые типы), это не большая проблема.) Попробуем и мы реализовать такую штуку, тем более, что это не сложно. II. Инструменты Для работы с именами функций и модулями мы будем использовать библиотеку DBGHELP.DLL, входящую в базовый комплект поставки детища дяди Билла. (По странной причине я не обнаружил в MS VC++ 6.0 к ней библиотеки импорта, хотя хидер там был - чудеса :). Пришлось спереть либу из masm32.) В ней для нас полезны следующие функции: SymInitialize: Подготовка процесса к работе с символами Код:
BOOL SymInitialize(SymGetSymFromAddr: Возвращает имя функции и ее стартовый адрес по адресу какого-то, принадлежащего ей, байта. Код:
BOOL SymGetSymFromAddr(Код:
typedef struct _IMAGEHLP_SYMBOL {Код:
BYTE lpMemory[256];SymGetModuleBase: возвратить базовый адрес загрузки модуля по адресу какого-то его байта Код:
DWORD SymGetModuleBase(Код:
DWORD SymSetOptions(SYMOPT_CASE_INSENSITIVE Поиск имен ведется без учета регистра символов SYMOPT_UNDNAME Все имена представлены в обычном виде (без постфиксов @N, характерных для соглашения вызова stdcall) SYMOPT_DEFERRED_LOADS Имена не загружаются, пока не возникнет ссылка на них. Это наиболее быстрый способ использования имен. SYMOPT_NO_CPP Во всех именах C++, содержащих '::', будет произведена замена на '__' SYMOPT_LOAD_LINES Загрузить информацию о номерах строк Так же нам понадобится стандартная API GetModuleFileName() III. Кодинг Первое, что мы напишем, будет функция получения имени функции и ее стартового адреса - оболочка для SymGetSymFromAddr. Сначала надо проинициализировать процесс. Мы вызовем SymSetOptions и укажем DBGHELP.DLL производить отложенную загрузку и раздекорировать имена. Далее мы выделим память для структуры IMAGEHLP_SYMBOL и вызовем SymGetSymFromAddr. Итак, Код:
/* GetProcBaseAddressAndName()Код:
/* GetModuleAndFunctionNameByAddress()Код:
/* StackUnwind()... |
Теперь мы можем вызвать эту функцию прямо из WinMain и узнать, что творится в стеке главного потока нашей программы. Но мы для наглядности напишем пару-тройку вызывающих друг друга процедур, чтобы проследить местоположение адресов возврата в стеке.
Код:
void f3()Результат анализа стека показан ниже: Цитата:
Но что за странные строчки после WinMain и до конца? А все дело в том, что выполнение любого потока (кроме главного потока программы) начинается с функции BaseThreadStart, а главного потока программы - с BaseProcessStart. Она устанавливает некоторые параметры потоку, в том числе и истинную точку входа и передает управление на неё - в данном случае, на WinMain. Почему же не распозналась функция BaseProcessStart, а вместо нее идет RegisterWaitForInputIdle, которая, казалось бы, совсем не к месту? А все дело в причудливом расположении этих функций в kernel32.dll. Сначала идет BaseProcessStart, в которой есть переход на 64С0 байт вперед, за ее пределы, потом идет RegisterWaitForInputIdle, а потом идет продолжение BaseProcessStart, куда она перепрыгивала с 64С0 байт назад. Выглядит все это примерно так (листинг IDA Pro): Код:
.text:7C810867 BaseProcessStart proc near ; DATA XREF: sub_7C81059D+47D4oСмещение 0x0046 относительно RegisterWaitForInputIdle - это адрес инструкции Код:
.text:7C816D4C call dword ptr [ebp+8]Код:
.text:7C816D4F push eax ; dwExitCodeАдрес возврата в ntdll!NtSetInformationThread появляется просто по причине того, что в ntdll содержатся функции-переходники для Native API. Как раз NtSetInformationThread является одним из таких переходников. Смещение 0x000c относительно NtSetInformationThread - это адрес инструкции после call, затолкнутый процессором в стек при выполнении инструкции call. Код:
.text:7C90E642 public ZwSetInformationThreadПопробуем теперь распотрошить стек любого другого процесса. Что же нам потребуется - найдем первый попавшийся поток этого процесса, приостановим, снимем его контекст, снимем дамп его стека и возобновим поток. Потом будем производить анализ стека точно так же, как и для текущего процесса. Поскольку я использую Microsoft Visual C++ 6.0 и ленюсь обновить свой давно устаревший SDK :), а функция OpenThread отсутствовала в ранних версиях Windows, мне пришлось написать оболочку для динамического вызова OpenThread: Код:
HANDLE WINAPI OpenThread(BOOL StackRemoteUnwind(DWORD dwProcessId, DWORD dwThreadId, DWORD nSize=10, BOOL bSkipUnknownModules = TRUE) dwProcessId - ID процесса dwThreadId - ID потока nSize - сколько адресов анализировать bSkipUnknownModules - пропускать ли адреса, чьи модули не известны. Для начала открываем дескрипторы процесса и потока: Код:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwProcessId);Код:
CONTEXT ctx = {CONTEXT_FULL};Код:
char buffer[10240] = "";Код:
/* GetPIDbyName()Код:
// Создаем снимок всех потоков системыВообще то, на этом стоит и закончить и без того затянувшееся повествование об устройстве потоков :) Замечу только, что в сорце к статье вместо MessageBox для вывода информации используется самописная функция AlternateMessageBox, которая выводит диалоговое окно с текстовым полем для удобства копирования текста из него. Выход производится по нажатию любой клавиши. Код этой функции, если сильно повезет, ты найдешь в сорсе к статье, и я думаю, что знакомые с программированием окон на Win32 API, без труда его разберут. На сим откланяюсь, удачного компилирования :) (С) Great, 2006. Source: http://cribble.by.ru/data/stackunwind.cpp |
| Время: 02:21 |