GUIDA Reverse Engineering, CrackMe #3: Legacyy's Keygen me Quick!

Pubblicità

DispatchCode

Moderatore
Staff Forum
Utente Èlite
Messaggi
2,332
Reazioni
1,928
Punteggio
134

Legacyy's Keygen me Quick!​

Autore: Legacyy


Proseguiamo con l'esplorazione del reversing, questa volta con un Keygenme. Nulla di complicato, il livello di difficoltà è classificato come 2.0, ma potrebbe essere anche qualcosa meno (secondo me). Ci ho impiegato circa una decina di minuti (in una pausa da lavoro).

Per chi fosse interessato, riporto gli altri articoli della serie Reversing:

0. Prime analisi​



Il primo controllo l'ho fatto come sempre usando PEiD, il cui risultato è riportato di seguito:

peid.webp

il compilatore è C++, e non ci sono protezioni.
Guardando da PEView si vede subito la sezione data che contiene delle stringhe in chiaro, quindi sono anche piuttosto sicuro non ci siano stringhe cifrate al suo interno.

peview_img.webp

1 - Esecuzione tramite OllyDbg​



Ho deciso di lanciare direttamente l'eseguibile da Olly, senza breakpoint e senza ricerche di nessun tipo, solo per vedere che messaggi vengono riportati e come funziona.
Viene richiesta una password; inserisco alcuni caratteri a caso e compare questo:

keygen1_exec.webp

Il messaggio mostrato è ben più di un aiuto. In un colpo solo conosciamo la lunghezza del seriale e com'è suddiviso (quanti caratteri ci sono per ogni "sezione").

Sapendo che le stringhe sono in chiaro, cerco il messaggio di "complimenti" che sarà sicuramente presente...

olly1.webp

e infatti eccolo, con tutte le altre stringhe.

2. L'algoritmo di controllo​



Nell'immagine sopra si noterà un breakpoint - il rettangolino rosso sulla parte sx dell'immagine - su una call. La CALL è quella che verifica se il seriale inserito è corretto oppure no. Per la verità non sono andato a vedere per scoprirlo, mi sentivo abbastanza sicuro della cosa; e questo per un motivo molto semplice:

Codice:
CPU Disasm
Address   Hex dump          Command                                  Comments
007F6FCF  |.  85C0          TEST EAX,EAX
007F6FD1  |.  74 0D         JZ SHORT 007F6FE0
007F6FD3  |.  68 84608500   PUSH OFFSET 00856084                     ; ASCII "Congrats, you did it!"
007F6FD8  |.  E8 6FA9FFFF   CALL 007F194C
007F6FDD  |.  83C4 04       ADD ESP,4
007F6FE0  |>  EB 0D         JMP SHORT 007F6FEF
007F6FE2  |>  68 9C608500   PUSH OFFSET 0085609C                     ; ASCII "Invalid key format, must be in the form XXXX-XXXX-XXXX."
007F6FE7  |.  E8 60A9FFFF   CALL 007F194C

il JZ che si trova appena sotto alla CALL testa il risultato che torna e quindi sceglie il messaggio da mostrare. Sopra a questo ci sono altri due JZ, e tutti saltano al medesimo indirizzo. Si tratta quindi di un totale di 3 controlli (come le parti che compongono la key).
Per chiarezza riporto l'intero codice in questione:

Codice:
CPU Disasm
Address   Hex dump          Command                                  Comments
007F6F94  |. /74 4C         JZ SHORT 007F6FE2
007F6F96  |. |68 6C608500   PUSH OFFSET 0085606C                     ; ASCII "Key in correct format."
007F6F9B  |. |E8 ACA9FFFF   CALL 007F194C
007F6FA0  |. |83C4 04       ADD ESP,4
007F6FA3  |. |8D45 EC       LEA EAX,[LOCAL.5]
007F6FA6  |. |50            PUSH EAX
007F6FA7  |. |E8 B9A9FFFF   CALL 007F1965
007F6FAC  |. |83C4 04       ADD ESP,4
007F6FAF  |. |85C0          TEST EAX,EAX
007F6FB1  |. |74 2D         JZ SHORT 007F6FE0
007F6FB3  |. |8D4D EC       LEA ECX,[LOCAL.5]
007F6FB6  |. |51            PUSH ECX
007F6FB7  |. |E8 8BBDFFFF   CALL 007F2D47
007F6FBC  |. |83C4 04       ADD ESP,4
007F6FBF  |. |85C0          TEST EAX,EAX
007F6FC1  |. |74 1D         JZ SHORT 007F6FE0
007F6FC3  |. |8D55 EC       LEA EDX,[LOCAL.5]
007F6FC6  |. |52            PUSH EDX
007F6FC7  |. |E8 BEACFFFF   CALL 007F1C8A
007F6FCC  |. |83C4 04       ADD ESP,4
007F6FCF  |. |85C0          TEST EAX,EAX
007F6FD1  |. |74 0D         JZ SHORT 007F6FE0
007F6FD3  |. |68 84608500   PUSH OFFSET 00856084                     ; ASCII "Congrats, you did it!"
007F6FD8  |. |E8 6FA9FFFF   CALL 007F194C
007F6FDD  |. |83C4 04       ADD ESP,4
007F6FE0  |> |EB 0D         JMP SHORT 007F6FEF
007F6FE2  |> \68 9C608500   PUSH OFFSET 0085609C                     ; ASCII "Invalid key format, must be in the form XXXX-XXXX-XXXX."

3. Primo check sulla key​



A questo punto seguo la prima CALL, e mi ritrovo a questo codice:

Codice:
CPU Disasm
Address   Hex dump          Command                                  Comments
007F6E10  /> \55            PUSH EBP
007F6E11  |.  8BEC          MOV EBP,ESP
007F6E13  |.  83EC 08       SUB ESP,8
007F6E16  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1
007F6E1D  |.  C745 FC 00000 MOV DWORD PTR SS:[LOCAL.1],0
007F6E24  |.  EB 09         JMP SHORT 007F6E2F
007F6E26  |>  8B45 FC       /MOV EAX,DWORD PTR SS:[LOCAL.1]
007F6E29  |.  83C0 01       |ADD EAX,1
007F6E2C  |.  8945 FC       |MOV DWORD PTR SS:[LOCAL.1],EAX
007F6E2F  |>  837D FC 04    |CMP DWORD PTR SS:[LOCAL.1],4
007F6E33  |.  7D 25         |JGE SHORT 007F6E5A
007F6E35  |.  8B4D 08       |MOV ECX,DWORD PTR SS:[ARG.1]
007F6E38  |.  034D FC       |ADD ECX,DWORD PTR SS:[LOCAL.1]
007F6E3B  |.  0FBE11        |MOVSX EDX,BYTE PTR DS:[ECX]
007F6E3E  |.  83FA 30       |CMP EDX,30
007F6E41  |.  7C 0E         |JL SHORT 007F6E51
007F6E43  |.  8B45 08       |MOV EAX,DWORD PTR SS:[ARG.1]
007F6E46  |.  0345 FC       |ADD EAX,DWORD PTR SS:[LOCAL.1]
007F6E49  |.  0FBE08        |MOVSX ECX,BYTE PTR DS:[EAX]
007F6E4C  |.  83F9 39       |CMP ECX,39
007F6E4F  |.  7E 07         |JLE SHORT 007F6E58
007F6E51  |>  C745 F8 00000 |MOV DWORD PTR SS:[LOCAL.2],0
007F6E58  |>^ EB CC         \JMP SHORT 007F6E26
007F6E5A  |>  837D F8 00    CMP DWORD PTR SS:[LOCAL.2],0
007F6E5E  |.  75 0D         JNE SHORT 007F6E6D
007F6E60  |.  68 00608500   PUSH OFFSET 00856000                     ; ASCII "Check one failed."
007F6E65  |.  E8 E2AAFFFF   CALL 007F194C
007F6E6A  |.  83C4 04       ADD ESP,4
007F6E6D  |>  8B45 F8       MOV EAX,DWORD PTR SS:[LOCAL.2]
007F6E70  |.  8BE5          MOV ESP,EBP
007F6E72  |.  5D            POP EBP
007F6E73  \.  C3            RETN

