Содержание материала

//  * Unit Name : HookDLL

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

//  * Author    : Александр (Rouse_) Багель

//  * Version   : 1.00

 

Код не маленький, посему вынесем отдельно

 

 

Code:

////////////////////////////////////////////////////////////////////////////////

//

//  ****************************************************************************

//  * 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.

 

 


Приложение:
 

Code:

////////////////////////////////////////////////////////////////////////////////

//

//  ****************************************************************************

//  * 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.

 

 
Автор: Александр (Rouse_) Багель

 

Демонстрационный пример перехвата вызовов API функций, посредством изменения таблицы импорта.

 Александр (Rouse_) Багель

https://rouse.drkb.ru

 

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить