Winamp 5.04
simple cracking, advanced keygenning [sha-1, base32 e altre meraviglie].

Data

by bender0

 

gg/mese/aaaa

UIC's Home Page

Published by Quequero

"?"

Bender stavolta complimenti, hai fatto un tutorial veramente completo e chiaro, bravo davvero!!!

"!"

....

E-mail: [email protected]

....

Difficoltà

( )NewBies ( )Intermedio (X)Avanzato ( )Master

 

studiamoci un bello schema protettivo made in Nullsoft.


Winamp 5.04
simple cracking, advanced keygenning [sha-1, base32 e altre meraviglie].
Written by bender0

Introduzione

il buon vecchio winamp, ultima sfavillante versione. come saprete le ultime features inserite in winamp hanno impegnato i programmatori parecchio, quindi per sfruttare le potenzialità aggiunte - cd ripping\burning, chi usa winamp per queste cose? - bisogna sborsare una quindicina di euro per comprarsi un serial, scaricarsi un crack\keygen\serial (non si fanno queste cose...) o rimboccarci le maniche e lavorare di disassembler. a voi la scelta ;)
chiaramente non ho reversato winamp per registrarlo, ma per vedere che protezione potevano costruire dei programmatori a cui non manca l'inventiva.

Tools usati

IDA pro [e che altro per analizzare un'algoritmo?]
Ollydbg 1.10 [indispensabile in coppia con IDA]
PeID [assicuratevi di avere il plugin KANAL (Krypto ANALyzer)]
RFC3548: US Secure Hash Algorithm 1 (SHA1) [rfc, documento informativo essenziale]
RFC3174: The Base16, Base32, and Base64 Data Encodings [altra rfc]

URL o FTP del programma

h**p://www.winamp.com

Notizie sul programma

sapete cos'è winamp vero?

Essay

come al solito il nostro target (winamp.exe) si farà un bel giro sul PeID, qualcosa salta sempre fuori. "Microsoft Visual C++ 6.0", ok non è packato, ed è compilato con il vc++6, lo stesso che uso io su win. è ora di usare KANAL. ecco il responso:
BASE64 table
CRC32
SHA1 [Compress]
ehm... sha1? winamp non è il più semplice dei programmi, e posso capire l'encoding in base64 (magari per gli url) e il crc, ma se c'è davvero implementata la sha1 qui dentro, ha sicuramente a che fare con l'algoritmo che convalida i serial.

ecco il menù del giorno ;)

Attacchiamo Winamp 5.04:
1.simple cracking
2.advanced keygenning


parte prima
simple cracking
"praesidium dirumpere"
~

è conveniente usare il debugger per questo tipo di attacco. ricordatevi di settare l'opzione "allow multiple instances" nei settings generali del winamp, se volete continuare ad usarlo mentre ci lavorate. fatto questo, lanciamo winamp e sotto "help"->"register winamp pro" scriviamo i due soliti valori fittizi; notiamo che nel box di registrazione già ci dice che aspetto dovrebbe avere il serial, ovvero XXXXX-XXXXX-XXXXX-XXXXX, dove per ogni X abbiamo un numero\lettera, in un range non meglio specificato (si suppone A->Z, 0->9). ma ora il nostro obiettivo è di far accettare qualunque serial, al resto ci pensiamo nella seconda parte. notiamo anche che premendo ok ci poppa una carina msgbox. se non compare, avete azzeccato il serial, quindi cambiatelo e riprovate... armiamoci di zen e breakkiamo su MessageBoxA.

0042C8DC CALL <winamp.lavora_con_il_registro_44419C>
0042C8E1 ADD ESP,0C
0042C8E4 CALL <winamp.routine_interessante_4443B4>
0042C8E9 CMP DWORD PTR DS:[46A634],EBX
0042C8EF JE SHORT winamp.0042C8F7
0042C8F1 PUSH ESI
0042C8F2 JMP winamp.0042C836
0042C8F7 LEA EAX,DWORD PTR SS:[EBP-20]
0042C8FA PUSH EAX
0042C8FB LEA EAX,DWORD PTR SS:[EBP-240]
0042C901 PUSH EAX
0042C902 PUSH ESI
0042C903 CALL <winamp.lavora_con_il_registro_44419C>
0042C908 ADD ESP,0C
0042C90B CALL <winamp.routine_interessante_4443B4>
0042C910 PUSH 30
0042C912 PUSH winamp.0045BCA8 ; "Winamp Pro Registration"
0042C917 PUSH winamp.0045BC8C ; "Invalid registration key"
0042C91C PUSH DWORD PTR SS:[EBP+8]
0042C91F CALL DWORD PTR DS:[<&USER32.MessageBoxA>]
0042C925 JMP winamp.0042C7FC

ho rinominato due calls riportando comunque gli offset. la prima si occupa di accedere al registro, e lavora sui due valori regname e regkey, creati da winamp sotto HKLM\Software\Nullsoft\winamp; come potete vedere il serial non viene riposto in chiaro nel registro. ma penseremo dopo a questo, ora concentriamoci sul flow: il salto evidenziato decide se quella messagebox deve comparire o meno; una call a una routine interessante (con name e serial come argomenti vi verrà un sospetto, o no ;) e un cmp con una dword a [46A634] lo precedono. entriamo nella call interessante. eccone l'ultimo pezzo, ho rinominato un'altra call.

004443F4 CALL <winamp.check_444413>
004443F9 ADD ESP,10
004443FC TEST EAX,EAX
004443FE JNZ SHORT winamp.0044440A
00444400 MOV EAX,DWORD PTR SS:[EBP-4]
00444403 MOV DWORD PTR DS:[46A634],EAX
00444408 LEAVE
00444409 RET
0044440A AND DWORD PTR DS:[46A634],0
00444411 LEAVE
00444412 RET

ecco di nuovo quell'indirizzo, a quanto pare la dword a [46A634] è una flag; globale o locale? non lo sappiamo ancora. a quanto pare il valore di ritorno che segnala un serial corretto è zero, e la dword a [ebp-4] finisce all'indirizzo incriminato; nel caso contrario la flag viene azzerata. se riavviate winamp e mettete un memory breakpoint all'indirizzo [46A634] clickando sul menu help vedrete come il valore sia controllato per decidere se togliere o meno il menù di registrazione, ecco confermati i nostri sospetti: ogni parte a pagamento di winamp (provate anche con il ripping o con il burning) decide di partire o meno a seconda di cosa contiene quel flag, zero o nonzero.

riguardiamo il secondo pezzo di codice che ho riportato: lo spazio tra 4443FC e 444403 sembra ideale per settare la flag a nonzero a ogni check. possiamo quindi sostituire quelle istruzioni con un "mov dword ptr ds:[46A634],1" e riempire il resto con una coppia di innocui "inc eax"-"dec eax". salviamo il tutto su un'altro file e lanciamolo; che ci crediate o meno, era veramente così facile.




parte seconda
advanced keygenning
"praesidii imago esse"
~

reversing

sappiamo tutti che la creazione di un keygen è il metodo di attacco più pulito ed elegante, nonchè il meno rischioso (in termini di check nascosti, controlli di integrità etc); d'altronde nella maggior parte dei casi è anche la tecnica che richiede più tempo e concentrazione, sommate a un buon occhio e una certa esperienza. l'algoritmo usato da winamp per validare i serial non è dei più semplici, ma ci aiuta il fatto che si appoggia su altri algoritmi ben conosciuti.

nei link in cima ho aggiunto due rfc, la prima parla della codificazione in base16, 32 e 64; la seconda parla del secure hash algorithm, o sha-1. ho aggiunto queste referenze perchè la protezione di winamp si avvalora di versioni modificate di questi algoritmi (base32 e sha-1).

farò un analisi passo passo dell'algoritmo senza riportare il disassemblato, quindi conviene avere una copia del programma per seguire; al limite potete riferirvi al listato completo (commentato e analizzato) che ho aggiunto a fine essay. per smontare questa routine ho usato ida e ollydbg in combinazione, il primo semplicemente perchè è il miglior disassembler esistente, il secondo perchè ha delle funzioni che aiutano parecchio nell'analisi e perchè spesso vedere il codice eseguito aiuta a capirlo in breve tempo.



step#1.primo loop, "base32" [00444413->004444FF].

in questo frammento di routine (oltre alle solite istruzioni di inizio funzione) troviamo un loop piuttosto imponente, che si può riassumere così:

.prendi un carattere dal serial
.se è un numero o una lettera processalo, altrimenti ignoralo
.riponi il carattere processato in un buffer
.passa al prossimo carattere e ricomincia

a fine loop è importante fare bene attenzione a come venga storato il carattere processato. il contatore dei caratteri processati aumenta di cinque in cinque, e ogni output del loop è un numero da cinque byte; questi cinque bytes vengono via via incatenati nel buffer di destinazione. insomma, base32.

l'algoritmo di encoding base32 permette di convertire una stringa di caratteri appartenenti a un subset dell'ascii (A->Z,2->7) in una stringa di bit in cui ogni carattere è rappresentato da 5 bit, secondo una tabella di encoding (vedi rfc). la versione implementata in winamp si differenzia da quella standard (che scartava 0,1,8 e 9) perchè ammette in quattro casi che caratteri diversi producano lo stesso valore base32, ovvero: 0 e O danno entrambi 0, I, L e 1 danno 1, 6 e G danno 6... "h4x0r speech"? mah. ad ogni modo questo fa sì che il prog accetti diversi serial per lo stesso nome ;)

finito il loop, ricordandoci che il serial dovrebbe essere nella forma XXXXX-XXXXX-XXXXX-XXXXX e che i trattini sono scartati (infatti non serve neanche metterli :) ci aspetteremo 20*5 = 100 bit, ovvero 12 byte e 4 bit (un nibble). e infatti quel nibble lo troviamo a fine buffer, ma a quanto pare non dipende dal name bensì dal resto del serial. winamp xora il valore 0Bh con tutti i char del serial una volta codificati, apparte l'ultimo. il serial non viene accettato se i quattro bit meno significativi del risultato di questi xor sono diversi dal nibble finale; e così l'ultimo carattere del serial lo calcoleremo in base agli altri.

step#2.secondo loop, "one-way?" [00444504->00444543].

questo loop genera due numeri (uno da un byte e uno da due byte) basandosi sull'output del primo loop, cioè il serial codificato. niente di speciale, se non fosse che questi numeri sono uniti al nome nel calcolo della hash, e questo sembrerebbe rendere l'algoritmo one-way, ovvero non reversabile. king_KINK del gruppo CORE a quanto pare non si è accorto che l'algoritmo si poteva invece reversare perchè il suo keygen, che ho scaricato per sentire l'altra campana, fa un piccolo bruteforce (ma si, tiriamocela :).

ad ogni modo ragionando un pò si potrebbe ridurre il tempo di bruteforce del 98.44%, notando che il primo numero non può superare 3fh e il secondo non andrà mai oltre 0FFFh, reversare per credere ;)

osservando più attentamente la prima metà del loop ci accorgiamo infatti che viene effettuato un AND tra il byte encodato e 80h, ovvero viene controllato se il bit più alto è settato; il risultato (80h o 00h) viene shiftato a destra di due bit il primo giro, tre il secondo e così via fino a 7. quindi se tutti i bit alti fossero settati il risultato sarebbe 00111111, ovvero 3Fh.

nella seconda metà il byte viene invece shiftato a destra di sei bit, quindi vengono considerati i due bit più alti. poi il risultato viene shiftato a sinistra con i valori del contatore (0,2,4,6,8,10); nel caso peggiore otterremo quindi 0000111111111111 cioè 0FFFh, ed ecco svelato il mistero.

l'importante è ricordare che per generare questi tre byte vengono usati solo i bit più alti dei primi sei byte e i due bit più alti degli altri sei.

step#3.hash, "sha-1" [00444545->004445CE].

perchè proprio sha-1? la procedura sha1_init (00444608) inizializza un buffer con le cinque famose dwords di partenza dello sha1. questo è molto, ma non abbastanza; analizzando la procedura sha1 (0044464F) vediamo invece chiaramente che l'algoritmo qui implementato è proprio quello: i cinque cicli, le quattro dwords xorate tra di loro all'inizio del primo loop, certe sequenze di operazioni... possiamo stare certi che si tratti di sha-1.

nell'implementazione dello sha-1 che troviamo nella rfc viene utilizzata una struttura, la struttura SHA1Context, che contiene informazioni quali lunghezza del messaggio da cui trarre l'hash, il messaggio stesso (64 bytes), e l'hash una volta calcolato. questa struttura deve essere inizializzata con le cinque dwords di partenza, che vengono copiate nella struttura allo stesso indice dove poi troveremo l'hash. questo lo fa la funzione SHA1Reset; in winamp lo fa sha1_init. la struttura SHA1Context in winamp è leggermente diversa da quella della rfc perchè i membri sono ordinati diversamente e perchè la struttura stessa contiene il buffer di ottanta doublewords che viene utilizzato dopo nel calcolo dell'hash.

il messaggio da hashare non viene copiato direttamente nella struttura ma passato alla funzione SHA1Input, che si occupa di riempire il buffer e aumentare di conseguenza la lunghezza riportata nella struttura, e calcolare poi l'hash intermedio (cioè l'hash di quanto è stato inserito finora nel buffer) chiamando la funzione SHA1ProcessMessageBlock. tutto questo è svolto dalla funzione sha1 in winamp, che però non genera hash temporanei perchè controlla che il buffer di 64 bytes sia pieno prima di proseguire.

prima di calcolare l'hash del messaggio questo deve essere paddato, ovvero tutti i byte non occupati dai dati passati a SHA1Input sui 64 totali del message block devono essere "riempiti" secondo regole ben precise: il primo bit successivo al messaggio viene settato; tutto il resto viene azzerato apparte gli ultimi otto byte; negli ultimi otto byte è riposta la rappresentazione in quadword della lunghezza totale del messaggio. dato che winamp non segue queste regole alla lettera, il padding lo dovremo fare in casa.

fatte queste premesse, vediamo in concreto cosa fa winamp:

° chiama sha1_init passandogli un certo indirizzo, è il puntatore alla struttura SHA1Context (viene passato a TUTTE le funzioni che riguardano lo sha-1); dopo la chiamata potete vedere le dwords di inizializzazione all'inizio della struttura.

° chiama sha1_A passandogli il nostro name. sha1_A è solo un wrap di sha1 che scarta alcuni caratteri non validi dal nome, lo passa a sha1 e aggiunge lo zero terminatore. potete vedere il vostro nome nel buffer della struttura SHA1Context (i byte delle dwords sono a rovescio, contrariamente a quanto accade con la versione rfc dello sha-1).

° chiama tre volte sha1 (tre parametri: puntatore alla struttura SHA1Context, buffer di dati da inserire nella struttura, lunghezza del buffer) passandogli prima il primo dei due numeri generati nello step#2 (di un byte), poi il byte basso del secondo e infine il byte alto del secondo.

° chiama di nuovo sha1 passandogli 16 bytes fissi, i seguenti:
0xE7,0x4A,0x67,0x3C,0x44,0x05,0x0B,0x39,0xB9,0xD2,0xEB,0x63,0xCC,0xFD,0x07,0x69

° chiama sha1_B passandogli un indirizzo. sha1_B ha l'incarico di paddare il messaggio: aggiunge 80h dopo il messaggio (cioè setta il primo byte dopo il messaggio) e con il loop seguente azzera tutto il resto. fin qui tutto normale, ora dovrebbe aggiungere la lunghezza del messaggio a fine buffer con l'ultima chiamata a sha1. e invece ci posiziona due bytes che sono apparentemente senza senso, ma andando a vedere da dove saltano fuori si scopre che non sono nient'altro che la lunghezza del messaggio (prima del padding) moltiplicata per 25h. così facendo la lunghezza del messaggio arriva a 64 byte e sha1 calcola l'hash. il loop successivo si occupa di copiare i primi dodici bytes dell'hash appena generato nel buffer passato come argomento a sha1_B; poi viene chiamata sha1_init per resettare la struttura.



step#4.terzo loop, "check" [004445D1->00444607].

il terzo loop non fa altro che xorare byte a byte la hash calcolata nello step#3 con il serial codificato nello step#1. poi il risultato dello xor viene testato con 7Fh (01111111) per i primi sei byte e con 3Fh (00111111) per i secondi sei. se in uno di questi test la zero flag viene unsettata il serial non supererà il check; quindi per i primi sei byte lo xor deve dare 80h o 0, per i secondi C0h,80h,40h o 0. in soldoni questo significa che il contenuto dei bit più alti dei primi sei byte e dei due bit più alti dei secondi sei non viene neppure considerato, quindi le informazioni per generare i tre byte dello step#2 non influiscono sul controllo finale; quei tre byte sono praticamente stenografati nel serial. questo inoltre rende l'algoritmo perfettamente reversabile.

