#Titel: Coding a PE-Crypter (oder: Wie man EXE Dateien verschlüsselt) #Datum: 29.09.2008 #Author: Eddy14 #Webseite: www.eddys-blog.6x.to #Email: eddy141@hotmail.de #Inhaltsverzeichnis: ### 0x1 - Vorwort ### 0x2 - Einleitung ### 0x3 - Runtime vs Scantime ### 0x4 - Das PE-Format ### 0x5 - Wie gehen wir vor? ### 0x6 - Sicherheit ### 0x7 - Ein bisschen Code ### 0x8 - Schlusswort _________________________ \/\/\/\/\/0x1\/\/\/\/\/\/ ######################### # Vorwort # ######################### Hallo und herzlich Willkommen in meinem zweiten Tutorial dieses Jahr, und das sechste insgesamt (leider, muss ich sagen, ich hätte gerne mehr Tutorials geschrieben, glaubt mir :/) Kurz gefasst, geht es in diesem Tutorial, um das verschlüsseln von exe Dateien. Wie in jedem meiner Tutorials, sag ich erstmal, was ihr alles an Vorwissen braucht, um dieses Tutorial erfolgreich zu meistern :) -Gute Kenntnisse in Delphi (für spätere, kleine Codeschnippsel) -Kenntnisse in OOP (ein bisschen reicht schon ;) ) und Strukturen -Man sollte schonmal eine Exe Datei gestartet haben :P -Kenntnisse im Bereich des Reverse-Engineering (Manual Unpacking wäre cool!) -Kenntnisse in Assembler (zum Schreiben des Stubs; und auch sonst sehr nützlich und wichtig) -Das letzte und schwierigste ist: Das PE-Format kennen! Alles in allem, ist natürlich hier mehr gefordert als wirklich nötig ist :) Aber wenn ihr all das aufgezeigte könnt, versteht ihr das Tutorial am besten. Zum letzten Abschnitt muss ich aber sagen, dass ich auf das PE-Format auch eingehen werde. Jedoch kann ich nicht komplett darauf eingehen, da es einfach den Umfang dieses Tutorials sprengen würde. Ich werde aber ein paar sehr gute Dokumentationen darüber verlinken. Also, seid ihr bereit auf ein neues Abenteuer mit Eddy? Auf gehts in die Einleitung! _________________________ \/\/\/\/\/0x2\/\/\/\/\/\/ ######################### # Einleitung # ######################### Heute, vor genau 2 Jahren, stellte ich eine Frage im opensc.ws Forum, wie man denn einen Crypter codet, und suchte gezielt nach einem Tutorial. Wer hätte es anders erwartet, es existierten keine Tutorials, und auch kein richtiger "Strang" wie man denn nun auf das gewünschte Ergebnis kommt. Also, machte ich einfach wie bisher, vergaß das mit dem Crypter ganz schnell wieder und widmete mich ganz anderen Sachen. Was dabei heraus kam, ist dieses Tutorial, nach 2 Jahren :P (nein ich habe nicht 2 Jahre an diesem Tutorial gearbeitet, sondern nur einen Tag, aber ich habe 2 Jahre gebraucht um mir dieses Wissen anzueignen). Wieso hat das so lange gedauert? Zum einen, weil ich nicht wusste WAS ich genau lernen musste, und zum anderen, war ich sehr viel mit anderen Sachen beschäftigt (Spieleprogrammierung ftw!). Irgendwann kam ich aber zum Reverse-Engineering. Ich lernte, wie man Shareware freischaltet, Keygens schreibt, und zuletzt, das aller wichtigste, wie man gepackte Programme wieder entpackt. Manual Unpacking nennt sich das ganze, und lehrt einem sehr viel über das PE-Format. Ich muss gestehen, ich bin nicht der King darin, aber ich weiß genug, um etwas damit umgehen zu können :O Schande über euch, die ein Tutorial von einem Noob lesen! :P Naja, im nächsten Kapitel werde ich euch den Unterschied zwischen Runtime und Scantime erklären. Also dann, lets go! :) _________________________ \/\/\/\/\/0x3\/\/\/\/\/\/ ######################### # Runtime vs Scantime # ######################### Viele Kiddies lesen dieses Stück geschriebene Theorie. Alle denken sich "Jeah, geil, porno, hammer, ficken, wie code ich einen 1337 crypter ?!?!?!?!?". Und zugleich kommt von etwas fortgeschrittenen Noobies die Frage "Scantime kann ich ja schon crypten, aber Runtime? WIE GEHT DAS VERDAMMT?!" Erstmal ganz ruhig. Ich unterstütze euch Kiddies nicht! Ich kann euch nur sagen, in "Wirklichkeit" existiert nur eine Art von Cryptern, nämlich die Runtime Crypter! Die Scantime Crypter sind die Erfindung von kleinen Trojaner Kiddies! ;) Sieht man doch schon an dem Wort "Scan". Der Unterschied ist ganz klar, Runtime bedeutet, zur Laufzeit, und Scantime, zu der Zeit wo das Programm noch nicht gestartet wurde. Die Kiddies unterscheiden dies, weil ein Antivirus Programm die Möglichkeit hat, Programme auf der Festplatte zu analysieren. Ein Programm, welches Runtime crypted, macht es im Arbeitsspeicher, direkt während es noch läuft! Sowas merkt ein AV nicht. Aber ein Crypter, welches Scantime cryptet, der entpackt das entschlüsselte Programm wieder auf die Festplatte. Unsinn oder? Das ist ganz einfach, die Leute wissen nicht, wie ein Crypter überhaupt funktioniert! Ich erkläre euch mal Scantime so: Die exe Datei wird verschlüsselt, wie jede andere Datei auch (von Anfang der Datei bis zum Ende). So in dieser Form ist es natürlich nicht lauffähig! Woher soll der Windows Loader wissen, wie es dieses verkrüppelte Teil ausführt? Der Scantime Crypter fügt nun diese verschlüsselte Datei z.B. als Resource in den "Stub". Der Stub, ist hier das Programm, welches die verschlüsselte Datei später wieder entpackt. So, nun sitzt die besagte Datei im Stub. Der Stub ist nun etwas größer geworden (es beinhaltet die gecrypteten Datei). Dieser "fertige" Stub wird nun ausgeführt. Der AV erkennt da nichts, die Stub ist undetected (jedenfalls, solange die AV-Leute es noch nicht erkannt haben) und darin ist nur verstümmmelter Code, die der AV nicht kennt. Bei der Ausführung der fertigen Stub wird nun etwas ganz einfaches gemacht. Die Resource Datei entpackt, wird entschlüsselt, und mit einem einfachen Execute Kommando gestartet! Einfach oder? Aber der AV erkennt dies natürlich, der Virus liegt ja jetzt wieder in seiner Ursprungsform auf der Platte! Ein Runtime Crypter macht etwas ganz anderes. Es muss noch erwähnt werden, dass nicht nur EINE Crypt-Methode existiert, sondern sehr viele verschiedene (je nach kreativität ;) ) Ich werde euch in diesem Tutorial eine Methode vorstellen, welches einen Stub, welches nur 23 Bytes groß ist, in die bereits existierende Sektion einer Datei steckt. Dies ist wohl die einfachste und faulste Methode, einen Crypter zu coden. Aber es reicht, für dieses Tutorial. Ich hoffe euch ist der Unterschied klar geworden. Nochmal zum mitschreiben: Scantime => Virus wird nicht erkannt, zum Zeitpunkt des Scans auf der Festplatte, ABER nach der Decryption! Runtime => Virus wird nicht erkannt, zum Zeitpunkt des Scans auf der Festplatte, SOWIE nach der Decryption! Zuletzt will ich aber noch sagen, dass ich unter keinen Umständen Viren, Trojaner etc. mit diesem Tutorial unterstütze! Die Kiddies werden das Tutorial aber sowieso nicht erfolgreich durchstehen :) Da ich als Linux Nutzer schon vergessen habe, wie es war jeden Tag Angst vor Viren zu haben, kann ich eure Gefühle leider nicht ganz verstehen und deuten :O Dennoch, wisset eins: Viren sind böse, Virenschreiber nicht :) Anstatt "Virus" könnt ihr auch jedes andere Programm nehmen, welches nicht direkt einsehbar sein soll (was die Funktionalität angeht) wenn es in einen Disassembler geladen wird :) Also, Shareware = Virus *g* _________________________ \/\/\/\/\/0x4\/\/\/\/\/\/ ######################### # Das PE-Format # ######################### Ich werde euch jetzt nicht mit historischem ärgern, sondern nur kurz und knapp sagen: Exe und DLL Dateien (und soweit ich noch weiß, ein paar andere) verwenden das PE-Format (Portable Executable). Ein Dateiformat legt fest, wie eine Datei gegliedert und aufgebaut ist. D.h. wo sich welche Daten befinden, wie diese abzurufen sind usw. z.B. ist eine JPG Datei ganz anders aufgebaut als eine RAR Datei. Nur weil dein Programm beispielsweise mit JPG Dateien umgehen kann, heißt es nicht, dass es das auch mit RAR Dateien kann. Denn beides sind komplett unterschiedlich und beinhalten auch unterschiedliche Arten von Daten, obwohl sie beide >Dateien< sind. Um einen Crypter für Exe Dateien zu schreiben, MÜSST ihr das PE-Format in seiner ganzen Pracht kennen! Denn ihr werdet damit arbeiten. Ihr müsst bestimmte Daten abrufen können, abspeichern können, berechnen können usw. Dafür braucht ihr erstmal eine anständige Dokumentation. Ich kann euch da (entweder die Google Such- ergebnisse oder) folgendes empfehlen: http://arteam.accessroot.com/tutorials.html?fid=112 (sehr nett geschrieben :) ) Ich selbst habe mir eine Delphi-Unit geschrieben, welches solch eine Datei laden und bearbeiten kann. Der Umgang damit ist ziemlich simple. Es kann auch noch nicht wirklich alles, aber schon einiges! Es umfasst schon 1400 Zeilen Code, geschrieben in ca. 2 Wochen. Wenn ihr euch also wirklich damit auseinandersetzen wollt, macht euch auf viel Tipp-Arbeit gefasst! Ihr könnt natürlich auch meine fertige Library verwenden, was euch aber nichts nützen wird, da ihr das PE-Format trotzdem kennen müsst (die Library macht nichts automatisch für euch, es stellt euch nur die Daten der PE-Datei zur verfügung). Lest euch ein bisschen in das Format ein. Gibt nicht auf! Schreibt am besten ein kleines C oder Delphi Programm, wo ihr gewisse Bereiche einer Pe-Datei ausliest und ausgibt. Für den Anfang sollte es reichen die 2 Magic-Bytes "MZ" und "PE" auszulesen ;) Dann seid ihr sicherlich motiviert genug, den Rest auch noch zu schaffen :) Zuletzt, gibt es noch zu sagen: Lernt Reverse Engineering, da lernt ihr das Format am besten kennen! _________________________ \/\/\/\/\/0x5\/\/\/\/\/\/ ######################### # Wie gehen wir vor? # ######################### OK, wir werden in diesem Tutorial nur die Runtime Crypter besprechen (daraus könnt ihr die Scantime Dinger sowieso ableiten). Der Plan hinter meinem simplen Crypter ist folgendes: Wir setzen an das Ende der Code-Section einer Datei unseren Stub rein, verschlüsseln die ganze Section (ohne unseren Stub natürlich), setzen den Entrypoint auf unseren Stub und fertig! Leicht gesagt, viel Arbeit ;) Schritt für Schritt: -Stub schreiben -Ganze Code-Section verschlüsseln -Stub hinten dranhängen -Entrypoint setzen Kommen wir erstmal zum Schreiben des Stubs. Das ist eigentlich eine einfache, aber zugleich eine schwierige Aufgabe! Erst einmal, was macht ein Stub nochmals? Ein Stub, entschlüsselt die verschlüsselten Bereiche der Datei. Die verschlüsselung der Datei wird unser "Builder" tun. Die entschlüsselung, unser Stück Code den wir "Stub" nennen. Unser Stub muss also die ganze Section (von Anfang bis Ende) durchgehen, und alles entschlüsseln! Ich hab dafür so einen Stub geschrieben (Achtung! Assembler!): | | B9 XXXXXXXX MOV ECX,[Adresse zum Anfang der Section] | --> 36:8031 0E XOR BYTE PTR SS:[ECX],0E | | 41 INC ECX | | 81F9 XXXXXXXX CMP ECX,[Anfang der Stub] | |-- 75 F3 JNZ SHORT [zum XOR zurück] |----- E9 XXXXXXXX JMP [Adresse zum OEP] Ist kurz (23 Bytes; illuminatus ftw!) und einfach! Ich erklär euch mal kurz was da abgeht. Erst, wird in ECX der Anfang der Sektion gesteckt. Danach wird ein XOR durchgeführt. Undzwar wird der Byte, der ausglesen wurde, mit 0xE (also 14 in Dezimal) geXORed. Nehmen wir an, dort stand ein "POP EBX", was in Binär ein 0x5B darstellen würde. Dieses 0x5B geXORed mit einem 0xE ergibt 0x55, in ASM wäre dass dann ein "PUSH EBP". Kompliziert? Nein ist es nicht! Es wurde einfach nur ein Byte geXORed. Wer nicht weiß was ein XOR ist, schnell Wikipedia gucken, oder sich nochmal die Voraussetzungen für dieses Tutorial lesen. :-O Der Vorteil eines XOR ist es, dass wenn man nochmal mit dem gleichen Wert XORed, ergibt es wieder den Ursprungswert. Also 0x55 mit 0xE XORen, ergibt wieder 0xEB :) Im nächsten Schritt wird ECX um 1 erhöht. Das heißt, es kommt das nächste Byte dran! Nun findet ein Vergleich statt, und zwar ob wir schon mit dieser Section durch sind. Falls dem nicht so ist, zurück zum XOR, und das nächste Byte XORen. Falls dem doch so ist, dann sind wir fertig, und können zum OEP springen (OEP = Original Entry Point, der EntryPoint der ungecrypteten Datei). Das Programm startet also ganz normal, als wäre nix gewesen. Wir haben das Programm im Speicher selbst verändert! :) Dafür bedarf es aber noch ein bisschen Arbeit. Die Code-Section muss aber noch das Writable Flag besitzen (findet man im Format unter IMAGE_SECTION_HEADER.Characteristics) damit es während der Lauf- zeit beschreibbar/veränderbar ist. Jetzt fragt ihr natürlich: "Hey eddy, alles klar? Der cryptet und sowas, hab alles gecheckt [ok, ich habs nur kopiert, aber egal], jetzt hab ich noch 'ne Frage. Und zwar: Wie schreibe ich den Stub?" Meine Antwort dazu wäre: Hey Fragensteller, jo bei mir alles ok, und bei dir? Jedenfalls brauchst entweder einen Assembler (nasm) oder z.B. einen Disassembler der auch assemblieren kann (OllyDBG). Bei ersterem hat man einen Binär-Modus, wo man direkt binär in eine Datei reinschreiben kann ohne jegliche Formate (also kein PE-Format oder irgendwelcher schnick-schnack usw. nur roh!). Leider weiß ich nicht mehr welche Option es in nasm war (ich glaub "-f" müsste es sein), damit man das Ausgabeformat einstellen konnte. Jedenfalls könnt ihr die ausgegebene Datei mit einem Hex-Editor öffnen und jeweils so benutzen. Oder aber, ihr benutzt die Methode die ich anwende (schöner und einfacher, wie ich finde!). Ihr benutzt OllyDBG! Ladet euch irgendeine Datei in den Debugger, und scrollt irgendwohin (am besten in einen schönen Platz wo euch der ganze Restcode nicht stört). Und dort macht ihr dann auf einen Eintrag einen Doppelklick, und jetzt könnt ihr euren ASM-Code da reinschreiben :) Das Gute ist jetzt: Ihr habt farbliche Darstellung des Codes (ihr seht auch Sprünge usw.) und ihr könnt auch die Binäre Darstellung in Hex sehen, kopieren usw. Bleiben natürlich noch ein paar offene Fragen. z.B. wohin mit dem Stub? Dafür bedarf es wieder ein bisschen Theorie. Es gibt so etwas wie einen "Code-Cave", das ist ein Bereich in einer Section dass frei ist und nicht benutzt wird. Dieser freie Speicher bildet sich durch das Padding (na, schon in das Pe-Format eingelesen ?). Padding wird in Sectionen benutzt. Wie groß das Padding ist, zeigt der Wert in IMAGE_OPTIONAL_HEADER32.FileAlignment. Die Größe der Section ist immer ein mehrfaches davon. D.h. dass eigentlich so gut wie immer ein (kleiner) Bereich leer bleibt. Genau dieser Bereich nennt sich "Code-Cave". Dieser Bereich wird sehr gerne und häufig verwendet, z.B. beim Gamehacking oder Reverse-Engineering um irgendwas "inline" zu machen ;) Wir benutzen diesen Bereich für unseren Stub. Leider existiert dieser Bereich nicht immer, d.h. es wird voll benutzt. Das ist häufig der Fall wenn andere Crypter/Packer bei dieser Datei bereits benutzt wurden, oder der Compiler des Programmes sowas nicht bietet. Dann wirkt meine simple Methode leider nicht mehr. Man müsste dann zu einer anderen Methode greiffen, z.B. leicht abgewandelt => eine neue Section in die Datei hinzufügen, wo dann unser Stub drin landet (ist auch viel cooler und professioneller ;) ) OK, noch einmal bildlich dargestellt, so sieht die EXE-Datei VOR dem verschlüsseln aus: ________________ | | | Code-Section | | | | .text | | | | | ---------------- | | |Andere-Section| | | | .rdata | | | | | ---------------- | | |Andere-Section| | | | .data | | | | | ---------------- | | |Andere-Section| | | | .rsrc | | | | | ________________ Das ist nur ein Beispiel. Die Sectionen können willkürlich anders heißen; es können auch weniger oder mehr Sectionen existieren! Betrachten wir uns mal die Code-Section näher. BTW, die Code-Section ist einfach nur die Sektion, welches unseren Code beinhaltet. Es gibt einen Executable-Flag (unter Characteristics, siehe oben) welches hier gesetzt ist. Die Code-Section kann man aber auch finden, indem man schaut, in welcher Section der EntryPoint liegt. Darauf kommen wir aber später zu sprechen. ________________ <---| <---------------| | | | | | Code-Section | | | | | | Länge des | die ganze code-Section | .text | | Bereiches | geht von Anfang | | | welches | bis Ende, | | | genutzt | jedoch ist das Ende | --- | <---| wird | meistens (durch Padding) |Freier Bereich| | leer, also unbenutzt |in der Sektion| | |durch Padding | | ---------------- <----------------------| Kapisch? Und genau in diesen freien Bereich, tun wir unseren Stub rein! Nun, woher wissen, wo dieses Code-Cave genau ist? Das kann man ganz einfach berechnen! Besorgt euch einen PE-Editor, wie z.B. LordPE oder Peditor (google!) und ladet euch irgendeine Datei herein. Geht in den "Sections" Bereich, und ihr seht so etwas wie: Section |Virtual Size |Virtual Offset|Raw Size |Raw Offset |Characteristics| --------------------------------------------------------------------------------------- .text | 00003256 | 00001000 | 00003400 | 00000400 | 60000020 | (ich erspare es mir mal die anderen Sectionen hier abzubilden) Hier sind nun irgendwelche komischen Werte. Wie immer gilt, PE Dokumentation aufschlagen, und nachschlagen! (da fällt mir dieser eine Spruch mit dem "Du bist wie ein Lexikon, aufschlagen, zuschlagen, immer wieder naschlagen" ein, hmh, egal). Aus dem oben genannten Werten kann man nun den Code-Cave berechnen! Erst einmal, kurz zu den Werten (falls eure Doku unübersichtlich sein sollte): Virtual Size => Die Länge der Section wenn es in den Arbeitsspeicher geladen wurde Virtual Offset => Die Addresse an dem die Section liegt, wenn es in den Arbeitsspeicher geladen wurde Raw Size => Die wirkliche Größe der Section Raw Address => Die Addresse wo die Section in der Datei liegt (das könnt ihr dann direkt in einem HexEditor nachschlagen) Characteristics => Legt die Flags fest. z.B. Writable, Readable, Executable etc. (alles was mit der Section gemacht wird, sozusagen, Zugriffsberechtigungen und Sachen wie, was sich alles in der Section befindet etc.) Als Vergleich zu der Section in Original und im Arbeitsspeicher: in der Datei: im Arbeitsspeicher: ---------------- ---------------- | | | | | Code-Section | | Code-Section | | | | | | .text | | .text | | | | | | | | | | | |--------------| | | | | | | | frei | ________________ ________________ Wie gesagt, alles wegen dem schönen Padding :) Und noch einmal damit ihr wisst auf was sich die Adressen beziehen: in der Datei: ________________ <- Raw Offset (400h) <---| | | | | Code-Section | | | | | | .text | | | | | Raw Size (3400h) | | | | | | | | | | | | ---------------- <- Section Ende (3800h) <-| D.h. dann, die Section in der Datei geht von 400h bis 3800h (400h + 3400h) und hat also eine Länge von 3400h. Das gleiche könnten wir jetzt mit den Werten für den Arbeitsspeicher machen. Das würde dann ergeben, dass die Section bei 1000h anfängt und bei 4256h auf sein Ende trifft. Aber moment. Schaut euch jetzt erstmal die zwei Werte, VirtualSize und RawSize an. Eigentlich, müssten beide Werte ja genau gleich sein. Aber, wegen dem Padding, sind sie es nicht. VirtualSize ist kleiner als RawSize. Und zwar um genau 426 Bytes! (3400h - 3256h => 1AAh => 426d). Das heißt, unser Code-Cave ist 426 Bytes groß. Groß genug für unseren Stub. :) Aber jetzt mal angenommen, unser Crypter ist nun soweit den Stub in die Datei zu schreiben. Wo schreibt es sie rein? Wir müssen berechnen, welchen Offset in der Datei der Anfang des Code-Caves hat. Wir wissen, virtuell gesehen, fängt unser Code-Cave bei Adresse 4256h an (siehe oben). Aber als Offset in der Datei? Da ist wieder Mathe gefragt! Wir machen folgendes: RawOffset + VirtualSize => 400h + 3256h => 3656h so, an dieser Addresse fängt also unser Code-Cave in der Datei auf der Festplatte an! :) OK, also zusammengefasst, wir haben unseren Stub nun an die korrekte Adresse geschrieben. Jetzt müssen wir noch die ganze Code-Section (bis zum Anfang des Code-Caves) verschlüsseln! Das macht nicht unser Stub (der es ja entschlüsselt) sondern unser "Builder" oder "Crypter" (nennt es wie ihr wollt). Jetzt müssen wir noch den EntryPoint auf den Anfang des Code-Caves setzen und VirtualSize um die Größe des Stubs erhöhen. -Code-Section suchen (EntryPoint muss zwischen VirtualOffset und VirtualOffset+VirtualSize liegen) -Code-Cave in dieser Section suchen -schauen ob Code-Cave genug Platz für unseren Stub hat -Stub da rein schreiben -Ganze Section (bis zum Anfang der Code-Cave) crypten -Neuen Entrypoint auf den Code-Cave setzen -VirtualSize um die Größe des Stubs erhöhen -Fertig Das alles ist kein Problem und nur ein paar Zeilen Code, WENN man sich bereits eine PE-Library geschrieben hat ;) Das eigentliche Problem ist erst der Stub! Ich hab mir eine eigene Methode überlegt. Ich hab einfach einen String welches so aussieht: B9SSSSSSSS3680310E4181F9LLLLLLLL75F3E9JJJJJJJJ Das ist nichts weiter als mein Stub: | | B9 XXXXXXXX MOV ECX,[Adresse zum Anfang der Section] | --> 36:8031 0E XOR BYTE PTR SS:[ECX],0E | | 41 INC ECX | | 81F9 XXXXXXXX CMP ECX,[Anfang der Stub] | |-- 75 F3 JNZ SHORT [zum XOR zurück] |----- E9 XXXXXXXX JMP [Adresse zum OEP] in Hex-Form! Der Vorteil dieser Methode ist ganz klar: ich kann meinen Stub leicht bearbeiten. Aber wieso bearbeiten? Fragen sich jetzt einige. Die Antwort ist einfach, weil ich MUSS! Mein Stub braucht doch einige Daten wie z.B. der OEP, der Anfang des Code-Caves (siehe oben was mein stub alles tut) usw. In meinem besagten String-Stub ("B9SSSSSSSS3680310E4181F9LLLLLLLL75F3E9JJJJJJJJ") erkennt man einige "S", "L" und "J". Das sind einfach nur Markierungen. Die ganzen "S" markieren, dass hier die Addresse vom Anfang der Sektion hingehört. Die "L"s repräsentieren den Anfang des Code-Caves. Und last but not least, die "J"s welches das OEP sein soll :) Zum letzteren muss noch gesagt werden, dass hier speziell der JMP berechnet werden muss. Es gilt => 0 - (SourceAddress - DestinationAddress) - 5 Mal kurz etwas über die Formel hier. Wenn etwas in den Minus Bereich geht, also z.B. "-1" dann wird es als "FF" dargestellt. -2 wäre dann "FE" usw. Da in unserem Fall, der SourceAddress (also die Adresse, von der wir springen) größer ist, als der DestinationAddress (die Adresse, an die wir wollen), müssen wir dies von 0 abziehen womit wir dann ins negative rutschen. Es wird bei JMP also eine relative Adresse zum Ort angegeben. Hier also, eine Negative Adresse, was der CPU sagt, dass wir zurück springen wollen. Bei einem positivem Sprung, müssten wir statt "0 -" ein "0 + " machen. Die "- 5" am Ende, zieht noch die Anzahl der Bytes ab, welches unser JMP benötigt hat. So. Diese Daten tun wir jetzt in den String (in Delphi einfach per StringReplacE() ). Dann fehlt noch ein Schritt. Aus dem String muss jetzt wieder Binär werden. Dafür hab ich mir eine HexToBin Funktion geschrieben. Diese Funktion geht einfach nur 2-Byte weise durch den String, und konvertiert alles zurück zu Dezimal und speichert dies in einem Byte. Ich hoffe, das hat euch noch nicht zu viel verwirrt :) _________________________ \/\/\/\/\/0x6\/\/\/\/\/\/ ######################### # Sicherheit # ######################### Zur Sicherheit des hier vorgestellten Crypters gibt es nicht viel zu sagen: es ist schlecht! Um es anders auszudrücken: ein Anfänger kann mit so einem Target das Manual Unpacking etwas erlernen :) Dazu muss man einfach nur auf das letzte JMP gehen (in eurem Lieblingsdebugger; in meinem Fall wäre es OllyDBG), einmal Enter drücken und ihr seid am OEP! Einmal noch F2 damit wir dort einen Breakpoint setzen können. Nun das Programm mal mit F9 laufen lassen und ihr werdet schnell breaken. Jetzt könnt ihr das Target ganz einfach dumpen und abspeichern :) Einfach oder? So Kinder, aber das tolle ist ihr habt gerade das Wunder der Geburt ... *äh* ... das Wunder des runtime entschlüsselns gesehen! BTW, ihr könnt euren Crypter natürlich so viel verbessern wie ihr wollt. Ihr könnt daraus gleich einen Protector machen, aber eins sage ich euch gleich: es ist nicht einfach, und vieles muss umgeschrieben und beachtet werden! Dennoch, viel Spaß :) _________________________ \/\/\/\/\/0x7\/\/\/\/\/\/ ######################### # Ein bisschen Code # ######################### Wie bereits erwähnt (oder auch nicht) wollte ich dieses Tutorial NUR theoretischer Grundlage basieren lassen. Das hat den Grund, dass nicht mehr Leute einfach den Source rippen ohne daraus zu lernen. Freie Informationen, aber keine Desinformiertheit! Aus meinen älteren Tutorials wurde nur gerippt :O Deswegen, nur noch wenig Code, das nur in Zusammenhang mit der Theorie Sinn ergibt. Ich werde euch zeigen, wie ihr in Delphi das PE-Format auslesen könnt. Alles hängt von diesem Format ab. Versteht ihr es, dieses Format zu benutzen, dann steht euch nichts im Weg, einen Crypter zu coden (wenn ihr dieses Tut verstanden habt!). Weil es nur ein kleines Stück Code als Demo sein soll, werde ich nur die IMAGE_DOS_HEADER auslesen. Also erstellen wir uns ein kleines record (unter C würden sie struct heißen), die folgendermaßen aussieht: TPeDosHeader = record e_magic: WORD; e_cblp: WORD; e_cp: WORD; e_crlc: WORD; e_cparhdr: WORD; e_minalloc: WORD; e_maxalloc: WORD; e_ss: WORD; e_sp: WORD; e_csum: WORD; e_ip: WORD; e_cs: WORD; e_lfarlc: WORD; e_ovno: WORD; e_res: Array[0..3] of WORD; // 4 WORDs e_oemid: WORD; e_oeminfo: WORD; e_res2: Array[0..9] of WORD; //10 WORDs e_lfanew: WORD; end; (für näheres, einfach in eine PE-Dokumentation schauen) So, nun schreiben wir eine Unit, die nenne ich mal "PeFormat", wo wir auf alle strukturen einer PE-Datei zugreiffen können (in diesem Beispiel ja nur auf den DOS-Header): -------------------------------------------------------------------------- unit peformat; interface uses windows, dialogs, classes, sysutils, stdctrls, variants; const type DWORD = longword; //definition of DWORD //the DOS Header (IMAGE_DOS_HEADER) TPeDosHeader = record e_magic: WORD; e_cblp: WORD; e_cp: WORD; e_crlc: WORD; e_cparhdr: WORD; e_minalloc: WORD; e_maxalloc: WORD; e_ss: WORD; e_sp: WORD; e_csum: WORD; e_ip: WORD; e_cs: WORD; e_lfarlc: WORD; e_ovno: WORD; e_res: Array[0..3] of WORD; // 4 WORDs e_oemid: WORD; e_oeminfo: WORD; e_res2: Array[0..9] of WORD; //10 WORDs e_lfanew: WORD; end; TPeConstruction = record IMAGE_DOS_HEADER: TPeDosHeader; end; //TPeFormat is the main class TPeFormat = class(TObject) public Format: TPeConstruction; constructor Create; function LoadFromFile(thefile: string): integer; end; implementation //the constructor, do some important //stuff (load variables or something) constructor TPeFormat.Create; begin //do something here end; //main procedure of this unit //loads the pe file, and fills the //variable IMAGE_DOS_HEADER function TPeFormat.LoadFromFile(thefile: string): integer; var myfile: TFileStream; begin result := 0; //is this file existing? if(FileExists(thefile) = false) then begin result := -1; exit; end; myfile := TFileStream.Create(thefile, fmOpenRead); try //fill our PE-Format with the informations in the file myfile.Read(self.Format.IMAGE_DOS_HEADER, sizeof(self.Format.IMAGE_DOS_HEADER)); finally myfile.free; end; end; -------------------------------------------------------------------------- Es ist simple aufgebaut! Garnicht schwer! TFileStream macht es möglich :) Der read-Funktion können wir einfach einen Buffer geben, welches direkt mit dem Inhalt aus der Datei gefüllt wird, wie praktisch oder? myfile.Read(self.Format.IMAGE_DOS_HEADER, sizeof(self.Format.IMAGE_DOS_HEADER)); es wird direkt aus der Datei gelesen, und die ganze Struktur mit allen Daten gefüllt die wir brauchen! Nun könnten wir es direkt ansprechen mit PeFormat.Format.IMAGE_DOS_HEADER.e_magic. So könnt ihr eure ganze Unit aufbauen, und sehr schnell alle Strukturen aus dem Format implementieren. Später habt ihr wahrfreien Zugriff auf alle Elemente, wie z.B. den EntryPoint, alle Sektionen, Imports, Exports etc. :) _________________________ \/\/\/\/\/0x8\/\/\/\/\/\/ ######################### # Schlusswort # ######################### Ich weiß, das Tutorial ist entweder zu kompliziert, zu theoretisch, zu kiddig oder zu kurz ausgefallen. Aber ich hoffe doch trotzdem, dass es irgendjemandem auf der Welt geholfen hat. Wenigstens ein bisschen :) Das Tutorial darf verbreitet und verändert werden wie es gewünscht ist. Gebt aber bitte an, woher das Original stammt, und zeigt, wo ihr welche Änderungen gemacht habt (damit die Leute nicht MICH dissen, falls ihr scheisse reingeschrieben habt :P) Ah, und noch was. Vergebt mir bitte die ganzen schlechten Witze im Text. Mir wird beim Tutorialschreiben immer so langweilig, dass ich automatisch versuche mich selbst zu amüsieren :D Übersetzungen dieses Textes sind immer gerne gesehen (ins Englische wäre sehr nett!). Falls ihr also eine schreibt, dann kontaktiert mich, und ich veröffentliche es auf meiner Webseite :) Ich hoffe es hat euch Spaß gemacht. Wenn ihr noch irgendwelche Fragen habt, oder Bugs gefunden habt (oder wie auch immer) könnt ihr ganz einfach Kontakt mit mir aufnehmen Email/MSN: eddy141@hotmail.de ICQ: 235-152-168 Jabber: eddy14@jabber.ccc.de Web: www.eddys-blog.6x.to