Risolviamo il BruteMe 2 di Spider (la semplicità innanzitutto)

 

Pensavi di esserti liberato di me spido? Io brutalizzerò anche il tuo bruteme2, che tu lo voglia oppure no, ihihih.

 

[Si ma non ti montare la  testa... NdSpider]

 

Questa volta nel readme spider ha pure scritto:

 

Ecco il mio secondo BruteMe... Se la prima volta un volontario buco rendeva la soluzione piuttosto semplice, stavolta le cose sono più complicate... L'idea di fondo è la stessa, ma in questo caso non si ha praticamente nessun riferimento a ciò che potrebbe essere contenuto nella parte di codice criptata, tanto più che nel codice criptato vi è un secondo layer di decrypt... ve lo dico per risparmiarvi un po' di tempo :)

 

Solo ??? Spider mi deludi, stai diventando prevedibile, sarà l'età...

 

[Troppo tardi, ti sei già montato la testa...  :P  NdSpider]

 

Ok, basta scherzi, mano a IDA e iniziamo a guardarci il codice, come sempre il punto da brute forcare lo troviamo subito:

 

:00401135   push    3E8h

:0040113A   push    dword ptr [ebp+8]

:0040113D   call    GetDlgItemInt             ; prende il testo come numero

:00401142   push    eax                       ; pusha eax

:00401143   movzx   ecx, ax                   ; ne prende la prima word e la mette in ecx

:00401146   shr     eax, 10h                  ; shifta eax di 16 bit

:00401149   mov     edx, eax                  ; edx = eax

:0040114B   add     edx, ecx                  ; edx = edx + ecx

:0040114D   pop     eax                       ; recupera il numero originale

:0040114E   jz      loc_401227                ; salta a errore se = 0

:00401154   test    dx, dx                    ; dx = 0 ?

:00401157   jnz     loc_401227                ; se no, allora salta a errore

:0040115D   pusha                             ; salva il contenuto dei registri

:0040115E   mov     edx, offset loc_401175    ; ecx = offset byte crittati

:00401163   mov     ecx, 26h                  ; ecx = 38d

:00401168

:00401168 loc_401168:

:00401168                           

:00401168   xor     [edx], eax                ; xora i byte con la cifra da noi inserita

:0040116A   sub     dword ptr [edx], 5C5893h  ; sottrae 5C5893h

:00401170   add     edx, 4                    ; incrementa l'offset

:00401173   loop    loc_401168                ; continua la decrittzione

 

Le istruzioni mi sembrano chiare, spider ci semplifica un poco le cose per quanto riguarda il fattore tempo per la decrittazione, infatti il range che ci consente di usare per la decrittazione si limita appunto ad una word, spiegazione: noi inseriamo un numero, il programma considera la dword xxxxyyyy che è il nostro numero e mette la low word in ecx, quindi avremo ecx = yyyy, dopodiché shifta il numero di 16 bit quindi avrà in eax la cifra xxxx, quindi controlla che xxxx + yyyy sia uguale a 0 (in word intendiamoci, in dword avremo 10000h), altrimenti la cifra immessa non è valida. Fatto questo controllo limitando la cifra da noi immessa, il programma procede col decrypting.

 

Prima di parlare come risolvere facciamo una cosina:

 

:00401075   push    offset sub_0_4012C1

:0040107A   push    64h            

:0040107C   push    0               

:0040107E   push    0              

:00401080   call    SetTimer  ; noppiamo queste istruzioni

 

E rinominiamo il titolo della main window in "BruteMe2" (per questo ci avvaliamo del resource hacker).

 

Questo perché nel brute mi baso sul titolo della window e non sul nome della classe, e purtroppo spider ha messo un effetto grafico che a tempo cambia il titolo della window. Fatta questa piccola modifica iniziamo a vedere come brute forcare.

 

