Unregistered
|
Ё Низя файл присоединить... | Цитата | Перехват вызовов функций
Введение В этой статье продемонстрирован один из способов перехвата вызовов функций. Перехватываемые функции не обязательно должны быть экспортируемыми из DLL. Единственное требование к ним - они должны использовать соглашение о вызове stdcall (стандартный способ вызова функций в Win32). stdcall выбран лишь потому, что он наиболее распространён в Win32. Каких-то принципиальных сложностей с другими типами вызова нет, и ловушки для них можно реализовать аналогично приведённой в данной статье. Перехват вызовов функций может использоваться для отладки своих приложений или для исследования чужих. При исследовании чужих приложений, например, можно определять протоколы взаимодействия нескольких модулей друг с другом (в том числе и взаимодействие по сети - перехватив функции socket, send, recv и другие). Ещё одно применение перехвата функций - изменение работы приложения: перехватив функцию, вы можете изменять передаваемые аргументы и возвращаемый результат, или вообще не вызывать "правильную" функцию.
Механизм перехвата Для перехвата вызова производится изменение кода уже загруженного в память модуля. Изменяются первые 5 байт перехватываемой функции (вставляется jmp hook). Такой способ позволяет перехватывать даже функции, которые не импортируются явно через таблицу импорта, а вызываются по адресу, полученному, например, через GetProcAddress() или просто по фиксированному адресу. Функция-ловушка должна находиться в адресном пространстве того же процесса, в котором перехватывается вызов. Она может быть внедрена туда несколькими способами. Например, через SetWindowHookEx() или прямой записью кода в память процесса. Минусом этого способа перехвата является невозможность перезаписи памяти в Windows 9x в диапазоне адресов от 0x80000000 до 0xBFFFFFFF. По этим адресам располагаются некоторые системные DLL (например, kernel32.dll). Ещё одним минусом является то, что ловушка ставится в уже запущенном процессе, а некоторые (возможно важные) шаги в программе уже сделаны. Оба недостатка можно обойти, если вставить код ловушки не в работающий процесс, а в файл интересуемого модуля.
Подробнее о функции-ловушке Функция-ловушка должна в первую очередь сохранить адрес возврата и положение аргументов. Произведя какие-то свои задачи, функция-ловушка может вызвать "правильную" функцию с какими-то аргументами или не вызывать её вообще. Так как заранее может быть не известно, по каким адресам расположится код ловушки, в ней не следует использовать абсолютных адресов. Т.е. ловушка сама узнаёт свой адрес по регистру eip и относительно него адресует свои структуры данных. Альтернативным вариантом является использование абсолютных адресов, но в этом случае потребуется настройка кода ловушки программой, которая её внедряет. Чтобы не нарушить работу программы, функция должна перед возвратом восстановить регистры (все регистры общего назначения, кроме eax, ecx) и очистить стек от адреса возврата и аргументов (stdcall подразумевает, что функция сама удаляет аргументы, которые ей передали). Для вызова "правильной" функции сначала необходимо вернуть на место старые 5 байт, чтобы избежать повторных вызовов ловушки из самой себя и зацикливания. После возврата из "правильной" функции 5 байт должны быть опять переписаны, чтобы при следующих вызовах управление передавалось в функцию-ловушку. Сложность принципиального характера проявляется при использовании таких ловушек в многопотоковом приложении. При одновременном вызове ловушки из разных потоков может возникнуть ситуация, когда один поток вернул старые 5 байт, а второй в этот момент вызвал функцию. В результате второй поток не попадает в ловушку, а вызывает только "правильную" функцию. Если функция-ловушка небольшая, то не трудно найти для неё место в памяти процесса. Варианты:
DOS часть заголовка модуля. В структуре IMAGE_DOS_HEADER (располагается непосредственно по адресу image base, который для exe-файлов обычно равен 0x400000) для Windows важны только поля e_magic (первое) и e_lfanew (последнее). Соответственно всё, что между ними (58 байт), может использоваться ловушкой. Ещё одна неиспользуемая область - dos stub (код, который работает при запуске программы в режиме DOS и выдаёт сообщение "This program can’t be run in DOS mode."). Эта область начинается сразу после IMAGE_DOS_HEADER и заканчивается там, где начинается IMAGE_NT_HEADERS. Обычно её размер составляет около 200 байт.
Relocation table. Область данных, на которую ссылается IMAGE_DIRECTORY_ENTRY_BASERELOC. После загрузки модуля она не нужна. Её размер обычно достигает нескольких килобайт. В exe-файлах её обычно нет, но она почти всегда есть в dll.
Неиспользуемые области, выравнивающие секции на соответствующие границы. Их не трудно найти, просмотрев все структуры IMAGE_SECTION_HEADER.
Если подумать, наверно можно найти ещё доступные для безопасной перезаписи области. Если же функция-ловушка большая, то проще всего её внедрить, используя SetWindowsHookEx(). Кстати, в этом случае не придётся настраивать абсолютные адреса - это сделает Windows при загрузке dll.
Практика К данной статье прилагаются исходники, реализующие вариант ловушки. Для большинства случаев его окажется достаточно и вам не придется разбираться в низкоуровневых механизмах. Ловушка реализована в виде класса C++. Она выполняет всю необходимую работу, связанную с получением аргументов и восстановлением стека при возврате. Для использования ловушки вы должны реализовать свою функцию, которая будет вызываться из ловушки.
Вот объявление требуемой функции:
typedef unsigned (_stdcall *YOURPROC)(unsigned param, unsigned* arg1, unsigned* countOfArgs); Значение, которое вернёт ваша функция, будет возвращено как результат функции, которая перехватывается.
Описание аргументов функции: param - произвольное значение, которое вы можете передать при установке ловушки. Это может быть, например, указатель на структуру данных. arg1 - указатель на первый аргумент, переданный основной программой. Чтобы получить значение первого и последующих аргументов нужно просто подставлять индексы: arg1[0], arg1[1], arg1[2], ... countOfArgs - по этому адресу ваша функция должна сохранить число аргументов, которое имеет перехватываемая функция. Функция-ловушка реализована универсально, и поэтому она не может знать, сколько аргументов передано. Но зато она может быть использована на функциях с любым количеством аргументов.
Объявление класса:
class CAPIHook { public:
CAPIHook(void);
~CAPIHook();
bool Attach(void* process, void* addrOfAPI, void* addrOfHook, YOURPROC addrOfYourProc, unsigned param);
bool Detach(void);
unsigned GetStatus(void);
private:
... }; Для установки ловушки необходимо вызвать CAPIHook::Attach(). Описание аргументов: process - HANDLE процесса, в котором требуется перехватить вызов. HANDLE должен иметь доступ к процессу PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION. Все аргументы-указатели, переданные данной функции должны указывать на область памяти в том процессе, в котором перехватывается функция. addrOfAPI - адрес перехватываемой функции. addrOfHook - адрес, по которому будет прописана базовая часть ловушки, реализованная в CAPIHook. Размер ловушки, реализованной в CAPIHook, составляет 80 байт. addrOfYourProc - адрес вашей функции в адресном пространстве исследуемого процесса. Способ внедрения этой функции остаётся на ваше усмотрение. param - произвольный 32-битный параметр, который будет передан вашей функции в качестве одного из аргументов.
Для снятия ловушки необходимо вызвать CAPIHook::Detach().
Если при выполнении Attach() или Detach() произойдёт ошибка, то они вернут значение false (иначе - true). Более подробную информацию об ошибке можно получить функцией GetStatus(), которая возвращает стандартный код ошибки Win32.
В качестве примера использования CAPIHook реализован перехват MessageBox() в Notepad.exe. Ловушка убирает первую букву в заглавии сообщений, выдываемых в Notepad. Так как MessageBox() находится в user32.dll, этот пример не будет работать в Windows 9x. Пример был проверен в Windows 2000.
Здесь приводится только информация о перехвате вызовов функций. О внедрении своих функций в адресное пространство других процессов существуют другие статьи. Для интересующихся, будет полезно посмотреть один из способов внедрения DLL в чужой процесс здесь.
Приложение: Скачать можно здесь. (Скачано 3911 раз)
Идея написать статью родилась после обсуждения проблем, связанных с перехватом функций, на форуме [Programming] сайта www.BugTraq.ru. Сама идея вставлять jmp hook в начало перехватываемой функции принадлежит NeuronViking. За заслуги перед отечеством большое спасибо: NeuronViking, Heromantor - принимавшему участие в обсуждении перехватов вызовов функций, Dr.Golova - за дельные замечания по статье.
[c]Copyright 2001, ggg, [email protected] Статья написана специально для UInC (http://www.uinc.ru).
| Добавлено @ 11:33 кстати, все работает.
|