PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Universal Decrypter



EBFE
08.04.2009, 16:24
Kurze Anmerkung: das Programm hat einen recht spezifischen Anwendungszweck ;). Muss also nicht jeder verstehen oder sinnvoll finden.
In erster Linie wurde es für "Learning by doing" geschrieben (bisschen PE Format wiederholen). Weiterhin ein schöner "PoC", dass sehr viele "Crypter" nicht nur komplett auf RunPe-Prinzip basieren, sondern auch nichtmal irgenwelche Änderungen beinhalten.


Ebfe's universal decrypter
Sprache: MASM32

Anwendungszweck: Entpacken von Anwendungen,
die mit RunPE/CreateProcessEx/LoadExe&CO basierenden Cryptern gepackt wurden.

getestet: Trojka 2/3, SC Crypter v1
OSgetestet: Vista SP1, XP SP2/3

Anleitung:
Wenn der Dump erfolgreich war, kommt eine Erfolgsmeldung *g*

Zu beachten:

a) Es existiert keine Prüfung, ob die Zielanwendung nun wirklich gecryptet ist.
Es wird einfach drauf los versucht sie zu entpacken. Dafür gibt es zwei Gründe:
1) der Unpacker ist generisch
2) Zuverlässige signaturbasierende Cryptererkennung schaffen noch nichtmals AVs *g*

b) Es sind auf jeden Fall Debugrechte nötig - also als Administrator oder
entsprechender User starten!

c)Es kann sein, dass die Stub mehrere Kindprozesse startet (wofür auch immer),
es werden alle gedumpt und gefixt. Es bleibt dem Anwender überlassen, den "richtigen"
Dump auszuwählen. Hint: Icon und Dateigröße sollten in 90% aller Fälle eindeutig sein.

d)Timeout ist relativ hoch gesetzt - sollte etwas schiefgehen, kann es bis zu 20Sek
dauern, bis eine Meldung kommt.Inhalt:
\readme.txt
\undecrypt.exe
\source\dialog.inc
\source\makeit.bat
\source\undecrypt.asm

Zweck: 90% aller "Sczenecrypter" entpacken *g*
Zu beachten: wer das selbst assemblieren möchte, sollte in der makeit.bat den Pfad anpassen.

MD5:
release.zip: 0e122aedb8b23fecaf2008424734fd2d
http://files.mail.ru/S46EN9

Mofo
16.10.2010, 19:04
Sry das ich diesen alten Thread wieder hervorhole, aber ich denke er ist noch nicht ganz veraltet :P

Mich würde interessieren wie du vorgehst, nicht detailliert, aber ein kurzes résumée wäre toll, denn ich bin noch asm-Anfänger und kann daher deinem Source kaum etwas entnehmen..

Du ermittelst den Kind-Prozess der fraglichen pe, liest die Peteile aus dem ram und schreibst die 'gedumpte?' pe auf die Platte?

EBFE
16.10.2010, 20:48
Es basiert auf der Annahme, dass die üblichen RunPE APIs genutzt werden: CreateProcess, ZwUnmapViewOfSection, WriteProcessMemory, GetThreadContext, SetThreadContext, ResumeThread. Genauer: es hookt die ResumeThread und WriteProcessMemory API. Daher funktioniert es heutzutage nicht mehr wirklich zuverlässig, da viele "C0der" auf "ZwResumeThread" und "ZwWriteProcessMemory" Aufrufe umgestiegen sind ;)


Zuerst starte ich den Prozess "Suspended":


invoke CreateProcess,file_path,0,0,0,FALSE,CREATE_SUSPEND ED,0,0,addr startup,addr proc_info Nun nutze ich die Tatsache aus, dass zumindest bis unter Vista die Kernel/NTDLL in allen Prozessen die gleiche Adresse hat und ermittele "WriteProcessMemory" und "ResumeThread" Adressen in der Kernel32.DLL meines Programms (die ich dann auf andere Programme "übertragen" kann). Diese speichere ich (zugegebenermaßen unschönerweise) in EDI / ESI, anstatt extra Variablen dafür anzulegen.

Nun warte ich in einem Loop:


invoke ResumeThread,proc_info.hThread
invoke Sleep,1
invoke SuspendThread,proc_info.hThread
invoke ReadProcessMemory,proc_info.hProcess,edi,addr temp,1,addr startup
test eax,eax ;wenn rückgabe=0 dann ist gar nichts verfügbarbis die Kernel32.DLL in dem Zielprozess verfügbar ist (EDI enthält dabei die Adresse von ResumeThread API und es wird immer wieder versucht, daraus etwas zu lesen. Sobald das gelingt, heißt es, dass die DLL im anderen Prozess nun verfügbar ist)