L'idea che mi è venuta consiste nel caricare il bruteme, immettere il seriale, mandare il messaggio di check del seriale e controllare se il prog ci mostra il msgbox di seriale corretto, se no allora ricomincia il tutto incrementando il seriale. Ah ovviamente questa soluzione è possibile effettuarla solo su sistemi NT, su architetture 9x il pc vi freeza se viene eseguito un comando come ad esempio outsd in modo scorretto (il che con la decrittazione è quasi sicuro). Ho molta poca stima nei confronti degli os che non differenziano tra codice eseguito in user o in kernel mode diciamolo subito.

 

Vediamo adesso il codice che assicuro mi è costato solo 10 minuti appena (anzi mi scuso se non fosse proprio stupendo).

 

; bm2.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

include \masm32\include\gdi32.inc

includelib \masm32\lib\gdi32.lib

 

.const

ID_OPENDLG  equ 40002d

ID_REGISTER equ 1

 

 

.data

Bruteme2 db "BruteMe2.exe", 0

Main  db "BruteMe2", 0

Registra db "Registra", 0

Sfotti   db "Sfotti?", 0

Titolo   db "Seriale BruteMe2:", 0

ws       db "%u", 0

Serial   db 11 dup(?)

 

sinfo STARTUPINFO <>

pinfo PROCESS_INFORMATION <>

 

.data?

hWnd dd ?

hReg dd ?

count dd ?

 

.code

 

start:

      ; fa in modo che in caso di crash del processo

      ; noi non veniamo avvertiti tramite dlgbox dall'os

      push SEM_NOGPFAULTERRORBOX

      call SetErrorMode

 

      ; azzera eax

      xor eax, eax

 

next:

      ; incrementa eax e lo mette in count

      inc eax

      mov count, eax

 

      ; crea il processo

      push offset pinfo

      push offset sinfo

      push NULL

      push NULL

      push HIGH_PRIORITY_CLASS    

      push FALSE

      push 0     

      push 0

      push NULL

      push offset Bruteme2

      call CreateProcess

 

      ; se non è stato creato chiude

      test eax, eax

      jz close

 

repeat_f:

      ; dorme 150 millisecondi

      push 150d

      call Sleep

 

      ; cerca la main window

      push offset Main

      push NULL

      call FindWindow

 

      ; se non la trova, riprova

      test eax, eax

      jz repeat_f

 

      ; apre la dlg di registrazione

      ; uso postmessage dato che mando

      ; un msg ad window che non fa parte

      ; del mio processo

      push NULL

      push ID_OPENDLG

      push WM_COMMAND

      push eax

      call PostMessage

 

      ; calcola il seriale

      mov ecx, count

 

      mov esi, 10000h

      sub esi, ecx

      shl esi, 10h

      mov si, cx

 

      ; lo stampa come stringa

      push esi

      push offset ws

      push offset Serial     

      call wsprintf

 

      ; dorme 20 millisecondi

      push 20d

      call Sleep

 

      ; cerca la dlg di registrazione

      push offset Registra

      push NULL

      call FindWindow

 

      ; se non la trova chiude il prog

      test eax, eax

      jz close

 

      ; mette in hReg l'handle ricavato

      mov hReg, eax

 

      ; prende l'handle dell'edit box della

      ; window di registrazione

      push 1000d

      push eax

      call GetDlgItem

 

      ; se l'handle è nullo, chiude

      test eax, eax

      jz close

 

      ; mette l'handle in hWnd

      mov hWnd, eax

 

      ; controlla la grandezza del buffer/seriale

      push offset Serial

      call lstrlen

 

      ; da qua in poi stampa carattere per carattere

      ; nella edit box della dlg di registrazione

      mov esi, eax

      lea edi, Serial

      add esi, edi

 

post:

      xor ebx, ebx

      mov bl, byte ptr [edi]

 

      push 0

      push ebx

      push WM_CHAR

      push hWnd

      call PostMessage

 

      inc edi

 

      cmp edi, esi

      jl post

 

      ; dorme 120 millisecondi

      push 120d

      call Sleep

 

repeat_r:

     

      mov eax, hReg

 

      ; manda il msg di controllare il seriale

      ; alla dlg di registrazione

      push NULL

      push ID_REGISTER

      push WM_COMMAND

      push eax

      call PostMessage

 

      test eax, eax

      jz close

 

      ; dorme 140 millisecondi

      push 140d

      call Sleep

 

      ; controlla che la window di registrazione

      ; sia ancora presente sullo schermo

      push offset Registra

      push NULL

      call FindWindow

     

      ; se non lo è ricomincia il ciclo e incrementa il serial

      test eax, eax

      jz get_next

 

      ; controlla che non sia arrivata il msgbox

      ; che ci dice che il seriale non è corretto

      ; questo può accadere se non è passato abbastanza

      ; tempo fra l'input del seriale e il getdlgitemint che

      ; prende il serial

      push offset Sfotti

      push NULL

      call FindWindow

 

      ; se ha trovato la window allora ripete il comando register

      test eax, eax

      jnz repeat_r

 

      ; se tutte queste cose non si sono verificate

      ; allora è possibile che il seriale sia giusto

      ; e il prog lascia al bruteme tutto il tempo

      ; per decrittazione e esecuzione del codice

      ; 1 secondo netto per la precisione

      ; (non si sa mai)

      push 1000d

      call Sleep

 

      ; prende la finestra col focus

      call GetForegroundWindow

     

      mov edx, hReg

 

      ; controlla se la finestra col focus è

      ; la dlg di registrazione, se così non fosse

      ; allora il seriale è corretto e quindi

      ; salta al msgbox

      cmp eax, edx

      jnz msgbox

 

get_next:

 

      ; termina il processo

      push 0

      push pinfo.hProcess

      call TerminateProcess

     

      ; dorme per 30 millisecondi

      push 30d

      call Sleep

 

      ; controlla che il count sia minore o uguale

      ; a FFFFh se è così può ricominciare il ciclo

      ; altrimenti esce (messo per sicurezza il check)

      mov eax, count

      cmp eax, 0FFFFh

      jle next

      jmp close

 

msgbox:

      ; ci mostra il seriale corretto

      push NULL

      push offset Titolo

      push offset Serial

      push NULL

      call MessageBox

 

     

close:

      push NULL

      call ExitProcess

 

end start

 

; -------------------------------------------------------

 

Chiaramente i tempi prestabiliti di attesa sono impostati per la mia cpu, diversa velcità del pc -> diversi tempi di attesa.

 

Lascio eseguire il brute per un po' (manco pochissimo eh), e dopo qualche tempo mi arriva il msgbox di congratulazioni e il seriale valido in un msgbox, il seriale sarebbe: 1468901491 che in hex sarebbe 578DA873h.

 

Ma ora voglio vedermi il codice del bruteme2 su IDA (non ho voglia di caricare il sice) quindi mi faccio il solito idc script. Facciamo l'undefine sui bytes crittati e poi applichiamo lo script:

 

; decrypt.idc -------------------------------------------

 

static decrypt1()

{

      auto x, mod;

 

      for(x = 0x00401175; x <= 0x00401175 + (0x26 * 4); x = x + 4)

      {

            mod = Dword(x);

            mod = mod ^ 1468901491;

            mod = mod - 0x5C5893;

 

            PatchDword(x, mod);

 

 

      }

 

}

 

; -------------------------------------------------------

 

Definiamo i byte decrittati come code e ricaveremo il secondo layer di decrypt:

 

:00401175   mov     ecx, 36DB6DB6h

:0040117A   push    0

:0040117C   pop     eax

:0040117D   xchg    eax, ecx        ; eax = 36DB6DB6h && ecx = 0

:0040117E   add     ecx, 5Ah        ; ecx = 5Ah

:00401184   push    371B7F4Ah

:00401189   pop     ebx

:0040118A   sub     ebx, eax        ; ebx = 371B7F4Ah - 36DB6DB6h

