|
Antwort |
mr_emre_d
Hiho liebe Leute.
Ich möchte euch heute erklären, wie man in Delphi einen simplen Crypter schreibt ! In diesem Tutorial wird erklärt wie man die ".text" Section einer Exe Datei verschlüsselt, den Einsprungspunkt auf eine von uns selbst geschrieben (ASM) Procedure/Code umleitet, der die Section wieder entschlüsselt und zum orginalen Einsprungspunkt zurückkehrt ! (Nur so nebenbei: in der .text Section befindet sich der eigentliche Code und deshalb habe ich es in diesem Tutorial (unabsichtlich) abwechslend "text" und "code" verwendet - damit meinte ich aber dasselbe) Voraussetzungen:
Zuerst einmal möchte ich euch warnen:
Inhaltsangabe:
1. Wichtige Details über eine Exe bzw den verschiedenen Headers Ich hatte nicht gewusst, dass die einzelnen Headers in Delphi schon vordefiniert sind und deshalb machte ich mich auf die Suche nach bestimmten Artikeln, die diese beschrieben ! Da boten sich einige gute an ! Ihr könnt euch die von wotsit.org besorgen ! Sucht dort nach "PE-Header" bzw Portable Executable.. Eine Exe ist wie folgt aufgebaut: (wichtige Teile)
Ihr solltet euch diese drei Strukturen einmal genauer anschauen: Der Dos-Header ist simple aufgebaut und steht am Anfang der Exe:
Delphi-Quellcode:
Für uns sind folgende Einträge wichtig:
// alle hier vorgestellten Typen habe ich aus den Dokumentationen extrahiert !
Signature : Array[0..1] of Char; // 4Dh 5Ah (MZ) LastPageSize : Word; // number of bytes in last page FilePages : Word; // number of 512 byte pages RelocItems : Word; // number of entries in table HeaderParas : Word; // size of header in 16 byte paras MinAlloc : Word; // minimum memory required in paras MaxAlloc : Word; // maximum memory wanted in paras PreRelocSS : Word; // offset in paras to stack segment InitialSP : Word; // starting SP value NegativeChecksum : Word; // currently ignored PreRelocIP : Word; // execution start address PreRelocCS : Word; // preadjusted start segment RelocTableOffset : Word; // is offset from start of file) OverlayNumber : Word; // ignored if not overlay Nothing : Cardinal; // end ... Reserved_Unused : Array[0..6] of Cardinal; PEOff : Cardinal; // offset of the header at off 60
Der NT-Header (Delphi: IMAGE_NT_HEADERS) besteht seinerseits wiederum aus drei Teilen:
Für uns sind unter Umständen folgende Einträge wichtig: FileHeader: o NumberOfSections - dazu komme ich später OptionalHeader: o AddressOfEntryPoint - Einsprungspunkt o ImageBase - die Adresse, wo die Exe (Image) hingeladen wird Dann gibts da noch die Sections ! Im Fileheader steht die Anzahl der Sections nämlich in NumberOfSections ! Delphi Deklaration von einer Section: _IMAGE_SECTION_HEADER Da der Eintrag "Misc" euch ein wenig verwirren kann, werde ich euch auch diese Struktur erklären
Delphi-Quellcode:
für uns sind wiedermal folgende Sachen wichtig:
TSection_Table = packed record
Object_Name : Array[0..7] of Char; Virtual_Size : Cardinal; RVA : Cardinal; Physical_Size : Cardinal; Physical_Offset : Cardinal; Reloc_Offset : Cardinal; Line_Numbers : Cardinal; Relocation_Number : Word; Line_Numbers_Number : Word; Object_Flags : Cardinal; end; o Object_Name = beinhaltet, so wie der Name schon sagt, den Namen der Section o Virtual_Size = gibt die virtuelle Größe der Section an (die Größe die es nach dem Laden ins Memory hat) o RVA = Relative Virtual Address = die Adresse im Memory Ihr fragt euch nun "auf was relativ" ? Relativ auf die ImageBase-Adresse !! o Physical_Size = die Größe in der Exe o Physical_Offset = Adresse in der Exe o Object_Flags = Nähere Definition, um was für eine Section es sich handelt - ob es beschreibbar/lesbar/ausführbar/... ist 2. Ungefähres Design des Programmes Der Crypter muss folgende Schritte durcharbeiten: 1. Lese die komplette Exe ein 2. Prüfe ob die DOS.Signature von der Exe "MZ" lautet - wenn ja, dann fahre fort 3. Ermittle den PEOffset 4. Prüfe ob die NTH.Signature von der Exe "PE" lautet - wenn ja, dann fahre fort Ermittle wichtige Informationen: 5. ImageBase 6. EntryPoint 7. NumberOfSections 8. Offset von ".text" - Section 9. ermitle die einzelnen Parameter dieser Section 10. Prüfe, ob diese Section einen Code-Cave hat UND ob diese >= unserem (ASM) Decrypter ist 11. Crypte diese Section (in unserem Fall mit Xor MagicNumber) 12. Passe unseren ASM-Code an 13. Füge den ASM-Code an das Ende der Section hinzu 14. setze den neuen Einsprungspunkt (Entrypoint) und passe ggf. Werte an Im Großen und Ganzen wars das auch schon ! 3. Das Problem mit den Adressen Hier gibts eigentlich nicht viel zu sagen. Ich werds trotzdem tun, da irgendwie viele an diesem Punkt scheitern: Um die Adresse einer Sections im Speicher ermitteln zu können müsst ihr die Imagebase von der Exe kennen und ihr braucht noch die VirtualAddress der Section ! Die Adresse im Speicher = ImageBase + Section.VirtualAddress 4. Die Implementierung Folgende Codeschnipsel habe ich aus meinem Projekt, welches ich mit angehangen habe, entnommen ! 1. Diesen Schritt müsstet ihr schon alleine hinkriegen xD Ich empfehle euch, die Daten in ein ByteArray (TByteArr = Array of Byte) zu speichern, da folgende Codeschnipsel darauf aufbauen ! 2. Einfach schauen, ob die ersten 2 Bytes = MZ sind : Result := (Chr(FileData[0]) + Chr(FileData[1])) = 'MZ' 3. Ermitteln von PE_Offset P.PE_Offset := PCardinal( @FileData[$3C] )^; Der Offset von PE_Offset im Dos_Header ist $3C = 60 dezimal Dort lesen wir halt 4 Bytes (PE_Offset = DWord) ein Ach übrigens - P = TParameters
Delphi-Quellcode:
4. Handelt es sich um einen validen PE-Header ?
TParameters = packed record
{header:} PE_Offset : Cardinal; // at $3C in the dos_header EntryPoint : Cardinal; // offset to org.ep ImageBase : Cardinal; SecAlign : Cardinal; {section:} Sections : Word; // cnt of sections CodeOffset : Cardinal; // offset to code (.text) : .. and his values: VirtualSize : Cardinal; VirtualAddr : Cardinal; RawSize : Cardinal; RawAddr : Cardinal; SectionCave : Cardinal; end; Result := chr( FileData[PEOffset] ) + chr( FileData[PEOffset+1] ) = 'PE'; 5. Hole ImageBase P.ImageBase := PCardinal( @FileData[P.PE_Offset+$34] )^; 6. Der Einsprungspunkt wird hier eingelesen P.EntryPoint := PCardinal( @FileData[P.PE_Offset+$28] )^; 7. Ermittle die Anzahl der Sections P.Sections := PWord( @FileData[P.PE_Offset+6] )^; 8. Offset von ".text" - Section
Delphi-Quellcode:
Hier werden die Sections eingelesen und es wird geprüft ob es sich um die
// ÜbergabeParameter: FileData: TByteArr; SectionName: TObject_Name; var P: Parameters
// -- nur so nebenbei: TObject_Names = Array[0..7] of Char var Section: TSection_Table; x: Word; begin P.CodeOffset := P.PE_Offset + $D0; // 48 x := 0; repeat inc( P.CodeOffset, SizeOf( TSection_Table ) ); Section := PSection_Table( @FileData[P.CodeOffset] )^; inc(x); until (Section.Object_Name = SectionName) or (x > P.Sections); if P.CodeOffset = P.PE_Offset + $D0 then P.CodeOffset := 0; gesuchte Section handelt ! Dies wird solange gemacht, bis man alle Sections durch hat. Falls nichts gefunden werden konnte, wird 0 zurückgegeben ! 9. Ermittle einzelne Werte dieser Section:
Delphi-Quellcode:
10. Cave ermitteln:
// Übergabe Parameter = FileData: TByteArr; var P: TParameters
with PSection_Table( @FileData[P.CodeOffset] )^ do with P do begin P.VirtualSize := Virtual_Size; P.VirtualAddr := RVA; P.RawSize := Physical_Size; P.RawAddr := Physical_Offset; end; Die Größe des Caves kann man wie folgt ermitteln: RawSize - VirtualSize // RawSize = Physical_Size 11. Crypten der Section
Delphi-Quellcode:
12. ASM-Code erstellen und anpassen:
// Übergabe Parameter = var FileData: TByteArr; SectionStart, SectionEnd: Cardinal;
// Magic: Byte // Magic wird für die Xor-Verschlüsselung verwendet ! var i: Integer; begin for i := SecStart to SecEnd do FileData[i] := FileData[i] xor Magic; Zuerst einmal erstellen wir einen Container.. ja so nenne ich das - "Container" Diesen Container befüllen wir nachher mit den gewünschten Adressen und zwar: SectionStart SectionEnd OrginalEntryPoint
Delphi-Quellcode:
Man kann die einzelnen Bytes durch das Debuggen ermitteln:
mov eax, .codestart // eax := CodeStart ...
mov edx, .codeend mov ecx, orginal entrypoint // mainloop start: xor ss[eax], magic // Eax^ := Eax^ xor magic inc eax // inc( eax ) cmp eax, edx // if not CodeStart = CodeEnd then jne mainloop // repeat ... springe zurück zu mainloop // jmp to entrypoint // else jmp ecx // springe zum EntryPoint
Code:
Ich habe ein Array mit diesen Werten befüllt und ein Paar Index-Konstanten deklariert, damit ich später leichter darauf zugreifen kann !
$B8, $00, $00, $00, $00,
$BA, $00, $00, $00, $00, $B9, $00, $00, $00, $00, // mainloop start: $36, $83, $30, $00, $40, $39, $D0, $75, $F7, // jmp to entrypoint $FF, $E1 ); Wie ihr sehen in den ersten 3 Zeilen unseres Code jede Menge Nullen. Diese werden wie schon erwähnt mit den Adressen befüllt ! Unserer Container-Ersteller Procedure/whatever übergeben wir folgende Parameter: CodeStart: ImageBase + (Section)VirtualAddr CodeEnd: ImageBase + (Section)VirtualAddr + (Section)VirtualAddr EntryPoint: ImageBase + EntryPoint Magic: Magic ! Dieser Wert muss = Wert, mit dem ihr die Section gecrypted habt, sein ! 13. Den "Container" in die Datei einfügen
Delphi-Quellcode:
Offset = RawAddr+VirtualSize !!
// Übergabe Parameter: Offset zur Ende der Section
Move( Stub[0], FileData[Offset], Length(Stub) ); 14. Den neuen Einsprungspunkt setzen und Werte anpassen: Dazu habe ich mir eine Hilfsfunction Namens "Parameters" geschrieben, die mir eine TParameters-Struktur mit den übergegebenen Werten zurück liefert; Diese Procedure SetSectionParams setzt die Parameter und was noch wichtig ist: Es setzt die Flags auf writeable / executable ! Object_Flags := SECTION_WRITEABLE_EXECUTEABLE;
Delphi-Quellcode:
den neuen EntryPoint setzen:
SetSectionParams( FileData, Params,
Parameters(PE_Offset, EntryPoint, ImageBase, SecAlign, Sections, CodeOffset, VirtualSize + Length(Stub), VirtualAddr, RawSize, RawAddr, SectionCave) );
Delphi-Quellcode:
NewEntryP = VirtualAddr+VirtualSize
// Übergabe Parameter: var FileData: TByteArr; P: Parameters; NexEntryP: Cardinal
PCardinal( @FileData[P.PE_Offset+$28] )^ := NewEntryP; So puh ... War n ganzes Stück Arbeit. Ich bin erschöpft; Ich hätte nie gedacht, dass Tutorials schreiben so anstrengend sein können Ich hoffe auf konstruktive Kritik ! Wie schon ganz am Anfang erwähnt ist dies mein erstes Tutorial ! Achja: Ich weiß gerade gar nicht wie die ganze Formatierung schlussendlich aussehen wird -> deshalb werde ich öfters editieren müssen Also wundert euch nicht, wenn ihr eine zweistellige Zahl unten bei "X - Mal bearbeitet" seht ! Falls es euch gefällt, könnte ich euch noch das Decrypten erklären! EDIT: Achja bevor ich es vergesse - es gibt keine Fehlerbehandlungen ! MfG und gute Nacht Emre |
mr_emre_d |
mr_emre_d
|
#11
Ach ja? Ich dachte mir immer, dass beide im Ram liegen (Programm und Stack).
Wenn das so ist wie du es sagst, ist es wunderlich, dass es überhaupt funktioniert. MfG |
Zitat |
Turbo Delphi für Win32 |
#12
Es ist nicht verwunderlich, dass es funktioniert. Windows verwendet Segmentation praktisch nicht, beispielsweise zeigen alle Segmente auf die gleiche Startadresse. ss[eax] ist in diesem Sinne aber erstens irreführend und zweitens kostet es ein Byte im Code. Wenn du nur [eax] schreibst, wird implizit das ds-Segment verwendet und das Segment Override Prefix fällt weg.
|
Zitat |
Delphi 12 Athens |
#13
Zitat von mr_emre_d:
Zitat von spawn89:
Guten Morgen,
Irgendwie verstehe ich den Sinn dieser ganzen Angelegenheit nicht. Das einzige, was ich mir vorstellen könnte wäre es, Programme wie "DeDe" auszutricksen, damit man seine Codeinhalte "versteckt". Stimmt das? und um die "einfachen" Decompiler zu ärgern, reicht es schon z.B. einige unwichtige Delphitypische Teile zu löschen/verändern. Und was z.B. das Speichermanagement und die Virenscanner betrifft ... hier gab's schon öfters mal Theman zu Exe-Packern und so. |
Zitat |
|
#14
Nennt mich ThreadLeichen-Schänder, aber ich hab da mal ne Frage bzw. ein Problem.
Die Sache mit dieser Assembler Stub finde ich unglaublich spannend! Wegen dem Tutorial hab ich mich gleich auf Assembler gestürtzt Deswegen wollte ich nicht den selben Code nehmen wie im Tutorial. Damit ein wenig spannender wird, wollte ich das ganze etwas komplizierter gestalten, nur geht es jetzt nicht mehr : Nach dem Tut wird jedes Byte der Section mit dem Key geXORed. (Funktioniert ja auch wunderbar) Ich wollte das folgendermaßen machen: Ich Xore das ERSTE Byte der Section mit dem Key (Magic) Das 2. Byte Xore ich mit dem Ersten (was ja schon mit Magic Xored ist) Danach Xore ich das 3. Byte mit dem 2. Danach das 4. mit dem 3. usw. bis ich am Ende ankomme. Funktioniert soweit. Zum Entschlüsseln gehe ich der Einfachheit halber von hinten nach vorne durch die Section: Ich nehme das vorletzte Byte und Xore es mit dem Letzten ( = Letztes Byte jetzt im Klartext) Ich nehme das vorvorletzte Byte und Xore es mit dem vorletzten ( = Vorletztes Byte jetzt im Klartext) Ich nehme das vorvorvorletzte Byte und Xore es mit vorvorletzten ( = Vorvorletztes Byte im Klartext) ... Bis ich beim ERSTEN Byte angekommen bin. Das wiederum Xore ich mit dem Key ( = Ganze Section entschlüsselt) Das ganze habe ich in Assembler geschrieben: (Alle $1100.. und $00 Adressen werden nacher durch echte Adressen ersetzt)
Delphi-Quellcode:
Das Funktioniert aber leider nicht. Die Exe ist nacher beschädigt
asm
MOV eax, $11000000 // ENDE der Section ImageBase + V.Addr + V.Size MOV edx, $11000000 // Echter EntryPoint DEC eax // Einen Runter @loop: CMP eax, $11000000 // Wenn gößer als der START Imagebase + V.Addr JE @lastbyte // Wenn gleich dann Springen DEC eax //Zum Byte davor gehen MOV ecx, [eax] //Byte Davor speichern INC eax //Wieder zu dem noch verschlüsselten zurück XOR [eax], ecx //Mit ECX entschlüsseln DEC eax // Wieder aufs vorige Byte zurück JMP @loop @lastbyte: XOR [eax], $00 // Letztes byte mit Key entschlüsseln JMP edx // Zum Echten Entry Point springen end; Hat Irgendjemand ein wenig Ahnung von Assembler und kann mir sagen was ich dort falsch mache? Die Idee dahinter funktioniert. Ich habe die verschlüsselte Datei mal nacher mit einem "Zeile für Zeile von ASM nach Delphi"-Code entschlüsselt. Ergebnis: Exe funktioniert. (Ich benutze ein in Delphi geschriebenen Pizza-Timer ) Hier ist der "Zeile für Zeile von ASM nach Delphi"-Code:
Delphi-Quellcode:
Wie gesagt hier gings nur darum die Funktion des ASM exakt nachzubilden.
eax := SecEnd; // ENDE der Section ImageBase + V.Addr + V.Size
Dec(eax); loop: if eax = SecStart then // Wenn gößer als der START Imagebase + V.Addr goto lastbyte; // Wenn gleich dann Springen Dec(eax); //Zum Byte davor gehen ecx := FileData[eax]; //Byte Davor speichern Inc(eax); //Wieder zu dem noch verschlüsselten zurück FileData[eax] := FileData[eax] XOR ecx; //Mit ECX entschlüsseln Dec(eax); // Wieder aufs vorige Byte zurück goto loop; lastbyte: FileData[eax] := FileData[eax] XOR Magic; // Letztes byte mit Key entschlüsseln Aber es funktioniert. Jetzt meine Frage: Was macht mein "Wannabe"-ASM Code richtig, was mein echter ASM Code falsch macht? Geändert von Xen (27. Jun 2011 um 21:04 Uhr) |
Zitat |
Delphi 12 Athens |
#15
Wenn du deine beiden Ver-/Entschlüsselungscodes in Funktionen verpackst, dann kannst du sie ganz einfach mal nacheinander im Delphi-Debugger über einen Speicherblock jagen und schauen was wann warum schief läuft.
PS:
Delphi-Quellcode:
DEC eax //Zum Byte davor gehen
MOV ecx, [eax] //Byte Davor speichern INC eax //Wieder zu dem noch verschlüsselten zurück XOR [eax], ecx //Mit ECX entschlüsseln
Delphi-Quellcode:
MOV ecx, [eax-1] //Byte Davor speichern
XOR [eax], ecx //Mit ECX entschlüsseln Wobei du auch von vorne nach hinten gehn kannst.
Delphi-Quellcode:
eax := Startposition
temp := Startwert loop: temp2 := [eax] [eax] := [eax] xor temp temp := temp2 inc eax goto loop PPS: dein Vorgehen ist sogar noch unsicherer denn alles, bis auf das erste Byte kann ohne Kenntnis des Startwertes (der Verschlüsselung) entschlüsselt werden, da man den Startwert der Entschlüsselung kennt. OK, bei einer so einfachen Verschlüsselung mit einem 1 Byte-Schlüssel braucht man eh nur durchschnittlich 128 Versuche und man hat es auch so entschlüsselt. Geändert von himitsu (27. Jun 2011 um 23:16 Uhr) |
Zitat |
Turbo Delphi für Win32 |
#16
"Das Funktioniert aber leider nicht. Die Exe ist nacher beschädigt"
Das kann zunächst einmal an folgendem liegen: 1. du packst in EDX die absolute Adresse rein - du solltest aber mit Relativen arbeiten -> ImageBase + relative Adresse Da die ImageBase von Windows PE Loader geändert werden kann und somit die Image an einem ganz anderen Ort liegt 2. fügst du deinen Stub wirklich in ein Cave und überschreibst keine wichtigen Daten bzw. hat es so funktioniert, wie du es von meinem Tutorial übernommen hast?! Geändert von Aphton (11. Aug 2014 um 18:25 Uhr) |
Zitat |
Delphi 2006 Professional |
#17
Da die ImageBase von Windows PE Loader geändert werden kann und somit die Image an einem ganz anderen Ort liegt
Michael
|
Zitat |
|
#19
@himitsu
Wow, der Code fürs vorwärts entschlüsseln ist ja nochmal deutlich kürzer! Zum Rückwärtscode: Kann man das denn überhaupt so schreiben???
Code:
[eax-1] Sieht irgendwie so "Delphisch" aus. Ich hab gedacht jede Arithmetik Operation muss man extra angeben (w.z.B. DEC oder INC)
MOV ecx, [eax-1] //Byte Davor speichern
XOR [eax], ecx //Mit ECX entschlüsseln @Aphton: 1. Ja ich verwende die relative Andressen. Also immer ImgBase + VAddr für den Start, und ImgBase + VAddr + VSize fürs Ende. Also exakt wie im Tutorial beschrieben. 2. Zum Cave. Ja ... der wird mit 100% Sicherheit hinter das letzte beschriebene SectionByte gesetzt. Also wirklich da wo vie V.Size endet. Das ganze habe ich mehrmals im Hex-Editor überprüft. Um zu überprüfen das es am ASM Code liegen muss, habe ich das Ganze (also En/Decrypter) in die Unit aus dem Tutorial verfrachtet. Mit selben Ergebnis: Exe Kaput bei meinem ASM Code. Exe heil bei mr_emre_d's Original ASM Code. Ist denn der ASM Code wie ich ihn gepostet habe von der Logic u. Syntaktik korrekt? Da der hier angebotene ASM Crashkurs nicht allzuumfangreich ist habe ich mir vieles einfach zusammen gereimt. War ich denn richtig der Annahme das "[eax]" auf den Inhalt der Adresse die in eax selbst gespeichet ist zugreift? Was mir Wahrscheinlich extrem helfen würde wäre den decrypting Prozess in der "Ziel-Exe" direkt im Ram verfolgen zu können. Wie kann ich das bewerkstelligen? Kann ich für sowas den Delphi Debugger nehmen oder lieber einen externen wie OllyDBG? Schonmal vielen lieben Dank für die Hilfe! Geändert von Xen (29. Jun 2011 um 00:42 Uhr) |
Zitat |
Turbo Delphi für Win32 |
#20
Zitat:
Ist denn der ASM Code wie ich ihn gepostet habe von der Logic u. Syntaktik korrekt?
Da der hier angebotene ASM Crashkurs nicht allzuumfangreich ist habe ich mir vieles einfach zusammen gereimt.
Zitat:
War ich denn richtig der Annahme das "[eax]" auf den Inhalt der Adresse die in eax selbst gespeichet ist zugreift?
Zitat:
Was mir Wahrscheinlich extrem helfen würde wäre den decrypting Prozess in der "Ziel-Exe" direkt im Ram verfolgen zu können.
Wie kann ich das bewerkstelligen? Kann ich für sowas den Delphi Debugger nehmen oder lieber einen externen wie OllyDBG? Ollydbg. wäre natürlich noch angenehmer, aber da muss man zuerst einmal die Stelle finden, was für Anfänger nicht so einfach ist. Geändert von Aphton (29. Jun 2011 um 01:03 Uhr) |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |