Entwickler-Ecke
Algorithmen, Optimierung und Assembler - [Delphi] Assembler Hook
s!lenCe - Di 15.12.09 23:16
Titel: [Delphi] Assembler Hook
Hallo allerseits,
folgendes ich möchte einen Hook in einer externen Anwendung per DLL-Injection setzen.
Hintergrund: Ich will eine Erweiterung für einen Game-Server schreiben.
Da ich noch nicht so ganz in der Materie mit Assembler und Hooks stecke habe ich ein paar Fragen.
Ich habe die .exe des Servers disassembled und mir die Adresse der Zielfunktion geholt.
Diese ist sagen wir mal "00555550". Dort ist also praktisch der Anfang der Funktion, die erste Anweisung.
Die ersten drei Anweisungen sehen wie folgt aus:
Delphi-Quelltext
1: 2: 3:
| push ebp mov ebp, esp sub esp, 2D4h |
Ich möchte diese 9 Bytes jetzt überschreiben mit einem Sprung zu meiner Funktion. In der Funktion werden zuerst dann die überschriebenen
Anweisungen ausgeführt und anschließend zum nächsten Anweisung gesprungen. Das mache ich momentan so:
Delphi-Quelltext
1: 2: 3: 4: 5:
| asm mov dword ptr ds:[$00555550], $051EBCE9 mov dword ptr ds:[$00555554], $90909000 mov byte ptr ds:[$00555558], $90 end; |
Und in meiner Funktion
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| procedure Test; begin asm push ebp mov ebp, esp sub esp, 2D4h mov eax, $00555559 jmp eax; end; end; |
Ich kopiere also die einzelnen Opcodes in die entsprechenden Stellen.
Die Opcodes bedeuten folgendes:
Delphi-Quelltext
1: 2: 3: 4: 5:
| jmp 004a4d91 nop nop nop nop |
Ich habe die Adresse zu der gesprungen wird (004a4d91) frei erfunden, denn da müsste die Adresse meiner eigenen (Delphi-)Funktion
hin. Nur weiß ich nicht wie ich die Adresse meiner Funktion ermitteln kann und sie in die einzelnen Opcodes aufteilen, sodass ich sie
in den Anweisungen oben anwenden kann.
Vielleicht gibt es auch einen viel einfacheren Weg. Ich bin für alles offen ^^.
greetz silenCe
s!lenCe - Mi 16.12.09 09:24
Keiner eine Idee? :?
Allesquarks - Mi 16.12.09 11:04
Eigentlich sollte das mit einem Typecast von der Prozedur gehen:
Delphi-Quelltext
1:
| myaddress:=integer(@myfunction); |
Schöner sind Methoden- oder Funktionenzeiger. In Delphi definiert man einen Funktions- oder ProzedurTyp!! Einer Variable dieses Typs kann man dann eine Funktion zuweisen. Das ist natürlich nichts anderes als die Adresse. Den Typ braucht nur der Compiler, damit er weiß, was in die Register und auf den Stack gehört. mit einem Typecast dieser Variablen solltest du deine Sprungadresse haben.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| TMouseEvent = procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of Object;
var OnMouseDown:TMouseEvent; OnMouseDown:=@MouseDownHandler;
OnMouseDown(bla); myaddress:=integer(OnMouseDown); |
Ersteres geht wohl eher Hardgecoded, mit Letzterem kannst Dir ne Funktion schreiben der du die zu injizierende Funktion übergeben kannst.
Opcodes solltest du dir dann mit ein paar logischen Operatoren und Schieben bauen können.
s!lenCe - Mi 16.12.09 20:11
So habe es jetzt mit der HookCode aus der uallCollection gemacht.
Er springt in die Callback-Funktion sobald die Funktion vom Server aufgerufen wird
(mittles Showmessage getestet). Nur wenn ich dann in der Callback-Funktion zur Original-Funktion
zurückspringe, dann crasht der Server. An sich sollte das aber richtig sein. Liegt es eventuell daran,
dass die Funktion eigentlich (wenn Sie im Server aufgerufen wird) per __fastcall aufgerufen wird, ich sie
aber normal aufrufe? Würde das einen Crash verursachen?
greetz silenCe
uall@ogc - Mi 16.12.09 20:31
__fastcall ist nicht wirklich definiert, d.h. es kann daran liegen. In Delphi liegen die Parameter in EAX,EDX,ECX,Stack ...
Dies muss bei Fastcall nicht sein, werden z.b. nur 2 Register verwendtet crashedesnatürlich(und die Parameter liegen and der falschen Stelle)
s!lenCe - Mi 16.12.09 21:18
Ich müsste das doch umgehen können indem ich in meiner Callback
per ASM Routine selber wieder zum Original-Code springe (d.h. nach dem eingefügten JMP bzw. push/ret)
oder? Denn dann muss ich ja selber die Funktion eigentlich nicht aufrufen.
btw.: Finde deine uallCollection echt super. Gute Arbeit :)
greetz silenCe
uall@ogc - Mi 16.12.09 21:27
wenn es reich am schluss ie Funktion normal aufzurufen dann mach einfach
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| procedure myCallBack; begin asm jmp[nextHook] end; end; |
kommst halt nur net so leicht an die parameter
s!lenCe - Mi 16.12.09 21:44
Hmm habe es gemacht wie du geschrieben hast am Ende der Callback-Funktion den Jmp zur originalen
Funktion, habe aber halt noch vor dem jmp die ASM-Anweisungen, die durch den Hook überschrieben worden
sind reingeschrieben. Der Server crasht aber immernoch an der Stelle.
Wenn der Hook platziert wird macht er ja am Anfang der Funktion eine Push/Ret Anweisung, um zur Callback zu
springen. Beeinflusst das auch nachwirkend den Stack (wegen dem Push) oder wird er durch das Ret wieder in den Urzustand
versetzt?
greetz silenCe
Flamefire - Mi 16.12.09 21:50
am besten geht es immer noch, alles selbst zu machen.
also z.b.:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| var ret:Pointer; function hooked(a,b:Cardinal); stdcall; begin end;
function hookDispatch();stdcall;asm POP ret PUSHAD MOV EAX,[ESP+$24] MOV EDX,ESI PUSH EAX PUSH EDX CALL hooked POPAD push ebp mov ebp, esp sub esp, 2D4h mov eax, $00555559 jmp eax; end;
function DoHook(); var code:Array[0..4] of Byte; begin code[0]:=$EB; PCardinal(@code[1])^:=Cardinal(@hookDispatch) - offset - 5; Move(Ptr(offset)^,code[0],5); end; |
Ich habe als Umleitung einen CALL genommen, hat den Vorteil, dass du in deiner Dispatch funktion prüfen kannst, woher das Ding kam. Auf die NOPs habe ich verzichtet. sind eigendlich nicht nötig, da der Code nie ausgeführt wird.
Ansonsten: Zeige mal den Code, den du jetzt hast
s!lenCe - Mi 16.12.09 22:00
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
| var NewFunction : procedure();
procedure CallbackFunction(); begin asm push ebp mov ebp, esp sub esp, 2D4h push $00452ED9 ret end; end;
procedure TMainThread.InstallHook; var Address : DWORD; begin Address := $00452ED0; HookCode(Pointer(Address),@CallbackFunction,@NewFunction); end; |
Da ist er. ^^
Flamefire - Mi 16.12.09 22:58
So weit ich weiß, übernimmt der uallhook schon das zurückersetzen der überschriebenen bytes.
wenn du das so lösen willst reicht ein:
Delphi-Quelltext
1: 2: 3: 4:
| procedure CallbackFunction() asm JMP[NewFunction] end; |
Bin mir aber nicht sicher. Probiers aus und guck dir mit nem Debugger an, was aus den Anweisung wird. Geht auch mit dem Delphi-Debugger, wenn du eine eigene funktion hookst.
Dann kennst du zumindest das Prinzip.
s!lenCe - Mi 16.12.09 23:09
So wie es oben steht ist es eigentlich von der Reihenfolge richtig, wenn ichs mir im Debugger anschaue.
Er springt am Anfang der Original-Funktion zu meiner Funktion und in meiner Funktion stehen dann die ASM-Anweisungen, die überschrieben
worden sind und dann springt er wieder zum Anfang der Original-Funktion + 9 Bytes (9, da ich die überschriebenen 9 Bytes ja schon in meiner Funktion stehen habe),
da wo praktisch der Code weitergeht.
Der Server hängt sich aber trotzdem weg. Mein Ziel ist es eigentlich nur, wenn der Hook sitzt die Parameter mit der die Funktion aufgerufen wird auszuwerten.
Also ich selbst will diese Funktion niemals mit eigenen Parametern aufrufen. Muss ich vielleicht zusätzlich irgendwas beachten, da die Funktion eigentlich eine
Methode einer Klasse ist?
greetz silenCe
uall@ogc - Do 17.12.09 08:29
uallHook übernimmt den aufruf der überschriebenen Bytes.
Das Push/Ret ändert nichts am Stack und ist somit nichts anderes als ein Jump.
Wie gesagt: einfach ein JMP [nextHook] (dieser hat die überschriebenen Bytes am Anfang und übernimmt somit alles).
Step doch mal durch und schau was genau passiert.
s!lenCe - So 20.12.09 19:13
Hallo,
da bin ich wieder ^^.
Habe nun ein anderes Problem. Ich versuche eine andere Funktion zu hooken und benutze dafür wieder HookCode. Das Problem ist aber, dass er an der Adresse, an der er den Hook setzen soll einfach nichts macht. Er lässt den Originalen Code einfach stehen, anstatt das push / ret reinzuschreiben. Ich dachte, dass der Bereich evtl. schreibgeschützt ist und habe ihn mit VirtualProtect schreibbar gemacht, was aber keinen Unterschied macht.
Woran kann es liegen, dass er die Bytes nicht überschreibt?
Edit: Ok habs gelöst in dem ich in der uallHook.pas das Exit beim unknown assembler instruction Fehler in der Funktion HookCodeNt weggenommen habe.
greetz silenCe
Moderiert von
Narses: Überflüssige Zeilenumbrüche/Leerzeilen entfernt.
BenBE - Mo 21.12.09 02:05
Könntest Du bitte zu diesem Problem ein wenig mehr Details schreiben?
So z.B. Start der zu hookenden Funktion usw.? ggf. kann man da in der uallHookCode insgesamt einen Patch einbauen, damit es korrekt geht.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!