Внимание. Нижеизложенный метод пригоден только для *NT!
Данное ограничение связано с тем, что в Win9x системные библиотеки (KERNEL32, USER32, GDI32, ADVAPI32, ...) располагаются в общей памяти между 2 и 3 GB, и "мирным" способом перехватывать функции невозможно (функции VirtualProtect и WriteProcessMemory не срабатывают). Кроме того, учитывая то, что память - общая и изменеия в ней "видят" все процессы, то нельзя использовать участки памяти (функции, переменные, хэндлы, heap'ы и т.п.), относящиеся только к одному конкретному приложению. Другими словами, если вы пишите процедуру фильтрации вызова API функции, то она (процедура) должна быть самодостаточной.

Вступление и общий обзор методик перехвата вызовов
Как ни странно, существует несколько различных методов перехватов вызовов определенных функций. Однако каждый из таких методов имеет недостатки. Первый метод - непосредственно отладка приложения (внизу можно скачать пример). При отладке, к отлаживаемому процессу подключается debugger и, собственно, может делать все что угодно (в NT для этого нужны привилегии SeDebugPrivilege). Однако при закрытии отладчика, закрывается и отлаживаемое приложение (избежать этого невозможно (если у Вас не WinXP или 2k sp3)). Кроме того, не так просто получить переданные удаленному thread'у параметры (т.е. мы узнаем, что функцию вызвали, а вот с какими параметрами - ...). Другой метод - "IAT Patching"; IAT - Import Address Table - таблица импортов (для примера - сделайте поиск в Google Groups с фразой "hook"+"pig latin"). В приложениях Windows есть таблица (см. рис.) в которой указаны ссылки на размещенные в памяти подключенные модули. Понятно, что эти ссылки можно подправить "как надо", и, таким образом, перенаправить вызов на свою процедуру. Недостаток этого метода в одном - он неприменим для запакованных или "защищенных от взлома" (ASProtect) файлов. Тем не менее, последный метод послужил основой другого, более универсального метода, лишенного всех вышеперечисленных недостатков.


Рис. Как получить реальный адрес процедуры

Как это происходит
Начнем с того, что мы делаем глобальный перехватчик, однако метод, который мы будем использовать требует того, чтобы код, исполняемый при перехвате, находился в контексте перехватываемого приложения. Для этого, мы реализуем технологию SetWindowsHookEX, которая подключает DLL к большинству (но не ко всем!!!) запущеных процессов.
В кратце (на примере перехвата WinExec):
Мы узнаём реальный адрес (см. рис.) по которому размещена процедура (не просто @WinExec; по @WinExec размещен переход на другой (реальный) адрес!). Далее - мы переносим заголовок (6 байт) этой процедуры в спец-буфер. Вместо перемещенных 6 байт, мы вставляем ссылку на свою процедуру. Затем, в спец-буфер дописываем ссылку (6 байт) на оригинальный WinExec смещенную на 6 байт вперед. В своей процедуре, в конце - приводим в порядок стэк и "исполняем" спец-буфер.

Что нужно знать
Инструкция перехода по адресу, указанному Pointer'ом (JMP ptr) в памяти выгладит так: FF 25 xx xx xx xx, где xx xx xx xx - адрес указателя на процедуру
Инструкция относительного перехода от текущего адреса в памяти выгладит так: E9 xx xx xx xx xx, где xx xx xx xx - кол-во байт, на которое нужно сдвинуться (вверх или вниз - в зависимости от знака)




Листинг
 unit ApiHijack;
 
 interface
 uses Windows, PEStuff, classes, sysutils, ShellAPI;
 
 type
 TImportCode = packed record //Нужно для выяснения реального, а не IAT-адреса процедуры
 JumpInstruction: Word; // инструкция JMP ptr. должно быть $25FF, простой JMP $E9
 AddressOfPointerToFunction: PPointer;
 end;
 PImportCode = ^TImportCode;
 
 procedure HookCProc;
 procedure UnhookCProc; //пока не реализовано
 
 implementation
 
 var
 WinExecBuf: pchar; // тот самый буфер, который фо Flash-ке назван TBUF
 
 function NewWinExec(lpCmdLine: LPCSTR; uCmdShow: UINT): UINT; stdcall; //наша процедура
 begin
 windows.beep(1000,1000);
 
 asm
 pop ecx //сделаем то, что обычно делает end
 pop ebp
 jmp DWORD(WinExecBuf) //вызовем наш супер-буфер как процедуру
 end;
 end;
 
 function FinalFunctionAddress(Code: Pointer): Pointer; //находим "реальный" адрес процедуры
 var
 instr: word;
 q, pto: DWORD;
 begin
 Result:=Code;
 if Code=nil then exit;
 try
 ReadProcessMemory(GetCurrentProcess, code, @instr, 2, q);
 ReadProcessMemory(GetCurrentProcess, pointer(dword(code)+2), @pto, 4, q);
 if instr=$25FF then
 Result:=pointer(pto);
 except
 Result:=nil;
 end;
 end;
 
 procedure Patch(var Buf: PChar; NewF, OldF: pointer);
 var
 q, jto: DWORD;
 tbuf: WORD;
 instr: byte;
 begin
 GetMem(Buf, 12);
 ReadProcessMemory(GetCurrentProcess, OldF, Buf, 6, q); // копируем заголовок оригинального WinExec в буфер
 
 instr:=$E9; // JMP
 WriteProcessMemory(GetCurrentProcess, pointer(Dword(buf)+6), @instr, 1, q); //дописываем в буфер переход на продолжение WinExec'а
 jto:=-Dword(Buf)+dword(OldF)-5;
 WriteProcessMemory(GetCurrentProcess, pointer(Dword(buf)+7), @jto, 4, q);
 
 instr:=$E9; // JMP
 WriteProcessMemory(GetCurrentProcess, OldF, @instr, 1, q); //правим заголовок оригинального WinExec'а перенаправляя вызов на себя
 jto:=-Dword(OldF)+dword(NewF)-5;
 WriteProcessMemory(GetCurrentProcess, pointer(Dword(OldF)+1), @jto, 4, q);
 
 end;
 
 procedure HookCProc;
 begin
 Patch(WinExecBuf, @NewWinExec, pointer(FinalFunctionAddress(@WinExec)^));
 end;
 
 procedure UnhookCProc;
 begin
 //пока не реализовано
 end;
 
 
 finalization
 UnhookCProc;
 end.


Copyright
Перепечатка статьи разрешается только с уведомления автора и ссылкой на первоисточник.

Примеры
Вышеразобранный исходник
Пример перехвата с помощью отладки
Top100 Rambler's Top100
Hosted by uCoz