Dann kommt die "Debug" Funktion. Ihr Ziel ist es, die Stub / RUNPE Code solange ausführen zu lassen, bis sie den Code entpackt hat - aber nicht den letzen Schritt (ResumeThread) machen lassen.

Dafür werden "Breakpoints" aka EBFEs Platziert ;)
zurst Speicher beschreibbar setzen:


invoke VirtualProtectEx,proc_info.hProcess,edi,4,PAGE_EXE CUTE_READWRITE,addr tempund dasselbe, nur mit ESI statt EDI als Parameter. Ich errinnere nochmal - unschönerweise nutze ich Register als Variablen (weil ich diese ziemlich oft brauche und weil ich faul bin). ESI == WritePRocessMemory Adresse, EDI == ResumeThread Adresse.
Ich platziere einen EBFE ähm Breakpoint auf ResumeThread, dann lese ich "Originalbytes" aus der Adresse von ESI==WriteProcessMemory und platziere schließlich auch da einen Breakpoint.



;BPs platzieren
invoke WriteProcessMemory,proc_info.hProcess,edi,offset BP1,2,addr temp
test eax,eax
jz error
invoke ReadProcessMemory,proc_info.hProcess,esi,addr orig_bytes,2,addr temp
test eax,eax
jz error
invoke WriteProcessMemory,proc_info.hProcess,esi,offset BP1,2,addr temp
test eax,eax
jz error
Nun warte ich in einer Schleife - einmal pro Sekunde pausiere ich und prüfe, ob das Programm an einem meiner BPs angehalten wurde:

laufen lassen + anhalten


invoke ResumeThread,proc_info.hThread
invoke Sleep,1000
invoke SuspendThread,proc_info.hThreadnun BPs prüfen:


mov context.ContextFlags,CONTEXT_FULL
invoke GetThreadContext,proc_info.hThread,addr context

.if context.regEip==edi ;ist EIP==BP Adresse?
mov eax,base
jmp @f
.elseif context.regEip==esi ;WriteProcessMemory
;originalbytes scheiben
invoke WriteProcessMemory,proc_info.hProcess,esi,addr orig_bytes,2,addr temp
;Base auslesen:
mov ecx,context.regEsp
add ecx,8
invoke ReadProcessMemory,proc_info.hProcess,ecx,addr base,4,addr temp
das Prinzip ist simpel - sobald EIP von dem "debuggten" Prozess gleich ESI (== WritePrzessMemory) Adresse ist, weiß ich, dass nun zum ersten mal etwas in den Speicher der Exe geschrieben wird. Dank MSDN weiß ich auch, dass WriteProcessMemory Function (Windows) (http://msdn.microsoft.com/en-us/library/ms681674%28VS.85%29.aspx) den lpBase Parameter an 2-ter Stelle hat. Also lese ich den ESP des "debuggten" Prozess aus, addiere eine 8 dazu (damit erhalte ich die Adresse des 2-ten Parameters auf dem Stack, plus 8 muss sein, weil 4 Bytes für Parameter1 draufgehen und 4 Bytes für die Rücksprungadresse) und lese nun die Zieladresse des ersten "WriteProzessMemory" Aufrufs. Und das ist halt in 99,9% aller RunPEs die ImageBase der "gecrypteten" Exe, die gerade geschrieben wird.

Danach schreibe ich die originalbytes (die für den EBFE "Breakpoint" des WriteProzessMemory überschrieben wurden) und setze den Prozess fort.

Sobald die EIP von dem "debuggten" Prozess gleich EDI (== ResumeThread) ist, weiß ich, dass der RunPE Code nun alles lauffertig entpackt hat und den Code nun laufen lassen möchte. Damit ist die "Debugfunktion" quasi fertig - und springt zum RET (k.A warum ich das so hässlich gelöst habe, wahrscheinlich stand hier vorher noch irgendeine Fehlerbehandlunsroutine. Solche "hässlichen" Codestellen sind meistens überbleibsel von "Erweiterungen" oder "Codekürzungen" ;) )

Die Debug-Funktion gibt (in EAX) die "ImageBase" der nun "entcrypteten" Exe zurück.
Außerdem weiß ich, (solange sie nicht -1 ist), ist alles ok und die Stub hat alles "lauffertig" entcryptet. Nun bräuchte ich nur noch zu dumpen:


;debugge (lasse die Stub die Anwendung entpacken)
invoke Debug
cmp eax,-1
je quit

