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

 

http://pmode.cjb.net/