ma c'è dell'altro. secondo voi i programmatori hanno sprecato questo veicolo di informazioni? certamente no; infatti si può vedere che il primo numero generato (quello da un solo byte) va a finire all'indirizzo della flag che abbiamo crackato nella prima parte dell'essay. come sapete, se quella flag contiene zero il serial viene considerato non valido. e gli altri valori? scavando un pò si scopre che il programma decide (jnz a 0042FE04) se mostrare il messaggio "Valid 5.0 registration key" o "Valid > 5.0 registration key" a seconda che il valore della flag sia uno (5.0) o maggiore di uno (> 5.0). noi chiederemo all'utente che valore inserire nel serial come primo byte; non ci interessa l'informazione contenuta negli altri due, quindi passeremo zero per entrambi.


keygenning

abbiamo ora abbastanza info per scrivere il nostro keygen. prima vediamo quale procedura dovrà seguire, poi pasto direttamente il suo sorgente minuziosamente commentato.



° il nostro keygen dovrà innanzitutto raccogliere il nome e il valore da assegnare al byte che indica la versione del serial. poi sarà possibile calcolare gli ultimi due bytes del buffer, corrispondenti a 25h volte la lunghezza del messaggio: nel messaggio abbiamo il nome, il suo zero terminatore, i tre bytes dello step#2, i 16 bytes fissi; per un totale di 20 bytes più la lunghezza del nome, da moltiplicare per 25h.

° dobbiamo quindi dichiarare una struttura SHA1Context e riempirne il buffer assemblando tutti i pezzi che ho elencato nello step#3, farci il padding e appenderci gli ultimi due bytes che abbiamo calcolato prima. dato che per passare questo buffer abbiamo chiamato SHA1Input, ora l'hash è già calcolato.

° ora copiamo i primi 12 bytes dell'hash in un nostro buffer, rovesciando l'ordine dei byte nelle doublewords (questo perchè in winamp sono al contrario).

° ora azzeriamo tutti i bit più alti dei primi sei byte e i due bit più alti dei secondi sei; poi copiamo il numero inserito dall'utente nei bit più alti dei primi sei byte.

° calcoliamo l'ultimo byte xorando tutti gli altri con 0Bh, e aggiungiamolo a fine hash.

° consideriamo l'hash così ottenuto una stringa codificata in base32 e la decodifichiamo, con le regole di winamp, per ottenere una stringa di venti caratteri, inframmezzati da trattini (per farlo sembrare un vero serial).

° stampiamo il serial su schermo e lo copiamo negli appunti (così basta fare ctrl+v per pastarlo in winamp).



ecco tutto, ora pasto il sorgente (c++) del keygen così vediamo come riprodurre gli steps di qui sopra nella pratica. per compilarlo servono sha1.h, sha1.cpp (trovate i sorgenti nella rfc) e stdint.h. se non avete quest'ultimo ve lo potete fare a mano; il mio è così:

--- bender's stdint.h start ---
typedef unsigned long uint32_t;
typedef unsigned char uint8_t;
typedef unsigned short int_least16_t;
--- stdint.h end ---

basta sostituire la linea in sha1.h che recita
#include <stdint.h>
con
#include "stdint.h"
per fargli usare il nostro stdint.h; anche perchè con l'originale ho avuto non pochi problemi di compilazione (relativi ai typedef per gli int a 64 bit).


--- winamp504keygen.cpp start ---
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// windows.h per copiare il serial negli appunti
#include <windows.h>
// l'include per le funzioni dello sha1
#include "sha1.h"

// il buffer da passare a SHA1Input e alcune delle sue componenti
unsigned char msg[64];
unsigned char nums[3]={0,0,0};		// nums[0]: 0 = non valido; 1 = 5.0; >1 = > 5.0
unsigned char padbyte[1] = {0x80};	// dobbiamo paddare noi il messaggio
unsigned char chksum[2];			// qui metteremo 25h volte la lunghezza del messaggio
unsigned char bytes[16] = {			// i 16 bytes fissi
	0xE7,0x4A,0x67,0x3C,0x44,0x05,0x0B,0x39,
	0xB9,0xD2,0xEB,0x63,0xCC,0xFD,0x07,0x69
};


// la funzione decode accetta come argomenti l'hash una volta "elaborata"
// e un buffer di testo (24 bytes) per l'output. legge 5 byte e li converte
// in caratteri indicizzando la tabella qui sotto. è codata in modo
// orribile, lo so...
unsigned char base32table[] = "0123456789ABCDEFHJKMNPQRSTUVWXYZ";

void decode(unsigned char* hash,char* dest)
{
	dest[0] = base32table[ hash[0]&0x1F ];
	dest[1] = base32table[ hash[0]>>5 | (hash[1]&0x03)<<3 ];
	dest[2] = base32table[ (hash[1]&0x7F)>>2 ];
	dest[3] = base32table[ hash[1]>>7 | (hash[2]&0x0F)<<1 ];
	dest[4] = base32table[ hash[2]>>4 | (hash[3]&0x01)<<4 ];
	dest[5] = '-';
	dest[6] = base32table[ (hash[3]&0x3F)>>1 ];
	dest[7] = base32table[ hash[3]>>6 | (hash[4]&0x07)<<2 ];
	dest[8] = base32table[ hash[4]>>3 ];
	dest[9] = base32table[ hash[5]&0x1F ];
	dest[10] = base32table[ hash[5]>>5 | (hash[6]&0x03)<<3 ];
	dest[11] = '-';
	dest[12] = base32table[ (hash[6]&0x7F)>>2 ];
	dest[13] = base32table[ hash[6]>>7 | (hash[7]&0x0F)<<1 ];
	dest[14] = base32table[ hash[7]>>4 | (hash[8]&0x01)<<4 ];
	dest[15] = base32table[ (hash[8]&0x3F)>>1 ];
	dest[16] = base32table[ hash[8]>>6 | (hash[9]&0x07)<<2 ];
	dest[17] = '-';
	dest[18] = base32table[ hash[9]>>3 ];
	dest[19] = base32table[ hash[10]&0x1F ];
	dest[20] = base32table[ hash[10]>>5 | (hash[11]&0x03)<<3 ];
	dest[21] = base32table[ (hash[11]&0x7F)>>2 ];
	dest[22] = base32table[ hash[11]>>7 | (hash[12]&0x0F)<<1 ];
	dest[23] = 0;
}