Sotto si osservano due funzioni analoghe, con un messaggio simile, ma che riguarda il secondo e il terzo check. Ma dedichiamoci a questa, nel mentre...

LOCAL.1 è un indice, viene usato come contatore del ciclo. Il ciclo in questione è un for inizia all'istruzione sotto alla JMP, come si può notare (Olly racchiude il ciclo usando | e \, /).
Il corpo del ciclo possiamo spezzarlo in due parti da analizzare/commentare separatamente:

Codice:
007F6E26  |>  8B45 FC       /MOV EAX,DWORD PTR SS:[LOCAL.1]
007F6E29  |.  83C0 01       |ADD EAX,1
007F6E2C  |.  8945 FC       |MOV DWORD PTR SS:[LOCAL.1],EAX
007F6E2F  |>  837D FC 04    |CMP DWORD PTR SS:[LOCAL.1],4
007F6E33  |.  7D 25         |JGE SHORT 007F6E5A

questa è la parte di "inizializzazione" del for, e quindi verrà eseguita solo dal secondo ciclo (LOCAL.1 == 1). In sostanza verifica solamente che LOCAL.1 sia minore di 4. Questa è infatti l'ampiezza della prima parte della key.

Codice:
007F6E35  |.  8B4D 08       |MOV ECX,DWORD PTR SS:[ARG.1]
007F6E38  |.  034D FC       |ADD ECX,DWORD PTR SS:[LOCAL.1]
007F6E3B  |.  0FBE11        |MOVSX EDX,BYTE PTR DS:[ECX]
007F6E3E  |.  83FA 30       |CMP EDX,30
007F6E41  |.  7C 0E         |JL SHORT 007F6E51
007F6E43  |.  8B45 08       |MOV EAX,DWORD PTR SS:[ARG.1]
007F6E46  |.  0345 FC       |ADD EAX,DWORD PTR SS:[LOCAL.1]
007F6E49  |.  0FBE08        |MOVSX ECX,BYTE PTR DS:[EAX]
007F6E4C  |.  83F9 39       |CMP ECX,39
007F6E4F  |.  7E 07         |JLE SHORT 007F6E58
007F6E51  |>  C745 F8 00000 |MOV DWORD PTR SS:[LOCAL.2],0

