45821 (API Spying), страница 3
Описание файла
Документ из архива "API Spying", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.
Онлайн просмотр документа "45821"
Текст 3 страницы из документа "45821"
Не содержит в себе ничего сложного. Работает по алгоритму установки одной функции-шпиона, в качестве сохранения информации о перехваченной функции сообщает внешнему приложению имя функции и получает в ответ соответствующий этой функции номер.
void* __stdcall myGetProcAddress(HMODULE hLib, const char* name) { // Вызываем настоящую GetProcAddress, получаем адрес функции void* address = _GetProcAddress(hLib, name); if (address == 0) { // Не судьба return NULL; } char full_name[MAX_PATH * 2]; GetModuleFileNameA(hLib, full_name, sizeof(full_name)/sizeof(full_name[0])); strcat(full_name, " "); if (reinterpret_cast(name) > 0x0000ffff) { // Копируем имя strcat(full_name, name); } else { // А некоторые функции экспортируются по ординалам... char ordinal[10]; strcat(full_name, "by ordinal: "); strcat(full_name, itoa(reinterpret_cast(name), ordinal, 16)); } COPYDATASTRUCT cd = {0}; // 1 требуется, чтобы учесть в длине завершающий NULL-символ. cd.cbData = strlen(full_name) + 1; cd.lpData = full_name; // посылаем строчку int number = SendMessage(g_hSecretWindow, WM_COPYDATA, 0, reinterpret_cast(&cd)); // Генерируем функцию-шпиона try { // См. «Чем же всё это закончится?» void* spyMem = HeapAlloc(GetProcessHeap(), 0, sizeof(spy_function)); spy_function* spy = new(spyMem) spy_function; // Устанавливаем её параметры. spy->number.value = number; spy->statistic.address.set_absolute(collectStatistic); spy->func.address.set_absolute(address); // Возвращаем указатель на функцию-шпион. return spy; } catch (...) { // Не судьба PostMessage(g_hSecretWindow, WM_CANNOTHOOK, number, 0); // Возвращаем указатель на функцию return address; } } |
collectStatistic
Поскольку данных мало и посылать их несложно, функция collectStatistic получилась просто замечательная:
void __stdcall collectStatistic(unsigned long n) { // Посылаем номер вызываемой функции PostMessage(g_hSecretWindow, WM_CALLED, n, 0); } |
Хранение и отображение
И тем и другим занимается внешнее приложение. Реализовано всё крайне незамысловато:
// Структура, хранящая статистику для одной функции struct func_descrition { std::string name; // Имя функции int count; // Количество вызовов }; // Вектор, хранящий всю статистику вообще std::vector functions; #define WM_CALLED (WM_USER + 1) #define WM_CANNOTHOOK (WM_USER + 2) // Процедура окна, которому внедрённая dll посылает данные LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { // Вызвана GetProcAddress case WM_COPYDATA: { // Получаем указатель на переданную структуру COPYDATASTRUCT* pcd = reinterpret_cast(lParam); // Получаем имя char* str = (char*)pcd->lpData; printf("New function: %s\n", str); // Новая функция func_descrition f; f.count = 0; f.name = str; // Добавляем её в вектор functions.push_back(f); } // Возвращаем номер return (functions.size() - 1); // Вызвана перехваченная функция case WM_CALLED: // Увеличиваем количество вызовов functions[wParam].count++; printf("Called %s\n", functions[wParam].name.c_str()); return 0; // Не удалось установиь перехватчик на функцию case WM_CANNOTHOOK: // Уведомляем пользователя printf("Can not hook %s\n", functions[wParam].name.c_str()); return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } |
ПРИМЕЧАНИЕ Для простоты этот код не проверяет имя функции на уникальность, поэтому в functions может оказаться несколько записей для одной и той же функции. |
Внедрение в приложение и перехват GetProcAddress
Так как эта статья не посвящена ни перехвату, ни внедрению (на эти темы есть много других хороших статей), для реализации выбраны простые, но радикальные средства. Внедрение сделано через CreateRemoteThread, а перехват GetProcAddress – заменой её первых пяти байт на команду jmp.
Для передачи внедрённой dll описателя окна, которому она должна посылать сообщения (g_hSecretWindow в примере), использована техника из статьи «HOWTO: Вызов функции в другом процессе».
Чем же всё это закончится?
Будет завершение процесса. Как известно, во время завершения процесса все dll выгружаются, и вся выделенная память освобождается. При этом могут произойти следующие неприятности:
Наша dll будет выгружена раньше времени.
Раньше времени будет освобождена память, в которой расположены сгенерированные функции.
В обоих случаях исследуемое приложение получит Access Violation, после чего говорить о том, что его работа не нарушена, будет достаточно сложно.
Невыгружаемая dll
Поскольку у нашей dll счётчик ссылок всегда больше 0 (LoadLibrary была вызвана, а FreeLibrary нет), она выгружается одной из последних, но в некоторых случаях этого может оказаться недостаточно. Радикальным решением проблемы является «ручная» загрузка dll, описанная в статье Максима М. Гумерова «Загрузчик PE-файлов». Это довольно трудоёмкий, но зато практически гарантированный вариант. Другим возможным решением (для NT/2000/…) может быть удаление dll из списка загруженных модулей в PEB, но как это сделать и будет ли это работать, я пока не знаю…
Последняя идея, пришедшая мне в голову:
честно загрузить dll в процесс, позволить загрузчику выполнить свою работу
скопировать получившийся образ
выгрузить dll
записать в то же место адресного пространства образ dll.
молиться.
Это один из самых «грязных хаков», которые я когда-либо проворачивал :) Иногда оно работает, иногда – нет. И даже если всё на первый взгляд работает, я не берусь сказать, какие будут побочные эффекты.
Подводя итог: если задача и имеет хорошее решение, его описание выходит далеко за рамки этой статьи. Поэтому наша dll будет выгружаться, хотя иногда это и может привести к проблемам.
Неосвобождаемая память
С памятью проще: чтобы её точно никто не освободил, достаточно отказаться от стандартного оператора new, и использовать вместо него placement new, выделяя память как-нибудь иначе.
ПРИМЕЧАНИЕ Во время тестов обнаружилось, что в Windows XP, при выделении памяти обычным new и статической линковке CRT, некоторые (не все и не всегда, но вполне воспроизводимо) блоки памяти с функциями-шпионами оказываются освобождены. При использовании CRT в dll этой проблемы не было, с чем всё это связано, я не знаю. |
Результат
Yes! Оно работает!! :)
ПРЕДУПРЕЖДЕНИЕ Нормального тестирования не проводилось, кроме того, у меня под рукой не оказалось Windows NT 4. Но на Windows 2000, XP и 2003 Server проверил, на первый взгляд всё путём… И даже XP SP2 не страшен :) |
Для успешного старта надо положить spyloader.exe и apispy.dll в один каталог, после чего запустить spyloader, передав ему в командной строке путь к exe-файлу исследуемого приложения.
Только приготовьтесь к тому, что GetProcAddress – довольно популярная функция, и получить сотню функций-шпионов (то есть вызовов GetProcAddress) при исследовании notepad.exe – не вопрос, достаточно попытаться открыть какой-нибудь файл. А уж если вы запустите справку и немного по ней походите… У меня получилось 530 функций-шпионов за две минуты :) Поэтому, если вы действительно будете реализовывать нечто подобное, то лучше фиксировать не всё подряд, а фильтровать вызовы хотя бы по имени модуля.
Список литературы
Тихомиров В.А. «Перехват API-функций в Windows NT/2000/XP».
Игорь Филимонов «Методы перехвата API-вызовов в Win32»
Intel Corporation «IA-32 Intel Architecture Software Developer’s Manual», части 2A и 2B
Максим М. Гумеров «Загрузчик PE-файлов»
Сергей Холодилов «HOWTO: Вызов функции в другом процессе»
Для подготовки данной работы были использованы материалы с сайта http://www.rsdn.ru/