// copytoclipboard copia il buffer di testo passato come parametro
// negli appunti, così lo possiamo pastare direttamente con ctrl+V
void copytoclipboard(char* text)
{
	if (!OpenClipboard(NULL)) return;
	EmptyClipboard();

	HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE,strlen(text)+1);
	if (hmem == NULL) {CloseClipboard(); return;} 

	char* strbuf = (char*) GlobalLock(hmem); 
	memcpy(strbuf, text, strlen(text)+1);
	GlobalUnlock(hmem); 

	SetClipboardData(CF_TEXT,hmem);
	CloseClipboard();
	GlobalFree(hmem);
}

void main()
{
	// un contatore
	int i;
	// due buffer di testo, uno per il name e uno per il numero
	unsigned char name[31];
	unsigned char num[5];
	// il numero verrà salvato qui
	unsigned char val;

	// raccolta dati
	printf("winamp 5.04 keygen\ncoded by bender0\n");
	printf("enter your name (no spaces, no dots): ");
	gets((char*)name);
	printf("enter a number between 1 and 63: ");
	gets((char*)num);
	val = atoi((char*)num);

	// se non è tra 0 e 0x3F cancelliamo il resto
	// se è 0 lo settiamo a 1, poi lo copiamo nel buffer
	val &= 0x3F;
	if (!val) val++;
	nums[0] = val;

	// calcoliamo i due byte finali con la lunghezza del nome
	int len = strlen((char*)name);
	int checksum = 0x25 * (20+len);
	// spostiamo i due byte nel buffer
	chksum[1] = checksum & 0xFF;
	chksum[0] = (checksum & 0xFF00) >> 8;

	// dichiariamo e inizializziamo la struttura
	SHA1Context ct;
	SHA1Reset(&ct);

	// riempiamo il buffer:
	for (i=0;i<64;i++) msg[i]=0;	// lo azzeriamo
	memcpy(msg,name,len+1);			// il nome
	memcpy(msg+len+1,nums,3);		// poi i tre byte
	memcpy(msg+len+4,bytes,16);		// i byte fissi
	memcpy(msg+len+20,padbyte,1);	// il byte per il padding
	memcpy(msg+62,chksum,2);		// i due byte calcolati prima
	// passiamo il buffer nella struttura e ci facciamo calcolare l'hash
	SHA1Input(&ct,msg,64);

	// un buffer per contenere l'hash e il byte aggiuntivo
	unsigned char hash[13];
	// copiamo 12 byte dell'hash dalla struttura al nostro buffer
	// la hash è il primo membro della struttura, quindi lo uso
	// come pointer
	unsigned char* phash = (unsigned char*)&ct;
	// copiamo rovesciando l'ordine dei bytes nelle doublewords
	for(i=0;i<12;i++)
	{
		hash[i] = phash[(i-(i%4))+(3-(i%4))];
	}

	// eliminiamo tutte le informazioni extra, cioè il bit più alto
	// dei primi 6 bytes e i due bit più alti dei secondi 6
	for(i=0;i<6;i++)
	{
		hash[i] &= 0x7F;
	}
	for(i=6;i<12;i++)
	{
		hash[i] &= 0x3F;
	}

	// copiamo bit a bit il numero immesso dall'utente nei bit alti
	// dei primi 6 bytes
	if (nums[0] & 0x20) hash[0] |= 0x80;
	if (nums[0] & 0x10) hash[1] |= 0x80;
	if (nums[0] & 0x08) hash[2] |= 0x80;
	if (nums[0] & 0x04) hash[3] |= 0x80;
	if (nums[0] & 0x02) hash[4] |= 0x80;
	if (nums[0] & 0x01) hash[5] |= 0x80;

	// calcoliamo il byte extra xorando tutti i byte con 0Bh
	unsigned char tmp=0x0B;
	for(i=0;i<12;i++)
	{
		tmp ^= hash[i];
	}
	// spostiamolo alla fine del buffer
	hash[i] = tmp;

	// creiamo un buffer per il serial e lo facciamo riempire a decode
	unsigned char serial[24];
	decode(hash,(char*)serial);

	// et voilà!
	printf("serial: %s\n",serial);

	// copiamolo anche negli appunti informando l'utente...
	copytoclipboard((char*)serial);
	printf("the serial has been copied to the clipboard, paste it with ctrl+V.\n");

	// bye bye
	system("pause");
}
--- winamp504keygen.cpp end---


riporto qui l'analisi della routine che valida i serial, copiandola direttamente da ida.
la pasto solo per referenza, leggere le parti evidenziate è sufficiente; ma se volete
vedere più da vicino cosa fa ogni istruzione della procedura potete leggere anche il resto.


