Демонстрационный пример хука и подмены API в приложениях

=== Демонстрационный пример хука и подмены API в приложениях ===
////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Unit Name : HookDLL
// * Purpose : Демонстрационный пример хука и подмены API в приложениях...
// * Author : Александр (Rouse_) Багель
// * Version : 1.00
// ****************************************************************************
//
library HookDLL;
uses
 Windows,
 Messages,
 Winsock;
const
 GlobMapID = 'Global Hook for API Interception {2E662583-74C4-45DB-B6DF-FE318C94258D}';
const // Константы нотификаций
 NOTIFY_DLL_INJECT = 1;
 NOTIFY_API_CALL = 2;
 NOTIFY_API_INTERCEPT_SUCCESS = 3;
 NOTIFY_API_INTERCEPT_FAILED = 4;
type
 // Структура для нотификации приложения
 TLogData = record
 AppName: ShortString; // Имя приложения
 FuncName: String[8]; // Имя функции
 FuncPointer: Integer; // Адрес функции
 IP: String[15]; // IP адрес
 Port: Cardinal; // Порт
 Buff: array [0..$FFFF] of Char; // Содержимое буффера
 BuffSize: Word; // Размер буфера
 end;
 // Структура с рабочей информацией хука
 PShareInf = ^TShareInf;
 TShareInf = record
 AppWndHandle: HWND;
 OldHookHandle: HHOOK;
 hm:THandle;
 end;
 // Структуры для работы с таблицей импорта
 TIIDUnion = record
 case Integer of
 0: (Characteristics: DWORD);
 1: (OriginalFirstThunk: DWORD);
 end;
 PImageImportDescriptor = ^TImageImportDescriptor;
 TImageImportDescriptor = record
 Union: TIIDUnion;
 TimeDateStamp: DWORD;
 ForwarderChain: DWORD;
 Name: DWORD;
 FirstThunk: DWORD;
 end;
 PImageThunkData = ^TImageThunkData32;
 TImageThunkData32 = packed record
 _function : PDWORD;
 end;
 function ImageDirectoryEntryToData(Base: Pointer; MappedAsImage: ByteBool;
 DirectoryEntry: Word; var Size: ULONG): Pointer; stdcall; external 'imagehlp.dll';
var
 MapHandle: THandle = 0;
 ShareInf: PShareInf = nil;
 OldRecv: FARPROC = nil;
 Replaced: Boolean;
 AppTitle: ShortString;
// Перехват API посредством подмены в таблице импорта
// =============================================================================
function ReplaceIATEntryInOneMod(const OldProc, NewProc: FARPROC): Boolean;
var
 ImportEntry: PImageImportDescriptor;
 Thunk: PImageThunkData;
 Protect, newProtect: DWORD;
 ImageBase: Cardinal;
 DOSHeader: PImageDosHeader;
 NTHeader: PImageNtHeaders;
