Im Anhang (oder
hier) befindet sich vier Units inkl. einer kleinen Demo-Anwendung, die durch Code-Analyse das generische Ersetzen von beliebigen Funktionen ermöglichen.
Dabei werden die ersten 5 Bytes der betreffenden Funktion mit einen Sprung an die neue Adresse überschrieben. Gleichzeitig wird aber auch ein neues Code-Fragment erstellt, dass die überschriebenen CPU-Instruktionen enthält (also >= 5 Bytes) und einen Sprung zur ersten gültigen Adresse
hinter dem an der Originaladresse eingefügten Sprung.
Das bietet zwei Möglichkeiten:
1. Es wird damit möglich, Bibliotheksfunktionen und -prozeduren der
RTL zu überschreiben (bzw. erweitern),
ohne den Code komplett neu schreiben zu müssen.
2. Man kann damit
API-Funktionen direkt an der Einsprungadresse patchen und somit die Aufrufe
aller geladenen DLLs umleiten; und auch eine via
GetProcAddress geholte Adresse wird somit erfasst.
Beispiel
Wenn man z.B. die Funktion
ReadFile aus
kernel32.dll hooken will, dann sieht das etwa so aus:
Delphi-Quellcode:
uses
GenCodeHook;
type
TFnReadFile = function(hFile: THandle; var Buffer;
nNumberOfBytesToRead: DWORD; var lpNumberOfBytesRead: DWORD;
lpOverlapped: POverlapped): BOOL; stdcall;
var
OldReadFile: TfnReadFile;
function NewReadFile(hFile: THandle; var Buffer;
nNumberOfBytesToRead: DWORD; var lpNumberOfBytesRead: DWORD;
lpOverlapped: POverlapped): BOOL; stdcall;
begin
// ... Aktion vorher
Result := OldReadFile(hFile, Buffer, nNumberOfBytesToRead,
lpNumberOfBytesRead, lpOverlapped);
// ... Aktion hinterher
end;
procedure InstallPatch;
var
Module: HMODULE;
begin
Module := GetModuleHandle('kernel32.dll');
CreateGenericCodeHook(GetProcAddress(Module, 'ReadFile'),
@NewReadFile, @OldReadFile);
end;
Die Beispielanwendung zeigt recht schön, was man damit so alles machen kann.
Funktionsweise
Die eigentliche Arbeit leistet dabei die Funktion
GetCpuInstructionLength in der
Unit CodeLen.pas, die die Länge der CPU-Instruktion bei einer bestimmten Speicheradresse zurückgibt.
Dadurch wird es möglich, nicht nur einfach eine feste Instruktion wie JMP oder CALL zu patchen, sondern beliebigen Code der mindestens 5 Bytes lang ist. Hier am Beispiel von
ReadFile.
Der originale ReadFile-Code steht bei Adresse $7C80180E:
Code:
kernel32.ReadFile:
7C80180E 6A20 push $20
7C801810 68D89B807C push $7c809bd8
7C801815 E8B10C0000 call $7c8024cb ; <-- Schnittpunkt
7C80181A 33DB xor ebx,ebx
7C80181C 8B4D14 mov ecx,[ebp+$14]
...
Wir benötigen 5 Bytes für die JMP-Instruktion, die wir an dieser Stelle einfügen möchten. Der nächste mögliche `Schnittpunkt´ ist also bei Adresse $7C801815, das liegt 7 Bytes hinter dem Eintrittspunkt der Funktion (7 ist also der Wert, den die Funktion
GetCpuInstructionSequence(pointer($7C80180E), 64, 5) hier zurückliefern würde).
Unsere neue Funktion
NewReadFile steht bei Adresse $456D28. Nach dem Einfügen der JMP-Instruktion sieht der gepatchte Code dann so aus:
Code:
kernel32.ReadFile:
7C80180E E91555C583 jmp NewReadFile
7C801813 80
db $80 ; Rest des urspr. Codes
7C801814 7C
db $7C ; Rest des urspr. Codes
7C801815 E8B10C0000 call $7c8024cb ; <-- Schnittpunkt
7C80181A 33DB xor ebx,ebx
7C80181C 8B4D14 mov ecx,[ebp+$14]
...
Wie man sieht, steht bei $7C801815 die erste noch gültige Instruktion des originalen Codes, also exakt 7 Bytes hinter dem Eintrittspunkt. An genau diese Stelle springt jetzt das von
CreateGenericCodeHook erstellte Codefragment, und das sieht dann so aus:
Code:
00A90FE0 6A20 push $20
00A90FE2 68D89B807C push $7c809bd8
00A90FE7 E92908D77B jmp $7c801815
Die ersten 7 Bytes wurden aus dem Originalcode von
kernel32.ReadFile herüber kopiert und der anschließende Sprung zeigt 7 Bytes dahinter.
Beispielanwendung
Die Beispielanwendung erweitert die Funktionen
ReadFile und
WriteFile und zeigt auf dem Formular zwei Indikatoren in grün und rot, die die jeweilige E/A-Aktivität anzeigen.
Wenn man zwei Dateinamen auswählt und dann auf "Start" klickt, dann wird die erste Datei mit der
API-Funktion
CopyFile über die zweite kopiert. Die beiden Indikatoren zeigen dabei den Lese- und Schreibzugriff an. Am deutlichsten wird dies, wenn man ein langsames Medium wählt, wie z.B. eine
Diskette.
Interessant ist es auch schon, einfach nur das Flackern der Indikatoren zu beobachten, während man mit dem OpenDialog oder SaveDialog navigiert; besonders deutlich wird dies, wenn man über die rechte Maustaste das Kontextmenü von Dateien aufruft, insbesondere das "Senden an"-Menü.
ACHTUNG: NICHT UNTER 95/98/ME AUSPROBIEREN!
Bitte
Falls euch irgendetwas auffällt, insbesondere bei den CPU-Instruktionen in
CodeLen.pas, dann teilt mir das bitte mit. Es sollten alle nativen, Fließkomma, MMX, SSE, SSE2, SSE3 und 3DNow CPU-Instruktionen enthalten sein, aber 100%-ig sicher bin ich mir da nicht.
[edit=Chakotay1308]Anhang geupdatet. Mfg, Chakotay1308[/edit]