Qui ARG.1, e di conseguenza ECX, è l'indirizzo base della stringa inserita in input. LOCAL.1 contiene sempre l'indice, che viene sommato alla base della stringa per prelevare il carattere in quella posizione e spostarlo in EDX (estendendo con segno l'altro byte).
Se EDX è minore di 0x30 (il numero 0), salta alla fine (restituendo come mex il "Check one failed."); se passa questo test, allora effettua un nuovo test con 0x39, il numero 9: qui salta se è minore o uguale a 9, viceversa in LOCAL.2 verrà inserito il valore 0.

4. Secondo check sulla key​



Il codice che riguarda il secondo controllo è questo:

Codice:
CPU Disasm
Address   Hex dump          Command                                  Comments
007F6E80  /> \55            PUSH EBP
007F6E81  |.  8BEC          MOV EBP,ESP
007F6E83  |.  83EC 08       SUB ESP,8
007F6E86  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1
007F6E8D  |.  C745 FC 05000 MOV DWORD PTR SS:[LOCAL.1],5
007F6E94  |.  EB 09         JMP SHORT 007F6E9F
007F6E96  |>  8B45 FC       /MOV EAX,DWORD PTR SS:[LOCAL.1]
007F6E99  |.  83C0 01       |ADD EAX,1
007F6E9C  |.  8945 FC       |MOV DWORD PTR SS:[LOCAL.1],EAX
007F6E9F  |>  837D FC 09    |CMP DWORD PTR SS:[LOCAL.1],9
007F6EA3  |.  7D 17         |JGE SHORT 007F6EBC
007F6EA5  |.  8B4D 08       |MOV ECX,DWORD PTR SS:[ARG.1]
007F6EA8  |.  034D FC       |ADD ECX,DWORD PTR SS:[LOCAL.1]
007F6EAB  |.  0FBE11        |MOVSX EDX,BYTE PTR DS:[ECX]
007F6EAE  |.  83E2 01       |AND EDX,00000001
007F6EB1  |.  74 07         |JZ SHORT 007F6EBA
007F6EB3  |.  C745 F8 00000 |MOV DWORD PTR SS:[LOCAL.2],0
007F6EBA  |>^ EB DA         \JMP SHORT 007F6E96
007F6EBC  |>  837D F8 00    CMP DWORD PTR SS:[LOCAL.2],0
007F6EC0  |.  75 0D         JNE SHORT 007F6ECF
007F6EC2  |.  68 14608500   PUSH OFFSET 00856014                     ; ASCII "Check two failed."
007F6EC7  |.  E8 80AAFFFF   CALL 007F194C
007F6ECC  |.  83C4 04       ADD ESP,4
007F6ECF  |>  8B45 F8       MOV EAX,DWORD PTR SS:[LOCAL.2]
007F6ED2  |.  8BE5          MOV ESP,EBP
007F6ED4  |.  5D            POP EBP
007F6ED5  \.  C3            RETN

l'indice questa volta viene inizializzato a 5 MOV DWORD PTR SS:[LOCAL.1],5. La stringa è sempre quella completa, e il carattere finale ha indice 9.
In questo caso il controllo è un pò differente: usa una AND e verifica che il primo bit (il bit più a destra) sia 1. Se lo è il numero è dispari, e in tal caso il bit Z del registro EFLAGS sarà settato a 0 e il salto non avverrà. Di conseguenza LOCAL.2 varrà 0.

Questo cosa significa? Che tutti i caratteri dovranno essere numeri pari.

5. Terzo check sulla key​



Il codice del terzo check è il seguente:

Codice:
CPU Disasm
Address   Hex dump          Command                                  Comments
007F6EE0  /> \55            PUSH EBP
007F6EE1  |.  8BEC          MOV EBP,ESP
007F6EE3  |.  83EC 08       SUB ESP,8
007F6EE6  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1
007F6EED  |.  C645 FC 52    MOV BYTE PTR SS:[LOCAL.1],52
007F6EF1  |.  C645 FD 33    MOV BYTE PTR SS:[LOCAL.1+1],33
007F6EF5  |.  C645 FE 4B    MOV BYTE PTR SS:[LOCAL.1+2],4B
007F6EF9  |.  C645 FF 54    MOV BYTE PTR SS:[LOCAL.1+3],54
007F6EFD  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]
007F6F00  |.  83C0 0A       ADD EAX,0A
007F6F03  |.  50            PUSH EAX
007F6F04  |.  8D4D FC       LEA ECX,[LOCAL.1]
007F6F07  |.  51            PUSH ECX
007F6F08  |.  E8 F7CEFFFF   CALL 007F3E04
007F6F0D  |.  83C4 08       ADD ESP,8
007F6F10  |.  85C0          TEST EAX,EAX
007F6F12  |.  74 07         JZ SHORT 007F6F1B
007F6F14  |.  C745 F8 00000 MOV DWORD PTR SS:[LOCAL.2],0
007F6F1B  |>  837D F8 00    CMP DWORD PTR SS:[LOCAL.2],0
007F6F1F  |.  75 0D         JNE SHORT 007F6F2E
007F6F21  |.  68 28608500   PUSH OFFSET 00856028                     ; ASCII "Check three failed."
007F6F26  |.  E8 21AAFFFF   CALL 007F194C
007F6F2B  |.  83C4 04       ADD ESP,4
007F6F2E  |>  8B45 F8       MOV EAX,DWORD PTR SS:[LOCAL.2]
007F6F31  |.  8BE5          MOV ESP,EBP
007F6F33  |.  5D            POP EBP
007F6F34  \.  C3            RETN

Il terzo check è un pò differente; guardate però queste istruzioni:

Codice:
007F6EE6  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1
007F6EED  |.  C645 FC 52    MOV BYTE PTR SS:[LOCAL.1],52
007F6EF1  |.  C645 FD 33    MOV BYTE PTR SS:[LOCAL.1+1],33
007F6EF5  |.  C645 FE 4B    MOV BYTE PTR SS:[LOCAL.1+2],4B
007F6EF9  |.  C645 FF 54    MOV BYTE PTR SS:[LOCAL.1+3],54
007F6EFD  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]

LOCAL.1 viene indirizzato usando degli indici; si tratta in sostanza di un array, dichiarato all'interno della funzione, i cui caratteri sono "R3KT". Al momento teniamoli qui, e proseguiamo. In EAX si trova sempre la stringa immessa in input.

Codice:
007F6F00  |.  83C0 0A       ADD EAX,0A
007F6F03  |.  50            PUSH EAX
007F6F04  |.  8D4D FC       LEA ECX,[LOCAL.1]
007F6F07  |.  51            PUSH ECX
007F6F08  |.  E8 F7CEFFFF   CALL 007F3E04
007F6F0D  |.  83C4 08       ADD ESP,8

l'incremento di EAX è per raggiungere l'indice corretto che rappresenta l'ultimo quartetto della chiave. EAX viene salvato sullo stack, e viene ottenuto l'indirizzo di LOCAL.1 (LEA = Load Effective Address) per salvarlo poi anch'esso sullo stack. EAX e EDX sono in realtà due parametri che vengono passati alla funzione nella CALL appena sotto.

Codice:
CPU Disasm
Address   Hex dump          Command                                  Comments
008221A0  /> \53            PUSH EBX
008221A1  |.  56            PUSH ESI
008221A2  |.  8B4C24 0C     MOV ECX,DWORD PTR SS:[ARG.1]
008221A6  |.  8B5424 10     MOV EDX,DWORD PTR SS:[ARG.2]
008221AA  |.  8B5C24 14     MOV EBX,DWORD PTR SS:[ARG.3]
008221AE  |.  F7C3 FFFFFFFF TEST EBX,FFFFFFFF
008221B4  |.  74 50         JZ SHORT 00822206
008221B6  |.  2BCA          SUB ECX,EDX
008221B8  |.  F7C2 03000000 TEST EDX,00000003
008221BE  |.  74 17         JZ SHORT 008221D7
008221C0  |>  0FB60411      MOVZX EAX,BYTE PTR DS:[EDX+ECX]
008221C4  |.  3A02          CMP AL,BYTE PTR DS:[EDX]
008221C6  |.  75 48         JNE SHORT 00822210
008221C8  |.  85C0          TEST EAX,EAX
008221CA  |.  74 3A         JZ SHORT 00822206
008221CC  |.  42            INC EDX
008221CD  |.  83EB 01       SUB EBX,1                                ; Switch (cases 0..1, 2 exits)
008221D0  |.  76 34         JBE SHORT 00822206
008221D2  |.  F6C2 03       TEST DL,03                               ; Default case of switch keygenme_1.8221CD
008221D5  |.^ 75 E9         JNZ SHORT 008221C0
008221D7  |>  8D0411        /LEA EAX,[EDX+ECX]
008221DA  |.  25 FF0F0000   |AND EAX,00000FFF
008221DF  |.  3D FC0F0000   |CMP EAX,0FFC
008221E4  |.^ 77 DA         |JA SHORT 008221C0
008221E6  |.  8B0411        |MOV EAX,DWORD PTR DS:[EDX+ECX]
008221E9  |.  3B02          |CMP EAX,DWORD PTR DS:[EDX]
008221EB  |.^ 75 D3         |JNE SHORT 008221C0
008221ED  |.  83EB 04       |SUB EBX,4                               ; Switch (cases 0..4, 2 exits)
008221F0  |.  76 14         |JBE SHORT 00822206
008221F2  |.  8DB0 FFFEFEFE |LEA ESI,[EAX+FEFEFEFF]                  ; Default case of switch keygenme_1.8221ED
008221F8  |.  83C2 04       |ADD EDX,4
008221FB  |.  F7D0          |NOT EAX
008221FD  |.  23C6          |AND EAX,ESI
008221FF  |.  A9 80808080   |TEST EAX,80808080
00822204  |.^ 74 D1         \JZ SHORT 008221D7
00822206  |>  33C0          XOR EAX,EAX                              ; Cases 0, 1 of switch keygenme_1.8221CD
00822208  |.  5E            POP ESI
00822209  |.  5B            POP EBX
0082220A  |.  C3            RETN
0082220B  |.  EB 03         JMP SHORT 00822210
0082220D  |   CC            INT3
0082220E  |   CC            INT3
0082220F  |   CC            INT3
00822210  |>  1BC0          SBB EAX,EAX
00822212  |.  83C8 01       OR EAX,00000001
00822215  |.  5E            POP ESI
00822216  |.  5B            POP EBX
00822217  \.  C3            RETN