begin
 Result := False;
 if OldProc = nil then Exit;
 if NewProc = nil then Exit;
 ImageBase := GetModuleHandle(nil);
 // Зная структуру PE заголовка - находим начало таблицы импорта
 DOSHeader := PImageDosHeader(ImageBase);
 if IsBadReadPtr(Pointer(ImageBase), SizeOf(TImageNtHeaders)) then Exit;
 if (DOSHeader^.e_magic <> IMAGE_DOS_SIGNATURE) then Exit;
 NTHeader := PImageNtHeaders(DWORD(DOSHeader) + DWORD(DOSHeader^._lfanew));
 if NTHeader^.Signature <> IMAGE_NT_SIGNATURE then Exit;
 ImportEntry := PImageImportDescriptor(DWORD(ImageBase) +
 DWORD(NTHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
 if DWORD(ImportEntry) = DWORD(NTHeader) then Exit;
 if ImportEntry <> nil then
 begin
 // Бежим по записям таблицы ...
 while ImportEntry^.Name <> 0 do
 begin
 Thunk := PImageThunkData(DWORD(ImageBase) + DWORD(ImportEntry^.FirstThunk));
 // ... пока таблица не кончится ...
 while Thunk^._function <> nil do
 begin
 // ... или не найдем нужную нам запись.
 if (Thunk^._function = OldProc) then
 begin
 // Производим подмену, сначала так...
 if not IsBadWritePtr(@Thunk^._function, SizeOf(DWORD)) then
 begin
 Thunk^._function := NewProc;
 Result := True;
 end
 else
 begin // ... ну а если не получилось - тогда вот так
 if VirtualProtect(@Thunk^._function, SizeOf(DWORD), PAGE_EXECUTE_READWRITE, Protect) then
 begin
 Thunk^._function := NewProc;
 newProtect := Protect;
 VirtualProtect(@Thunk^._function, SizeOf(DWORD), newProtect, Protect);
 Result := True;
 end;
 end;
 end
 else
 Inc(PChar(Thunk), SizeOf(TImageThunkData32));
 end;
 ImportEntry := Pointer(Integer(ImportEntry) + SizeOf(TImageImportDescriptor));
 end;
 end;
end;
// Наша функция которая будет работать вместо оригинальной ...
// =============================================================================
function InterceptedRecv(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
type
 TrecvImage = function(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
var
 CDS: TCopyDataStruct;
 SockAddr: TSockAddr;
 AddrLen: Integer;
 Data: TLogData;
begin
 // Первоначально вызываем оригинальную функцию, но данные будем писать в свой буфер...
 Result := TrecvImage(OldRecv)(s, Data.Buff[0], len, flags);
 // Получаем информацию кто с кем связался
 if getpeername(s, SockAddr, AddrLen) = SOCKET_ERROR then Exit;
 Data.IP := inet_ntoa(SockAddr.sin_addr);
 Data.Port := ntohs(SockAddr.sin_port);
 Data.BuffSize := Result;
 Data.AppName := AppTitle;
 // Тут можно встроить проверку (к примеру по какому нибудь порту)
 if True then
 Move(Data.Buff[0], Buf, Result) // проверка успешна - пишем в буфер полученные данные
 else
 Result := SOCKET_ERROR; // в противном случае говорим что вызов неуспешен.
 // Отправляем полученные данные нашему приложению...
 CDS.dwData := NOTIFY_API_CALL;
 CDS.cbData := SizeOf(TLogData);
 CDS.lpData := @Data;
 SendMessage(ShareInf^.AppWndHandle, WM_COPYDATA, 0, Integer(@CDS));
end;
// Начало и завершение работы нашего хука ...
// =============================================================================
procedure DLLEntryPoint(dwReason: DWORD); //stdcall; <- вот это как раз не нужно...
var
 CDS: TCopyDataStruct;
 Data: TLogData;
 ImageBase: Cardinal;
 FileName: array [0..MAX_PATH - 1] of Char;
begin
 case dwReason Of
 DLL_PROCESS_ATTACH:
 begin
 // Все данные во избежании разрыва цепочки хуков храним в отображаемом в память процесса файле,
 // только тогда все экземпляры хука будут владеть достоверной информацией
 MapHandle := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TShareInf), GlobMapID);
 ShareInf := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TShareInf));
 // Получаем информацию о процессе в который подгружена наша библиотека
 Replaced := False;
 OldRecv := GetProcAddress(GetModuleHandle('wsock32.dll'), 'recv');
 DisableThreadLibraryCalls(hInstance);
 ImageBase := GetModuleHandle(nil);
 ZeroMemory(@FileName, SizeOf(FileName));
 GetModuleFileName(ImageBase, @FileName, SizeOf(FileName));
 AppTitle := String(FileName);
 // Нотифицируем приложение о успешном внедрении библиотеки
 // И сообщаем информацию о процессе
 ZeroMemory(@Data, SizeOf(TLogData));
 Data.AppName := AppTitle;
 Data.FuncName := 'recv';
 Data.FuncPointer := Integer(OldRecv);
 CDS.dwData := NOTIFY_DLL_INJECT;
 CDS.cbData := SizeOf(TLogData);
 CDS.lpData := @Data;
 SendMessage(ShareInf^.AppWndHandle, WM_COPYDATA, 0, Integer(@CDS));
 // Подменяем процедуры своими (если это нужное нам приложение)
 if Pos('NETCHAT.EXE', AnsiUpper(@FileName)) <>0 then
 begin
 if OldRecv <> nil then
 // Смотрим - успешно ли подменилась запись в таблице импорта?
 if ReplaceIATEntryInOneMod(OldRecv, @InterceptedRecv) then
 begin
 CDS.dwData := NOTIFY_API_INTERCEPT_SUCCESS; // Успешно...
 Replaced := True; // Ставим флаг, что была замена...
 end
 else
 CDS.dwData := NOTIFY_API_INTERCEPT_FAILED; // Не успешно...
 // Нотифицируем наше приложение о результате подмены...
 CDS.cbData := SizeOf(TLogData);
 CDS.lpData := @Data;
 SendMessage(ShareInf^.AppWndHandle, WM_COPYDATA, 0, Integer(@CDS));
 end;
 end;
 DLL_PROCESS_DETACH:
 begin
 UnMapViewOfFile(ShareInf);
 CloseHandle(MapHandle);
 // Возвращаем изменения как они и были (если замена была удачна)
 if Replaced then
 ReplaceIATEntryInOneMod(@InterceptedRecv, OldRecv);
 end;
 end;
