logo  
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
 
Inhalt:

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

1. Einleitung:

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.

4. IAT fixen pt. I

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.

5. IAT fixen pt. II

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.

6. Imports fixen pt. I

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

9. Das war's schon?

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.

12. Credits

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/