Внимание. Нижеизложенный метод пригоден только для *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 - кол-во байт, на которое нужно сдвинуться (вверх или вниз - в зависимости от знака)
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);