2. Voraussetzungen
3. Code-Cave Injection Allgemein
3.1 Allgemeines zu Code-Caves
3.2 Verwendungszweck von Code-Caves
4. Code-Cave mit Speicher-Reservierung für ASM-Befehle
4.1 Grundlagen zu theoretisch unendlich großen Code-Caves
4.2 Weiterführendes zu generierten Code-Caves und Pseudo-Code auf Basis der WinApi
4.3 In der Praxis auftretende Probleme
4.4 Kommunikation zwischen dem Code-Cave und externen Prozessen
5. CCInject.au3 UDF
5.1 Grundlegendes zur CCInject.au3
5.2 _AllocateMemoryForVariable & _CreateASM_CopyRegisterToVariable & _ReadMemoryVariable
5.3 _InjectASMAtAddress
6. Beispiel einer Skriptgesteuerten Code-Cave-Injection an einem Programm
-------------------------------------------------------------------------------------------------
1. Allgemeines
Es stellen sich sicher einige Leute die Frage, warum die Welt noch ein derartiges Tutorial braucht, wo es doch schon 1000 verschiedene Tutorials zu diesem Thema gibt.
Nunja die Antwort ist relativ einfach:
Keines der bisher gelesenen Tutorials konnte meinen Anforderungen gerecht werden.
Entweder fehlte ein Überblick über das komplette Thema und es wurde sich auf ein kleiner
Teilaspekt konzentriert oder aber es wurde einem ein Copy&Paste-Code vor die Nase geknallt mit den Worten:
"Friss oder Stirb". So musste ich zum Einstieg in das Thema zuerst etliche Seiten durchwälzen und einige Tests machen bevor ich verstand,
was CodeCaves sind und wie man sie erstellt/benutzt.
Ich versuche deswegen in diesem Tutorial zwar alles ziemlich genau anzusprechen, so dass man in der Lage sein müsste selbst Funktionen dazu zu schreiben, werde aber auf der anderen Seite auch einen Copy&Paste-Code erklären, damit auch die faulen AutoItler etwas von dem Tutorial haben.
-------------------------------------------------------------------------------------------------
2. Voraussetzungen
Leider ist das Thema Injection und Memory-Editing, wenn man sich nicht auf Cheat-Engine
und ein paar Addressen beschränkt, etwas komplizierter als andere Themen, finde ich.
Aber ich versuche in diesem Tutorial von Anfang an alles zu erklären, die einzigen Voraussetzungen sind:
1. Grobes Assembler-Grundwissen (Wir werden mit der FASM-Syntax arbeiten)
2. Schon einmal mit Olly Dbg gearbeitet haben
Recht viel mehr braucht es eigentlich nicht, wir werden zwar später ab Kapitel 5 alle
Beispielskripte mit AutoIt realisieren, bis dahin wird das Tutorial aber für alle Programmiersprachen
verwendbar sein, da ich das ganze sehr allgemein und theoretisch halten werden.
Somit profitieren nicht nur AutoItler von diesem Tutorial.
-------------------------------------------------------------------------------------------------
3. Code-Cave Injection Allgemein
3.1 Allgemeines zu Code-Caves
Zuerst einmal sollte man klären was ein Code-Cave ist. Ein Code-Cave ist eine Stelle in einem Code, der leer ist.
Code = Skript/Code
Cave = Höhle
An der Stelle des Code-Caves passiert also gar nichts, diese Stellen sind bereits im Programm.
Strenggenommen ist ein Code-Cave eine Stelle im Code an der man Befehle wegnehmen kann, ohne dass sich an dem Programm etwas ändert.
So könnte ein Code-Cave so aussehen (von Code Caves spricht man ausschließlich bei Assembler, bei allen anderen Sprachen macht das wenig Sinn):
Code:
Sprungmarke1: MOV eax,10 ADD eax,20 NOP NOP NOP JMP Sprungmarke1
In unserem Beispiel also:
Code:
NOP NOP NOP
Also könnte das Programm auch so aussehen und hätte dieselben Funktionen:
Code:
Sprungmarke1: MOV eax,10 ADD eax,20 JMP Sprungmarke1
Einige dienen zur Strukturierung des ASM-Codes (z.b. Abgrenzung von verschiedenen Klassen), andere hingegen sind vom Programmierer eingebaut worden (meistens ohne es zu wissen).
So wäre die einfachste Möglichkeit in C/C++ ein NOP zu realisieren folgende Zeile Code:
Code:
;
Neben dem NOP gibt es noch viele Stellen an denen Code-Caves zu finden sind. Wir werden uns aber auf die NOPs beschränken, da diese für einen Anfänger die einfachste und sicherste Methode ist Code-Caves zu finden/nutzen.
Die Möglichkeiten, die man hat, wenn man ein Code-Cave gefunden hat sind theoretisch unbegrenzt, praktisch jedoch sind die Möglichkeiten so gering, dass es nichts bringt ein Code-Cave gefunden zu haben.
Denn jeder ASM-Befehl braucht eine unterschiedliche Anzahl an Bytes im Programm an Speicherplatz.
So braucht NOP immer genau 1 Byte, aber schon alleine ein einfaches
Code:
MOV eax, 1
Wie man sieht muss man also extrem viele NOPs hintereinander haben um ein sinnvolles Programm schreiben zu können, so viele NOPs wird man jedoch nur selten direkt hintereinander finden.
Jetzt könnte man sich denken, dass Code-Caves in einem Programm dadurch nutzlos geworden sind und das sind sie auch in den meisten Fällen.
Diesem Problem werden wir uns jedoch erst später widmen.
-------------------------------------------------------------------------------------------------
3.2 Verwendungszweck von Code-Caves
So jetzt wo wir theoretisch wissen wie ein Code-Cave funktioniert und wie man ihn findet, stellt sich die Frage was man damit anstellen kann.
Wir gehen bei den folgenden Annahmen davon aus, dass wir ein unendlich großes Code-Cave gefunden haben, wir können also beliebig viele Befehle in diesen Code-Cave reinpacken.
Wenn wir also von einem unendlich großen Code-Cave ausgehen, dann kann man damit einiges anstellen:
1. Man kann in Echtzeit die Werte der Register (z.B. EAX, EDX, usw.) auslesen und auch verändern.
2. Man kann ASM-Funktionen im Code-Cave aufrufen, die von außen nur schwer oder gar nicht aufrufbar sind.
3. Man kann direkt ASM-Funktionen an bestimmte Stellen im Code einschleußen und damit Werte, Addressen, Schleifen, usw. verändern.
4. Code-Caves kann man anstatt von Addressen verweden für Werte, die sich praktisch zufällig verändern (und dadurch schwer lokalisierbar per Cheat-Engine sind).
5. Es ist auch möglich werte auszulesen, die nur sehr kurz im Speicher liegen und anschließend sofort wieder gelöscht/überschrieben werden.
Ein einfaches Beispiel hierfür ist ein Zufallszahlgenerator.
Gehen wir davon aus, dass wir ein sehr kleines Programm haben, dass eine Zufallszahl erstellt, diese Zahl per Cheat-Engine zu finden wäre die Hölle und würde lange dauern.
Per OllyDbg hat man jedoch schnell die Funktion gefunden, in welcher die Zufallszahl erstellt wird. Jetzt nimmt man einfach ein Code-cave, das an dieser Stelle ist und fügt dort sein Code ein, der den Wert der Zufallszahl ausliest und an unser externes Programm schickt, somit verlieren wir z.b. nie wieder bei Schere/Stein/Papier.
Die ganzen Ansätze oben werden weiterhin unter der Annahme gemacht, dass man an einer beliebigen Stelle im Code ein unendlich großen Code-Cave einfügen kann, das muss man im Hinterkopf behalten.
-------------------------------------------------------------------------------------------------
4. Code-Cave mit Speicher-Reservierung für ASM-Befehle
4.1 Grundlagen zu theoretisch unendlich großen Code-Caves
Wie wir bereits in den bisherigen Kapiteln festgestellt haben, sind Code-Caves im Programm an sich eigentlich nutzlos, da sie zu wenig Platz für ASM-Befehle Bereit halten.
Jetzt gibt es aber tatsächlich eine Method an fast jeder beliebigen Stelle ein theoretisch unendlich großen Code-Cave zu erzeugen.
Das ganze beruht auf einem relativ einfachen Prinzip:
Man lässt sich einfach ein bisschen Platz im Arbeitsspeicher reservieren und führt diese Befehle vom eigentlichen Programm aus aus.
Einige werden jetzt denken, dass es dazu sicherlich eine Funktion gibt, um ASM-Code im Arbeitsspeicher ausführen zu lassen.
Diese Funktion gibt es tatsächlich, sie ist nur leider für unsere Zwecke nicht benutzbar, da sie den Code im Arbeitsspeicher als eigenständiges Programm sieht und nicht als Teile eines großen Programmes.
Also werden wir wohl auf guten alten ASM-Code zurückgreifen müssen.
Das ganze ist vom der Idee her relativ einfach.
Wir lassen von unserem Programm aus ein JMP auf das generierte Code-Cave in unserem Arbeitsspeicher machen
und springen am Ende des Code-Caves wieder zurück zur alten Stelle und das Programm wird dann weiter ausgeführt als wäre nichts gewesen.
Da wir aber einen Befehl im normalen Programm überschreiben, müssen wir diesen auch irgendwo wieder ausführen lassen.
Diesen Befehl kopiert man einfach an den Anfang des Code-Caves, so dass er ausgeführt wird als wäre nichts gewesen.
Anschließend kann man seinen eigenen ASM-Code ausführen lassen und muss dann aber am Ende des Code-Caves wieder zurück springen, wo man herkam, um den normalen Programmfluss garantieren zu können.
Das ganz kann man sich wohl besser an einem Beispiel klar machen.
Schauen wir uns einmal folgenden Code an:
Code:
MOV EAX, 10 MOV EDX, 22 XOR EDX, EAX INC EDX MOV EAX, EDX XOR EDX, EDX
"MOV EAX, EDX"
Wir ersetzen also den Code "MOV EAX, EDX" durch ein JMP auf unsere Befehle im Ram und den alten Code im Ram.
Anschließend springen wir wieder zurück zu der ursprünglichen Addresse.
Das ganze sieht veranschaulicht so aus (Rot ist der verschobene original Code, schwarz ist der unveränderte Code und grün ist unser Code):
So mittlerweile wissen wir wie das ganze abläuft, nur 2 Sachen fehlen uns noch zum endgültigen Glück:
1. Wie reserviert man Speicher im Arbeitsplatz für ASM-Befehle?
2. Woher kennen wir die Addresse, an der unser Code im Arbeistspeicher stehen soll?
Die Frage 1 ist relativ einfach beantwortet:
Windows bietet uns zum Glück hier eine sehr schöne Funktion um in einem Programm Arbeitsspeicher zu reservieren.
Wir können durch eine bestimmte Funktion also einem fremden Prozess mehr Arbeitsspeicher zukommen lassen und in diesen Arbeitsspeicher schreiben wir dann unseren ASM-Code.
Spätestens jetzt sehen wir auch warum die Code-Caves theoretisch nur unendlich groß sind, denn der Arbeitsspeicher ist nicht unendlich groß, sondern hat nur eine bestimmte Größe.
Der Befehl, den wir brauchen heißt "VirtualAllocEx".
Werfen wir hierzu einen Blick in die WinApi-Dokumentation:
Wir sehen folgenden Aufbau:
Code:
LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect );
Hier kommt also ein Handle von einem Prozess rein.
Der 2. Parameter (lpAddress) entscheidet darüber wo wir den Speicher reservieren lassen wollen, also an welcher Stelle,
da das uns aber egal ist als Code-Caver, können wir hier NULL einfügen.
Der 3. Parameter (dwSize) entscheidet über die Größe des zu reservierenden Speichers in bytes.
Hier geben wir also an wie viel Bytes wir für unseren ASM-Code reservieren lassen wollen im Arbeitsspeicher.
Der nächste Parameter (flAllocationType) definiert die Art und Weise wie der Speicher reserviert werden soll, wer sich für diesen Parameter genauer interessiert, soll einen Blick in die WinApi-Dokumentation werfen, wir werden standardmäßig 0x00001000 verwenden.
Der letzte Parameter (flProtect) entscheidet über die Rechte, die wir über den reservierten Speicher bekommen, wir werden hier immer 0x40 benutzen,
was uns im Grunde alle Rechte (Code ausführen, Code lesen, Code schreiben), die wir jemals brauchen könnten, gewährt.
Die Parameter sollten ziemlich selbsterklärend sein, außer eventuell der 3. Parameter.
Ich denke nicht jedem ist bewusst wie man auf die benötigte Speicherplatz Größe des ASM-Codes kommt.
Und in diesem Punkt muss ich euch leider enttäuschen, man selbst kommt da nicht einfach durch einfache Rechnungen drauf.
Man muss den ASM-Code erst in Maschinen-Code umwandeln lassen und zählt da dann die Bytes, die der Maschinen-Code einnimmt.
Achtung: Ein Byte ist 2 Zeichen in Maschinen-Code lang.
Beispiel:
Code:
01203938
Also brauchen wir für den oberen Code nicht 8 Byte Platz, sondern nur 4.
Und nun zur 2. Frage:
2. Woher kennen wir die Addresse, an der unser Code im Arbeistspeicher stehen soll?
Die Addresse, an der unser Speicher jetzt reserviert wurde, ist relativ leicht rauszufinden, denn VirtualAllocEx liefert uns automatisch, wenn die Speicher-Reservierung erfolgreich war, die Addresse zurück an der unser Speicher reserviert wurde.
-------------------------------------------------------------------------------------------------
4.2 Weiterführendes zu generierten Code-Caves und Pseudo-Code auf Basis der WinApi
Nun wissen wir schon einiges aber es gibt noch ein paar Kleinigkeiten zu beachten,
so sind CALLs, die durch Code-Caves ersetzt werden schlecht, genau so wie JMPs.
Man sollte also meiden Calls und JMPs in seine Code-Caves, die aus den Code-Cave heruasgehen, zu verwenden, sonst könnte es schnell einmal passieren, dass das Programm sich verabschiedet.
Soweit so gut, jetzt muss man nur noch folgendes wissen:
ASM-Code an sich kann nie direkt ohne Umwandlung in Maschinen-Code ausgeführt werden.
Das heißt in Wirklichkeit können wir folgendes um einen JMP zu realisieren nicht schreiben:
Code:
Sprungmarke1: Mov EAX . . . JMP Sprungmarke1
Dieser Maschinencode für den oberen Abschnitt unterscheidet sich aber je nach der Größe des vorherigen ASM-Codes.
Denn der JMP-Befehl in Maschinen-Code arbeitet nicht absolut, also springt immer zu derselben angegebenen Stelle, sondern springt relativ zu seiner momentanen Position.
Lassen wir also den JMP Befehl "JMP Sprungmarke1" in Maschinencode umwandeln und schauen uns dann den generierten Code an,
sehen wir, dass dort keine feste Addresse eingespeichert ist, sondern eine Relative, die Abhängig ist von der momentanen Position des JMP-Befehls.
Verschieben wir also den JMP-Befehl wird er nie wieder an seine Alte stelle hinspringen, sondern immer an eine andere Stelle.
Das Ganze macht das ein klein wenig komplizierter, aber prinzipiell ist das auch nicht über aus schwierig.
Also widmen wir uns doch einmal einer konkreten Funktion dazu (in Pseudo-Code auf Basis der WinApi).
Wir lassen ja an der Stelle, wo der Code-Cave hin soll ein JMP einfügen.
Dieser JMP ist immer 5 Bytes groß (wichtig um den Pseudo-Code zu verstehen),
(// Sind als einzeilige Kommentare zu sehen):
PHP Code:
$insert_at_address=0x02124334 // Addresse an der wir unseren Code einfügen wollen
$opcode_to_insert="03D0" // Entspricht folgendem ASM-Code: ADD EDX, EAX
$variable_to_safe_read_bytes=""
$process_handle=GetProcessHandleByName("test.exe")
ReadProcessMemory($process_handle,$insert_at_address,$variable_to_safe_read_bytes,5) // Wir brauchen den Maschinen-Code, der davor hier stand um ihn später in unser Code-Cave einfügen zu können.
$code_cave_address=VirtualAllocEx($process_handle,NULL,StringLength($opcode_to_insert)/2+5+5,0x00001000,0x40) // Wir müssen zusätzlich noch 5 Bytes für den Befehl, den wir später in den Code-Cave schreiben, reservieren und auch für das JMP, das am Ende des Code-Caves steht, also nochmals +5
WriteProcessMemory($process_handle,$insert_at_address,"E9"+NumberToBytes($code_cave_address-$insert_at_address-5),5)
// E9 = JMP
// Wir sind bereits, wenn wir den JMP-Befehl ausführen, an der Stelle $insert_at_address, deswegen müssen wir nur noch $code_cave_address-$insert_at_address Bytes weiterspringen.
// Da der JMP-Befehl bereits 5 Bytes Platz wegnimmt, müssen wir wieder 5 Bytes weniger weit springen und dann kommen wir auf:
// $bytes_to_jump=$code_cave_address-$insert_at_address-5
//Anschließend müssen wir unseren Code-Cave füllen:
WriteProcessMemory($process_handle,$code_cave_address,$variable_to_safe_read_bytes,StringLength($variable_to_safe_read_bytes)/2)
WriteProcessMemory($process_handle,$code_cave_address+5,$opcode_to_insert,StringLength($opcode_to_insert)/2)
// +5 weil in unserem Code-Cave bereits der Befehl aus der original Exe steht.
// Und nun wird unser JMP zurück eingefügt:
WriteProcessMemory($process_handle,$code_cave_address+StringLength($opcode_to_insert)/2,"E9"+NumberToBytes(($insert_at_address+5)-($code_cave_address+StringLength($opcode_to_insert)/2+5+5)),5)
// Das JMP muss ans Ende unseres Code-Caves, ohne den bereits bestehenden Code zu überschreiben:
// $code_cave_address+StringLength($opcode_to_insert)/2
// Da wir jetzt rückwärts springen müssen, müssen wir die Addresse, wo wir hin wollen von der abziehen wo wir momentan sind:
// $insert_at_address-$code_cave_address
// Da wir aber nicht wieder beim JMP landen wollen, addieren wir 5 zu unserer Addresse, wo wir hinwollen:
// ($insert_at_address+5)-$code_cave_address
// Wir sind bei unserem JMP im Code-Cave bereits an der Stelle StringLength($opcode_to_insert)/2+5
// Das +5 kommt dadurch Zustande, dass wir ja bereits am Anfang unseres Code-Caves einen ASM-Befehl kopiert haben, der schon 5 Bytes wegnimmt.
// Danach kommt noch einmal +5, da der JMP-Befehl am Ende des Code-Caves ja auch 5 Bytes groß ist.
// und damit sind wir dann bei: ($insert_at_address+5)-($code_cave_address+StringLength($opcode_to_insert)/2+5+5)
Der Code wie er also hier steht, soll nur die Grundlagen klären, in der Praxis würde man den oberen Code wohl nur in Ausnahmefällen einsetzen können, warum klären wir im nächsten Kapitel.
-------------------------------------------------------------------------------------------------
4.3 In der Praxis auftretende Probleme
Schauen wir uns einmal ein Beispiel an wie es in der "Natur" vorkommt:
Ausgangslage (2 zeichen = 1 Byte, die Leerzeichen müssen sich weggedacht werden):
Code:
3B F3 0F 8C 99 00 00 00 38 9D 03 FF FF FF 0F 85 F2 0E 00 00 8D 85 EC FE FF FF 50 8D 85 E0 FE FF FF 50 8D B5 F4 FE FF FF E8 F1 FD FF FF 8B F0 3B F3 7C 6E F6 45 08 01 74 24 64 A1 18 00 00 00 8B 40 30 39 58 10 74 16
Das erste was wir machen müssen ist sich die ganzen Bytes in ASM-Code umwandeln zu lassen.
Das ganze kann man ziemlich einfach und improvisiert mit OllyDbg machen.
Man öffnet einfach einen beliebigen Prozess, welcher keine Auswirkung auf die Prozessstabilität hat, markiert dort einfach einen beliebigen Teil des Programmes (lieber zu viel als zu wenig), macht einen Rechtsklick und klickt dann auf "Binary-->Binary paste"
wenn man das ganze dann anschaut, sieht das in etwa so aus (die Addressen unterscheiden sich natürlich immer, sind aber auch unwichtig)
Code:
004308B8 3BF3 CMP ESI,EBX 004308BA 0F8C 99000000 JL 00430959 004308C0 389D 03FFFFFF CMP BYTE PTR SS:[EBP-FD],BL 004308C6 0F85 F20E0000 JNZ 004317BE 004308CC 8D85 ECFEFFFF LEA EAX,DWORD PTR SS:[EBP-114] 004308D2 50 PUSH EAX 004308D3 8D85 E0FEFFFF LEA EAX,DWORD PTR SS:[EBP-120] 004308D9 50 PUSH EAX 004308DA 8DB5 F4FEFFFF LEA ESI,DWORD PTR SS:[EBP-10C] 004308E0 E8 F1FDFFFF CALL 004306D6 004308E5 8BF0 MOV ESI,EAX 004308E7 3BF3 CMP ESI,EBX 004308E9 7C 6E JL SHORT 00430959 004308EB F645 08 01 TEST BYTE PTR SS:[EBP+8],1 004308EF 74 24 JE SHORT 00430915 004308F1 64:A1 18000000 MOV EAX,DWORD PTR FS:[18] 004308F7 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30] 004308FA 3958 10 CMP DWORD PTR DS:[EAX+10],EBX 004308FD 74 16 JE SHORT 00430915
Das ganze klingt auf den ersten Blick nach einer sehr billigen Aufgabe, aber in Wirklichkeit haben wir nur ein paar wenige Stellen in unserem Code, wo das Code-Cave-Einfügen wirklich Sinn macht.
Gehen wir einfach einmal alle möglichen Stellen von oben nach unten durch:
1. CMP ESI,EBX
Das ganze sieht auf den ersten Blick sofort nach einem Volltreffer aus.
Aber so ist es leider nicht. Wir erinnern uns, dass unser JMP immer 5 Bytes größe braucht.
Wir haben aber nur 2 Bytes, nämlich "3B F3".
Das reicht uns nicht, da fehlen 3 Bytes, aber wir sind ja nicht auf den Kopf gefallen, lassen wir einfach 2 Befehle überschreiben:
Code:
3BF3 CMP ESI,EBX 0F8C 99000000 JL 00430959
Aber auch das ist kein Problem, füllen wir einfach die überschüssigen 3 Bytes mit Nops auf,
so dass unser Code so aussieht:
Code:
004308B8 JMP ARBEITSSPEICHER NOP NOP NOP 004308C0 389D 03FFFFFF CMP BYTE PTR SS:[EBP-FD],BL 004308C6 0F85 F20E0000 JNZ 004317BE 004308CC 8D85 ECFEFFFF LEA EAX,DWORD PTR SS:[EBP-114] 004308D2 50 PUSH EAX 004308D3 8D85 E0FEFFFF LEA EAX,DWORD PTR SS:[EBP-120] 004308D9 50 PUSH EAX 004308DA 8DB5 F4FEFFFF LEA ESI,DWORD PTR SS:[EBP-10C] 004308E0 E8 F1FDFFFF CALL 004306D6 004308E5 8BF0 MOV ESI,EAX 004308E7 3BF3 CMP ESI,EBX 004308E9 7C 6E JL SHORT 00430959 004308EB F645 08 01 TEST BYTE PTR SS:[EBP+8],1 004308EF 74 24 JE SHORT 00430915 004308F1 64:A1 18000000 MOV EAX,DWORD PTR FS:[18] 004308F7 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30] 004308FA 3958 10 CMP DWORD PTR DS:[EAX+10],EBX 004308FD 74 16 JE SHORT 00430915
Code:
3BF3 CMP ESI,EBX 0F8C 99000000 JL 00430959 JMP JUMP_ZURÜCK
Das Offset des Jump-Befehls (Also die Befehle, die der Jump-Befehl überspringt) liegt aber bei "99000000".
Das bedeutet jetzt jedoch nicht, dass der Jump-Befehl 0x99000000 Bytes überspringt, das wären auch viel zu viele.
Man muss die Bytes noch in eine entsprechende Hexadezimalzahl umrechnen und dies geht wie folgt:
Zahl in Bytes:
Code:
99000000
Code:
99 00 00 00
Und dann haben wir unsere Zahl:
Code:
0x00000099
Da wir aber nicht wissen ob in unserem Arbeitsspeicher bei der Addresse JMP_ADDRESSE+0x99 ein Befehl steht, sollten
wir es vermeiden allgemein Jump-Befehle in unserem Code-Cave aufzunehmen.
Also schauen wir unseren Code weiter an und sehendanach eine weitere mögliche Stelle, wo unser Code-Cave-Jump rein kann:
2. "CMP BYTE PTR SS:[EBP-FD],BL"
Diese Stelle wäre prinzipiell möglich zu benutzen, sie bietet genug Platz für unseren Code-Cave-Jump und sie springt nicht irgendwo wild in der Gegend herum.
Jedoch hat es einen großen Nachteil hier den Jump zu setzen (was aber theoretisch möglich wäre), da der Befehl CMP immer Flags für die nachfolgenden Jumps setzt, dürften wir keinerlei Befehle verwenden, die auch Flags setzen,
sonst könnte der nachfolgende Jump falsch ausgeführt werden.
Die nächste mögliche Stelle wäre dann:
3. "LEA EAX,DWORD PTR SS:[EBP-114]"
Und diesen Befehl können wir endlich ersetzen lassen, den Meisten wird der Befehl wohl nichts sagen, aber wofür gibt es eine Dokumentation:
Der Befehl bietet jedoch 6 Bytes an und wir brauchen nur 5.
Das heißt wir müssen ein NOP setzen.
Einige fragen sich jetzt eventuell wieso man nicht einfach den einen Byte in Ruhe lässt,
naja gehen wir das ganze einfach einmal theoretisch durch:
Das ist der Maschinen-Code:
"8D85 ECFEFFFF"
Wir überschreiben jetzt nur die ersten 5 Bytes mit unserem JMP:
"XX XX XX XX XX FF"
Dann sehen wir es bleibt ein "FF" übrig.
Dadurch kann es passieren, dass unser JMP nicht mehr als JMP angesehen wird, sondern z.B. als MOV oder als ADD oder noch schlimmer als CALL, was die Stabilität unseres Programmes extrem gefährden würde.
Deswegen müssen wir dieses eine Byte durch ein NOP ersetzen lassen.
Jetzt wo wir wissen, dass wir unseren JMP-Befehl nicht einfach immer 5 Bytes überschreiben lassen können ohne auf die übrigen Bytes des Befehls zu schauen, sollten wir auch unseren Pseudocode anpassen, sonst können wir diesen ja gar nicht benutzen:
PHP Code:
$insert_at_address=0x02124334 // Addresse an der wir unseren Code einfügen wollen
$opcode_to_insert="03D0" // Entspricht folgendem ASM-Code: ADD EDX, EAX
$variable_to_safe_read_bytes=""
$bytes_to_overwrite=6;
$process_handle=GetProcessHandleByName("test.exe")
ReadProcessMemory($process_handle,$insert_at_address,$variable_to_safe_read_bytes,$bytes_to_overwrite)
$code_cave_address=VirtualAllocEx($process_handle,NULL,StringLength($opcode_to_insert)/2+$bytes_to_overwrite+5,0x00001000,0x40)
WriteProcessMemory($process_handle,$insert_at_address,"E9"+NumberToBytes($code_cave_address-$insert_at_address-5),5)
WriteProcessMemory($process_handle,$code_cave_address,$variable_to_safe_read_bytes,StringLength($variable_to_safe_read_bytes)/2)
WriteProcessMemory($process_handle,$code_cave_address+$bytes_to_overwrite,$opcode_to_insert,StringLength($opcode_to_insert)/2)
WriteProcessMemory($process_handle,$code_cave_address+StringLength($opcode_to_insert)/2,"E9"+NumberToBytes(($insert_at_address+5)-($code_cave_address+StringLength($opcode_to_insert)/2+$bytes_to_overwrite+5)),5)
-------------------------------------------------------------------------------------------------
4.4 Kommunikation zwischen dem Code-Cave und externen Prozessen