| Manual
Unpack Max Payne v1.0 (Safedisc v2.30.33) |
| Autor: |
mr_magic |
| Datum: |
22.11.2004 |
| Target: |
MaxPayne.exe |
| Size: |
5.799.256 Bytes |
| Tools: |
SoftIce, IceExt 0.65,
PEditor 1.7, Imprec 1.6f, Procdump 1.6, Hex Workshop |
| Kategorie: |
Unpacking | |
| |
|
1. Einleitung 2. Analyse des EP/ Suche des
OEP 3. Analyse des Import Systems 4. IAT fixen pt. I 5. IAT
fixen pt. II 6. Imports fixen pt. I 7. Imports fixen pt. II
(Theorie, wird hier nicht gebraucht) 8. Imports fixen pt.
III 9. Das war's schon? 10. Dumpen und rebuilden 11. Was
haben wir gelernt? 12. Credits 13. Disassembled PE
Header
|
|
Der zweck dieses Tutorials ist es nicht, eine
Schritt-für-Schritt Anleitung zum Unpacken von Safedisc v2
geschützten Spielen zu liefern. vielmehr erkläre ich die WEGFINDUNG.
Deswegen führen auch viele der hier beschrieben Ansätze nicht zu dem
von uns gewünschten Ergebnis, aber sie stellen Erkenntniserweiternde
Punkte auf dem Weg dar.
Warum ausgerechnet Max Payne und eine
relativ alte SD Version? Tja, erstens ist es das neueste Spiel von
dem ich die Original CD besitze, zweitens sind die Macrovision
Programmierer faul und die hier beschriebenen Protections existieren
immernoch in aktuelleren Versionen. Sie werden nur durch noch mehr
Protections ergänzt (die allerdings ALLE nach dem selben Prinzip
geknackt werden können). Aber das werden die lernwilligen unter Euch
früh genug erkennen ;).
Jeder kennt es, jeder hasst es.
Safedisc v2, eine weiter unnütze Copy Protection von Macrovison. Man
erkennt sie an den Section Namen stx774 und stx371 oder an der
hässlichen MessageBox "Legen Sie die korrekte CD-ROM ein, klicken
Sie auf OK, und starten Sie die Anwendung neu" wenn man versucht,
das Spiel ohne oder mit ungenau gebrannter CD zu starten.
Die
Safedisc Versionsnr. steht am Ende des PE Headers nach dem String
"BoG_ *90.0&!! Yy>" in 3 unsigned longs. Bei Max Payne 1.0
ist das 020000001E00000021000000h, also 2.30.33.
Bevor wir
nun beginnen, uns irgendwelchen Code anzuschauen, treffen Wir die
Standard Vorbereitungen beim Unpacken:
Wir verschaffen und
Schreibrechte in den relevanten Sections. Hier hat Safedisc eine
Schwachstelle. Der PE Header wird nicht auf Modifikationen
untersucht!!! Wir benutzen also den PEditor, um die Eigenschaften
der .text Section auf E0000020 und die der .rdata Section auf
C0000040 zu setzen. Damit wir das Game einigermaßen ungestört
debuggen können, ist es notwendig, die IceExt Option "!Protect on"
zu aktivieren. Unter Icedump entfällt dieser Schritt.
Es sei
noch gesagt, dass Safedisc massiv Anti-Debugger Techniken anwendet.
Wird einmal ein Debugger erkannt, muss das ganze System neu
gestartet werden. All diese Dedugger Checks befinden sich in der
"secdrv.sys" im Windows\System32\driver Ordner. Möchte man Safedisc
v2 "deeper" analysieren, um z.B später einen generic Unwrapper zu
coden, der sogar ohne CD funktioniert, sollte man die "Secdrv.sys"
modifizieren. Darüber gibt es ein kleines Tut von
ArthaXerXes. Das ist jedoch zum nachvollziehen dieses Tuts nicht
nötig, da wir den ganzen Safedisc entpack-Code einfach überfliegen
werden, um uns dem wesentlichen zuzuwenden.
|
| 2.
Analyse des EP/ Suche des OEP: |
Um uns die Safedisc Protection etwas genauer
anzuschauen, werden Wir zunächst am Entypoint breaken. Dazu benutzen
wir entweder die "break'n' enter" funktion des PEditors oder setzen
den Opcode "CC" mithilfe eines Hexeditors an den
Entrypoint.
In beiden Fällen müssen Wir in Sofice (SI) einen
breakpoint auf int3 setzen. Also Strg+D
und
:bpint3
Wir doppelklicken auf die "MaxPayne.exe"
und finden uns sofort in SI wieder. Dort sehen Wir
folgendes:
008CB04A 55 PUSH EBP
008CB04B 8BEC MOV EBP,ESP
008CB04D 60 PUSHAD
008CB04E B8B9B08C00 MOV EAX,008CB0B9
008CB053 2D4AB08C00 SUB EAX,OFFSET MaxPayne.{ModuleEntryPoint}
008CB058 0305BAB08C00 ADD EAX,DWORD PTR DS:[8CB0BA]
008CB05E C7054AB08C00E9 MOV DWORD PTR DS:[{ModuleEntryPoint}],0E>
008CB068 A34BB08C00 MOV DWORD PTR DS:[8CB04B],EAX
008CB06D 6809B08C00 PUSH 008CB009
008CB072 A02DB08C00 MOV AL,BYTE PTR DS:[8CB02D]
008CB077 3C01 CMP AL,1
008CB079 7407 JE 008CB082
008CB07B B800000000 MOV EAX,0
008CB080 EB03 JMP 008CB085
008CB082 8B4508 MOV EAX,DWORD PTR SS:[EBP+8]
008CB085 50 PUSH EAX
008CB086 E833000000 CALL 008CB0BE ;Unpackroutine/Alles passiert hier
008CB08B 83C408 ADD ESP,8
008CB08E 83F800 CMP EAX,0 ;Eax==1 bedeutet Fehler beim Unpacken
008CB091 741C JE 008CB0AF ;Wenn alles glatt lief
008CB093 C7054AB08C00C2 MOV DWORD PTR DS:[{ModuleEntryPoint}],0C>
008CB09D C7054BB08C000C MOV DWORD PTR DS:[8CB04B],0C
008CB0A7 50 PUSH EAX
008CB0A8 A129B08C00 MOV EAX,DWORD PTR DS:[8CB029]
008CB0AD FFD0 CALL EAX
008CB0AF 61 POPAD ;springe hier hin
008CB0B0 5D POP EBP ;räume den stack auf
008CB0B1 EB06 JMP 008CB0B9
008CB0B3 7216 JB 8CB0CB
008CB0B5 61 POPAD
008CB0B6 13600D ADC ESP,DWORD PTR DS:[EAX+D]
008CB0B9 E97645EAFF JMP 0076F634 ;und springe zum OEP!!! Hierbei
sei gesagt, dass es uns nichts bringt, bei einer nicht Originalen CD
einfach eax == 0 zu setzen um den Check auszutricksen, da die Exe
mithilfe der digitalen Signatur der CD und dem daraus erstellten
TEA-Key decrypted wird (der allerdings auch ohne solch eine CD
herausgefunden werden kann, allerdings machen wir uns die Sache mit
CD viel einfacher ;) Ist also die CD fehlerhaft, ist auch der Key
fehlerhaft und die Exe wird falsch decrypted.Sie ist somit nicht
lauffähig.
Aber wenigstens den OEP können wir ganz leicht
finden :), da er nicht erst berechnet wird, sondern hardgecoded
vorhanden ist.
Am einfachsten erreichen wir ihn, indem Wir
vorerst noch keinen bpx setzen, da Safedisc während des decryptens
sehr empfindlich darauf reagiert und den präsenten Debugger erkennt,
SI stattdessen beenden und warten bis der bekannte Safedisc
"Loading..." Screen (der bei MaxPayne ganz weiss und klein ist)
verschwunden ist. Jetzt aktivieren wir SI und geben folgendes ein
(nur unter XP nötig)
:addr
um alle aktiven Prozesse
aufzulisten und ggf. durch
:addr $ProcessID
den
aktiven Speicherbereich zu ändern. Anschließend können wir einen bpx
auf das erste "Popad" setzen
:bpx #008CB0AF
Sobald
Wir breaken können Wir getrost zum OEP tracen. Das einfachste wäre
somit überstanden.
|
| 3.
Analyse des Import Systems: |
Wenn Wir uns an das gute, alte Safedisc v1
zurckerinnern, dann wissen wir dass damals die Kernel32 und User32
Imports gemangled wurden, d.h. zu Safedisc Routinen umgeleitet
wurden, die diese dann callten. Es würde mich wundern wenn das bei
Safedisc v2 anders wäre. Wenn wir ein Stück tracen werden wir nicht
enttäuscht.
0076F661 FF1550937C00 CALL DWORD PTR DS:[7C9350] Wir
bekommen unter SI keinen Import Namen angezeigt. Wenn wir rein
tracen sehen wir folgendes:
01C45759 68EA12EABF PUSH BFEA12EA ;Pusht einen Parameter
01C4575E 9C PUSHFD ;sichert
01C4575F 60 PUSHAD ;den
01C45760 54 PUSH ESP ;Stack
01C45761 689957C401 PUSH 1C45799 ;pusht Import_Adresse + 40h als 2. Parameter
01C45766 E825D54CFF CALL 01112C90 ;Call zur eigentlichen SD Routine
01C4576B 83C408 ADD ESP,8 ;-
01C4576E 6A00 PUSH 0 ;hier
01C45770 58 POP EAX ;gelangen wir
01C45771 61 POPAD ;niemals
01C45772 9D POPFD ;hin
01C45773 C3 RET ;-
So
wie es aussieht, werden der eigentlichen Safedisc Routine zum Call
auflösen 2 Parameter übergeben, aus denen sie die korrekte RVA zu
unserem Import ermittelt um diesen dann zu callen: Ein dword, der
immer mir BF beginnt und das Offset des gemangleten
Imports+40h.
Wenn wir uns jetzt weiter an SD v1 zurück
erinnern, benutzten wir dort einen ähnliche Code wie folgenden, um
die gemangelten Imports zu fixen:
mov ebx, $iat_start_offset - 4
_start:
add eax,4
cmp eax, $iat_ende
ja _ende
cmp dword ptr [ebx], $lower_range_of_mangled_calls
jl _start
cmp dword ptr [ebx], $upper_range_of_mangled_calls
ja _start
call ebx
mov dword ptr [ebx], ecx
jmp _start
_ende:
jmp eip
Man assembelte also ein Stückchen
Code, dass die IAT durchsuchte und jede RVA prüfte, ob sie zu der
Safedisc_Call_Auflös Routine führte. War das der Fall, callte man
die aktuelle RVA um die korrekte RVA zum richtigen Import zu
bekommen. Das ganze wurde später etwas komplizierter weil die
Safedisc_Call_Auflös Routine die korrekte RVA nicht mehr zurück
lieferte. Das ließ sich jedoch mit einem kleinen Patch
beheben.
Soweit zur Theorie. Probieren wir also aus, ob diese
Methode noch funktioniert. Dazu müssen wir jedoch zuerst das Ende
der Safedisc_Call_Auflös Funktion finden. Tracen wir also mit F8 in
den Call bei 01C45766 und dann mit F10 über alle restlichen, bis wir
an folgende Stelle kommen:
01113243 F0:FF0D F48F1201 LOCK DEC DWORD PTR DS:[1128FF4] ; LOCK prefix
0111324A 780D JS 01113259
0111324C 8B0DEC311301 MOV ECX,DWORD PTR DS:[11331EC]
01113252 51 PUSH ECX
01113253 FF15 64401101 CALL KERNEL32.SetEvent
01113259 EB07 JMP 01113262
0111325B 8BC0 MOV EAX,EAX
0111325D 7806 JS 01113265
0111325F 90 NOP
01113260 7903 JNS 01113265
01113262 EBF7 JMP 0111325B
01113264 88 --Junk Byte--
01113265 8B650C MOV ESP,DWORD PTR SS:[EBP+C] ;Stellt den
01113268 61 POPAD ;Stack
01113269 9D POPFD ;wieder her
0111326A C3 RET ;und springt zum richtigen Import
0111326B 76 04 JBE 01113271
0111326D 90 NOP
0111326E 90 NOP
Am Ende
der Call_Auflös Routine ist die richtige RVA zu unserem Import also
der oberste dword auf dem Stack. Der zweite ist unsere Return
Adresse, also das Offset des "CALL DWORD PTR DS:[7C9350]"+6. Mit
einer kleinen Modifikation bringen wir die Call_Auflös Routine dazu,
die richtige RVA nicht mehr zu callen, sonder sie uns stattdessen
zur Weiterverarbeitung zu übergeben.
Dank des vielen Junk
Codes haben wir dafür genug Platz. Z.B. zwischen dem Offset 01113259
und 01113265. Da unser Patch nur ein Byte groß ist, ist das mehr als
genug. Wir assembeln also:
:a 01113259
01113259 8B650C MOV ESP,DWORD PTR SS:[EBP+C] ;stellt den
0111325C 61 POPAD ;Stack
0111325D 9D POPFD ;wieder her
0111325E 5A POP EDX ;Speichert die korrekte RVA in EDX
0111325F C3 RET ;und springt zurück
Wir
tracen über das "RET" und befinden uns jetzt an der Return Adresse.
EDX beinhaltet nun die RVA zu unserem korrekten Import. In unserem
Fall ist das msvcrt.__set_app_type.
Wenn wir jetzt wieder an
den SD v1 Callfix Code denken, fehlen uns noch ein Paar Werte bevor
wir den Callfixer Code assembeln können. Wir bekommen sie am
einfachsten, indem wir die Exe im PEditor öffnen um den Anfang der
.rdata Section abzulesen und uns anschließend die entpackte IAT im
Speicher amschauen.
Start der IAT (== Start der .rdata
Section): 007C9000
Anfang der Range in die unsere
redirecteten Calls führen (dabei können wir ruhig Recht großzügig
sein, da keine DLL aufgerufen wird, die Exports in einer ähnlichen
Range hat): 01C00000
Ende der Range in die unsere
redirecteten Calls führen (wieder Recht großzügig):
01D00000
Länge des zu durchsuchenden IAT Abschnitts: 1000
(die IAT ist zwar eigentlich viel größer, beim Durchsuchen findet
man aber keine gemangleten Imports mehr nach $IAT_Start+1000h.
|
|
Wir haben nun alles vorbereitet, um unsere IAT zu
fixen. Jetzt müssen wir nur noch einen geeigneten Ort finden, um
unseren Code zu injecten. Ich habe mich für den PE Header+500h
entschieden, weil wir dort 500h zero padded Bytes Platz
haben.
mov ecx, 007C8FFC ;IAT Anfang-4
_start:
add ecx, 4 ;mache weiter mit der nächsten RVA
cmp ecx, 007CA000 ;haben wir das Ende erreicht?
ja _ende
cmp dword ptr [ecx], 01C00000 ;wenn nicht, überprüfe ob der Import
jl _start
cmp dword ptr [ecx], 01D00000 ;gemangled ist
ja _start
call dword ptr [ecx] ;wenn ja, calle ihn um die korrekte RVA zu erhalten
mov dword ptr [ecx], edx ;und ersetze die alte durch die neue RVA in der IAT
jmp _start
_ende:
jmp eip ;Endlos-loop um uns die Kontrolle zurückzugeben
Spätestens
wenn ecx == 007c9350 ist, sollten wir in den "call ecx" tracen. Zum
einen um zu sehen ob er die korrekte RVA zurückliefert wird, zum
anderen weil wahrscheinlich aus Performancegründen eine überprüfung
stattfindet, ob der Import schon aufgelöst wurde. Daraus würde dann
resultieren, dass wir wahrscheinlich nicht das gepatchten Ende der
Safedisc Routine erreichen. Beim Durchtracen fallen uns zwei Sachen
ins Auge:
...
01112E38 E8F3E0FFFF CALL 01110F30
01112E3D 83C40C ADD ESP,0C
01112E40 8945F4 MOV DWORD PTR SS:[EBP-C],EAX
01112E43 837DF400 CMP DWORD PTR SS:[EBP-C],0 ;wurde der Import noch nicht aufgelöst?
01112E47 0F847B010000 JE 01112FC8 ;ist das so, löse ihn auf
01112E4D 7507 JNZ 01112E56 ;sonst benutze die gespeicherte RVA
...
0111319B 837DF400 CMP DWORD PTR SS:[EBP-C],0 ;wurde der Import noch nicht aufgelöst?
0111319F 7529 JNZ 011131CA ;wenn doch, benutze die gespeicherte RVA
011131A1 8B45E0 MOV EAX,DWORD PTR SS:[EBP-20] ;setze eax auf die Funktionsnr.
011131A4 50 PUSH EAX ;übergebe sie als Parameter
011131A5 8B4DE8 MOV ECX,DWORD PTR SS:[EBP-18] ;setze ecx auf die DLLnr.
011131A8 51 PUSH ECX ;übergebe sie als Parameter
011131A9 E882F4FFFF CALL 01112630 ;berechne die korrekte RVA
... Es finden 2 überprüfungen statt. Wir patchen die
Routine also so, dass die RVA's immer neu berechnet werden. Das gibt
uns ein besseres Gefühl ;)
Der zweite Code Ausschnitt
erinnert mich sehr an SD v1. Es wurden auch Funktionsnr. und DLLnr.
verwendet um die korrekten RVA's zu berechnen. Der Unterschied ist
nur der, dass diese Nummern bei SD v2 erst auch noch berechnet
werden. zwei der Parameter dafür haben wir bereits gesehen. Sie
werden vor dem Call zur eigentlichen Safedisc Routine auf den Stack
gepusht.
Wenn wir jetzt über den "CALL 01112630" tracen,
müsste eax eignetlich die RVA zu msvcrt.__set_app_type
beinhalten.
:what eax
Bäh, war wohl nichts. Zwar haben
wir es jetzt auch mit einem msvcrt Import zu tun, allerdings nicht
mit dem richtigen. Wir überlegen also und kommen zu dem Ergebnis,
dass
01C45759 68EA12EABF PUSH BFEA12EA
01C45761 689957C401 PUSH 1C45799
nicht
die einzigen Parameter sind, die der SD Funktion übergeben werden.
Die Funktionsnr. wird demnach falsch berechnet. Wenn wir nochmal
kurz überlegen, wie die beiden Parameter, die wir kennen übergeben
werden, kommen wir schnell auf den Fehlenden.
Wenn die
eigentliche Call_Auflös Routine für "msvcrt.__set_app_type" normal
aus dem Programm gecallt wird, sieht der Stack folgendermaßen
aus:
99 57 C4 01 xx EA 12 EA BF 67 F6 76 00
----------- Die xx
stehen für die 28h Bytes Stacksicherung (durch PUSHFD, PUSHAD, PUSH
ESP). Da man den Stack "verkehrtherum" ließt, ist der fehlende
Parameter 0076F667, die Return Adresse nach dem "CALL DWORD PTR
DS:[7C9350]". Ein CALL ist nichts anderes als ein PUSH
$Call_Offset+6 und ein JMP $Call_Ziel in einem. Die ersten beiden
dwords sind der erste und der 2. Parameter.
Safedisc
berechnet die korrekte RVA also auch anhand des Offsets, von dem aus
die Call_Auflös Routine aufgerufen wird. Das zwingt uns, einen neuen
Code zum fixen der Calls zu assembeln.
|
|
Was wir also machen werden, ist den gesamten Code
nach Call [.ref]'s zu durchsuchen. Sobald wir einen gefunden habe,
überprüfen wir, ob er auf den Bereich zwischen $IAT_Anfang und
$IAT_Anfang+1000h zeigt. Wenn ja überprüfen wir, ob es sich um einen
gemangleten Import handelt oder nicht. ist das so, haben wir das
Offset wo er gecallt wird und somit den dritten Parameter für die
Call_Auflös Funktion.
Der gesamte Code befindet sich in der
.text Section. Sie beginnt bei 00401000 und endet bei
008c9fff.
mov eax, 00400FFF ;Start der .text Section-1
_start:
inc eax ;mache weiter mit der nächsten Funktion
cmp word ptr [eax], 15FF ;Haben wir es bei der aktuellen Funktion mit einem Call [.ref] zu tun?
je _ErsterCheck ;wenn ja, überprüfe ihn weiter
cmp eax, 008C9FFF ;haben wir das Ende erreicht?
jne _start
jmp eip
_ErsterCheck:
cmp dword ptr [eax+2], 007C9000 ;liegt das Ziel des Call [.ref]s in
jl _start
cmp dword ptr [eax+2], 007CA000 ;dem Bereich vom IAT Anfang bis +1000h?
ja _start
_ZweiterCheck:
mov ecx, dword ptr [eax+2] ;setze ecx auf das Offset des Imports in der IAT
cmp dword ptr [ecx], 01C00000 ;Ist die Import RVA
jl _start
cmp dword ptr [ecx], 01D00000 ;in der Safedisc Range?
ja _start
;Ab hier haben wir zu 100% einen
;redirecteten Call
push _ReturnAfterCallFix ;unser Offset, an dem Wir nach der SD Routine landen wollen
add eax, 6
push eax ;Unser dritter parameter (Call [.ref] Offset+6)
sub eax, 6
jmp ecx ;anstatt eines Calls verwenden wir nun einen Jmp, um den Stack unberührt zu lassen
_ReturnAfterCallFix:
mov dword ptr [ecx], edx ;Wir überschreiben die RVA der Call_Auflös Routine in der IAT mit der RVA des
korrekten Imports
jmp _start
Bevor wir starten, muss die
Safedisc Call_Auflös Routine noch etwas abgeändert werden. Wir
ändern:
01113259 8B65 0C MOV ESP,DWORD PTR SS:[EBP+C] ;stellt den
0111325C 61 POPAD ;Stack
0111325D 9D POPFD ;wieder her
0111325E 5A POP EDX ;Speichert die korrekte RVA in EDX
0111325F C3 RET ;und springt zurück in:
0111325F 83C404 ADD ESP, 4
01113262 C3 RET
Das "add esp,
4" wird nötig, weil wir einen zusätzliche "push" verwenden, um
wieder zu unserem Code zu gelangen. Wir müssen also den Stack um
einen dword erhöhen, um ihn zu korrigieren. Starten Wir also durch
und drücken kurz danach wieder Strg+D um uns an dem "jmp eip"
wiederzufinden.
Das sollte es also nun gewesen sein, oder
etwa nicht??? Wenn wir uns die Imports anschauen, von denen wir
wissen, zu welchen RVAs sie uns bringen müssten wenn die Call_Auflös
Routine regulär ausgeführt wird, sehen wir dass viele immernoch
falsch sind... Wo liegt das Problem? Wir haben mit unserem Code
perfekt die normale Ausführung der Call_Auflös Routine imitiert...
Wenn wir keinen Fehler gemacht haben, haben vielleicht die
Macrovision Programmierer Fallen für uns aufgestellt.
Was
wäre wenn sie es sich zu Nutze gemacht hätten, dass das Offset des
Call [ref]s mit zur Berechnung verwendet wird?
Wir haben ja
selber erlebt, dass die Call_Auflös Routine je nachdem von wo sie
aufgerufen wurde unterschiedliche RVAs zurückgeliefert hat. Es muss
also so sein, dass nun Call [ref]s das gleiche Offset der IAT lesen
um verschiedene RVAs zu erhalten. Diese Call [ref]s waren früher
verschiedene Imports, sehen für uns aber nun wie die selben aus. Wir
werden also nur durch das ändern der IAT alleine nie alle Imports
gleichzeitig fixen können.
|
|
Die folgenden überlegungen sind zwar logische
Schlussfolgerungen unserer geistigen Vorarbeit, allerdings wurde die
im Folgenden beschriebene Methode schon von r!sc und Peex
publiziert. Der Respekt dafür gebührt also dem Erfinder
:D
Wir müssen also nicht nur die IAT, sondern auch die .text
Section ändern. Zum ändern der .text Section brauchen wir allerdings
immer eine "originale" IAT, d.h. sie muss die gemangleten Imports
noch enthalten, damit wir für jeden Call [.ref] die korrekte RVA des
Imports berechnen lassen können.
Die Lösung wird also so
aussehen, dass wir uns eine temporäre gefixte IAT basteln und mit
dieser, wenn alle Call [.ref]s gefixt sind, die Originale ersetzen.
Zum erstellen dieser temporären IAT können wir den Code unseres
ersten Versuchs in leicht abgeänderter Form benutzen.
Jetzt
stellt sich nur noch die Frage, wo wir 1000h Bytes Platz für die
temporäre IAT haben... Na klar, die Safedisc Loader Section ist
4000h Bytes groß und wird nach dem decrypten des Games nicht mehr
gebraucht!!!
Sie beginnt bei 008CB000. Wir Coden also am PE
Header+ 500h:
mov ecx, 007C8FFC ;IAT Anfang-4
mov ebx, 008CAFFC ;Safedisc Loader Section-4
_Start:
add ecx, 4 ;mache weiter mit der nächsten RVA
add ebx, 4 ;erhöhe auch das Offset unserer temp. IAT
cmp ecx, 007CA000 ;haben wir das Ende erreicht?
ja _EndeBuildIAT
mov edx, dword ptr [ecx] ;setze edx auf die RVA des aktuellen Imports
cmp edx, 01C00000 ;liegt die RVA in
jl _BuildIAT
cmp edx, 01D00000 ;der Safedisc Range?
ja _BuildIAT
call edx ;wenn ja, calle den Import um die korrekte RVA zu erhalten
_BuildIAT:
mov dword ptr [ebx], edx ;Trage die korrekte RVA in die temp. IAT ein
jmp _Start
_EndeBuildIAT:
jmp eip ;Endlos-loop um uns die Kontrolle zurückzugeben
Wenn
dieser Code durchlaufen wurde, haben wir eine wundervoll gefixte
temp. IAT, mithilfe derer wir jetzt die Call [.ref]s fixen
können.
Die Call [ref]s sehen folgendermaßen aus: die ersten
2 Bytes sind immer FF15, dann folgt der dword Wert zu unserem IAT
Offset. Um das genauer zu verstehen gucken wir uns doch einfach den
ersten Call [.ref] nach dem OEP an.
0076F661 FF1550937C00 CALL DWORD PTR DS:[7C9350]
----********
| |
Opcode für Offset der der RVA unseres Imports in der IAT in little Endian
Call [.ref] Byte Order, d.h. es muss "rückwärts" gelesen werden.
Der
zweite Teil unseres Codes muss also die gesamte .text Section nach
dem Opcode FF15 durchsuchen. Wenn dieser gefunden wurde, werden wir
überprüfen ob es sich um einen gemangleten Call handelt. Ist das so,
werden wir ihn so ändern, dass er die richtige Stelle unserer
temporären IAT lesen würde, würde die sich an der Stelle unserer
originalen IAT befinden.
Bei dem Code ist wieder zu beachten,
dass ein zusätzliches "add esp, 4" am Ende der Call_Auflös Routine
vorhanden sein muss. Bei dem komplette Code am Ende dieses Tuts
werden wir das automatisch machen lassen.
mov eax, 00401000 ;Anfang der .text Section
_SucheNachCallRefs:
cmp word ptr [eax], 15FF ;Sind die ersten 2 Bytes fer aktuellen Funktion FF15?
je _CallRefGefunden ;Wenn ja, überprüfe den Call [.ref]
_SucheNachCallRefsLoop:
inc eax ;ansonsten mache weiter mit der nächsten Funktion
cmp eax, 008C9FFF ;Haben wir das Ender der .text Section erreicht?
jne _SucheNachCallRefs
jmp eip ;Endlos-loop um uns die Kontrolle zurückzugeben
_CallRefGefunden:
cmp dword ptr [eax+02], 007C9000 ;Liegt das gelesene Offset in den
jl _SucheNachCallRefsLoop
cmp dword ptr [eax+02], 007CA000 ;ersten 1000h Bytes unserer IAT?
ja _SucheNachCallRefsLoop
_CheckobGemangleterCallRef:
mov ecx, dword ptr [eax+02] ;Setze ecx auf das Offset des Imports in der IAT
mov ecx, dword ptr [ecx] ;setze ecx auf die RVA des aktuellen Imports
cmp ecx, 01C00000 ;liegt die RVA in
jl _SucheNachCallRefsLoop
cmp ecx, 01D00000 ;der Safedisc Range?
ja _SucheNachCallRefsLoop
Push _FixCallRefReturnPoint ;wenn ja, setze unseren Return Point
add eax, 6 ;Berechne den 3. Parameter (Call [.ref] offset+6)
push eax ;Und pushe ihn
sub eax, 6 ;setzte eax wieder auf den Anfang des Call [.ref]s
jmp ecx ;und führe die SD Routine aus, um die korrekte RVA zu erhalten
_FixCallRefReturnPoint:
mov ebx, 008CB000 ;setze ebx auf den Anfang der temp. IAT
_FixCallRefLoop:
cmp dword ptr [ebx], edx ;Suche die aktuelle RVA in der temp. IAT
je _FixCallRef
add ebx, 4 ;überprüfe nachsten Import in der temp. IAT
cmp ebx, 008CC000 ;haben wir das Ende der temp. IAT erreicht?
jl _FixCallRefLoop ;wenn nicht, mache weiter
jmp eip ;Sonst übergebe uns die Kontrolle zur Fehlersuche
_FixCallRef:
sub ebx, 008CB000 ;Setze ebx auf dad relative Offset, gemessen am IAT Anfang
add ebx, 007C9000 ;und auf das zukünftige Offset wenn die temp. IAT an die richtige Stelle
kopiert wurde
mov dword ptr [eax+02], ebx ;und update den Call [.ref] mit dem neuen Offset des Imports
jmp _SucheNachCallRefsLoop Am Ende dieser Routine
haben wir eine gefixte .text Section und alle Call [.ref]s callen
jetzt den korrekten Import.
|
| 7.
Imports fixen pt. II (Theorie, wird hier nicht gebraucht):
|
Leider sind die Call [.ref]s nicht die einzigen
Methoden, wie der Compiler der exe die Imports eingebunden hat. Es
gibt noch zwei weitere, von denen wir aber nur eine fixen müssen
:)
1. Es werden Register, wie z.B esi auf den dword ptr
[.ref] gesetzt und anschließend das entsprechende Register
gecallt. 2. Es existieren sogenannte Jumptables, eigentlich
funktionieren sie wie Call [.ref]s, nur dass sie beim Ausführen kein
Offset pushen. Es sind also Jmp [.ref]s. Sie werden durch Call
$Offset_Jmp_Ref aufgerufen.
Möglichkeit 1 müssen wir bei
dieser Safedisc Version noch nicht fixen. Trotzdem eine kurze
Erklärung wie das funktionieren würde:
mov ecx, $Anfang_Originale_IAT - 4
_DurchsucheIAT:
add ecx, 4 ;mache weiter mit der nächsten RVA
cmp dword ptr [ecx], 01C00000 ;gehört die RVA zur
jl _DurchsucheIAT
cmp dword ptr [ecx], 01D00000 ;Safedisc Range?
ja _DurchsucheIAT:
mov eax, $Anfang_Text_Section
_SucheNachReference:
cmp dword ptr [eax], ecx ;ließt die aktuelle Funktion die aktuelle RVA?
je _überprüfeObCallRegister ;wenn ja, überprüfe ob dies durch ein Register geschieht
_SucheNachReferenceLoop:
inc eax ;mache weiter mit der nächsten Funktion
cmp eax, 008C9FFF ;haben wir das Ende der .text Section erreicht?
jl _SucheNachReference ;wenn nicht, mache weiter
jmp eip
_überprüfeObCallRegister:
cmp word ptr [eax-2], 1D8B ;wird ebx auf unsere aktuelle RVA gesetzt?
je _ebxHandle
cmp word ptr [eax-2], 358B ;wird esi auf unsere aktuelle RVA gesetzt?
je _esiHandle
cmp word ptr [eax-2], 3D8B ;wird edi auf unsere aktuelle RVA gesetzt?
je _ediHandle
cmp word ptr [eax-2], 2D8B ;wird ebp auf unsere aktuelle RVA gesetzt?
je _ebpHandle
jmp _SucheNachReferenceLoop
_ebxHandle:
mov edx, D3FF ;setze den entsprechenden Suchsting für den "Call edx"
jmp _FindeCallRegister
_esihandle:
mov edx, D6FF ;setze den entsprechenden Suchsting für den "Call esi"
jmp _FindeCallRegister
_ediHandle:
mov edx, D7FF ;setze den entsprechenden Suchsting für den "Call edx"
jmp _FindeCallRegister
_ebpHandle:
mov edx, D5FF ;setze den entsprechenden Suchsting für den "Call ebp"
jmp _FindeCallRegister
_FindeCallRegister:
mov esi, eax
add esi, 4 ;setzt esi auf die nächste Funktion nach dem mov Register, [007C9...]
_FindeCallRegisterLoop:
cmp word ptr [esi], dx ;und sucht nach den entsprechenden 2 Bytes
je _CallRegisterGefunden
_SucheWeiterCallRegister:
inc esi
jmp _FindeCallRegisterLoop
_CallRegisterGefunden:
push _CallRegisterReturnPoint ;setzt unseren Return Point
add esi, 2 ;berechnet unseren dritten Parameter
push esi ;und pusht diesen
jmp dword ptr [ecx] ;Safedisc Call Auflös Routine um die korrekte RVA zu erhalten
_CallRegisterReturnPoint:
mov ebx, 008CB000 ;setze ebx auf den Anfang der temp. IAT
_FixCallRegisterLoop:
cmp dword ptr [ebx], edx ;Suche die aktuelle RVA in der temp. IAT
je _FixCallRegister
add ebx, 4 ;überprüfe nachsten Import in der temp. IAT
cmp ebx, 008CC000 ;haben wir das Ende der temp. IAT erreicht?
jl _FixCallRegisterLoop ;wenn nicht, mache weiter
jmp eip ;Sonst übergebe uns die Kontrolle
_FixCallRegister:
sub ebx, 008CB000 ;Setzt ebx auf das relative Offset, gemessen am IAT Anfang
add ebx, 007C9000 ;und auf das zukünftige Offset wenn die temp. IAT an die richtige Stelle
kopiert wurde
mov dword ptr [eax], ebx ;und update das mov Register [007C9...] mit dem neuen offset des Imports
jmp _SucheWeiterCallRegister
|
| 8.
Imports fixen pt. III: |
Möglichkeit 2 müssen wir in unserer Safedisc
Version noch fixen. Zwar sind die Jumptables alle in Ordnung, dafür
sind es aber die Calls zu diesen Jumptables nicht. Wie finden wir
nun diese Calls? Ganz einfach, es existieren ein paar Jumps, die auf
die Safedisc Section stx774 pointen. Tracen wir doch einfach mal ein
bisschen, bis wir einen dieser Jumps finden.
Die stx774
Section beginnt bei 008CA000 und ist 1000h Bytes groß.
Einer
diser Jumps ist hier:
00408E6C E9B61D4C00 JMP 008CAC27 wenn
wir ihn passieren, kommen wir hier heraus:
008CAC27 53 PUSH EBX
008CAC28 E800000000 CALL 008CAC2D
008CAC2D 870424 XCHG DWORD PTR SS:[ESP],EAX
008CAC30 9C PUSHFD
008CAC31 05D3F3FFFF ADD EAX,-0C2D
008CAC36 8B18 MOV EBX,DWORD PTR DS:[EAX]
008CAC38 6BDB6B IMUL EBX,EBX,6B
008CAC3B 035804 ADD EBX,DWORD PTR DS:[EAX+4]
008CAC3E 9D POPFD
008CAC3F 58 POP EAX
008CAC40 871C24 XCHG DWORD PTR SS:[ESP],EBX
008CAC43 C3 RET Tracen wir über
das "ret" am Schluss, finden wir uns in der Safedisc Range
wieder.
01C863A0 68 718E4000 PUSH 408E71 ;Der 3. Parameter wird manuell gepusht, da kein Call stattfindet
(Offset des jmps+5)
01C863A5 68 5711EABF PUSH BFEA1157 ;2. Parameter
01C863AA 9C PUSHFD
01C863AB 60 PUSHAD
01C863AC 54 PUSH ESP
01C863AD 68 E063C801 PUSH 1C863E0 ;1. Parameter
01C863B2 E8 D9C848FF CALL 01112C90 ;Call Auflös_Rutine
01C863B7 83C4 08 ADD ESP,8
01C863BA 6A 00 PUSH 0
01C863BC 58 POP EAX
01C863BD 61 POPAD
01C863BE 9D POPFD
01C863BF C3 RET
Diese Jumps
werden im Endeffekt also genauso aufgelöst wie die Call [.ref]s, nur
dass der 3. Parameter manuell gepusht wird, da der Call ersetzt
wurde. Wir brauchen die Routine also nicht weiter verändern
:)
Das einzige, das wir beachten müssen, ist dass die Jumps
erst berechnet werden müssen. Sie enthalten kein sofort ablesbares
Ziel Offset, so wie es bei den Call [.ref]s war. Long Jumps
bestehen aus einem Byte (E9) und einem dword Wert, der die Anzahl
von Bytes ab der nächsten Funktion nach dem Jump bis zum Ziel Offset
angibt.
Es sei noch angemerkt, dass nicht alle diese
Longjumps zur stx 774 Section füher einmal Calls zu Jumptables
gewesen sind. Das erkennt man schon daran, dass nicht für alle durch
die folgende Methode herausgefundenen Imports Jump [.ref]s
existieren. Einige dieser Long Jumps waren also einmal Call [.ref]s.
Der für uns entscheidende Unterschied zwischen einem Call
$Offset_Jump_Ref und einem Call [.ref] ist die unterschiedliche
Länge. Der Call $Offset_Jump_Ref ist nur 5 Bytes lang, während
der Call [.ref] 6 Bytes Länge hat. Würde man jetzt also einfach alle
Long Jumps, die zur stx774 Section führen mit Call [.ref]s
überschreiben, so würde man immer 1 Byte "richtigen" Codes
überschreiben. Das Game wäre nicht lauffähig.
Wie entscheiden
wir also, ob an eine Stelle ein Call $Offset_Jump_Ref gehört oder
ein Call [.ref]?
Ganz einfach, wir überprüfen einfach das
Byte das bei einem Call [.ref] überschrieben werden würde. Ist es
90h, also sowieso überflüssig, so benutzen wir einen Call [.ref].
Ist es etwas anderes, suchen wir nach dem passenden Jump [.ref].
Wird dieser gefunden, uberschreiben wir den Long Jump mit einem Call
$Offset_Jump_Ref. Wird der Jump [.ref] nicht gefunden, so lassen
wir uns die Kontrolle übergeben um die entsprechende Stelle von Hand
zu fixen. Zu 99,9999% ist es aber dann doch ein Call [.ref] und das
zu überschreibende Byte ist ein sogenanntes Junk Byte, das von
Macrovision eingesetzt wurde, um uns unsere Arbeit zu erschweren.
Das heist, eigentlich könnten wir auch statt dem jmp eip, ein jmp
_LongJumpIstCallRef assembeln. Da das jedoch nur ein paar jmps sind,
gehen wir lieber auf Nummer sicher.
Coden wir
also:
mov eax, 00401000 ;Anfang der .text Section
_SucheNachLongJumps:
cmp byte ptr [eax], E9 ;haben wir es möglicherweise mit einem Long Jump zu tun?
je _JumpGefunden
_SucheNachLongJumpsLoop:
inc eax ;überprüfe das nächste Byte
cmp eax, 008C9FFF ;Haben wir das Ende erreicht?
jne _SucheNachLongJumps
jmp eip ;Endlos-Loop
_JumpGefunden:
mov ecx, dword ptr [eax+01] ;Setze ecx auf den dword Wert des Abstandes
add ecx, eax ;Addiere ihn mit dem aktuellen Offset
add ecx, 00000005 ;und mit der länge des Jumps == Ziel Offset
cmp ecx, 008CA000 ;Ist das Ziel Offset in
jl _SucheNachLongJumpsLoop
cmp ecx, 008CB000 ;der stx774 Section?
jnb _SucheNachLongJumpsLoop
push _LongJumpFixReturnPoint ;Wenn ja, setze unseren Return Point
jmp ecx ;und berechne die korrekte RVA für unseren Import
_LongJumpFixReturnPoint:
mov ebx, 008CB000 ;setze ebx auf den Anfang unserer temp. IAT
_LongJumpFixLoop:
cmp dword ptr [ebx], edx ;und durchsuche sie nach unserer aktuellen RVA
je _LongJumpFix ;gefunden, dann springe
add ebx, 00000004 ;sonst überprüfe den nächsten dword
cmp ebx, 008CC000 ;haben wir das Ende erreicht?
jl _LongJumpFixLoop ;wenn nicht, mache weiter
jmp eip ;sonst Endlos-Loop
_LongJumpFix:
sub ebx, 008CB000 ;Setzt ebx auf dad relative Offset, gemessen am IAT Anfang
add ebx, 007C9000 ;und auf das zukünftige Offset wenn die temp. IAT an die richtige Stelle
kopiert wurde
cmp byte ptr [eax+05], 90 ;ist das Byte nach dem Long Jump ein NOP?
jne _SucheJumpTable
_LongJumpIstCallRef:
mov word ptr [eax], 15FF ;wenn ja, verwandle den Long Jump in einen Call [.ref]
mov dword ptr [eax+02], ebx ;mit dem passenden Offset unserer temp. IAT
jmp _SucheNachLongJumpsLoop ;und suche nach dem nächsten Long Jump
_SucheJumpTable:
mov ecx, 00401000 ;Anfang unsere .text Section
_SucheJumpTableLoop:
cmp word ptr [ecx], 25FF ;ist die aktuelle Funktion ein Jmp [.ref]?
je _CheckJmpRef ;wenn ja, überprüfe ihn
_SucheWeiterJmpTable:
inc ecx ;sonst überprüfe die nächste Funktion
cmp ecx, 008C9FFF ;haben wir das Ende erreicht?
jl _SucheJumpTableLoop ;wenn nicht, mache weiter
jmp eip ;Endlos-Loop, manuelles fixen. Zu 99,9999% "jmp _LongJumpIstCallRef"
_CheckJmpRef:
cmp dword ptr [ecx+02], ebx ;Ließt der aktuelle Jmp [.ref] unsere RVA aus der IAT?
jne _SucheWeiterJmpTable ;Wenn nicht, suche den nächsten
sub ecx, eax ;Wenn ja, berechne die Länge des
sub ecx, 00000005 ;Calls zu dem Jmp [.ref]
mov byte ptr [eax], E8 ;Und wandle den Long Jump ein einen
mov dword ptr [eax+01], ecx ;Call $Offset_Jump_Ref um
jmp _SucheNachLongJumps ;und suche den nächten Long Jump
|
|
Wenn wir all diese Teilprogramme hintereinander
haben laufen lassen, sind nun endlich alle Imports gefixt...
:) Bleibt uns nur noch, die alte IAT durch die neue zu ersetzen.
Das machen wir entweder unter SoftIce mit dem Command
:m
008CB000 L 1000 007C9000
oder wir Coden noch etwas
mehr:
_KopiereIAT:
mov ebx, 008CB000 ;Anfang der temp. IAT
mov ecx, 007C9000 ;Anfang der Originalen IAT
_KopiereIATLoop:
mov eax, dword ptr [ebx] ;setze eax auf die RVA der korrekten Imports
mov dword ptr [ecx], eax ;und schreibe ihn über die entsprechende Stelle in der orig. IAT
add ebx, 4 ;nächster dword der temp. IAT
add ecx, 4 ;nächster dword der originalen IAT
cmp ecx, 007CA000 ;Haben wir schon 1000h Bytes kopiert?
jle _KopiereIATLoop ;sonst mache weiter
jmp eip ;Endlos-Loop
|
| 10.
Dumpen und rebuilden: |
Jetzt sind wir endlich so weit, dass wir je nach
belieben jede Section einzelnd oder oder alle zusammen dumpen
können. Unter Win9x sollte es möglich sein, die Imports direkt mit
Procdump oder Icedump neu rebuilden zu lassen, weil die Engine auf
das System ausgelegt ist.
Unter XP dumpt man das ganze
einfach ersteinmal und lässte die Imports unangetastet. Die PE
Rebuilder Option kann man in Procdump natürlich trotzdem verwenden,
ich empfehle es sogar!!!
Weil die beiden Safedisc Sections am
Ende des Dumps nicht mehr gebraucht werden, markieren wir alles ab
dem Offset 004B6000 und löschen es. Danach öffnen wir den Dump im
PEditor und löschen auch die Einträge der Sections im PE Header.
Während all diesen Aktion darf die gefixte Exe im Speicher noch
nicht terminiert werden!!!
Wir müssen ja noch die Imports
fixen (zumindest unter XP). Dazu starten wir jetzt Imprec 1.6. Wir
wählen aus der Liste der aktiven Prozesse unsere Maxpayne.exe aus
und warten bis die benutzen Module geladen wurden. Dann geben wir
den OEP ein und klicken auf "IAT AutoSearch".
Wurde das
Programm fündig, klicken wir auf "Get Imports". Alles was jetzt noch
als Invalid angezeigt wird, löschen wir (wir habe ja schließlich
extra dafür gesorgt, dass alle Imports valid sind :D). Dann updaten
wir unseren Dump, indem wir auf "Fix Dump" klicken.
Das
einzige was jetzt noch übrig bleibt, ist unseren gefixten Dump zu
rebuilden um sicherzustellen, dass er auf allen Rechnern läuft. Dazu
benutzen wir den PE Editor unserer Wahl: Procdump, PEditor, LordPE,
etc...
|
| 11.
Was haben wir gelernt? |
Was können wir nach erfolgreichem Abschließen
dieses Tutorials über das dumpen und fixen von Games mit neueren
Safedisc Versionen sagen? Gibt es ein Schema?
Ja, das gibt
es. Da das Game ja laufen muss, werden wir immer in der Lage sein,
die gemangleten Stellen im Code durch die entsprechend gepatchte
Safedisc Routine zu fixen. Es gibt keine unbezwingbare Protection.
|
|
Thanx fly out to (no particular Order): alle CIP
Member, besonders quake_ger, r!sc, Peex, Black Check, dec0de12,
ArthaXerXes, meine RL Freunde und mein Soldier Girl ;)
|
| 13.
Disassembled PE Header |
So sollte es in einem nach diesem Tutorial
gepatcheten PE Header aussehen. Die Erklärungen werden aus
verständlichen Gründen ausgelassen ;)
:00000500 C705472E1101E97C0100 mov dword ptr [01112E47], 00017CE9 <- Patch für die
:0000050A 66C7059F3111019090 mov word ptr [0111319F], 9090 <- Call_Auflös
:00000513 C705593211018B650C61 mov dword ptr [01113259], 610C658B <- Routine
:0000051D C7055D3211019D5AC300 mov dword ptr [0111325D], 00C35A9D <- zum temp. IAT erstellen
:00000527 EB13 jmp 0000053C
:00000529 90 nop
:0000052A 90 nop
:0000052B 90 nop
:0000052C 90 nop
:0000052D C7055F32110183C404C3 mov dword ptr [0111325F], C304C483 <- add esp, 4 Patch für den restlichen Code
:00000537 EB39 jmp 00000572
:00000539 90 nop
:0000053A 90 nop
:0000053B 90 nop
:0000053C B900907C00 mov ecx, 007C9000 ;Anfang Build IAT
:00000541 BB00B08C00 mov ebx, 008CB000
:00000546 90 nop
:00000547 90 nop
:00000548 8B11 mov edx, dword ptr [ecx]
:0000054A 81FA0000C001 cmp edx, 01C00000
:00000550 7C0E jl 00000560
:00000552 81FA0000D001 cmp edx, 01D00000
:00000558 7706 ja 00000560
:0000055A 90 nop
:0000055B 90 nop
:0000055C FFD2 call edx
:0000055E 90 nop
:0000055F 90 nop
:00000560 8913 mov dword ptr [ebx], edx
:00000562 83C104 add ecx, 00000004
:00000565 83C304 add ebx, 00000004
:00000568 81F900A07C00 cmp ecx, 007CA000
:0000056E 7CD8 jl 00000548
:00000570 EBBB jmp 0000052D
:00000572 B800104000 mov eax, 00401000 ;Anfang Suche nach Call [.ref]s
:00000577 668138FF15 cmp word ptr [eax], 15FF
:0000057C 740C je 0000058A
:0000057E 40 inc eax
:0000057F 3DFF9F8C00 cmp eax, 008C9FFF
:00000584 75F1 jne 00000577
:00000586 EB72 jmp 000005FA
:00000588 90 nop
:00000589 90 nop
:0000058A 81780200907C00 cmp dword ptr [eax+02], 007C9000
:00000591 7CEB jl 0000057E
:00000593 81780200A07C00 cmp dword ptr [eax+02], 007CA000
:0000059A 77E2 ja 0000057E
:0000059C 90 nop
:0000059D 90 nop
:0000059E 8B4802 mov ecx, dword ptr [eax+02]
:000005A1 8B09 mov ecx, dword ptr [ecx]
:000005A3 81F90000C001 cmp ecx, 01C00000
:000005A9 7CD3 jl 0000057E
:000005AB 81F90000D001 cmp ecx, 01D00000
:000005B1 77CB ja 0000057E
:000005B3 90 nop
:000005B4 90 nop
:000005B5 68CB054000 push 004005CB
:000005BA 0506000000 add eax, 00000006
:000005BF 50 push eax
:000005C0 2D06000000 sub eax, 00000006
:000005C5 90 nop
:000005C6 90 nop
:000005C7 FFE1 jmp ecx
:000005C9 90 nop
:000005CA 90 nop
:000005CB BB00B08C00 mov ebx, 008CB000
:000005D0 3913 cmp dword ptr [ebx], edx
:000005D2 740F je 000005E3
:000005D4 83C304 add ebx, 00000004
:000005D7 81FB00C08C00 cmp ebx, 008CC000
:000005DD 7CF1 jl 000005D0
:000005DF EBFE jmp 000005DF
:000005E1 90 nop
:000005E2 90 nop
:000005E3 81EB00B08C00 sub ebx, 008CB000
:000005E9 81C300907C00 add ebx, 007C9000
:000005EF 895802 mov dword ptr [eax+02], ebx
:000005F2 90 nop
:000005F3 90 nop
:000005F4 EB88 jmp 0000057E
:000005F6 90 nop
:000005F7 90 nop
:000005F8 90 nop
:000005F9 90 nop
:000005FA B800104000 mov eax, 00401000 ;Anfang Suche nach Long Jumps
:000005FF 90 nop
:00000600 90 nop
:00000601 8038E9 cmp byte ptr [eax], E9
:00000604 740D je 00000613
:00000606 40 inc eax
:00000607 3DFF9F8C00 cmp eax, 008C9FFF
:0000060C 75F3 jne 00000601
:0000060E 90 nop
:0000060F EBFE jmp 0000060F
:00000611 90 nop
:00000612 90 nop
:00000613 8B4801 mov ecx, dword ptr [eax+01]
:00000616 03C8 add ecx, eax
:00000618 83C105 add ecx, 00000005
:0000061B 81F900A08C00 cmp ecx, 008CA000
:00000621 7CE3 jl 00000606
:00000623 81F900B08C00 cmp ecx, 008CB000
:00000629 73DB jnb 00000606
:0000062B 90 nop
:0000062C 6835064000 push 00400635
:00000631 FFE1 jmp ecx
:00000633 90 nop
:00000634 90 nop
:00000635 BB00B08C00 mov ebx, 008CB000
:0000063A 90 nop
:0000063B 90 nop
:0000063C 3913 cmp dword ptr [ebx], edx
:0000063E 740F je 0000064F
:00000640 83C304 add ebx, 00000004
:00000643 81FB00C08C00 cmp ebx, 008CC000
:00000649 7CF1 jl 0000063C
:0000064B EBFE jmp 0000064B
:0000064D 90 nop
:0000064E 90 nop
:0000064F 81EB00B08C00 sub ebx, 008CB000
:00000655 81C300907C00 add ebx, 007C9000
:0000065B 90 nop
:0000065C 90 nop
:0000065D 80780590 cmp byte ptr [eax+05], 90
:00000661 7511 jne 00000674
:00000663 90 nop
:00000664 90 nop
:00000665 66C700FF15 mov word ptr [eax], 15FF
:0000066A 895802 mov dword ptr [eax+02], ebx
:0000066D 90 nop
:0000066E 90 nop
:0000066F EB95 jmp 00000606
:00000671 90 nop
:00000672 90 nop
:00000673 90 nop
:00000674 B900104000 mov ecx, 00401000
:00000679 668139FF25 cmp word ptr [ecx], 25FF
:0000067E 740D je 0000068D
:00000680 41 inc ecx
:00000681 81F9FF9F8C00 cmp ecx, 008C9FFF
:00000687 7CF0 jl 00000679
:00000689 EBFE jmp 00000689
:0000068B 90 nop
:0000068C 90 nop
:0000068D 395902 cmp dword ptr [ecx+02], ebx
:00000690 75EE jne 00000680
:00000692 90 nop
:00000693 90 nop
:00000694 2BC8 sub ecx, eax
:00000696 83E905 sub ecx, 00000005
:00000699 C600E8 mov byte ptr [eax], E8
:0000069C 894801 mov dword ptr [eax+01], ecx
:0000069F E962FFFFFF jmp 00000606
|
Hast Du noch Fragen oder Probleme mit dem
Tutorial? Zögere nicht mir eine EMail zu schicken, ich werde
versuchen zu helfen: Contact
Besuch uns
wieder unter http://cip.myz.info/
oder
unser Board: http://board.cip.myz.info/ | |