;so, an dieser Position heißt es: Zielanwendung ist entpackt, nun alle kinder dumpen und fixen
invoke DumpChildProcess,proc_info.dwProcessId,eax
quit:
invoke Terminate_childs
retDumpChildProzess arbeitet mit CreateSnapshot32 und Process32First um die ganze Prozessliste durchzugehen. Dafür gibt es eher massig Beispiele in vielen Sprachen. Es vergleicht halt die ParentID mit der ID der Anwendung, die wir gereade "Decrypten". Hintergrund: viele Crypter haben ja diesen "integrierten" Bindermechanismus und starten mehrere Prozesse bzw. Trojka Crypter hat aufjedenfall mehrere gestartet und irgenwelche komischen Aktionen gemacht (so genau weiß ich es nun auch nicht mehr).

Im wesentlichen ruft es "invoke DumpAndFix,base,proc_entry.th32ProcessID" für die Prozesse auf.

Dessen Aufgabe ist klar ;). Das Fixen muss sein, da wir den kompletten Speicherblock auf die Platte speichern und dann natürlich die ganzen Sections nicht mehr den FileAlignment haben (und damit verschieben/ändern sich die ganzen RawAddress/RawSize Werte).

Dazu wird der Prozess geöffnet:

invoke OpenProcess,PROCESS_TERMINATE+PROCESS_VM_READ,FALS E,procid
test eax,eax
jz error_c
und dann versucht, über den PE Header an die ImageSize dranzukommen (um zu wissen, wieviel tatsächlich gedumpt werden muss)


read_pe:
mov ecx, base
add ecx, 03ch
invoke ReadProcessMemory,child_handle,ecx,addr temp,4,addr temp2
test eax,eax
jnz @f
....
....
add ecx,50h ;image size auslesen
invoke ReadProcessMemory,child_handle,ecx,addr imagesize,4,addr temp
Warum hier 03C und 0x50 Werte verwendet werden, sollte aus dem PE-Header Format klar sein ;)


dann wir der Speicherinhalt ausgelesen (zuerst extra dafür eigener Speicher alloziert):

;so, nun imagebase vorhanden+imagesize - auslesen und speichern
invoke VirtualAlloc,NULL,imagesize,MEM_COMMIT,PAGE_READWR ITE
test eax,eax
jz error_alloc
mov memory, eax
invoke ReadProcessMemory,child_handle,base,memory,imagesi ze,addr tempder PE Header des ausgelsenen Prozess wird dann gefixt


;fixen
invoke Fix,memory,imagesizeund anschließend das ganze gespeichert:


invoke CreateFile,offset dump,GENERIC_WRITE,FILE_SHARE_WRITE,0,CREATE_ALWAY S,(der Loop ist eine quick&dirty Lösung für automatische Namensvergabe bei mehreren Childprozessen... einfach vorstellen als:


i=0
while fileexist("dump"+str(i)+".exe":
do i = i+1
filename = "dump" + str(i) + ".exe"
Nun noch etwas zum Fix:
im wesentlichen werden hier die "originellsten" RunPE "Verunstaltungstricks" behandelt. Also 1) wird MZ wieder an den Anfang geschrieben (manche Crypter lassen das Weg bzw überschreiben es)
2) wird überprüft ob es einen PE Header gibt
3)in einem Loop werden die SectionHeaders durchlaufen und nun an die Dumpgrößte angepasst. Da wir eine gültige PE-Exe gedumpt haben, können wir davon ausgehen, dass die Sections auch "gültig" im Speicherplatziert waren (also SectionAlignment bzw alle VirtualAddress und VirtualSize Adressen waren ok). Daher brauchen wir auch nur die ganzen RAW Werte auf Virtual umzustellen und sind damit fertig:


fixloop:
mov eax,[edi].VirtualAddress
mov [edi].PointerToRawData,eax
add edi, sizeof IMAGE_SECTION_HEADER
loop fixloop

G36KV
18.10.2010, 20:00
Es basiert auf der Annahme, dass die üblichen RunPE APIs genutzt werden: CreateProcess, ZwUnmapViewOfSection, WriteProcessMemory, GetThreadContext, SetThreadContext, ResumeThread. Genauer: es hookt die ResumeThread und WriteProcessMemory API. Daher funktioniert es heutzutage nicht mehr wirklich zuverlässig, da viele "C0der" auf "ZwResumeThread" und "ZwWriteProcessMemory" Aufrufe umgestiegen sind ;)

Hier gibt es so ein ähnliches Tool, dass einige Anti-Bypass Methoden integriert hat. Für die meisten RUNPE Anwender sollte es unmöglich sein das zu bypassen:

hxxp://www.hackhound.org/forum/index.php?topic=20577.0