end;
// Это наш хук, он нужен только для внедрения в удаленный процесс ...
// =============================================================================
function Hook(Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall;
begin
 Result := CallNextHookEx(ShareInf^.OldHookHandle, Code, WParam, LParam); // вызываем след. ловушку
end;
// Установка хука ...
// =============================================================================
function SetHook(Wnd: HWND): BOOL; stdcall;
begin
 if ShareInf <> nil then
 begin
 ShareInf^.AppWndHandle := Wnd;
 ShareInf^.OldHookHandle := SetWindowsHookEx(WH_GETMESSAGE, @Hook, HInstance, 0); // <- Обратите внимание, не допускаем главной ошибки
 Result := ShareInf^.OldHookHandle <> 0;
 end
 else
 Result:=False;
end;
// Снятие хука ...
// =============================================================================
function RemoveHook: BOOL; stdcall;
begin
 Result := UnhookWindowsHookEx(ShareInf^.OldHookHandle);
 CloseHandle(ShareInf^.hm);
end;
exports
 SetHook, RemoveHook;
begin
 DLLProc := @DLLEntryPoint;
 DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
Приложение:
////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Unit Name : uMain
// * Purpose : Демонстрационный пример хука и подмены API в приложениях...
// * Author : Александр (Rouse_) Багель
// * Version : 1.00
// ****************************************************************************
//
unit uMain;
interface
uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, ActiveX;
const // Константы нотификаций
 NOTIFY_DLL_INJECT = 1;
 NOTIFY_API_CALL = 2;
 NOTIFY_API_INTERCEPT_SUCCESS = 3;
 NOTIFY_API_INTERCEPT_FAILED = 4;
type
 TLogData = record
 AppName: ShortString; // Имя приложения
 FuncName: String[8]; // Имя функции
 FuncPointer: Integer; // Адрес функции
 IP: String[15]; // IP адрес
 Port: Cardinal; // Порт
 Buff: array [0..$FFFF] of Char; // Содержимое буфера
 BuffSize: Word; // Размер буфера
 end;
 PLogData = ^TLogData;
 THADemo = class(TForm)
 memReport: TMemo;
 procedure FormCreate(Sender: TObject);
 procedure FormClose(Sender: TObject; var Action: TCloseAction);
 private
 procedure WMCopyData(var Msg: TMessage); message WM_COPYDATA;
 end;
 function SetHook(Wnd: HWND): BOOL; stdcall;
 external 'HookDLL.dll' name 'SetHook';
 function RemoveHook: BOOL; stdcall;
 external 'HookDLL.dll' name 'RemoveHook';
var
 HADemo: THADemo;
implementation
{$R *.dfm}
{ TForm1 }
procedure THADemo.FormCreate(Sender: TObject);
begin
 if not SetHook(Handle) Then
 MessageBox(Handle, 'Невозможно установить хук.', PChar(Application.Title), MB_OK OR MB_ICONHAND);
end;
procedure THADemo.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 if not RemoveHook Then
 MessageBox(Handle, 'Невозможно снять хук.', PChar(Application.Title), MB_OK OR MB_ICONHAND);
end;
procedure THADemo.WMCopyData(var Msg: TMessage);
const
 ReportInject = 'Библиотека внедрена в приложение "%s", функция "%s" имеет адрес: $%s';
 ReportIntercept = 'Приложение: "%s" IP %s:%d размер данных = %d буфер = "%s"';
 ReportSucceeded = 'Перехват функции "%s" в модуле "%s" успешен.';
 ReportFailed = 'Перехват функции "%s" в модуле "%s" неуспешен!!!';
var
 Data: TLogData;
 Buffer: String;
begin
 Data := PLogData(PCopyDataStruct(Msg.LParam)^.lpData)^;
 // Типы нотификаций
 case PCopyDataStruct(Msg.LParam)^.dwData of
 NOTIFY_DLL_INJECT: // Пришло уведомление о внедрении библиотеки в удаленный процесс
 with Data do
 memReport.Lines.Add(Format(ReportInject, [AppName, FuncName,
 IntToHex(Data.FuncPointer, 8)]));
 NOTIFY_API_CALL: // Уведомление о вызове функции
 begin
 SetLength(Buffer, Data.BuffSize);
 Move(Data.Buff[0], Buffer[1], Data.BuffSize);
 with Data do
 memReport.Lines.Add(Format(ReportIntercept, [AppName, IP, Port, BuffSize, Buffer]));
 end;
 NOTIFY_API_INTERCEPT_SUCCESS: // Уведомление о удачной подмене таблицы импорта
 with Data do
 memReport.Lines.Add(Format(ReportSucceeded, [FuncName, AppName]));
 NOTIFY_API_INTERCEPT_FAILED: // Уведомление о неудачной подмене таблицы импорта
 with Data do
 memReport.Lines.Add(Format(ReportFailed, [FuncName, AppName]));
 end;
end;
end.
Демонстрационный пример перехвата вызовов API функций, посредством изменения таблицы импорта.
 Скачать демонстрационный пример
 Александр (Rouse_) Багель
 http://rouse.drkb.ru
Author: Александр (Rouse_) Багель
Source: Взято из http://forum.sources.ru
ID: 02224