la parte che vale la pena sottolineare è questa:
Codice:
008221C0  |>  0FB60411      MOVZX EAX,BYTE PTR DS:[EDX+ECX]
008221C4  |.  3A02          CMP AL,BYTE PTR DS:[EDX]
008221C6  |.  75 48         JNE SHORT 00822210

avviene la verifica sul primo carattere inserito, con quello dell'array visto prima. In pratica il primo carattere deve essere 'R'.
Procedendo però si vede:

Codice:
008221CC  |.  42            INC EDX
008221CD  |.  83EB 01       SUB EBX,1                                ; Switch (cases 0..1, 2 exits)
008221D0  |.  76 34         JBE SHORT 00822206

EDX va al carattere successivo, ma EBX viene decrementato e da 1 passa a 0, quindi salta al termine del programma.
Detto in altre parole, il terzo chunk del seriale è composto da R come primo carattere, e poi da altri 3 caratteri che non verranno mai verificati.

Codice:
Welcome to KeygenMe 1 sir :)
Enter a key: 1234-2468-R000
Key in correct format.
Congrats, you did it!

6. Il keygen è servito!​



Ricapitolando le varie regole: la chiave è composta da tre blocchi lunghi ciascuno 4 caratteri, e separati da 1 trattino (o il segno meno, se preferite).
Il primo blocco deve contenere i numeri 0-9, il secondo solo caratteri pari e il terzo deve iniziare per R e i 3 caratteri successivi possono essere casuali.

Di seguito riporto il keygen che consente di generare password valide:
C:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

void third_chunk(char *str, int index, int loop) 
{
    str[10] = 'R';
    while(loop--) str[index++] = 33 + rand() % 96;
}

void second_chunk(char *str, int index, int loop) 
{
    while(loop) 
    {
        char ch = 33 + rand() % 96;
        if(!(ch & 1)) 
        {
            str[index++] = ch;
            loop--;
        }
    }
}

void first_chunk(char *str, int index, int loop) 
{
    while(loop--) str[index++] = '0' + rand() % 10;
}

int main() {
    srand(time(NULL));
    
    char string[15];
    
    string[4] = '-';
    string[9] = '-';
    
    first_chunk(string, 0, 4);
    second_chunk(string, 5, 4);
    third_chunk(string, 11, 3);
    
    printf("%s", string);
    
    return 0;
}

Usando come input alcune di queste password, riporto alcuni output del keygenme:
Codice:
Welcome to KeygenMe 1 sir :)
Enter a key: 3413-rp:l-R%]@
Key in correct format.
Congrats, you did it!

Welcome to KeygenMe 1 sir :)
Enter a key: 5966-~~d.-Rnc9
Key in correct format.
Congrats, you did it!

Welcome to KeygenMe 1 sir :)
Enter a key: 3257-<&N0-RIm'
Key in correct format.
Congrats, you did it!

Welcome to KeygenMe 1 sir :)
Enter a key: 3413-rp:l-R%]@
Key in correct format.
Congrats, you did it!

7. Conclusione​



Bene, spero che anche questo articolo, seppur breve, sia stato interessante. Se avete domande, chiedete pure qui sotto.
Ho scritto articolo e Keygen in poco più di 1 ora, se trovate qualcosa di errato... segnalatemelo. ?
 
Pubblicità
Pubblicità
Indietro
Top