So, wie versprochen, die Anleitung. Auf einzlene Verständnisfragen gehe ich gerne ein,
jedoch nicht wenn dem Fragesteller die Wissensbasis fehlt (ist nicht persönlich gemeint - denn schon alleine zu
DLL Erstellung oder MASM32 inbetriebname kann man Bände schreiben
)
Die Beispiele kann man auch so nachvollziehen - da die mitgelieferte DLL schon alles beherrscht
Eine Anleitung zur "Arthis_Crackme_NX_1.02_Final.exe".
Benötigte Programme: OllyDbg, Hexeditor, Programmiersprache eurer Wahl, die low-level fähig ist.
Vorgehnsweise: VB DLL gegen die VB-Crackme ausspielen.
Wie man unschwer merkt, ist die Crackme vollbepackt mit Antidebugger-methoden
und bis zur Unkenntlichkeit verschlüsselt. Natürlich kann man, wenn man fleißig
ist, nach vielen Stunden/Tagen die Crackme entpacken und durch den Schutz kommen.
Und mit Antidebuggern und Cryptschutz kann man mich eigentlich jagen
, ich muss
mir mal endlich ein paar MUP Tutorials durchlesen.
Aber warum sich Arbeit machen, wenn die Lösung schon mitgeliefert wird?
Und zwar ist es eine VB Crackme und benötigt somit auch eine VB-Runtime-DLL.
Eine MSVBVM60.DLL um genau zu sein. Sie wird geladen bevor die eigentliche Crackme
(also das Formular) startet.Und wie kommt man am besten durch den Debuggerschutz?
In dem man ohne Debugger durchgeht *g* . Worauf ich hinaus will: man kann der Crackme eine
falsche MSVBVM60.DLL unterschieben und mit deren Hilfe alles machen was man möchte.
Eigentlich hatte ich anfangs vor nur die vbaStrCmp zu belauschen und die Rückgabe zu manipulieren,
danach wollte ich die Crackme "on the run" patchen (die DLL machts möglich), danach wieder die Forumularausgabe
belauschen bis ich schließlich wieder fast am Anfang stand: vbaVarTstEq. Aber der Reihe nach.
Theorie zu DLLs: wenn man normale DLLs verwendet, also "MFCxxx.DLL" oder "msvbvmxx.dll"
dann bindet man diese in sein Programm unter einem "normalen" Namen ein. Und je nach der Methode, wie
die DLL geladen wird (LoadLibriary oder eben verlinkung mittels LIB) lädt/mappt entweder der PE-Loader oder
die API LoadLibriary die DLL in den (virtuellen) Speicher des Aufrufers. So oder so - die ist dann vom Programm
aus sichtbar, aber auch der Programmspeicher kann von der DLL bequem gelesen und manipuliert werden.
Mit "normalen" Namen ist der Name ohne Pfadangabe gemeint - wohl kaum einer kommt auf die Idee
den kompletten Pfad anzugeben "C:\WINDOWS\SYSTEM32\meinedll.dll" weil je nach Windowsversion der Windowsordner
WINNT oder WINDOWS oder XXX heißen kann (wie der User lustig ist) und auch das Laufwerk variiren kann.
Also ist "msvbvmxx.dll" ganz normale Angabe - und hier muss man nur wissen dass Windows zuerst den aktuellen
Ordner nach der DLL absucht und erst dann die Systemordner und andere (im PATH Umgebungsvariable) angegebene
Ordner. Also brauchen wir nur unsere eigne DLL da abzulegen und schon bindet die Crackme
diese ein und wir haben so einen Vollzugriff darauf.
DLLs Praxis: Wie man DLLs erstellt sollte man schon wissen oder google in Verbindung mit seinem
Compiler/ seiner IDE absuchen. Ich verwende hier MASM und alle Ausschnitte beziehen sich darauf.
Eine "mindest DLL" sieht in etwa so aus:
Code:
LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD
szText LmTitle,"tstdll's LibMain Function"
.if reason == DLL_PROCESS_ATTACH
szText ATTACHPROCESS,"PROCESS_ATTACH"
invoke MessageBox,NULL,ADDR ATTACHPROCESS,addr LmTitle,MB_OK
return TRUE
; -----------------------------
; If error at startup, return 0
; System will abort loading DLL
; -----------------------------
.elseif reason == DLL_PROCESS_DETACH
szText DETACHPROCESS,"PROCESS_DETACH"
invoke MessageBox,NULL,addr DETACHPROCESS,addr LmTitle,MB_OK
.elseif reason == DLL_THREAD_ATTACH
szText ATTACHTHREAD,"THREAD_ATTACH"
invoke MessageBox,NULL,addr ATTACHTHREAD,addr LmTitle,MB_OK
.elseif reason == DLL_THREAD_DETACH
szText DETACHTHREAD,"THREAD_DETACH"
invoke MessageBox,NULL,addr DETACHTHREAD,addr LmTitle,MB_OK
.endif
ret
LibMain Endp
Das ist zumindest ein "Lehrbeispiel" aus MASM32 Packet.
Allerdings läuft schon sowas ganz gut:
Code:
LibMain proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
LibMain Endp
Hauptsache es wird ein TRUE zurückgegeben.Ob es LibMain oder MeineLib oder XXX_LIB heißt ist übrigens wurscht.
Aber mit DLL_PROCESS_ATTACH können wir erkennen ob die DLL gerade geladen wird, genauso wie mit
DLL_PROCESS_DETACH ob sie "entladen" wird.
Also ein neues DLL-Projekt mit dem Namen MSVBVM60 anlegen (für MASM32 empfehle ich die IDE
Radasm, es geht aber natürlich auch ohne)
Folgender Code gibt immer eine MessageBox aus, wenn unsere DLL geladen oder entladen wird:
Code:
.386
.model flat, stdcall
option casemap :none ; case sensitive
; #########################################################################
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
; #########################################################################
.data
.data?
.code
; ##########################################################################
LibMain proc hInstDLL:DWORD, arg:DWORD, reserviert:DWORD
.if arg == DLL_PROCESS_ATTACH
invoke MessageBox,0,0,0,0
mov eax,1
ret
.elseif arg==DLL_PROCESS_DETACH
invoke MessageBox,0,0,0,0
.endif
ret
LibMain Endp
Die DLL erstellen und in den selben Ordner wie die Crackme kopieren.
Wie man unschwer erkennt, stürzt danach die Crackme nach der MessageBox ab - weil sie eben auf der
richtigen DLL aufsetzt und dessen Funktionen braucht. Aber man kann,
während man die MessageBox bewundert, die Crackme in OLLY attachen und viel interessantes
sehen. Einfach mal ausprobieren:
Crackme starten, jetzt Olly starten und die Crackme attachen.Eventuell sieht man erst mal
nichts - dann in View->Threads gehen und irgendeinen Thread wählen. Jetzt mal GoTo 401000 eingeben.
Auf jeden fall sollte man sich im Dump ein GoTo 400000 nicht entgehen lassen. Es sieht nämlich aus wie
eine Exe. Bekannterweise MZ & co. Versuche es mit Olly-Plugins zu dumpen werden scheitern. Man kann aber
immer noch markieren und kopieren oder die von mir mitgelieferte DLL nutzen um einen Dump zu machen.
Mit dem Dump kann man schon einiges anfangen - besonders wenn man dafür einen Entry Point finden sollte *fg*
Aber weiter zur unserer DLL(olly bitte noch offen lassen): wir müssen dafür sorgen dass die Crackme auf die
richtigen Funktionen zugreifen kann.Die Crackme importiert die Funktonen dieser DLL nämlich selber - per GetProcAddress.
Deshalb geben wir mal im Olly "Search for"=>"binary string" ein "GetProcAddress" ein und landen hier
Code:
00428535 0047 65 ADD BYTE PTR DS:[EDI+65],AL
was im Dump so aussschaut:
Code:
0042851C 32 B3 E5 77 86 AD E5 77 61 D9 E5 77 FD 98 E5 77 2??w? ?wa??wý??w
0042852C 76 64 D3 77 00 00 00 00 00 00 47 65 74 50 72 6F vdÓw......GetPro
0042853C 63 41 64 64 72 65 73 73 00 00 00 47 65 74 4D 6F cAddress...GetMo
0042854C 64 75 6C 65 48 61 6E 64 6C 65 41 00 00 00 4C 6F duleHandleA...Lo
0042855C 61 64 4C 69 62 72 61 72 79 41 00 00 00 45 78 69 adLibraryA...Exi
0042856C 74 50 72 6F 63 65 73 73 00 00 00 4D 65 73 73 61 tProcess...Messa
0042857C 67 65 42 6F 78 41 00 90 4D 69 6E 65 49 6D 70 6F geBoxA.MineImpo
0042858C 72 74 5F 45 6E 64 73 73 00 00 00 00 00 00 00 00 rt_Endss........
..
(ich hab etwas mehr Dumpausschnitt hinzugenommen)
Wie man unschwer erkennen kann, ist hier ab 42851C die Importtabelle der Kernel32
Funktionen - und ab 428535 stehen deren Namen. Uns interessiert nur der Wert auf
42851C: 32 B3 E5 77 es ist die Kerneladresse der GetProcAddress API (und kann natürlich je
nach Winversion oder gar SP variiren und bei euch anders aussehen).
Also laden wird jetzt die MSVBVMDLL selber und patchen in der Crackme die GetProcAdresse zu uns rüber,
damit wir, wenn die VB Funktionen importiert werden, selber die richtigen suchen und liefern (oder auch nicht *fg*)
Code:
.data
VBDLL db "msvbvm60.dll",0
Echte_VBDLL db "\System32\msvbvm60.dll",0
buffer db(MAX_PATH) dup(0)
jumpto dd 0
.data?
hEchteDLL dd ?
hWir dd ?
.code
LibMain proc hInstDLL:DWORD, arg:DWORD, reserviert:DWORD
.if arg == DLL_PROCESS_ATTACH
;als erstes natürlich die "echte" dll laden
invoke LoadLibrary,addr VBDLL
mov hWir,eax ;wir bzw. unser handle, da wir schon geladen sind
invoke GetWindowsDirectory,addr buffer,MAX_PATH
lea ebx,buffer
add ebx,eax
invoke szCopy, addr Echte_VBDLL,ebx
invoke LoadLibrary,addr buffer
mov hEchteDLL,eax
;jetzt patchen wir mal den GetProcadress aufruf, sonst droht böses,
lea eax,PatchIt
mov ebx,42851ch
mov dword ptr [ebx],eax
mov eax,1
ret
.elseif arg==DLL_PROCESS_DETACH
.endif
ret
LibMain Endp
PatchIt proc
mov eax, [esp+4] ;Handle zu welcher DLL?
cmp eax, hWir ;zu uns?
jne @f ;wenn nicht dann eben nicht
mov eax, hEchteDLL,eax
@@:
mov [esp+4],eax
jmp GetProcAddress
PatchIt endp
aber damit können wir viel mehr - wir könnten z.B alle Funktionen zuerst über uns laufen
lassen und mitprotokolieren oder bestimmte Rückgaben manipulieren. Dazu müssten wir allerdings
wissen was wann und wie aufgerufen wird. Und leider kann man nicht ohne genaue kenntnisse der
Parameteranzahl die Funktionen nicht wirklich gut belauschen.
Immerhin kann man die Patchit Funktion soweit erweitern dass sie mitprotokolliert
welche Funktion wann aufgerufen wurde. Wir liefern der Crackme dafür für alle Funktionen
unsere Adresse, so dass jeder Aufruf durch uns läuft. Leider wird der Name der Funktion in der Crackme
nach dem erfolgreichen GetProcAddress gelöscht/überschrieben, so dass man nicht im Protokol nicht darauf
verweisen kann. Als kleiner Hilfstrick dumpen wir die Namen und die zugehörigen Adressen vorher.
Natürlich muss man eine Tabelle mit den echten Adressen und unseren "gefälschten" erstellen,damit
man die Aufrufe auch weiterleiten kann.
dafür erweitern wir die PatchIt:
Code:
PatchIt proc
.data
jmpbuffer db(1000) dup(0) ; schätzung, wer will kann nachzählen oder eine dynamische Liste verwenden
jmpcount dd 0
template_funk db "Adresse %x Name: %s",13,10,0
template_funk2 db "Adresse %x Nummer: %d",13,10,0
.code
mov eax, [esp+4] ;Handle zu welcher DLL?
cmp eax, hWir ;zu uns?
jne fremde_dll ;wenn nicht dann eben nicht
;ansonsten hooken wir mal die Dinger - im Prinzip auch mit anderen DLLs möglich
;da Register überprüft werden müssen die gesichert sein
mov esp_,esp
pushad
mov eax,hEchteDLL
mov ebx,esp_
push [ebx+8] ;funktonsname
push eax ;handle zu der Echten DLL
call GetProcAddress
lea ebx,offset jmpbuffer
add ebx,jmpcount
mov [ebx+5],eax ;jmpbuffer aufbau: call auf die DumpIt,echte Adresse,
pushad ;da die Exe netterweise die Namen nur bei Bedarf lädt und wieder löscht selber eine Tabelle machen
mov ebx,esp_
mov ecx,[ebx+8]
cmp ecx,400000
jl @f
invoke wsprintf,addr buffer,addr template_funk,eax,ecx
jmp weiter
@@:
invoke wsprintf,addr buffer,addr template_funk2,eax,ecx
weiter:
invoke StrLen,addr buffer
invoke WriteFile,hFile,addr buffer,eax,addr esp_,0 ;_esp ist nur eine dummy weil die API hier eine verlangt
popad
;einen relativen Call zusammenbasteln (als Opcode), damit die Funktion aus dem Array zu uns spring
;ein Call muss sein, sonst weiß man nicht woher der Sprung kommt
lea eax,offset DumpIt
sub eax,ebx
sub eax,5 ;5 bytes belegt call,
;EBX - immre noch unser jmpbuffer
mov byte ptr[ebx],0E8h;CALL
mov [ebx+1],eax ;Adresse
popad
;rückgabe -wir geben unsere adresse an
lea eax,jmpbuffer
add eax,jmpcount ;sicherlich etwas unelegant, aber auf die schnelle reichts
;jetzt kann man auch jmpcount erhöhen
add jmpcount,9
;jetzt sollten alle VB funktionen durch uns gehen
add esp,12 ;stack in ordnung bringen
jmp dword ptr [esp-12]
fremde_dll:
jmp GetProcAddress
PatchIt endp
die DumpIt sieht so aus:
Code:
DumpIt:
.data
template db "Rücksprung: %x; Ziel: %x; Parameter %x ; ",13,10,0
.code
pushad
mov eax,[esp+4*8]
mov eax,[eax]
mov esp_,eax
;dumpen wir mal die Aufrufe:
mov ebx,[esp+4*8+4]
mov edx,[esp+4*8+8]
invoke wsprintf,addr buffer,addr template,ebx,esp_,edx
invoke StrLen,addr buffer
invoke WriteFile,hFile,addr buffer,eax,addr jmpcount,0
popad
pop jumpto ;irgendwie scheint VB auf Register zur Übergabe zu missbrauchen, deshalb sollte man alles
;sichern, was man nutzt
;push weiter ;rücksprugnsadresse - verweist in den Buffer
jmp dword ptr [esp_] ;dir richtige API-Funktion
Nach dieser Erweiterung assemblieren wir die DLL und rufen nun die Crackme auf,
jetzt gibts auch einen Log - den einfach öffnen und ans Ende scrollen.
Jetzt serial eintippen (irgendwas) und Enter - die Meldung "Falsch" kommt.
Im Log tauchen sehr viele Eingräge auf - also viel Spass wenn jemand die Serial
nachrechnen will. Wir schauen uns aber die letzen Einträge an:
Rücksprung: 408dcb; Ziel: 733b3fa7; Parameter 12f500 ;
Rücksprung: 408e53; Ziel: 7349abe6; Parameter 15321c ;
Rücksprung: 408e62; Ziel: 733b4d9b; Parameter 12f534 ;
Rücksprung: 408e6a; Ziel: 7349009e; Parameter 12f534 ;
wir schauen nach was das ist:
Adresse 7349abe6 Name: __vbaVarTstEq
Adresse 7349009e Name: __vbaFreeVar
Adresse 733b4d9b Name: __vbaFreeObj
die vbaVarTstEq scheint sehr interessant zu sein. Wir erweitern unsere Dumpit:
Code:
;dumpen wir mal die Aufrufe:
mov ebx,[esp+4*8+4]
mov edx,[esp+4*8+8]
;adreesse 408df8
.if ebx==408dcbh; <-erweiterung damit landen wir mal vor dem vbaVarTstEq aufruf
pushad
invoke MessageBox,0,0,0,0
popad
.endif
So, nun wieder assemblieren und Crackme aufrufen: kommt unsere MessageBox,
jetzt Olly öffnen und die Crackme attachen: goto 408fdcb,
Breakpoint drauf, Olly laufen lassen, auf die Messagebox kliken.
Schon sind wir wieder in der Crackme:
Code:
00408DCB 8985 78FFFFFF MOV DWORD PTR SS:[EBP-88],EAX
00408DD1 8D45 E8 LEA EAX,DWORD PTR SS:[EBP-18]
00408DD4 50 PUSH EAX
00408DD5 8B85 78FFFFFF MOV EAX,DWORD PTR SS:[EBP-88]
00408DDB 8B00 MOV EAX,DWORD PTR DS:[EAX]
00408DDD FFB5 78FFFFFF PUSH DWORD PTR SS:[EBP-88]
F7 bis hier(also praktisch bis zu unserem vbaVarTstEq aufruf (siehe Rücksprung: 408e53):
Code:
00408E44 50 PUSH EAX
00408E45 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
00408E48 05 DC000000 ADD EAX,0DC
00408E4D 50 PUSH EAX
00408E4E E8 B8440100 CALL 0041D30B
bei eax vor dem ersten PUSH in den Dump schauen:
Code:
0012F4EC 08 80 00 00 C1 11 00 10 B4 3D 15 00 2D 30 00 10 ?..Á.´=.-0.
die ersten 4 Bytes sind VB intern (String), die Bytes 8-12 sind die Adresse
des Strings - da machen wir ein Goto 153db4 im Dump und landen:
bei unserer Eingabe - nun nochmal F7,F7 und nochmal dasselbe Spielchen:
Bei EAX ein Follow in Dump:
Code:
00152D44 08 00 00 00 00 00 00 00 74 9B 15 00 00 00 00 00 .......t?.....
hier also ein Goto 159b74
und wo landen wir *g* ? Am besten die Ansicht im Dumpfenster auf Text->unicode
stellen und den Serial betrachten. Man kann denn kopieren und benutzen.
Die (richtige) Abfrage findet übrgigens schon an:
Rücksprung: 408cc4; Ziel: 7348fffe; Parameter 1532b4 ;
Adresse 7348fffe Name: __vbaVarTstNe
statt (ein paar Zeilen vorher im Dump.txt), jedenfalls reicht es wenn sie hier ein TRUE zurückgibt (die vbaTstEq wird dann nicht mehr bemüht).
Damit stehen wir eigentlich vor der Wahl: geben wir als die "MasterDLL"
bei einem vbaVarTstEq selber einen TRUE zurück und machen damit
jede Eingabe gültig, patchen wir die Abfrage danach oder lassen den Key in eine Textdatei dumpen?
Die letze Variante wäre am besten, denn das Patchen/manipulation geschieht während der
der Laufzeit und ist an die DLL gebunden.
Das wärs. Die DLL die alle diese Funktionen bietet (dumpen der Exe,keypatch,keydump,TRUE rückgabe).
samt Quelltext und makeit.bat ist im Anhang. Einfach entpacken und in den Crackmeordner schieben.
Und denkt daran: es gibt keinen Löffel...