Risolviamo il BruteMe 1 di Spider
Mi trovo casualmente a scrivere questo tutorial, niente di premeditato, semplicemente mi sono trovato un poco di tempo libero e non avevo di meglio da fare che reversarmi qualche cosina. Ho scelto il BruteMe di Spider perchè sapevo che sarebbe stato divertente (come tutti i crackme di Spider) anche se non difficile (il tempo stringe parecchio...). Inoltre dietro a ogni crackme che Spider fa c'è un'idea che può essere più complessa o meno, ma mai banale. In più visti i progetti un po' più grandi che sto mandando avanti mi pareva il caso di scrivere un tutorial visto che è già tanto che non ne scrivo uno (nell'attesa diciamo). Quindi cominciamo.
Il crackme si apre normalmente presentando una window che chiede di immettere un seriale, se il seriale è sbagliato veniamo avvertiti con un msgbox di seriale errato.
Apro IDA (poiché altro per analizzare il crackme non ci serve) e trovo immediatamente il punto in cui viene preso il testo (ovviamente è fatto di proposito ciò):
:0040148D push 14h ; la lunghezza massima da prelevare
:0040148F push offset unk_4031C0 ; il buffer
:00401494 push dword_4031F0 ; la window
:0040149A call GetWindowTextA ; prende il resto
:0040149F cmp eax, 10h ; confronta la lunghezza del testo con 16
:004014A2 jnz loc_40159A ; se non è 16 il seriale è errato
:004014A8 mov edx, offset unk_4031C0 ; edx = offset buffer
:004014AD xchg eax, ecx ; ecx = lunghezza
:004014AE
:004014AE loc_4014AE:
:004014AE
:004014AE mov al, [ecx+edx-1] ; al = Buffer[ecx--]
:004014B2 cmp al, 46h ; confrota al con 'F'
:004014B4 ja loc_40159A ; se al > 'F' -> seriale errato
:004014BA cmp al, 30h ; confronta al con '0'
:004014BC jb loc_40159A ; se al < '0' -> seriale errato
:004014C2 cmp al, 41h ; confronta al con 'A'
:004014C4 jnb short loc_4014CE ; se al >= 'A' -> salta all'istruzione di loop
:004014C6 cmp al, 39h ; confronta al con '9'
:004014C8 ja loc_40159A ; se al > '9' -> seriale errato
:004014CE
:004014CE loc_4014CE:
:004014CE
:004014CE loop loc_4014AE ; ricomincia il ciclo
; come avrete certamente capito questo ciclo non serve altro che
; per controllare che il seriale sia una cifra hex valida.
; Per convenienza diciamo di aver immesso un seriale come
; per esempio 0123456789ABCDEF
:004014D0 mov eax, [edx] ; mette in eax i primi quattro caratteri
; del seriale nel nostro caso eax = 33323130
:004014D2 bswap eax ; inverte l’ordine dei bit in eax quindi
; eax sarà = a 30313233
:004014D4 call sub_0_401668 ; questa call che ritroveremo spesso non serve
; altro che per convertire da stringa in numero
; in base alla numerazione hex
; abbiamo quindi eax in questa forma 0000xxxx dove le x
; rappresentano la word rappresentata dai primi 4 numeri del seriale
:004014D9 shl eax, 10h ; uno shift provoca uno spostamento in eax
; che adesso sarà xxxx0000
:004014DC xchg eax, ecx ; adesso ecx = xxxx0000
:004014DD mov eax, [edx+4] ; prende i successivi 4 caratteri
:004014E0 bswap eax ; eax = 34353637
:004014E2 call sub_0_401668 ; solita call (eax = 4567 -> 0000yyyy)
:004014E7 or eax, ecx ; l’or fa diventare eax = xxxxyyyy
:004014E9 mov dword_0_4031D4, eax ; salva eax
:004014EE cmp eax, 1B000000h ; confronta eax con 1B000000h
:004014F3 jnb loc_0_40159A ; se eax non è minore allora il seriale è errato
:004014F9 push eax ; pusha eax
:004014FA mov eax, [edx+8] ; muove i successivi 4 caratteri (eax = 42413938)
:004014FD bswap eax ; eax = 38394142
:004014FF call sub_0_401668 ; eax = 000089AB
:00401504 shl eax, 10h ; eax = 89AB0000
:00401507 xchg eax, ecx ; ecx = eax
:00401508 mov eax, [edx+0Ch] ; muove gli ultimi 4 caratteri
:0040150B bswap eax ; eax = 43444546
:0040150D call sub_0_401668 ; eax = 0000CDEF
:00401512 or eax, ecx ; eax = 89ABCDEF
:00401514 mov dword_0_4031D8, eax ; salva eax
:00401519 pop ecx ; ecx = prima parte del seriale
:0040151A add eax, ecx ; eax = prima + seconda parte del seriale
:0040151C xor eax, 98D969A7h ; xora eax con 98D969A7h
:00401521 jnz short loc_0_40159A ; risultato = 0? Se no, seriale errato
; il codice mi pare abbastanza chiaro, il programma ha semplicemente
; convertito il seriale in due dword e sommando queste due dword
; è necessario ottenere un risultato pari a 98D969A7h
:00401523 mov ebx, offset unk_0_401546 ; ebx = offset che punta all’istruzione
; seguente al loop loc_0_401532
:00401528 mov ecx, offset unk_0_40156C ; ecx = 00401546h + 26h
:0040152D sub ecx, ebx ; ecx = 26h (38d)
:0040152F shr ecx, 3 ; ecx = 4
:00401532
:00401532 loc_0_401532: ; loop
:00401532
:00401532 mov eax, dword_0_4031D4 ; eax = prima dword
:00401537 xor [ebx], eax ; xora la parte di codice puntata
; da ebx con eax
:00401539 mov eax, dword_0_4031D8 ; eax = seconda dword
:0040153E xor [ebx+4], eax ; xora la parte di codice puntata
; da ebx + 4 con eax
:00401541 add ebx, 8 ; incrementa ebx di 8
:00401544 loop loc_0_401532 ; ricomincia il ciclo
Come potete vedere in questa routine viene decrittato del codice e sappiamo che i bytes da decrittare sono 32d (4 * 8).
Al termine dei byte che vengono xorati ci troviamo di fronte a queste istruzioni:
:00401573 mov ebx, offset unk_0_401546
:00401578 mov ecx, offset unk_0_40156C
:0040157D sub ecx, ebx
:0040157F shr ecx, 3
:00401582
:00401582 loc_0_401582:
:00401582
:00401582 mov eax, dword_0_4031D4
:00401587 xor [ebx], eax
:00401589 mov eax, dword_0_4031D8
:0040158E xor [ebx+4], eax
:00401591 add ebx, 8
:00401594 loop loc_0_401582
:00401596 leave
:00401597 retn 10h
Queste istruzioni mi sembrano chiare, xorano con le stesse dword il codice già xorato in predecenza. Pare quindi evidente lo scopo del crackme, ovvero trovare una chiave che decritti correttamente il codice che ci mostrerà il MsgBox di seriale corretto e per questo dovremo fare un brute forcing. Che il prog mostri un MsgBox ovviamente è solo una mia supposizione però analizzando un secondo il prog si può dedurre che questa supposizione sia fondata. Ora a voi parrà una supposizione inutile ma su questa io ho basato il forcing, vediamo per esempio la chiamata al MsgBox di seriale errato:
:0040159A 6A 30 push 30h
:0040159C 68 6A 31 40 00 push offset aLamah ; ":-| Lamah!!!"
:004015A1 68 17 31 40 00 push offset aIlSerialInseri ; "Il serial inserito non Þ esatto. Contro"...
:004015A6 FF 75 08 push dword ptr [ebp+8]
:004015A9 E8 0C 01 00 00 call MessageBoxA
Il codice del brute si basa sul tentare tutte le chiavi possibili (e anche se è improbabile non ho messo nessun range per correttezza: per non puntare solo su supposizioni…), decrittare il codice e controllare se vi sono i byte che ho supposto esservi, prendendo l’opcode 68h dei due push, il push che specifica la window e l’E8h della call, potevo anche prenderne altri ma questi byte sono più che sufficenti, vediamo quindi il codice asm:
; forcer.asm ----------------------------------------
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
Block db 6Ch, 0ECh, 2Ah, 0DBh, 1Eh, 3Eh, 60h, 0F3h, 0DBh, 0A7h, 3Bh, 36h
db 00h, 3Dh, 20h, 14h, 6Fh, 44h, 81h, 2Bh, 08h, 3Dh, 0D8h, 09h
db 6Eh, 6Ch, 29h, 0E5h, 3Dh, 35h, 58h, 2Fh
S1 dd 0
ws db "The right key is: %08X%08X", 0
Caption db "BrutMe1 forcer by Ntoskrnl", 0
Serial db 11 dup(?)
.data?
S2 dd ?
.code
start:
; ciclo principale
inc S1
mov eax, 1B000000h
cmp S1, eax
jnl prg_end
mov eax, 98D969A7h
sub eax, S1
mov S2, eax
mov ecx, 4
lea ebx, Block
mov edx, ebx
mov edi, S1
mov esi, S2
; decritto i bytes
decrypt:
xor [ebx], edi
xor [ebx + 4], esi
add ebx, 8
loop decrypt
add edx, 2Eh
sub ebx, 21h
; cerco i byte per il MsgBox
find:
inc ebx
cmp ebx, edx
jnl not_found
cmp byte ptr [ebx], 0FFh
jne find
cmp byte ptr [ebx + 1], 75h
jne find
cmp byte ptr [ebx + 2], 8
jne find
cmp byte ptr [ebx + 3], 0E8h
jne find
cmp byte ptr [ebx - 5], 68h
jne find
cmp byte ptr [ebx - 10], 68h
je found ; se salta significa che abbiamo trovato il seriale
jmp find
not_found:
mov ecx, 4
lea ebx, Block
; ri-critta il seriale di modo che potremo decrittarlo
; con una nuova chiave
crypt:
xor [ebx], edi
xor [ebx + 4], esi
add ebx, 8
loop crypt
; ritorna all'inizio
jmp start
found:
; queste istruzioni mostrano in un MsgBox il seriale corretto
push esi
push edi
push offset ws
push offset Serial
call wsprintf
add esp, 4
push 0
push offset Caption
push offset Serial
push 0
call MessageBox
prg_end:
; termina l'esecuzione
push 0
call ExitProcess
end start
; --------------------------------------------------
Aspetto un poco di tempo, nel mio caso 2-3 minuti e d’un tratto mi arriva il MsgBox con seriale: 1A292C5F7EB03D48
Lo provo e mi arriva il MsgBox di seriale corretto, crackme risolto! Se come me volete vedere il codice in chiaro su ida fate un undefine sui byte da decrittare, aggiungete con idc file nel menu File il foglio idc:
; decrypt.idc --------------------------------------
static decrypt()
{
auto x, mod;
for(x = 0x00401546; x <= 0x00401546 + (3 * 8); x = x + 8)
{
mod = Dword(x);
mod = mod ^ 0x1A292C5F;
PatchDword(x, mod);
mod = Dword(x + 4);
mod = mod ^ 0x7EB03D48;
PatchDword(x + 4, mod);
}
}
; --------------------------------------------------
Premete poi nel menu file su idc command e inserite il comando “decrypt();”, premete ok e magicamente (molto magicamente) i byte si decritteranno, definite poi il tutto come code, et voilà, vedrete il codice in chiaro:
:00401546 xor eax, eax
:00401548 add eax, ecx
:0040154A push esi
:0040154B add edx, eax
:0040154D lea eax, [ebx+ecx*4+482C12h]
:00401554 nop
:00401555 push 30h
:00401557 push offset aWellDone ; ":-) Well done!!!"
:0040155C push offset aBravoHaiTrovat ; "Bravo, hai trovato il seriale di questo"...
:00401561 push dword ptr [ebp+8]
:00401564 call MessageBoxA
:00401569 pop edx
:0040156A jmp short loc_0_401573
Un’ultima cazzata che scrivo per completezza nel caso qualcuno si fosse chiesto come mai fra gli attributi della sezione .text non cè quello writeable ma a runtime il prog si comporta come se ci fosse (modificando il proprio codice in memoria): la risposta è molto semplice, i permessi sulle pagine in memoria vengono cambiati a runtime dal prog tramite la funzione Virtual Protect (che viene chiamata all’inizio del codice):
:00401013 push offset hWndParent ; lpflOldProtect
:00401018 push 40h ; attributi: PAGE_EXECUTE_READWRITE
:0040101A push eax ; dwSize
:0040101B push offset start ; lpAddress
:00401020 call VirtualProtect
Ok, mi pare di aver proprio finito adesso, come sempre spero che il tutorial sia stato di vostro gradimento.
Alla prossima.
Ntoskrnl