.text:00444413 ***************************************************************************** .text:00444413 check: .text:00444413 questa routine valida i serial di winamp 5.04. .text:00444413 ***************************************************************************** .text:00444413 check proc near ; CODE XREF: sub_4443B4+40p .text:00444413 .text:00444413 SHA1CONTEXT = byte ptr -17Ch .text:00444413 ADDR_HASH = byte ptr -20h .text:00444413 ENCODED_PTR = byte ptr -14h .text:00444413 COUNTER = dword ptr -4 .text:00444413 NAME = dword ptr 8 .text:00444413 SERIAL = dword ptr 0Ch .text:00444413 NUMGEN = dword ptr 10h .text:00444413 SHIFTCOUNTER = dword ptr 14h .text:00444413 .text:00444413 push ebp .text:00444414 mov ebp, esp .text:00444416 sub esp, 17Ch ; crea un frame piuttosto grande (per una struttura sha1context) .text:0044441C mov eax, [ebp+NUMGEN] ; smista i parametri .text:0044441F push ebx .text:00444420 mov ebx, [ebp+SHIFTCOUNTER] .text:00444423 push esi .text:00444424 push edi .text:00444425 xor edx, edx ; azzera edx .text:00444427 push 0Bh .text:00444429 mov [eax], edx .text:0044442B mov [ebx], edx .text:0044442D pop edi ; edi = 0Bh .text:0044442E mov [ebp+SHIFTCOUNTER], edx ; azzera una variabile .text:00444431 xor esi, esi ; azzera anche esi .text:00444433 mov [ebp+COUNTER], edx ; e il contatore .text:00444436 .text:00444436 ***************************************************************************** .text:00444436 PRIMO LOOP: .text:00444436 il seguente loop codifica il serial usando un algoritmo base32 modificato, .text:00444436 che risponde alla tabella riportata a fine loop. .text:00444436 ***************************************************************************** .text:00444436 .text:00444436 primo_loop: ; CODE XREF: check+74j .text:00444436 ; check+BBj .text:00444436 mov eax, [ebp+SERIAL] ; -= PRIMO LOOP =- .text:00444439 inc [ebp+SERIAL] .text:0044443C movsx eax, byte ptr [eax] .text:0044443F cmp eax, 'a' .text:00444442 jl short non_minuscola .text:00444444 cmp eax, 'z' .text:00444447 jg short non_minuscola .text:00444449 sub eax, 20h ; minuscola? falla maiuscola e continua .text:0044444C .text:0044444C non_minuscola: ; CODE XREF: check+2Fj .text:0044444C ; check+34j .text:0044444C cmp eax, 'G' .text:0044444F jnz short non_G .text:00444451 push 36h ; G? 6h .text:00444453 jmp short poppa_e_meno30h .text:00444455 ; --------------------------------------------------------------------------- .text:00444455 .text:00444455 non_G: ; CODE XREF: check+3Cj .text:00444455 cmp eax, 'I' .text:00444458 jz short I .text:0044445A cmp eax, 'L' .text:0044445D jnz short non_L .text:0044445F .text:0044445F I: ; CODE XREF: check+45j .text:0044445F push 31h ; I,L? 1h .text:00444461 .text:00444461 poppa_e_meno30h: ; CODE XREF: check+40j .text:00444461 pop eax .text:00444462 .text:00444462 meno30h: ; CODE XREF: check+64j .text:00444462 sub eax, 30h .text:00444465 jmp short fatto .text:00444467 ; --------------------------------------------------------------------------- .text:00444467 .text:00444467 non_L: ; CODE XREF: check+4Aj .text:00444467 cmp eax, 'O' .text:0044446A jnz short non_O .text:0044446C push 30h ; O? 0h .text:0044446E pop eax .text:0044446F .text:0044446F non_O: ; CODE XREF: check+57j .text:0044446F cmp eax, '0' .text:00444472 jl short meno_di_zero .text:00444474 cmp eax, '9' .text:00444477 jle short meno30h ; numero? togli 30h .text:00444479 .text:00444479 meno_di_zero: ; CODE XREF: check+5Fj .text:00444479 cmp eax, 'A' .text:0044447C jl short meno_di_A .text:0044447E cmp eax, 'Z' .text:00444481 jle short maiuscola .text:00444483 .text:00444483 meno_di_A: ; CODE XREF: check+69j .text:00444483 test eax, eax ; la stringa ha meno di 20 caratteri validi? .text:00444485 jz short esce_meno1 ; errore! .text:00444487 jmp short primo_loop ; eventuali altri caratteri sono ignorati .text:00444489 ; --------------------------------------------------------------------------- .text:00444489 .text:00444489 maiuscola: ; CODE XREF: check+6Ej .text:00444489 cmp eax, 'O' ; O era 0 .text:0044448C jle short minug_O .text:0044448E dec eax ; più di O? dec .text:0044448F .text:0044448F minug_O: ; CODE XREF: check+79j .text:0044448F cmp eax, 'L' ; L era 1h .text:00444492 jle short minug_L .text:00444494 dec eax ; M,N? dec .text:00444495 .text:00444495 minug_L: ; CODE XREF: check+7Fj .text:00444495 cmp eax, 'I' ; I era 1h .text:00444498 jle short minug_I .text:0044449A dec eax ; J,K? dec .text:0044449B .text:0044449B minug_I: ; CODE XREF: check+85j .text:0044449B cmp eax, 'G' ; G era 6h .text:0044449E jle short minug_G .text:004444A0 dec eax ; H? dec .text:004444A1 .text:004444A1 minug_G: ; CODE XREF: check+8Bj .text:004444A1 sub eax, 37h ; alla fine toglie 37h .text:004444A4 .text:004444A4 fatto: ; CODE XREF: check+52j .text:004444A4 mov ecx, [ebp+SHIFTCOUNTER] .text:004444A7 add [ebp+SHIFTCOUNTER], 5 ; inc di 5 lo shiftcounter .text:004444AB shl eax, cl ; sposta nella giusta posizione .text:004444AD or edx, eax ; e incastra in edx .text:004444AF cmp [ebp+SHIFTCOUNTER], 8 .text:004444B3 jle short shctr_minug8 ; ci sono 8 bit di dati codificati pronti? .text:004444B5 sub [ebp+SHIFTCOUNTER], 8 ; se sì, togli 8 al counter .text:004444B9 mov [ebp+esi+ENCODED_PTR], dl ; e salva il valore; esi conta il char encodati .text:004444BD movzx eax, dl ; prende l'ultimo byte fatto... .text:004444C0 xor edi, eax ; e lo xora con edi, che in origine è 0Bh .text:004444C2 inc esi ; incrementa il puntatore ai char encodati .text:004444C3 sar edx, 8 ; aggiusta edx .text:004444C6 .text:004444C6 shctr_minug8: ; CODE XREF: check+A0j .text:004444C6 add [ebp+COUNTER], 5 ; via così fino al 20o carattere valido .text:004444CA cmp [ebp+COUNTER], 64h .text:004444CE jl primo_loop .text:004444CE - .text:004444CE si ricava la seguente tabella di encoding\decoding: .text:004444CE (cfr. la tabella della base32 nella rfc) .text:004444CE - .text:004444CE Value Encoding Value Encoding Value Encoding Value Encoding .text:004444CE - .text:004444CE 0 0,O 9 9 18 K 27 V .text:004444CE 1 1,L,I 10 A 19 M 28 W .text:004444CE 2 2 11 B 20 N 29 X .text:004444CE 3 3 12 C 21 P 30 Y .text:004444CE 4 4 13 D 22 Q 31 Z .text:004444CE 5 5 14 E 23 R .text:004444CE 6 6,G 15 F 24 S .text:004444CE 7 7 16 H 25 T .text:004444CE 8 8 17 J 26 U .text:004444CE - .text:004444D4 cmp [ebp+SHIFTCOUNTER], 0 ; il contatore dello shift non deve essere zero. .text:004444D4 ; se il serial è del formato giusto qui sarà sempre 4, .text:004444D4 ; ad indicare il nibble che avanza dall'encoding. .text:004444D8 jle pusha_meno2_e_esce .text:004444DE cmp esi, 0Dh ; devono risultare esattamente 12 bytes codificati .text:004444E1 jge pusha_meno2_e_esce .text:004444E7 mov [ebp+esi+ENCODED_PTR], dl ; aggiunge alla fine il nibble "di troppo" .text:004444EB and edx, 0Fh ; poi lo riprende .text:004444EE and edi, 0Fh ; assieme ai primi 4 bit dell'ultimo char encodato .text:004444F1 xor edx, edi ; li confronta... .text:004444F3 jz short prosegui ; devono essere uguali .text:004444F5 push -3 ; altrimenti... errore .text:004444F7 jmp poppa_e_esce .text:004444F7 - .text:004444F7 dalla precedente porzione di codice si capisce che generando il serial codificato dovremmo .text:004444F7 calcolare parte dell'ultimo carattere (i primi 4 bit su 5) bit in base allo xor di prima. .text:004444F7 - .text:004444FC ; --------------------------------------------------------------------------- .text:004444FC .text:004444FC esce_meno1: ; CODE XREF: check+72j .text:004444FC or eax, -1 .text:004444FF jmp esce .text:00444504 ; --------------------------------------------------------------------------- .text:00444504 ***************************************************************************** .text:00444504 SECONDO LOOP: .text:00444504 qui vengono generati due numeri a partire dal serial encodato. .text:00444504 questi due numeri sono poi salvati ai due indirizzi passati come parametri. .text:00444504 ***************************************************************************** .text:00444504 .text:00444504 prosegui: ; CODE XREF: check+E0j .text:00444504 push -0Ch .text:00444506 lea eax, [ebp+ENCODED_PTR] ; eax punta al serial encodato .text:00444509 pop edi ; edi = -12, è il counter .text:0044450A xor esi, esi ; esi fa da counter dei byte .text:0044450C push 2 .text:0044450E pop edx ; edx = 2 .text:0044450F sub edx, eax ; edx = 2-pointer .text:00444511 .text:00444511 secondo_loop: ; CODE XREF: check+130j .text:00444511 test edi, edi ; -= SECONDO LOOP =- .text:00444513 jge short edi_positivo ; se edi è arrivato nei positivi .text:00444515 - .text:00444515 questa parte usa i primi 6 byte del serial encodato .text:00444515 - .text:00444515 lea eax, [ebp+esi+ENCODED_PTR] ; carica l'indirizzo di un char encodato in eax .text:00444519 mov cl, al .text:0044451B mov al, [eax] ; carica un char in al .text:0044451D add cl, dl .text:0044451F and eax, 80h ; and con 80h .text:00444524 shr eax, cl ; shift a destra (con i valori: 2,3,4,5,6,7) .text:00444526 mov ecx, eax .text:00444528 mov eax, [ebp+NUMGEN] .text:0044452B or [eax], ecx ; or con una dword puntata da un argomento .text:0044452D jmp short continua .text:0044452F ; --------------------------------------------------------------------------- .text:0044452F - .text:0044452F questa usa gli altri 6 byte. .text:0044452F - .text:0044452F .text:0044452F edi_positivo: ; CODE XREF: check+100j .text:0044452F movzx eax, [ebp+esi+ENCODED_PTR] ; byte encodato in eax .text:00444534 shr eax, 6 ; shift di 6 a destra .text:00444537 mov ecx, edi ; in ecx il contatore due a due (valori: 0,2,4,6,8,10) .text:00444539 shl eax, cl ; shift a sinistra con i valori del contatore .text:0044453B or [ebx], eax ; or con un'altra dword puntata da un argomento .text:0044453D .text:0044453D continua: ; CODE XREF: check+11Aj .text:0044453D inc esi ; esi incrementato .text:0044453E inc edi ; edi va di due in due .text:0044453F inc edi .text:00444540 cmp edi, 0Ch ; siamo alla fine? .text:00444543 jl short secondo_loop ; loop .text:00444545 ***************************************************************************** .text:00444545 HASH: .text:00444545 qui viene calcolato un hash a partire dal name e dai due numeri generati. .text:00444545 ***************************************************************************** .text:00444545 lea eax, [ebp+SHA1CONTEXT] .text:0044454B push eax ; pusha il context .text:0044454C call sha1_init .text:00444551 push [ebp+NAME] ; pusha il name .text:00444554 lea eax, [ebp+SHA1CONTEXT] .text:0044455A push eax ; pusha il context .text:0044455B call sha1_A .text:00444560 mov eax, [ebp+NUMGEN] .text:00444563 push 1 ; pusha n = 1 .text:00444565 mov al, [eax] ; carica il primo numero generato .text:00444567 mov [ebp+0Fh], al ; lo posiziona .text:0044456A mov al, [ebx] ; carica il secondo .text:0044456C mov [ebp+13h], al ; lo posiziona .text:0044456F mov eax, [ebx] .text:00444571 sar eax, 8 ; carica il byte alto del secondo numero generato .text:00444574 mov [ebp+17h], al ; lo posiziona .text:00444577 lea eax, [ebp+0Fh] .text:0044457A push eax ; pusha l'indirizzo del buffer (ebp+Fh) .text:0044457B lea eax, [ebp+SHA1CONTEXT] .text:00444581 push eax ; pusha il context .text:00444582 call sha1 .text:00444587 lea eax, [ebp+13h] .text:0044458A push 1 ; pusha n = 1 .text:0044458C push eax ; pusha l'indirizzo del buffer (ebp+13h) .text:0044458D lea eax, [ebp+SHA1CONTEXT] .text:00444593 push eax ; pusha il context .text:00444594 call sha1 .text:00444599 lea eax, [ebp+17h] .text:0044459C push 1 ; pusha n = 1 .text:0044459E push eax ; pusha l'indirizzo del buffer (ebp+17h) .text:0044459F lea eax, [ebp+SHA1CONTEXT] .text:004445A5 push eax ; pusha il context .text:004445A6 call sha1 .text:004445AB push 10h ; pusha n = 10h .text:004445AD lea eax, [ebp+SHA1CONTEXT] .text:004445B3 push offset unk_45A4E8 ; pusha un indirizzo fisso per il buffer .text:004445B8 push eax ; pusha il context .text:004445B9 call sha1 .text:004445BE lea eax, [ebp+ADDR_HASH] .text:004445C1 push eax ; pusha l'indirizzo finale per l'hash .text:004445C2 lea eax, [ebp+SHA1CONTEXT] .text:004445C8 push eax ; pusha il context .text:004445C9 call sha1_B .text:004445CE add esp, 44h .text:004445D1 ***************************************************************************** .text:004445D1 TERZO LOOP: .text:004445D1 questo loop xora l'hash appena calcolato con il serial codificato, .text:004445D1 byte per byte. lo xor dei primi 6 byte deve dare 80 o 0, quello dei secondi 6 .text:004445D1 deve dare C0,80,40 o 0. .text:004445D1 ***************************************************************************** .text:004445D1 xor eax, eax ; azzera il contatore .text:004445D3 .text:004445D3 terzo_loop: ; CODE XREF: check+1E3j .text:004445D3 movzx ecx, [ebp+eax+ADDR_HASH] ; pointer all'hash in ecx .text:004445D8 movzx edx, [ebp+eax+ENCODED_PTR] ; pointer al serial codificato in edx .text:004445DD xor ecx, edx ; xor in ecx .text:004445DF xor edx, edx ; azzera edx .text:004445E1 cmp eax, 6 ; se siamo almeno a metà... .text:004445E4 setnl dl ; setta dl .text:004445E7 dec edx ; edx-1 .text:004445E8 and edx, 40h ; and 40 .text:004445EB add edx, 3Fh ; add 3F .text:004445EE test edx, ecx ; test con ecx; .text:004445EE ; edx ora è 7F fino al sesto, poi 3F .text:004445F0 jnz short oooops_sbagliato .text:004445F2 inc eax ; prossimo byte .text:004445F3 cmp eax, 0Ch ; è l'ultimo? .text:004445F6 jl short terzo_loop ; loop .text:004445F8 xor eax, eax ; se supera il check, eax = 0 .text:004445FA jmp short esce .text:004445FC ; --------------------------------------------------------------------------- .text:004445FC .text:004445FC oooops_sbagliato: ; CODE XREF: check+1DDj .text:004445FC push -4 ; codice sbagliato: -4 .text:004445FE jmp short poppa_e_esce .text:00444600 ; --------------------------------------------------------------------------- .text:00444600 .text:00444600 pusha_meno2_e_esce: ; CODE XREF: check+C5j .text:00444600 ; check+CEj .text:00444600 push -2 .text:00444602 .text:00444602 poppa_e_esce: ; CODE XREF: check+E4j .text:00444602 ; check+1EBj .text:00444602 pop eax .text:00444603 .text:00444603 esce: ; CODE XREF: check+ECj .text:00444603 ; check+1E7j .text:00444603 pop edi .text:00444604 pop esi .text:00444605 pop ebx .text:00444606 leave .text:00444607 retn .text:00444607 check endp .text:00444607

Note finali

perdonatemi i sottotitoli in latino ma non ho saputo resistere all'idea :)
un grazie a ZaiRoN, perchè leggendo il suo tutorial su File Carbon (leggetevelo anche voi!) mi è venuto in mente di aver già visto da qualche parte lo sha-1, poi mi è venuto in mente che era in winamp, e poi mi è venuta l'ispirazione per scrivere questo essay ;)
ovviamente per chiarimenti, migliorie o correzioni mandatemi una mail.
ci vediamo al prossimo essay... bye.

bender0

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.