_________________ ___________ / _____/\_ ___ \\_ _____/ / \ ___/ \ \/ | __) \ \_\ \ \____| \ \______ /\______ /\___ / \/ \/ \/ The German Computer Freaks www.gcf.de Since 1997 /\ / \ ________________________________________________________________________/ # / Parameter für Windows-API Funktionen überschreiben (Delphi) Paper \ / © Timo, 2002 (31.08.02) \/ ------------------------------------------------------------------------ ACHTUNG: Es kann passieren, dass die folgenden Zeilen bei falscher Anwendung euer Windows zerstören, wenn nicht sogar euren PC oder sonstige Dinge. Daher solltet ihr den Quellcode UNBEDINGT verstehen, bevor ihr die DLL testet. Das ist kein Witz!!! Wenn ihr nicht GENAU wisst, was ihr tut, dann lasst es besser sein. Ich habe euch ausdrücklich gewarnt! ------------------------------------------------------------------------ Was ihr benötigt: - Borland Delphi (Gibts bei Borland.de kostenlos) - Delphi-Kenntnisse (Müsst ihr euch aneignen) - Windows-API Referenz (Ich empfehle AllApi.net) Damit jeder Anwender auf einem Betriebssystem Befehl ausführen kann, muss das Betriebssystem ein (A)pplication (P)Programming (I)nterface zur Verfügung stellen. Windows hat alle Funktionen in sogenannten DLL-Dateien, die dann vom Programm aus geladen werden müssen. Eine davon heisst "SetWindowsHookEx". Mit ihr können wir eine Funktion Hooken. Das heisst, wir setzen eine Art Verzweigung vor die Funktion, so dass alle Parameter, die an die Funktion gehen, auch an unser Programm gehen. Oft wird diese Funktion verwendet, um die Tastatur, oder die Maus zu überwachen. Im Grunde ist es ganz leicht eine Funktion zu überwachen. Hier ein Quellcode, der die Maus überwachen würde: ... var hMouseHook: HHOOK; procedure TForm1.FormCreate(Sender:TObject); begin hMouseHook := SetWindowsHookEx( WH_MOUSE, UnsereMouseFunktion, 0, 0); end; function UnsereMouseFunktion(nCode: integer; wparam: WPARAM; lParam: LPARAM):Integer; stdcall; begin if(wParam = WM_LBUTTONDOWN) then ShowMessage('Linke Maustaste gedrückt!'); Result := CallNextHookEx(hMouseHook, nCode, wParam, lParam); end; procedure TForm1.FormDestroy(Sender:TObject); begin UnhookWindowsHookEx(hMouseHook); end; Jetzt wird bei jedem Klick der linken Maustaste die Message 'Linke Maustaste gedrückt!' erscheinen. Wie schon gesagt, ist es Möglich alle Funktionen zu hooken. Nur sooo toll ist es auch nicht Funktionen zu überwachen. Deshalb gehen wir einen Schritt weiter. Wir wollen jetzt die Parameter Abfangen, so dass wir sie verändern können, bevor sie an die "echte" Funktion weitergegeben wird. Dazu habe ich im Internet einen sehr guten Quellcode gefunden, den ich hier erklären will: Damit das ganze ein wenig professioneller aussieht, erstellen wir uns eine DLL-Datei dafür :) Ich kann euch schon mal sagen, dass wir noch eine Unit schreiben, damit wir nachher nicht so ein Gewirr wird. Ausserdem können wir dann unsere Funktionen, die wir hooken wollen besser verändert. Hier der Quelltext unserer DLL: //8<-------------------------------------------------------------------------------- library inject; uses SysUtils, Windows, Classes, intercept in 'intercept.pas'; //Unsere Unit {$R *.RES} var HookHandle: THandle; function GetMsgProc(code: Integer; removal: Integer; Msg: Pointer): Integer; stdcall; begin Result := 0; end; procedure StartHook(); stdcall; begin HookHandle := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgProc, HInstance, 0); //Wir kennen sie von oben. end; procedure EndHook(); stdcall; begin UnhookWindowsHookEx(HookHandle);//Wir kennen sie von oben. end; exports StartHook, EndHook; //Die Funktionen sollen unserer Exe-Datei zugänglich gemacht werden. begin PatchAddresses(); //Wird benötig, um das Handle der "echten" Funktionen zu überschreiben. end. //>8--------------------------------------------------------------------------------- Das ist noch leicht zu verstehen, hoffe ich. (Bei Fragen, einfach mailen!) Jetzt gehts los. Wir wollen die Funktion MessageBoxA hooken, und einen Text hinzufügen. Ich setz den Quelltext der Unit mal hier hin und erkläre ihn später. // intercept.pas 8<------------------------------------------------------------------ unit intercept; interface uses Windows, SysUtils, Classes, PEStuff, Dialogs; procedure PatchAddresses(); procedure UnhookLoadLibrary(); implementation type TMessageBoxA = function(hWND: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; PPointer = ^Pointer; TImportCode = packed record JumpInstruction: Word; // sollte $25FF sein AddressOfPointerToFunction: PPointer; end; PImportCode = ^TImportCode; var OldMessageBoxA: TMessageBoxA = nil; function NewMessageBoxA(hWND: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; begin result := OldMessageBoxA(hWnd, PChar('Dedicated to the GCF:' + #10#13 + #10#13 + lpText), lpCaption, uType); end; function FinalFunctionAddress(Code: Pointer): Pointer; var func: PImportCode; begin Result := Code; if Code = nil then Exit; try func := code; if (func.JumpInstruction = $25FF) then begin Result := func.AddressOfPointerToFunction^; end; except Result := nil; end; end; function PatchAddress(OldFunc, NewFunc: Pointer): Integer; var BeenDone: TList; function PatchAddressInModule(hModule: THandle; OldFunc, NewFunc: Pointer): Integer; var Dos: PImageDosHeader; NT: PImageNTHeaders; ImportDesc: PImage_Import_Entry; rva: DWORD; Func: PPointer; DLL: string; f: Pointer; written: DWORD; begin Result := 0; Dos := Pointer(hModule); if BeenDone.IndexOf(Dos) >= 0 then Exit; BeenDone.Add(Dos); OldFunc := FinalFunctionAddress(OldFunc); if IsBadReadPtr(Dos, SizeOf(TImageDosHeader)) then Exit; if Dos.e_magic <> IMAGE_DOS_SIGNATURE then Exit; NT := Pointer(Integer(Dos) + dos._lfanew); // if IsBadReadPtr(NT,SizeOf(TImageNtHeaders)) then exit; RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; if RVA = 0 then Exit; ImportDesc := Pointer(Integer(Dos) + RVA); while (ImportDesc^.Name <> 0) do begin DLL := PChar(Integer(Dos) + ImportDesc^.Name); PatchAddressInModule(GetModuleHandle(PChar(DLL)), OldFunc, NewFunc); Func := Pointer(Integer(DOS) + ImportDesc.LookupTable); while Func^ <> nil do begin f := FinalFunctionAddress(Func^); if f = OldFunc then begin WriteProcessMemory(GetCurrentProcess, Func, @NewFunc, 4, written); if Written > 0 then Inc(Result); end; Inc(Func); end; Inc(ImportDesc); end; end; begin BeenDone := TList.Create; try Result := PatchAddressInModule(GetModuleHandle(nil), OldFunc, NewFunc); finally BeenDone.Free; end; end; procedure PatchAddresses(); begin if (@OldMessageBoxA = nil) then @OldMessageBoxA := FinalFunctionAddress(@MessageBoxA); PatchAddress(@OldMessageBoxA, @NewMessageBoxA); end; procedure UnhookLoadLibrary(); begin if (@OldMessageBoxA <> nil) then PatchAddress(@NewMessageBoxA, @OldMessageBoxA); end; initialization end. //>8--------------------------------------------------------------------------------- Die Funktion, die wir hooken wollen, muss zunächst als ein type definiert werden. Die Parameter könnt ihr auch in der API-Referenz nachlesen. Mit PatchAddresses wird das Handle der "echten" MessageBoxA mit einem Pointer auf OldMessageBoxA verbunden. Dann wird unsere NewMessageBoxA mit der OldMessageBoxA verknüpft. So werden alle Parameter erst an unsere Funktion NewMessageBoxA geschrieben. Dort können wir sie verändern (z.B. den Text). Am Ende dieser Funktion müssen wir die Parameter nur noch an die "echte" weitergeben und FERTIG!!!! Wie ihr jetzt die NewMessageBoxA gestaltet bleibt euch überlassen. Um die DLL zu erstellen, müsst ihr [STRG]+[F9] drücken. Um die Funktionsweise besser verstehen zu können, solltet ihr euch API-Tutorials organisieren und die mal durchgehen. Mit StartHook() und EndHook() könnt ihr aus eurer Executable jetzt den Hook setzen bzw. stoppen. Zuvor müssen sie aber noch eingebunden werden. Dazu muss die DLL im gleichen Verzeichnis liegen, wie eure Executable und folgende Zeilen im Deklarationsteil stehlen: procedure StartHook(); stdcall; external 'inject.dll'; //Vorrausgesetzt, eure File heisst inject.dll procedure EndHook(); stdcall; external 'inject.dll'; //sonst anderen Namen einsetzen. Ich denke, dass es ganz Sinnvoll ist, zu wissen wie man API-Funktionen "unterdrücken" kann, denn irgendwo benötigt man so etwas immer. Wenn ihr mal die API-Referenz durchgeht, dann werdet ihr schnell feststellen, dass man die das komplette System unter Kontrolle hat, wenn man die Funktionen oben richtig anwendet. Also viel Spass damit ;) Gruss Timo (Timo@gcf.de)