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:
- Fortgeschrittene Kenntnisse in Delphi
- Datenstrukturen und Pointer müsstet ihr gut beherrschen
- Assembler sollte kein Fremdwort sein
Zuerst einmal möchte ich euch warnen:
- Es ist ziemlich spät und ich bin sehr müde - ich hoffe mal dass ich nicht viele Fehler mache

- Ich achte meistens nicht auf die Richtigkeit meiner Texte sowohl im Sinne von Rechtschreibung als auch in der Grammatik ! (ich werds aber diesmal versuchen
)
- Ich versichere nicht auf 100% Richtigkeit meiner Angaben !
- Dies ist mein allererstes Tutorial !
- "Hier gibt es die Möglichkeit, dass Benutzer, die sich in einem Gebiet (oder mehreren Gebieten) besonders gut auskennen, ihr Wissen detailliert mit den andern teilen." -- ich würde nicht behaupten, dass ich mich besonders gut darin auskenne aber mein Code funktioniert !
Inhaltsangabe:- Wichtige Details über eine Exe bzw den verschiedenen Headers
- Ungefähres Design des Programmes
- Das Problem mit den Adressen
- Die Implementierung[/b]
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)
- Dos-Header (Delphi: IMAGE_DOS_SIGNATURE);
- ...Müll...
- NT-Header(s) (Delphi: IMAGE_NT_HEADERS)
- Sections (Delphi: _IMAGE_SECTION_HEADER);
(unwichtig...)
- Image Pages
- import info
- export info
- fixup info
- resource info
- debug info
Ihr solltet euch diese drei Strukturen einmal genauer anschauen:
Der Dos-Header ist simple aufgebaut und steht am Anfang der Exe:
Delphi-Quellcode:
// 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
Für uns sind folgende Einträge wichtig:
- Signature - zum Überprüfen, ob es sich ume eine Exe handelt (bzw Dos Datei)
- PEOff - hier ist der Offset zum eigentlichen Header - dem NT_Header
Der NT-Header (Delphi: IMAGE_NT_HEADERS) besteht seinerseits wiederum aus drei Teilen:
- Signature
- FileHeader]
- OptionalHeader
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:
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;
für uns sind wiedermal folgende Sachen wichtig:
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:
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;
4. Handelt es sich um einen validen
PE-Header ?
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:
// Ü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;
Hier werden die Sections eingelesen und es wird geprüft ob es sich um die
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:
// Ü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;
10. Cave ermitteln:
Die Größe des Caves kann man wie folgt ermitteln:
RawSize - VirtualSize // RawSize = Physical_Size
11. Crypten der Section
Delphi-Quellcode:
// Ü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;
12.
ASM-Code erstellen und anpassen:
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:
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
Man kann die einzelnen Bytes durch das Debuggen ermitteln:
Code:
$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 );
Ich habe ein Array mit diesen Werten befüllt und ein Paar Index-Konstanten deklariert, damit ich später leichter darauf zugreifen kann !
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:
// Übergabe Parameter: Offset zur Ende der Section
Move( Stub[0], FileData[Offset], Length(Stub) );
Offset = RawAddr+VirtualSize !!
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:
SetSectionParams( FileData, Params,
Parameters(PE_Offset, EntryPoint, ImageBase,
SecAlign, Sections, CodeOffset,
VirtualSize + Length(Stub), VirtualAddr, RawSize,
RawAddr, SectionCave) );
den neuen EntryPoint setzen:
Delphi-Quellcode:
// Übergabe Parameter: var FileData: TByteArr; P: Parameters; NexEntryP: Cardinal
PCardinal( @FileData[P.PE_Offset+$28] )^ := NewEntryP;
NewEntryP = VirtualAddr+VirtualSize
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