- Messaggi
- 2,332
- Reazioni
- 1,928
- Punteggio
- 134
aolvos's kgenme1.0
Autore: aolvos
E' il quarto articolo che scrivo che ha come argomento CrackMe/KeygenMe, gli altri potete trovarli nelle Guide per gli utenti, con altri articoli/guide.
Quello che presento oggi è particolarmente interessante secondo me. Si tratta di un keygenme, come il precedente; lo scopo principale è quindi quello di trovare il seriale e non di patchare il software modificando i controlli. La differenza con il precedente è la difficoltà: questo è classificato come 3.7 (il precedente era 2.0).
0. Prime analisi
Il primo controllo che faccio, come sempre, è con software come RGD Packer detector o PEiD. In questo caso ho scelto RDG, in quanto PEiD non individuava correttamente il compilatore:
nulla di anomalo, se non l'utilizzo di isDebuggerPresent() che ci viene segnalato e l'utilizzo di MSVC++ (più o meno so che API aspettarmi).
Oltre a ciò sappiamo anche che è un sw per x86.
1. Esecuzione con x32dbg
L'applicazione si presenta in questo modo:
inserendo un seriale errato il messaggio di errore che compare è il seguente:
A questo punto ho scelto un approccio differente rispetto alle altre volte. Invece di cercare le stringhe, ho messo un breakpoint (bp) sulla funzione GetWindowTextW. Una volta individuata ho lanciato il programma, in modo da farlo caricare; mettere un bp prima significherebbe ricevere un sacco di interruzioni.
A finestra caricata, ho piazzato un bp sul RET della funzione:
a questo punto premendo F8 si segue l'indirizzo di ritorno e ci si ritrova qui:
l'indirizzo contrassegnato con il rettangolino nero è la posizione corrente di EIP, l'istruzione dopo alla CALL. Da notare che infatti la funzione è stata chiamata usando il registro EDI, nel quale è presente il suo indirizzo.
Poco sotto vengono calcolate le lunghezze dei due input (user e pwd):
Codice:
00AF165E | 8DBC24 A0000000 | lea edi,dword ptr ss:[esp+A0] |
00AF1665 | 8D4F 02 | lea ecx,dword ptr ds:[edi+2] | ecx:L"SDA-SDAS-DASD-ADSA", edi+2:L"fsdfdda"
00AF1668 | 0F1F8400 00000000 | nop dword ptr ds:[eax+eax],eax |
00AF1670 | 66:8B07 | mov ax,word ptr ds:[edi] | edi:L"dfsdfdda"
00AF1673 | 83C7 02 | add edi,2 | edi:L"dfsdfdda"
00AF1676 | 66:85C0 | test ax,ax |
00AF1679 | 75 F5 | jne kgenme.AF1670 |
00AF167B | 2BF9 | sub edi,ecx | edi:L"dfsdfdda", ecx:L"SDA-SDAS-DASD-ADSA"
00AF167D | 8D9424 C8000000 | lea edx,dword ptr ss:[esp+C8] |
00AF1684 | D1FF | sar edi,1 | edi:L"dfsdfdda"
00AF1686 | 8D4A 02 | lea ecx,dword ptr ds:[edx+2] | ecx:L"SDA-SDAS-DASD-ADSA"
00AF1689 | 0F1F80 00000000 | nop dword ptr ds:[eax],eax |
00AF1690 | 66:8B02 | mov ax,word ptr ds:[edx] |
00AF1693 | 83C2 02 | add edx,2 |
00AF1696 | 66:85C0 | test ax,ax |
00AF1699 | 75 F5 | jne kgenme.AF1690 |
00AF169B | 2BD1 | sub edx,ecx | ecx:L"SDA-SDAS-DASD-ADSA"
Il codice prosegue con:
Codice:
00AF169F | 83FA 04 | cmp edx,4 |
00AF16A2 | 0F8C BC000000 | jl kgenme.AF1764 |
00AF16A8 | 83FF 13 | cmp edi,13 | edi:L"dfsdfdda"
00AF16AB | 0F8C B3000000 | jl kgenme.AF1764 |
00AF16B1 | 8D8C24 C8000000 | lea ecx,dword ptr ss:[esp+C8] |
00AF16B8 | E8 03FEFFFF | call kgenme.AF14C0 |
00AF16BD | 8BD7 | mov edx,edi | edi:L"dfsdfdda"
00AF16BF | 8D8C24 A0000000 | lea ecx,dword ptr ss:[esp+A0] |
00AF16C6 | 8BF0 | mov esi,eax |
00AF16C8 | E8 F3FDFFFF | call kgenme.AF14C0 |
00AF16CD | 3BF0 | cmp esi,eax |
00AF16CF | 0F85 8F000000 | jne kgenme.AF1764 |
sappiamo che in EDX c'è la lunghezza dell'username inserito. Viene confrontato con il valore 4 e se è inferiore avviene un JL a un indirizzo più in basso. In poche parole, viene saltato tutto, andando al messaggio di errore; il discorso è analogo per il controllo sotto, dove viene verificato se EDI è inferiore a 0x13 (19 in decimale). EDI è la lunghezza della key, che non può quini essere inferiore a 19.
Durante la fase di input abbiamo ricevuto un altro aiuto: digitando la key in automatico vengono inseriti dei "trattini" (il meno) ogni 4 caratteri della chiave. Quindi abbiamo 16 caratteri + 3 trattini che li separano.
Sino a qui tutto bene. A questo punto siamo a un check successivo; viene chiamata una funzione (0xAF14C0) passando come input prima l'username e poi la password.
Il ritorno viene salvato in EAX e poi salvato in ESI, e confrontato successivamente con EAX. Cosa possiamo aspettarci? Visto che la funzione è la stessa, verranno manipolati gli input (user e pwd) in modo tale da generare un qualche tipo di numero/hash che deve avere il medesimo valore per essere valido (il JNE salta al messaggio di errore se non sono uguali).
2. Algoritmo di verifica
Ahimè, andando a guardare quella funzione scopro che non è proprio semplicissima... almeno non da leggere, ecco. Fa uso del set SIMD (Single Instruction, Multiple Data). Si tratta di quelle istruzioni che introducono il "parallelismo a livello di istruzioni"; in questo caso il set è SSE.
Riporto il codice per intero sotto spoiler (tenetevi forte):
Codice:
00AF14C0 | 55 | push ebp |
00AF14C1 | 8BEC | mov ebp,esp |
00AF14C3 | 83EC 08 | sub esp,8 |
00AF14C6 | 53 | push ebx |
00AF14C7 | 57 | push edi |
00AF14C8 | 8BFA | mov edi,edx |
00AF14CA | 33C0 | xor eax,eax |
00AF14CC | 33D2 | xor edx,edx |
00AF14CE | 897D F8 | mov dword ptr ss:[ebp-8],edi |
00AF14D1 | 8BD9 | mov ebx,ecx | ecx:L"dfsdfdda"
00AF14D3 | 85FF | test edi,edi |
00AF14D5 | 0F8E D7000000 | jle kgenme.AF15B2 |
00AF14DB | 83FF 08 | cmp edi,8 |
00AF14DE | 0F82 BC000000 | jb kgenme.AF15A0 |
00AF14E4 | 833D 7853AF00 02 | cmp dword ptr ds:[AF5378],2 |
00AF14EB | 0F8C AF000000 | jl kgenme.AF15A0 |
00AF14F1 | 0F2835 0034AF00 | movaps xmm6,xmmword ptr ds:[AF3400] |
00AF14F8 | 8BCF | mov ecx,edi | ecx:L"dfsdfdda"
00AF14FA | 83E1 F8 | and ecx,FFFFFFF8 | ecx:L"dfsdfdda"
00AF14FD | 8D7A 07 | lea edi,dword ptr ds:[edx+7] |
00AF1500 | 894D FC | mov dword ptr ss:[ebp-4],ecx |
00AF1503 | 0F57ED | xorps xmm5,xmm5 |
00AF1506 | 0F57E4 | xorps xmm4,xmm4 |
00AF1509 | 56 | push esi |
00AF150A | 66:0F1F4400 00 | nop word ptr ds:[eax+eax],ax |
00AF1510 | 66:0F6EC0 | movd xmm0,eax |
00AF1514 | 8D77 FE | lea esi,dword ptr ds:[edi-2] |
00AF1517 | 66:0F70C8 00 | pshufd xmm1,xmm0,0 |
00AF151C | 8D4F 01 | lea ecx,dword ptr ds:[edi+1] | ecx:L"dfsdfdda"
00AF151F | F3:0F7E0443 | movq xmm0,qword ptr ds:[ebx+eax*2] |
00AF1524 | 8D57 FF | lea edx,dword ptr ds:[edi-1] |
00AF1527 | 66:0F3833C0 | pmovzxwd xmm0,xmm0 |
00AF152C | 66:0FFECE | paddd xmm1,xmm6 |
00AF1530 | 66:0F6EDE | movd xmm3,esi |
00AF1534 | 66:0F3840C1 | pmulld xmm0,xmm1 |
00AF1539 | 66:0FFEE8 | paddd xmm5,xmm0 |
00AF153D | 66:0F6ED1 | movd xmm2,ecx | ecx:L"dfsdfdda"
00AF1541 | 66:0F6EC7 | movd xmm0,edi |
00AF1545 | 83C7 08 | add edi,8 |
00AF1548 | 66:0F62D8 | punpckldq xmm3,xmm0 |
00AF154C | F3:0F7E4443 08 | movq xmm0,qword ptr ds:[ebx+eax*2+8] |
00AF1552 | 83C0 08 | add eax,8 |
00AF1555 | 66:0F6ECA | movd xmm1,edx |
00AF1559 | 66:0F62CA | punpckldq xmm1,xmm2 |
00AF155D | 66:0F62D9 | punpckldq xmm3,xmm1 |
00AF1561 | 66:0F3833C0 | pmovzxwd xmm0,xmm0 |
00AF1566 | 66:0F3840C3 | pmulld xmm0,xmm3 |
00AF156B | 66:0FFEE0 | paddd xmm4,xmm0 |
00AF156F | 3B45 FC | cmp eax,dword ptr ss:[ebp-4] |
00AF1572 | 7C 9C | jl kgenme.AF1510 |
00AF1574 | 8B7D F8 | mov edi,dword ptr ss:[ebp-8] |
00AF1577 | 66:0FFEE5 | paddd xmm4,xmm5 |
00AF157B | 0F28C4 | movaps xmm0,xmm4 |
00AF157E | 66:0F73D8 08 | psrldq xmm0,8 |
00AF1583 | 66:0FFEE0 | paddd xmm4,xmm0 |
00AF1587 | 0F10C4 | movups xmm0,xmm4 |
00AF158A | 66:0F73D8 04 | psrldq xmm0,4 |
00AF158F | 66:0FFEE0 | paddd xmm4,xmm0 |
00AF1593 | 66:0F7EE2 | movd edx,xmm4 |
00AF1597 | 5E | pop esi |
00AF1598 | 3BC7 | cmp eax,edi |
00AF159A | 7D 16 | jge kgenme.AF15B2 |
00AF159C | 0F1F40 00 | nop dword ptr ds:[eax],eax |
00AF15A0 | 8D48 01 | lea ecx,dword ptr ds:[eax+1] | ecx:L"dfsdfdda"
00AF15A3 | 0FB70443 | movzx eax,word ptr ds:[ebx+eax*2] |
00AF15A7 | 0FAFC1 | imul eax,ecx | ecx:L"dfsdfdda"
00AF15AA | 03D0 | add edx,eax |
00AF15AC | 8BC1 | mov eax,ecx | ecx:L"dfsdfdda"
00AF15AE | 3BC7 | cmp eax,edi |
00AF15B0 | 7C EE | jl kgenme.AF15A0 |
00AF15B2 | 5F | pop edi |
00AF15B3 | 0FB6C2 | movzx eax,dl |
00AF15B6 | 5B | pop ebx |
00AF15B7 | 8BE5 | mov esp,ebp |
00AF15B9 | 5D | pop ebp |
00AF15BA | C3 | ret |
questo set fa uso dei registri XMM, da 0 a 15 in x86. Sono dei registri di 128-bit. Un'istruzione come questa
pmulld xmm0,xmm3
significa testualmente "Multiply Packed Integers and Store Low Result". Come funziona? Bhe in sostanza consente di fare operazioni su quattro valori a 32bit nello stesso momento! Quindi non ci sarà una moltiplicazione (o una somma, dipende dall'istruzione) di un solo valore con un'altro, ma di più valori nello stesso momento.Istruzioni come queste velocizzano molto un'operazione. Immaginatevi qualcosa di semplice come due vettori di int a 32-bit che vanno sommati e i vari risultati vanno messe nelle rispettive i-esime posizioni; quindi v1[0] + v2[0] andrà in v3[0] e così via.
Un'operazione come questa fiene fatto nello stesso momento su 4 posizioni invece che su 1 sola.
A parte questa splendida notizia, direi di proseguire... e soffermarci sulla prima parte:
Codice:
00AF14C8 | 8BFA | mov edi,edx |
00AF14CA | 33C0 | xor eax,eax |
00AF14CC | 33D2 | xor edx,edx |
00AF14CE | 897D F8 | mov dword ptr ss:[ebp-8],edi |
00AF14D1 | 8BD9 | mov ebx,ecx | ecx:L"dfsdfdda"
00AF14D3 | 85FF | test edi,edi |
00AF14D5 | 0F8E D7000000 | jle kgenme.AF15B2 |
00AF14DB | 83FF 08 | cmp edi,8 |
00AF14DE | 0F82 BC000000 | jb kgenme.AF15A0 |
EDI contiene la lunghezza della stringa (che può essere user o pwd); prima viene verificato che sia diversa da 0. Successivamente avviene un altro controllo con il valore 8; se è minore di 8 salta all'indirizzo indicato da JB.
A quell'indirizzo si trova questo (riporto sempre il codice per chiarezza, ma potete vederlo dal listato lungo li sopra):
Codice:
00AF15A0 | 8D48 01 | lea ecx,dword ptr ds:[eax+1] | ecx:L"dfsdfdda"
00AF15A3 | 0FB70443 | movzx eax,word ptr ds:[ebx+eax*2] |
00AF15A7 | 0FAFC1 | imul eax,ecx | ecx:L"dfsdfdda"
00AF15AA | 03D0 | add edx,eax |
00AF15AC | 8BC1 | mov eax,ecx | ecx:L"dfsdfdda"
00AF15AE | 3BC7 | cmp eax,edi |
00AF15B0 | 7C EE | jl kgenme.AF15A0 |
il mio sospetto è che essendo inferiore a 8 non riesca ad applicare le operazioni usando i registri XMM, e quindi c'è questo blocco "personalizzato". Da notare che sappiamo che l'username è lungo minimo 4 caratteri e massimo 8, quindi direi rientri in qusesto caso.
LEA non fa altro che calcolare la somma di EAX+1, quindi in ECX al primo ciclo ci sarà 1. EAX viene usato come indice e sommato alla base della stringa (che si trova in EBX). Non vi sarà sfuggito però che moltiplica il valore 2, e quindi non sembra essere un normale array di caratteri.
In realtà lo è, si tratta di UTF-8 però: GetWindowTextW indica proprio questo. Quindi ogni carattere occupa 2-byte.
L'istruxione successiva moltiplica EAX (il carattere letto) con la sua posizione nella stringa + 1. E prosegue sino alla fine.
In sostanza quel codice è traducibile come:
C:
int i = -1;
int sum = 0;
while(i++ < len)
sum += (key[i] * (i+1));
dove la somma è in EDX.
A questo punto non ci resta che comprendere il set SSE4... almeno abbastanza da capire cosa avviene.
Ho utilizzato spesso google poichè non ricordavo i significati delle varie operazioni, come potete immaginare. Non riporto tutto il codice nuovamente, ma solo una parte:
Codice:
00AF14FD | 8D7A 07 | lea edi,dword ptr ds:[edx+7] |
00AF1500 | 894D FC | mov dword ptr ss:[ebp-4],ecx |
00AF1503 | 0F57ED | xorps xmm5,xmm5 |
00AF1506 | 0F57E4 | xorps xmm4,xmm4 |
00AF1509 | 56 | push esi |
00AF150A | 66:0F1F4400 00 | nop word ptr ds:[eax+eax],ax |
00AF1510 | 66:0F6EC0 | movd xmm0,eax |
00AF1514 | 8D77 FE | lea esi,dword ptr ds:[edi-2] |
00AF1517 | 66:0F70C8 00 | pshufd xmm1,xmm0,0 |
00AF151C | 8D4F 01 | lea ecx,dword ptr ds:[edi+1] | ecx:L"dfsdfdda"
00AF151F | F3:0F7E0443 | movq xmm0,qword ptr ds:[ebx+eax*2] |
00AF1524 | 8D57 FF | lea edx,dword ptr ds:[edi-1] |
00AF1527 | 66:0F3833C0 | pmovzxwd xmm0,xmm0 |
00AF152C | 66:0FFECE | paddd xmm1,xmm6 |
che cosa avviene qui? Se lo capite spiegatemelo ?
Scherzi a parte, da debugger si comprende meglio, potendo vedere il contenuto dei registri. Al termine dell MOVQ vengono spostati 4 caratteri in XMM0. Questi occupano metà della dimensione del registro; viene usata la PMOVZXWD per effettuare una MOV estendendo con 0 la WORD portandola a una DWORD. Facendo un esempio con la stringa che vedete nel commento sul codice qui sopra, il registro si presenta così
XMM0 = 00000000000000000064007300660064
. Dopo la PMOV la situazione è questa XMM0 = 00000064000000730000006600000064
I byte che vedete sono i primi 4 caratteri della stringa. In XMM1 vengono poi spostati gli indici
00000004000000030000000200000001
. Leggendo da destra vedete che 0x64 ha indice 1, 0x66 ha indice 2, 0x73 ha indice 3 e 0x64 ha indice 4, quindi in memoria (o meglio, nel registro) è salvata come "dsfd" (little endian).Questi due registri vengono moltiplicati e il risultato viene poi mantenuto in XMM5:
Codice:
00AF1534 | 66:0F3840C1 | pmulld xmm0,xmm1 |
00AF1539 | 66:0FFEE8 | paddd xmm5,xmm0 |
dopo la moltiplicazione, XMM0 si presenta come
0000019000000159000000CC00000064
.La parte dopo è analoga - ricorda un unrolled loop, ma solo per logica. In sostanza è lo stesso codice visto nel blocco sopra (quando la lunhezza è inferiore a 8). O meglio, non è lo stesso codice, ma il risultato è il medesimo.
Piccola nota: trattandosi di 19 caratteri, vengono lette due "tranche" da 8 (viene fatto 1 ciclo) per un totale di 16 caratteri. I 3 che rimangono entrano nell'if visto sopra, quello della lunghzza infeiore a 8 caratteri.
3. Primo keygen: generiamo le stesse somme
A questo punto siamo in grado di riprodurre questa parte e generare dei seriali che superino il controllo sull'uguaglianza delle moltiplicazioni e somme fatte sui caratteri.
Il controllo è il JNE riportato sotto:
Codice:
00AF16B1 | 8D8C24 C8000000 | lea ecx,dword ptr ss:[esp+C8] |
00AF16B8 | E8 03FEFFFF | call kgenme.AF14C0 |
00AF16BD | 8BD7 | mov edx,edi |
00AF16BF | 8D8C24 A0000000 | lea ecx,dword ptr ss:[esp+A0] |
00AF16C6 | 8BF0 | mov esi,eax |
00AF16C8 | E8 F3FDFFFF | call kgenme.AF14C0 |
00AF16CD | 3BF0 | cmp esi,eax |
00AF16CF | 0F85 8F000000 | jne kgenme.AF1764 |
Come detto nel paragrafo precedente ESI e EAX contengono i due valori numerici che devono essere identici. Dobbiamo creare la procedura che abbiamo più o meno compreso sopra.
Innanzitutto la password:
C:
int gen_password(char *key)
{
for(int i=0; i<PWD_LEN; i++)
key[i] = alphabet[rand() % 0x24]; // numbers and A-Z chars
key[4] = '-';
key[9] = '-';
key[14] = '-';
return check_value(key, PWD_LEN);
}
l'alfabeto sono sia numeri che lettere (maiuscole e minuscole).
Poi l'username:
C:
int gen_username(char *user, int *len, int pwd_n) {
while(true) {
*len = rand() % 4 + 4;
for(int i=0; i<*len; i++) {
user[i] = alphabet[rand() % 0x3e]; // all chars
}
user[*len] = 0;
int user_n = check_value(user, *len);
if(user_n == pwd_n) return user_n;
}
return -1; // never reached (I hope :P )
}
questo è un pò "forza bruta" in quanto cicla sino a che non trova una combinazione numerica uguale alla password. Ho scelto questo tipo di verifica perchè più efficiente rispetto al contrario (la pwd è più lunga).
La funzione che restituisce il numero è:
C:
int check_value(char *key, int len)
{
int i = -1;
int sum = 0;
while(i++ < len)
sum += (key[i] * (i+1));
return sum & 0xFF;
}
l'ultima AND è dovuta al fatto che il numero viene convertito (nel listato asm di sopra) e l'ampiezza massima che deve avere è appunto quella.
Da notare la semplicità rispetto al codice precedente (in asm), anche perchè non prendo chunk da 4 caratteri.
Il codice completo è questo:
C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#define USER_LEN 9
#define PWD_LEN 19
const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int check_value(char *key, int len)
{
int i = -1;
int sum = 0;
while(i++ < len)
sum += (key[i] * (i+1));
return sum & 0xFF;
}
int gen_password(char *key)
{
for(int i=0; i<PWD_LEN; i++)
key[i] = alphabet[rand() % 0x24]; // numbers and A-Z chars
key[4] = '-';
key[9] = '-';
key[14] = '-';
return check_value(key, PWD_LEN);
}
int gen_username(char *user, int *len, int pwd_n) {
while(true) {
*len = rand() % 4 + 4;
for(int i=0; i<*len; i++) {
user[i] = alphabet[rand() % 0x3e]; // all chars
}
user[*len] = 0;
int user_n = check_value(user, *len);
if(user_n == pwd_n) return user_n;
}
return -1; // never reached (I hope :P )
}
int main() {
srand(time(NULL));
int userlen = 0;
char username[USER_LEN] = {'\0'};
char password[PWD_LEN] = {'\0'};
int pwd_n = gen_password(password);
int usr_n = gen_username(username, &userlen, pwd_n);
printf("Username: %s\tUser value: %X\n", username, usr_n);
printf("Password: %s\tPwd value: %X\n", password, pwd_n);
return 0;
}
Di seguito alcuni output:
Codice:
Username: PVSph User value: BD
Password: HYLK-XV6K-NPHP-I8NN Pwd value: BD
Username: NclYYgh User value: BB
Password: O3LZ-3SKN-F57Q-IO15 Pwd value: BB
Username: pQbbf5R User value: 3A
Password: RFT6-588B-BDYC-WMUZ Pwd value: 3A
Già che c'è il programma con un input errato, lo eseguo, mettendo un bp sul JNE. In questo modo mi assicuro che salti (essendo sicuramente diversi i valori numerici calcolati):
Ora prendo il primo input generato li sopra e lo inserisco:
come si nota questa volta il salto non avviene, quindi la somma è giusta!
Se non fosse...
4. Completamento del keygen
Eh, se non fosse che poco più sotto c'è un altro controllo (proprio superato quel JNE):
Codice:
00AF16D5 | 0FB78424 A4000000 | movzx eax,word ptr ss:[esp+A4] |
00AF16DD | BA 05000000 | mov edx,5 |
00AF16E2 | 0FB7B424 A6000000 | movzx esi,word ptr ss:[esp+A6] |
00AF16EA | 03F0 | add esi,eax |
00AF16EC | 0FB78424 A2000000 | movzx eax,word ptr ss:[esp+A2] |
00AF16F4 | 03F0 | add esi,eax |
00AF16F6 | 0FB78424 A0000000 | movzx eax,word ptr ss:[esp+A0] |
00AF16FE | 03F0 | add esi,eax |
00AF1700 | C1EE 02 | shr esi,2 |
00AF1703 | 0F1F40 00 | nop dword ptr ds:[eax],eax |
00AF1707 | 66:0F1F8400 00000000 | nop word ptr ds:[eax+eax],ax |
00AF1710 | 0FB78454 A2000000 | movzx eax,word ptr ss:[esp+edx*2+A2] |
00AF1718 | 0FB78C54 A0000000 | movzx ecx,word ptr ss:[esp+edx*2+A0] |
00AF1720 | 03C8 | add ecx,eax |
00AF1722 | 0FB78454 A6000000 | movzx eax,word ptr ss:[esp+edx*2+A6] |
00AF172A | 03C8 | add ecx,eax |
00AF172C | 0FB78454 A4000000 | movzx eax,word ptr ss:[esp+edx*2+A4] |
00AF1734 | 03C8 | add ecx,eax |
00AF1736 | C1E9 02 | shr ecx,2 |
00AF1739 | 3BF1 | cmp esi,ecx |
00AF173B | 75 27 | jne kgenme.AF1764 |
00AF173D | 83C2 05 | add edx,5 |
00AF1740 | 3BD7 | cmp edx,edi |
00AF1742 | 7E CC | jle kgenme.AF1710 |
00AF1744 | 6A 40 | push 40 |
00AF1746 | 68 A433AF00 | push kgenme.AF33A4 | AF33A4:L"Success!"
00AF174B | 68 B833AF00 | push kgenme.AF33B8 | AF33B8:L"Registration successful!"
00AF1750 | FF7424 1C | push dword ptr ss:[esp+1C] |
00AF1754 | FF15 9030AF00 | call dword ptr ds:[<&MessageBoxW>] |
00AF175A | 6A 00 | push 0 |
00AF175C | FF15 D830AF00 | call dword ptr ds:[<&PostQuitMessage>] |
00AF1762 | EB 16 | jmp kgenme.AF177A |
00AF1764 | 6A 10 | push 10 |
00AF1766 | 68 5833AF00 | push kgenme.AF3358 | AF3358:L"Error!"
00AF176B | 68 6833AF00 | push kgenme.AF3368 | AF3368:L"You've entered invalid data!"
Concentriamoci sulla prima parte:
Codice:
00AF16D5 | 0FB78424 A4000000 | movzx eax,word ptr ss:[esp+A4] |
00AF16DD | BA 05000000 | mov edx,5 |
00AF16E2 | 0FB7B424 A6000000 | movzx esi,word ptr ss:[esp+A6] |
00AF16EA | 03F0 | add esi,eax |
00AF16EC | 0FB78424 A2000000 | movzx eax,word ptr ss:[esp+A2] |
00AF16F4 | 03F0 | add esi,eax |
00AF16F6 | 0FB78424 A0000000 | movzx eax,word ptr ss:[esp+A0] |
00AF16FE | 03F0 | add esi,eax |
00AF1700 | C1EE 02 | shr esi,2 |
SS:[ESP+A0] è il nostro seriale che c'è sullo stack, è la prima posizione. Lo riporto per comodità
HYLK-XV6K-NPHP-I8NN
. Quindi il primo carattere che viene preso è 'L', seguito da 'K'. I due caratteri vengono sommati, e il risultato è in ESI.Viene fatta la medesima cosa con 'Y' e 'H'. Fatto ciò viene fatto lo shift di 2bit verso destra, quindi una divisione per 4.
Proseguiamo con il secondo blocco:
Codice:
00AF1710 | 0FB78454 A2000000 | movzx eax,word ptr ss:[esp+edx*2+A2] |
00AF1718 | 0FB78C54 A0000000 | movzx ecx,word ptr ss:[esp+edx*2+A0] |
00AF1720 | 03C8 | add ecx,eax |
00AF1722 | 0FB78454 A6000000 | movzx eax,word ptr ss:[esp+edx*2+A6] |
00AF172A | 03C8 | add ecx,eax |
00AF172C | 0FB78454 A4000000 | movzx eax,word ptr ss:[esp+edx*2+A4] |
00AF1734 | 03C8 | add ecx,eax |
00AF1736 | C1E9 02 | shr ecx,2 |
EDX veniva inizializzato nel blocco precedente a 5, ora sappiamo perchè. Vengono saltati i primi 5 caratteri; ovviamente, trattandosi di 2-byte per carattere, viene moltiplicato per 2 (ESI+EDX*2+A2 e gli altri). L'operazione è del tutto analoga alla precedente. Vengono sommati tra loro i caratteri del secondo chunk del seriale. Il risultato è ancora diviso per 4.
Il controllo procede al chunk successivo; quando ESI (primo chunk) è diverso da ECX (quello corrente) la verifica termina, e viene restituito "false", non superando il check sulla password.
A questo punto ho aggiornato il codice, e la versione finale è quella di seguito:
C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#define USER_LEN 9
#define PWD_LEN 19
const char alphabet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int check_value(char *key, int len)
{
int i = -1;
int sum = 0;
while(i++ < len)
sum += (key[i] * (i+1));
return sum & 0xFF;
}
bool is_valid(char *key, int len) {
int esi = (key[0]+key[1]+key[2]+key[3]) >> 2;
return (esi == (key[5]+key[6]+key[7]+key[8]) >> 2) &&
(esi == (key[10]+key[11]+key[12]+key[13]) >> 2) &&
(esi == (key[15]+key[16]+key[17]+key[18]) >> 2);
}
int gen_password(char *key)
{
do {
for(int i=0; i<PWD_LEN; i++)
key[i] = alphabet[rand() % 0x24]; // numbers and A-Z chars
key[4] = '-';
key[9] = '-';
key[14] = '-';
} while(!is_valid(key, PWD_LEN));
return check_value(key, PWD_LEN);
}
int gen_username(char *user, int *len, int pwd_n) {
while(true) {
*len = rand() % 4 + 4;
for(int i=0; i<*len; i++) {
user[i] = alphabet[rand() % 0x3e]; // all chars
}
user[*len] = 0;
int user_n = check_value(user, *len);
if(user_n == pwd_n) return user_n;
}
return -1; // never reached (I hope :P )
}
int main() {
srand(time(NULL));
int userlen = 0;
char username[USER_LEN] = {'\0'};
char password[PWD_LEN] = {'\0'};
int pwd_n = gen_password(password);
int usr_n = gen_username(username, &userlen, pwd_n);
printf("Username: %s\tUser value: %X\n", username, usr_n);
printf("Password: %s\tPwd value: %X\n", password, pwd_n);
return 0;
}
Questi sono alcuni seriali generati:
Codice:
Username: z2l4 User value: F2
Password: 3VM1-ZB09-8KK6-4O0S Pwd value: F2
Username: ttrL5Z User value: 7
Password: Q8JG-FFV9-M9Z8-Y1DK Pwd value: 7
Username: 9gVrr User value: B
Password: LJAT-1JUY-PIFI-PJ8Y Pwd value: B
Username: d0TdLuh User value: 62
Password: C9EJ-XG46-IE1K-MN59 Pwd value: 62
E questi sono gli screenshot dopo aver inserito alcuni dei codici mostrati qui sopra:
5. Conclusione
Anche questo articolo è giunto al termine! Spero sia stato interessante. Se avete domande, come sempre... ;)