:0040118A                           ; ebx = offset decrittazione

:0040118C

:0040118C loc_0_40118C:

:0040118C                         

:0040118C   xor     [ebx], al       ; xora il byte puntato da ebx con al

:0040118E   ror     eax, 8          ; ruota i bit di eax di 8

:00401191   inc     ebx             ; incrementa l'offset

:00401192   loop    loc_0_40118C    ; ripete il ciclo

 

E me scrivo il dovuto idc script:

 

; decrypt.idc -------------------------------------------

 

static decrypt2()

{

 

      auto count, mod, calc, x;

 

      x = 0;

 

      for(count = 0x00401194; count <= 0x00401194 + 0x5A; count++)

      {

            mod = Byte(count);

 

            if(x == 0) calc = 0xB6;  // tanto per non usare il ror

            if(x == 1) calc = 0x6D;  // fa schifo ma tanto non ce ne importa

            if(x == 2) calc = 0xDB;

            if(x == 3) calc = 0x36;

 

            if(x == 3) x = 0;

            else x++;

 

            mod = mod ^ calc;

            PatchByte(count, mod);

      }    

 

}

 

; -------------------------------------------------------

 

E vediamo il codice decrittato:

 

:00401194   push  40h

:00401196   call  sub_0_4011A1                    ; salta..

:0040119B   push  edi

:0040119C   outsd

:0040119D   ja    short near ptr loc_0_4011BE+2

:0040119F   and   [eax], eax

:004011A1

:004011A1 sub_0_4011A1 proc near                

:004011A1   call  sub_0_4011E6                    ; qua e risalta...

:004011A6   inc   ebx

:004011A7   outsd

:004011A8   ins   dword ptr es:[edi], dx

:004011A9   jo    short near ptr loc_0_401212+5

:004011AB   imul  ebp, [ebp+65h], 2169746Eh

:004011B2   and   [eax+61h], cl

:004011B5   imul  esp, [eax], 766F7274h

:004011BB   popa

:004011BC   jz    short near ptr loc_0_401227+6

:004011BE

:004011BE loc_0_4011BE:                          

:004011BE   and   [ecx+6Ch], ch

:004011C1   and   [ebx+65h], dh

:004011C4   jb    short loc_0_40122F

:004011C6   popa

:004011C7   ins   byte ptr es:[edi], dx

:004011C8

:004011C8 loc_0_4011C8:                          

:004011C8   and   gs:[ebx+6Fh], ah

:004011CC   jb    short near ptr loc_0_40123A+6

:004011CE   db      65h

:004011CE   jz    short near ptr loc_0_401241+4

:004011D1   outsd

:004011D2   and   [ecx+ebp*2+20h], ah

:004011D6   jno   short near ptr loc_0_40124B+2

:004011D8   db      65h

:004011D8   jnb   short near ptr loc_0_40124B+4

:004011DB   outsd

:004011DC   and   [ebx+72h], ah

:004011DF   popa

:004011E0   arpl  [ebx+6Dh], bp

:004011E3   and   gs:[eax], eax        

:004011E3 sub_0_4011A1 endp

:004011E3

:004011E6

:004011E6 sub_0_4011E6 proc near                       

:004011E6   push  dword ptr [ebp+8]                   ; qua       

:004011E9   call  MessageBoxA                         ; chiama il msg box di congratulazioni

 

Ok, direi che abbiamo finito. Come potete notare la soluzione è molto semplice, quella che aveva in mente spider era un po' più complessa, praticamente si trattava di trasformare il crackme in un autokeygen, quindi sarebbe stato doveroso eseguire il codice in un thread a parte, mettere seh ecc. ecc. Io il codice per brute forcare l'ho scritto in 10 minuti, quindi credo che valutando il tempo necessario per l'implementazione della soluzione di spider e la sua esecuzione, sia nel complesso più lungo della mia idea (ebete).

 

Alla prossima.

 

Ntoskrnl