Trucchetto Furbetto |
|
|
2-09-2000 |
by Alga |
|
|
Published by Quequero |
|
La matematica [...] invece di ricercare quel che può essere definito e
dedotto dalle asserzioni iniziali, cerca i concetti ed i principi più generali, nei cui
termini quello che era il punto di partenza può essere definito o dedotto Bertrand Russel |
È sempre un piacere per me poter leggere i tutorial di Olga e credo che lo sarà anche per voi, anche questo tutorial è un gioiello. |
Tutti gli uomini sono mortali, Socrate è mortale, tutti gli uomini sono
Socrate Socrate |
UIC's form | UIC's form | |
Difficoltà |
Nessuna. E' già tutto spiegato... |
L'argomento di questo scritto riguarda la descrizione dell'algoritmo di validazione dei codici di sblocco della versione shareware di Ultimate Paradox CryptoExplorer 1.7. L'implementazione non è particolarmente originale o complicata; la peculiarità riguarda l'utilizzo, nella protezione, del codice di sblocco, che sembra abbia funzionato secondo quanto progettato dall'Autore (non esistono, in giro, crack funzionanti del programma)
Introduzione |
Tools usati |
URL o FTP del programma |
Notizie sul programma |
Essay |
44D31F call 004265F8 44D324 mov eax, dword ptr [ebp-04] puntatore al codice inserito 44D327 lea ecx, dword ptr [ebx+000002D4] prepara qualcosa 44D32D lea edx, dword ptr [ebx+000002D0] preparane qualche altra 44D333 call 004477A8 valuta il codice 44D338 test al, al valore restituito dalla funzione 44D33A je 0044D348 se è zero il codice è errato 44D33C mov dword ptr [ebx+0000022C], 00000001 se non lo è setta questa variabile... 44D346 jmp 0044D35D ...ed esci * Referenced by a Jump at Address 44D33A(C) | 44D348 push 00000000 44D34A mov cx, word ptr [0044D380] 44D351 mov dl, 02 * Possible StringData Ref from Code Obj ->"The registration string you entered " ->"is bad." | 44D353 mov eax, 0044D38C 44D358 call 004470D0 44D35D xor eax, eax 44D35F pop edx 44D360 pop ecx 44D361 pop ecx 44D362 mov dword ptr fs:[eax], edx
* Referenced by a CALL at Addresses: |:0044E418 , :0044E54A | 44E658 push ebp 44E659 mov ebp, esp 44E65B push 00000000 44E65D push ebx 44E65E push esi 44E65F push edi 44E660 mov edi, ecx 44E662 mov esi, edx 44E664 mov ebx, eax 44E666 xor eax, eax 44E668 push ebp 44E669 push 0044E6C2 44E66E push dword ptr fs:[eax] 44E671 mov dword ptr fs:[eax], esp 44E674 mov byte ptr [ebx+0000031C], 01 QUI 44E67B lea eax, dword ptr [ebp-04] 44E67E mov ecx, esi * Possible StringData Ref from Code Obj ->"for " | 44E680 mov edx, 0044E6D8 E QUI 44E685 call 00403BB0 44E68A mov edx, dword ptr [ebp-04]
Dal listato è anche possibile vedere come la 44E658 venga chiamata anche da 44E418,
procedura che legge dal registro di sistema.
La maniera di eseguire il crack è quindi "pretty straightforward": basta
inserire 1 in EBX+22C ed in EBX+31C seguendo il metodo che più vi aggrada (variando i
salti condizionali, cambiando l'istruzione che pone "0" nelle stesse locazioni,
etc.). Basta W32Dasm, non occorre IDA, non occorre nemmeno SoftICE, non ci sono segmenti
di codice da riscrivere, questo è cracking for dummies e questo tutorial è una
zata.
OK detto, fatto. Nel mio caso, feci in modo che la funzione di valutazione del codice di
sblocco ritornasse comunque 1 in AL; ciò avrebbe provocato (per quanto detto prima) la
creazione della chiave nel registro di sistema e redirezionato correttamente tutti i
salti. Il valore restituito dalla funzione dipende dal valore inserito in EBX (in BL)
all'uscita dalla procedura; poiché in 4479C1 viene pushato nello stack il valore 4479E6,
quest'ultimo sarà il valore che verrà estratto dallo stack con l'istruzione
"ret" in 4479EE (se non mi credete fatevi il conto :P), cosicchè all'uscita
l'esecuzione passerà a questo segmento di codice:
4479E6 mov eax, ebx 4479E8 pop edi 4479E9 pop esi 4479EA pop ebx 4479EB mov esp, ebp 4479ED pop ebp 4479EE retn
All'avvio del programma, premetti il pulsante Register sul pannello principale, inserii
"fessochilegge" come codice, premetti "Register", il programma si
profuse nei soliti ringraziamenti, il pulsante "Register" venne disabilitato, al
posto della scritta "Shareware version" comparve la scritta "for"
...tutto perfetto!
Mi rimisi al lavoro; anzi, rimisi al lavoro il programma, che cominciò a rigirare il
disegnino del dado sul pannello principale. Dopo due ore di...rigiramenti, tre possibili
soluzioni cominciarono a delinearsi.
1) Ultimate Paradox CryptoExplorer non è in grado di trovare un bel nulla
2) Ultimate Paradox CryptoExplorer non poi così veloce e chi aveva protetto la tabella
aveva inserito una password di lunghezza semi-infinita (ma che vuol dire esattamente
semi-infinita? Penso voglia dire "infi" oppure "nita". Aaaaagh!!!)
3) Il mio crack faceva schifo
Creai una tabella che protessi con la password "ab"; dopo mezz'ora il programma
non aveva trovato nulla. Questo escludeva l'ipotesi 2). Utilizzai la versione shareware
che trovò la password della mia tabella istantaneamente; e questo escludeva anche
l'ipotesi 1). Non restava che la 3).
A questo punto decisi di dare un'occhiata più in profondità (sempre metaforica: le alghe
non hanno occhi neanche quando sono in profondità) alla funzione di valutazione della
password. Questa è la parte iniziale:
4477A8 push ebp 4477A9 mov ebp, esp 4477AB add esp, FFFFFFEC 4477AE push ebx 4477AF push esi 4477B0 push edi 4477B1 xor ebx, ebx 4477B3 mov dword ptr [ebp-14], ebx 4477B6 mov dword ptr [ebp-0C], ebx 4477B9 mov dword ptr [ebp-08], ecx 4477BC mov edi, edx 4477BE mov dword ptr [ebp-04], eax il puntatore alla stringa era in EAX: salvalo 4477C1 mov eax, dword ptr [ebp-04] 4477C4 call 00403D18 4477C9 xor eax, eax 4477CB push ebp 4477CC push 004479DF 4477D1 push dword ptr fs:[eax] 4477D4 mov dword ptr fs:[eax], esp 4477D7 mov eax, dword ptr [ebp-04] recuperalo 4477DA call 00403B64 conta i caratteri della stringa 4477DF and eax, 80000001 tutto questo vuol dire... 4477E4 jns 004477EB verifica se il numero... 4477E6 dec eax ...di caratteri è pari... 4477E7 or eax, FFFFFFFE ...signed o unsigned che sia. 4477EA inc eax 4477EB test eax, eax 4477ED jne 004477FC esci se è dispari 4477EF mov eax, dword ptr [ebp-04] non lo è 4477F2 call 00403B64 conta di nuovo i caratteri 4477F7 cmp eax, 0000000C 4477FA jge 00447803 continua solo se il numero è maggiore di 11d 4477FC xor ebx, ebx 4477FE jmp 004479B9 447803 lea edx, dword ptr [ebp-14] 447806 mov eax, dword ptr [ebp-04] puntatore all'inizio della stringa 447809 call 0040775C rende maiuscoli i caratteri 44780E mov edx, dword ptr [ebp-14] 447811 lea eax, dword ptr [ebp-04] 447814 call 00403980 447819 lea eax, dword ptr [ebp-0C] 44781C push eax 44781D mov eax, dword ptr [ebp-04] 447820 call 00403B64 447825 mov edx, eax 447827 dec edx 447828 mov ecx, 00000002 44782D mov eax, dword ptr [ebp-04] 447830 call 00403D68 447835 lea eax, dword ptr [ebp-04] 447838 push eax 447839 mov eax, dword ptr [ebp-04] 44783C call 00403B64 conta i caratteri 447841 mov ecx, eax risultato in ECX 447843 sub ecx, 00000002 ECX=nr caratteri-2 447846 mov edx, 00000001 44784B mov eax, dword ptr [ebp-04] 44784E call 00403D68 447853 mov [ebp-0E], 73 inizializza:inserisci 155 in questa locazione 447857 mov eax, dword ptr [ebp-04] 44785A call 00403B64 44785F mov ebx, eax nr caratteri in EBX 447861 test ebx, ebx 447863 jle 00447878 447865 mov esi, 00000001 inizializza contatore nr d'ordine carattere 44786A mov eax, dword ptr [ebp-04] puntatore all'inizio della stringa 44786D mov al, byte ptr [eax+esi-01] puntatore al carattere attuale 447871 xor byte ptr [ebp-0E], al XOR con il carattere: lascia il risultato nella locazione 447874 inc esi incrementa contatore 447875 dec ebx decrementa contatore caratteri 447876 jne 0044786A chiudi loop se non hai finito 447878 lea edx, dword ptr [ebp-0D] 44787B mov eax, dword ptr [ebp-0C] puntatore a ultimi due caratteri 44787E call 00447748 genera numero d'ordine 447883 test al, al errore (carattere > P?) 447885 jne 0044788E no, continua 447887 xor ebx, ebx 447889 jmp 004479B9 44788E mov al, byte ptr [ebp-0D] numero d'ordine ultimi due caratteri 447891 cmp al, byte ptr [ebp-0E] confronta con risultato loop precedente 447894 je 0044789D uguale? 447896 xor ebx, ebx no, errore! 447898 jmp 004479B9
Il listato risulta forse poco chiaro per via di una serie di chiamate a procedure che si
occupano essenzialmente di trattamento stringhe. Le "call" rilevanti e che si
ripetono più volte nel corso della procedura, sono quella a 403D68, che valuta il numero
di caratteri che compongono una stringa, e quella a 447748. Quest'ultima verifica se una
data coppia di caratteri sia alfabetica ed i caratteri siano compresi tra "A" e
"P", e in caso affermativo, genera un byte in cui il nibble più significativo e
quello meno significativo corrispondono rispettivamente ai numeri d'ordine nell'alfabeto
(in base 0) dei due caratteri considerati. Essa in pratica funziona così:
1) ottiene l'indirizzo di inzio della stringa ASCII "ABCDEFGHIJKLMNOP" hardcoded
nel programma (anche W32Dasm ve la mostrerà), che chiameremo addr1
2) confronta sequenzialmente il carattere considerato con i caratteri che compongono la
stringa
3) se lo trova, ottiene l'indirizzo del byte corrispondente, che chiameremo addr2
4) esegue la sottrazione addr2-addr1.
5) memorizza il risultato in ECX
6) esegue lo stesso processo per il secondo carattere della coppia
7) shifta a sinistra di quattro posizioni il contenuto di ECX
8) somma il secondo risultato
9) copia il registro in una locazione di memoria e setta AL
10) se non è stata trovata alcuna corrispondenza azzera EAX
Se, tanto per fissare le idee, la stringa "ABCDEFGHIJKLMNOP" si trova
all'indirizzo 480000, la "B" nella stessa stringa si troverà all'indirizzo
480001, la "C" in 480002 e così via. Sottraendo per la "C"
480002-480000 si otterrà 2, e cioè il numero d'ordine della "C" in base 0
(cioè, "A"=0). Verrà restituito errore (EAX=0) solo se il carattere sarà
maggiore di "P" in quanto il numero d'ordine di "P" è 15 e cioè F in
esadecimale, ed un nibble non può essere maggiore di F.
Detto ciò, vediamo in maniera più "esplicativa" cosa fa questa parte di
codice:
4477B3 :… 4777B9 inizializzazione :… :… :… 4777D7 puntatore all'inizio della stringa 4777DA conta i caratteri 4777E4 sono pari? :… :… 4777ED no , errore :… :… 4777F2 riconta 4777F7 sono almeno 12? :… 4777FE no, errore :… :… 477809 rendi maiuscoli tutti i caratteri :… :… 447828 considera due caratteri :… 447830 gli ultimi due: copiali da qualche parte :… :… 44783C conta i caratteri :… 447843 considera numero di caratteri-2 :… 447853 inserisci 155d nell'appropriata locazione di memoria :… :… 447865 comincia a considerare il primo carattere :… 44786D punta al carattere corrente 447871 esegui lo XORing del carattere corrente con il contenuto della locazione di memoria appropriata e lascia lì il risultato 447874 incrementa il puntatore 447875 finito? 447876 no, carattere successivo :… 447878 prepara locazione per risultato2 44787B sì, considera i due caratteri finali 44787E calcola il numero d'ordine dei due componenti e memorizza in locazione per risultato2 447883 valuta se sono maggiori di "P" :… 447889 sì, errore 44788E considera locazione per risultato2: numero posizione degli ultimi due caratteri 447891 confronta con risultato finale dell'operazione di XORing sequenziale 447894 uguali? 447896 no, errore etc, etc, etc.
Che vuol dire in pratica tutto questo? Vuol dire:
- se il codice è composto da un numero pari di caratteri
- se essi sono almeno dodici
- se essi sono tutti alfabetici e compresi tra "A" e "P"
- esegui lo XORing del primo carattere con 155
- esegui lo XORing del secondo carattere con il risultato
- e così via, fino al terzultimo carattere
- il risultato finale deve essere uguale al numero d'ordine degli ultimi due caratteri
Una cosa che salta all'occhio considerando la parte seguente del listato è che le
successive uscite dalla procedura con zero in AL avvengono solo se l'errore viene
generato dalla 447748, e cioè, passato il controllo in 447891, se il codice non contiene
caratteri maggiori di "P" tutto naviga felicemente e senza intoppi verso
l'uscita (positiva) dalla procedura di valutazione. In pratica, questa è la routine di
validazione del codice di sblocco, che deve essere composto da almeno dodici caratteri di
cui gli ultimi due servono da checksum per il resto della stringa.
Basta allora generare una sequenza casuale di almeno dieci caratteri compresi tra
"A" e "P", eseguire lo XORing sequenziale, indi porre codice_ASCII_del_penultimo_carattere=65+nibble_più_significativo_del_risultato
e codice_ASCII_dell'_ultimo_carattere=65+nibble_meno_significativo_del_risultato
per generare una stringa valida.
Con dieci "A" il codice risultante è, ad esempio, "AAAAAAAAAAHD".
Provai ad inserire nella versione originale del programma per l'appunto la stringa
"AAAAAAAAAAHD", ed ottenni subito la scritta "Thanks for
support
bla
bla". "Le alghe sono delle gran furbe" pensai
"ed i programmatori di cryptoexplorers sono degli scemi".
Feci ripartire, sulla mia tabella con password "ab", il programma registrato, il
quale cominciò a rigirare il solito dadino. Dopo una mezz'oretta realizzai che non
avrebbe mai trovato un bel nulla.
La cosa doveva essere ben più complicata.
Cercai in giro se qualcuno avesse fatto di meglio, e trovai una patch della PhrozenCrew:
"Apply Patch.exe to be a registered user !". Funzionava? Il programma patchato
continuava a rigirare l'inutile dadino: anche PhrozenCrew aveva toppato clamorosamente. A
pensarci bene, forse le alghe non sono poi così furbe; e mica tanto scemi i programmatori
di cryptoexplorers! L
L'Algoritmo ed il Trucchetto
Come chiunque si occupi di cracking comprenderà benissimo, ormai della password della
tabella Paradox al lavoro non me poteva fregà ddemeno; l'obiettivo principale era
divenuto un altro.
Una prima cosa da notare era: la patch di PhrozenCrew o il mio crack (che era del tutto
equivalente) facevano comparire "for" al posto di "Shareware version"
sul pannello principale. Con l'inserimento di "AAAAAAAAAAHD" compariva "for
7", mentre con "AAAAAAAAAAAAHD" compariva "for 7n". In generale,
l'aggiunta di una coppia di lettere provocava la comparsa di un carattere in più; spesso
il carattere non era alfanumerico. Una possibilità era allora che il programma eseguisse
il controllo sui caratteri generati, e bloccasse l'esecuzione se ne avesse trovato
qualcuno "illegale". Ma perché utilizzare dieci caratteri (oltre i due di
controllo) per generarne uno, dodici per generarne due, e così via? Restavano sempre otto
caratteri "suppletivi" che dovevano avere qualche significato.
Mi rassegnai ad eseguire il reversing completo della routine di controllo del codice.
Eccovi quindi il solo listato commentato e poi spiegato per sommi capi; per evitare a
qualcuno di voi la tentazione di commettere un alghicidio mi asterrò dalla pedanteria
delle pedisseque spiegazioni fornite prima.
004477A8 push ebp 004477A9 mov ebp, esp 004477AB add esp, 0FFFFFFECh 004477AE push ebx 004477AF push esi 004477B0 push edi 004477B1 xor ebx, ebx 004477B3 mov [ebp+var_14], ebx ; inizializza 004477B6 mov [ebp+var_C], ebx 004477B9 mov [ebp+var_8], ecx 004477BC mov edi, edx 004477BE mov [ebp+var_4], eax 004477C1 mov eax, [ebp+var_4] 004477C4 call System __linkproc__ LStrAddRef(void) 004477C9 xor eax, eax 004477CB push ebp 004477CC push offset loc_4479DF 004477D1 push dword ptr fs:[eax] 004477D4 mov fs:[eax], esp 004477D7 mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 004477DA call conta_caratteri 004477DF and eax, 80000001h ; deve essere pari 004477E4 jns short loc_4477EB 004477E6 dec eax 004477E7 or eax, 0FFFFFFFEh 004477EA inc eax 004477EB 004477EB loc_4477EB: ; CODE XREF: sub_4477A8+3C j 004477EB test eax, eax 004477ED jnz short loc_4477FC 004477EF mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 004477F2 call conta_caratteri 004477F7 cmp eax, 0Ch ; deve essere almeno 12 caratteri 004477FA jge short loc_447803 004477FC 004477FC loc_4477FC: ; CODE XREF: sub_4477A8+45 j 004477FC xor ebx, ebx 004477FE jmp loc_4479B9 00447803 ; --------------------------------------------------------------------------- 00447803 00447803 loc_447803: ; CODE XREF: sub_4477A8+52 j 00447803 lea edx, [ebp+var_14] 00447806 mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 00447809 call rendili_maiuscoli 0044780E mov edx, [ebp+var_14] ; maiuscoli 00447811 lea eax, [ebp+var_4] ; puntatore all'inizio della stringa 00447814 call System __linkproc__ LStrLAsg(void) 00447819 lea eax, [ebp+var_C] 0044781C push eax 0044781D mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 00447820 call conta_caratteri 00447825 mov edx, eax 00447827 dec edx 00447828 mov ecx, 2 0044782D mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 00447830 call System __linkproc__ LStrCopy(void) 00447835 lea eax, [ebp+var_4] 00447838 push eax 00447839 mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 0044783C call conta_caratteri 00447841 mov ecx, eax ; caratteri in ECX 00447843 sub ecx, 2 ; sottrai due (togli gli ultimi due caratteri) 00447846 mov edx, 1 0044784B mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 0044784E call System __linkproc__ LStrCopy(void) 00447853 mov [ebp+var_E], 73h ; valore di base per XORing 00447857 mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 0044785A call conta_caratteri 0044785F mov ebx, eax ; nr caratteri in EBX 00447861 test ebx, ebx 00447863 jle short loc_447878 00447865 mov esi, 1 ; puntatore caratteri stringa 0044786A 0044786A INIZIA_LOOP: ; CODE XREF: sub_4477A8+CE j 0044786A mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 0044786D mov al, [eax+esi-1] ; punta all'indirizzo del carattere attuale 00447871 xor [ebp+var_E], al ; XOR carattere attuale con precedente risultato 00447871 ; il nuovo risultato viene lasciato nella locazione di partenza 00447874 inc esi ; incrementa puntatore 00447875 dec ebx ; decrementa contatore 00447876 jnz short INIZIA_LOOP ; chiude il loop 00447878 00447878 loc_447878: ; CODE XREF: sub_4477A8+BB j 00447878 lea edx, [ebp+var_D] ; prepara buffer per risultato 0044787B mov eax, [ebp+var_C] ; ultimi due caratteri 0044787E call genera_numero_posizione 00447883 test al, al 00447885 jnz short loc_44788E 00447887 xor ebx, ebx 00447889 jmp loc_4479B9 0044788E ; --------------------------------------------------------------------------- 0044788E 0044788E loc_44788E: ; CODE XREF: sub_4477A8+DD j 0044788E mov al, [ebp+var_D] ; nr posizione ultimi due caratteri 00447891 cmp al, [ebp+var_E] ; confronta con risultato del loop precedente 00447894 jz short loc_44789D ; continua se uguali 00447896 xor ebx, ebx 00447898 jmp loc_4479B9 ; esci se diversi 0044789D ; --------------------------------------------------------------------------- 0044789D 0044789D loc_44789D: ; CODE XREF: sub_4477A8+EC j 0044789D lea eax, [ebp+var_C] ; ultimi due 004478A0 push eax 004478A1 mov eax, [ebp+var_4] ; puntatore all'inizio della (stringa - gli ultimi due) 004478A4 call conta_caratteri 004478A9 mov edx, eax 004478AB sub edx, 7 ; sottrai sette dal numero dei caratteri 004478AE mov ecx, 8 004478B3 mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 004478B6 call System __linkproc__ LStrCopy(void) 004478BB lea eax, [ebp+var_4] 004478BE push eax 004478BF mov eax, [ebp+var_4] ; puntatore all'inizio della stringa 004478C2 call conta_caratteri 004478C7 mov ecx, eax 004478C9 sub ecx, 8 ; sottrai otto al numero dei caratteri 004478CC mov edx, 1 ; inizializza puntatore all'interno della stringa 004478D1 mov eax, [ebp+var_4] ; prepara buffer 004478D4 call System __linkproc__ LStrCopy(void) 004478D9 mov [ebp+var_F], 37h ; inizializza locazione per lo XORing con multipli di 55d 004478DD mov eax, edi 004478DF call System __linkproc__ LStrClr(System::AnsiString &) 004478E4 mov eax, [ebp+var_4] 004478E7 call conta_caratteri 004478EC mov ebx, eax 004478EE sar ebx, 1 ; verifica se sono pari 004478F0 jns short loc_4478F5 004478F2 adc ebx, 0 004478F5 004478F5 loc_4478F5: ; CODE XREF: sub_4477A8+148 j 004478F5 test ebx, ebx 004478F7 jle short loc_44794D 004478F9 mov esi, 1 ; inizializza puntatore all'interno della stringa 004478FE 004478FE INIZIA_LOOP2: ; CODE XREF: sub_4477A8+1A3 j 004478FE lea eax, [ebp+var_14] 00447901 push eax 00447902 mov eax, esi 00447904 dec eax 00447905 mov edx, eax 00447907 add edx, edx 00447909 inc edx 0044790A mov ecx, 2 0044790F mov eax, [ebp+var_4] 00447912 call System __linkproc__ LStrCopy(void) 00447917 mov eax, [ebp+var_14] 0044791A lea edx, [ebp+var_E] ; prepara buffer 0044791D call genera_numero_posizione 00447922 test al, al ; devono essere alfabetici 00447924 jnz short loc_44792D 00447926 xor ebx, ebx 00447928 jmp loc_4479B9 0044792D ; --------------------------------------------------------------------------- 0044792D 0044792D loc_44792D: ; CODE XREF: sub_4477A8+17C j 0044792D lea eax, [ebp+var_14] ; punta alla coppia di lettere 00447930 mov dl, [ebp+var_E] ; numeri d'ordine alfabetici in base zero 00447930 ; delle due lettere considerate 00447933 xor dl, [ebp+var_F] ; XORing tra i numeri d'ordine della coppia 00447933 ; di caratteri ed il multiplo di 55d 00447936 call unknown_libname_33 0044793B mov edx, [ebp+var_14] 0044793E mov eax, edi 00447940 call System __linkproc__ LStrCat(void) 00447945 add [ebp+var_F], 37h ; incrementa il valore di XORing per un multiplo di 55d 00447949 inc esi ; incrementa puntatore 0044794A dec ebx ; decrementa contatore 0044794B jnz short INIZIA_LOOP2 ; chiudi il ciclo se non hai terminato 0044794B ; passa alla routine successiva se hai terminato 0044794D 0044794D loc_44794D: ; CODE XREF: sub_4477A8+14F j 0044794D mov eax, [ebp+var_8] ;inizializza locazione per ultima parte 00447950 xor edx, edx 00447952 mov [eax], edx 00447954 mov eax, [ebp+var_C] ; puntatore a caratteri suppletivi 00447957 call conta_caratteri ; conta 0044795C mov ebx, eax ; numero caratteri suppletivi (sempre otto) 0044795E sar ebx, 1 ; e dividi per due 0044795E ; In pratica, verifica se sono pari 00447960 jns short loc_447965 00447962 adc ebx, 0 00447965 00447965 loc_447965: ; CODE XREF: sub_4477A8+1B8 j 00447965 test ebx, ebx 00447967 jle short loc_4479B7 ; OK 00447969 mov esi, 1 ; inizializza puntatore 0044796E 0044796E loc_44796E: ; CODE XREF: sub_4477A8+20D j 0044796E lea eax, [ebp+var_14] 00447971 push eax 00447972 mov eax, esi 00447974 dec eax 00447975 mov edx, eax 00447977 add edx, edx 00447979 inc edx 0044797A mov ecx, 2 0044797F mov eax, [ebp+var_C] 00447982 call System __linkproc__ LStrCopy(void) 00447987 mov eax, [ebp+var_14] 0044798A lea edx, [ebp+var_E] ; prepara buffer 0044798D call genera_numero_posizione 00447992 test al, al ; devono essere alfabetici 00447994 jnz short loc_44799A 00447996 xor ebx, ebx 00447998 jmp short loc_4479B9 0044799A ; --------------------------------------------------------------------------- 0044799A 0044799A loc_44799A: ; CODE XREF: sub_4477A8+1EC j 0044799A mov al, [ebp+var_E] ; numeri d'ordine coppia corrente qui 0044799D xor al, 77h ; XORing con 77h (119d) 0044799F and eax, 0FFh ; azzera gli altri byte di EAX 004479A4 mov edx, [ebp+var_8] ; indirizzo di questa locazione in EDX 004479A7 mov edx, [edx] ; contenuto della stessa locazione in EDX 004479A9 shl edx, 8 ; sposta il contenuto di un byte verso sinistra 004479AC add eax, edx ; aggiungi al byte meno significativo il risultato dell'ultimo XORing 004479AE mov edx, [ebp+var_8] 004479B1 mov [edx], eax ; rimetti il tutto dove lo hai preso 004479B3 inc esi ; incrementa puntatore 004479B4 dec ebx 004479B5 jnz short loc_44796E 004479B7 004479B7 loc_4479B7: ; CODE XREF: sub_4477A8+1BF j 004479B7 mov bl, 1 004479B9 004479B9 loc_4479B9: ; CODE XREF: sub_4477A8+56 j 004479B9 ; sub_4477A8+E1 j ... 004479B9 xor eax, eax 004479BB pop edx 004479BC pop ecx 004479BD pop ecx 004479BE mov fs:[eax], edx 004479C1 push offset loc_4479E6 004479C6 004479C6 loc_4479C6: ; CODE XREF: sub_4477A8+23C j 004479C6 lea eax, [ebp+var_14] 004479C9 call System __linkproc__ LStrClr(System::AnsiString &) 004479CE lea eax, [ebp+var_C] 004479D1 call System __linkproc__ LStrClr(System::AnsiString &) 004479D6 lea eax, [ebp+var_4] 004479D9 call System __linkproc__ LStrClr(System::AnsiString &) 004479DE retn 004479DF ; --------------------------------------------------------------------------- 004479DF 004479DF loc_4479DF: ; DATA XREF: sub_4477A8+24 o 004479DF jmp System __linkproc__ HandleFinally(void) 004479E4 ; --------------------------------------------------------------------------- 004479E4 jmp short loc_4479C6 004479E6 ; --------------------------------------------------------------------------- 004479E6 004479E6 loc_4479E6: ; DATA XREF: sub_4477A8+219 o 004479E6 mov eax, ebx 004479E8 pop edi 004479E9 pop esi 004479EA pop ebx 004479EB mov esp, ebp 004479ED pop ebp 004479EE retn
da 44789D a 44794D la procedura:
- considera coppie di caratteri dall'inizio della stringa con l'esclusione degli ultimi
dieci
- per ogni coppia calcola i numeri d'ordine delle due componenti e li inserisce in un solo
byte
- effettua lo XORing di questo byte con un multiplo di 37h (55d) e se il risultato è
maggiore di FF considera solo i due primi nibble
- tratta il risultato come il codice ASCII del carattere da visualizzare sul pannello
principale
In pratica l'algoritmo di generazione potrebbe essere espresso con:
codice ASCII del carattere = ((55*nr_d'ordine_della_coppia_nella_stringa) MOD256) XOR
(nr_d'ordine_primo_carattere+ nr_d'ordine_secondo_carattere)
nel caso della coppia "AA" posta all'inizio è allora 55 XOR 00 = 55 che è
appunto il codice ASCII di "7", mentre se la coppia "AA" si trova al
secondo posto il carattere diviene (55*2) XOR 00 = 110 che è il codice ASCII di
"n". Questo è il motivo per cui la sequenza iniziale "AAAA" originava
"for 7n" sul pannello principale.
Stabilito ciò, è abbastanza agevole creare il generatore di caratteri invertendo
l'algoritmo:
((55*nr_d'ordine_nella_stringa) MOD256) XOR (ASCII carattere_con_quel_numero_d'ordine)
= ASCII(MSN+65) ASCII (LSN+65).
dove "MSN" e "LSN" significano, rispettivamente, "most
significant nibble" e "least significant nibble".
Infine da 44794D a 4479B5 il programma:
- considera gli otto caratteri "suppletivi" a coppie
- per ogni coppia calcola il solito byte con i numeri d'ordine
- esegue lo XORing con 77h
- memorizza i quattro byte in sequenza in un unico DWORD ed esce
A cosa serve quest'ultimo DWORD così calcolato? Perché il programma si dà tanta pena
per eseguire quest'ultimo calcolo? Evidentemente (le alghe si pongono sempre domande la
cui risposta è ovvia) per utilizzarne il risultato da qualche altra parte; e per trovare
agevolmente questa "qualche altra parte" il metodo più sicuro, rapido ed
indolore resta SoftICE.
Carichiamo il programma nel loader, inseriamo un breakpoint in 4479B1, inseriamo
"AAAAAAAAAAHD" come codice di registrazione, steppiamo affinchè il programma
termini il ciclo generando il DWORD "77777777", indi inseriamo un BreakPoint on
Memory range in lettura sulla locazione contenuta in EDX e facciamo continuare
l'esecuzione. SoftICE arresterà l'esecuzione qui:
0044E535 mov ecx, [eax+2D4h] ; codice controllo (caratteri suppletivi) in ECX 0044E53B mov eax, ds:off_4504FC ;QUI 0044E540 mov eax, [eax] 0044E542 mov edx, [eax+2D0h] 0044E548 mov eax, esi 0044E54A call sub_44E658 0044E54F push 0
ECX a questo punto conterrà "77777777". Seguiamo il flusso del programma per scoprire a che serve il codice generato. All'interno della "call 44E658" succede questo:
0044E65B push 0 0044E65D push ebx 0044E65E push esi 0044E65F push edi 0044E660 mov edi, ecx ; codice controllo in EDI 0044E662 mov esi, edx 0044E664 mov ebx, eax 0044E666 xor eax, eax 0044E668 push ebp 0044E669 push offset loc_44E6C2 0044E66E push dword ptr fs:[eax] 0044E671 mov fs:[eax], esp 0044E674 mov byte ptr [ebx+31Ch], 1 0044E67B lea eax, [ebp+var_4] 0044E67E mov ecx, esi 0044E680 mov edx, offset _str_for_.Text 0044E685 call System __linkproc__ LStrCat3(void) 0044E68A mov edx, [ebp+var_4] 0044E68D mov eax, [ebx+30Ch] 0044E693 call Controls::TControl::SetText(System::AnsiString) 0044E698 xor edx, edx 0044E69A mov eax, [ebx+2F8h] 0044E6A0 mov ecx, [eax] 0044E6A2 call dword ptr [ecx+60h] 0044E6A5 mov eax, edi ; codice controllo in EAX 0044E6A7 call sub_4480B4 0044E6AC xor eax, eax 0044E6AE pop edx 0044E6AF pop ecx 0044E6B0 pop ecx 0044E6B1 mov fs:[eax], edx
all'interno della "call 4480B4" succede questo:
004480B4 push ebx 004480B5 mov ebx, eax ; codice controllo in EBX 004480B7 mov eax, offset unk_44FE1C ;indirizzo base lista 1 004480BC mov edx, ebx ; codice controllo in EDX 004480BE call sub_4479F0 004480C3 mov eax, offset unk_44FF1C ;indirizzo base lista 2 004480C8 mov edx, ebx 004480CA call sub_4479F0 004480CF mov eax, offset unk_45001C ;indirizzo base lista 3 004480D4 mov edx, ebx 004480D6 call sub_4479F0 004480DB pop ebx 004480DC retn 004480DC sub_4480B4 endp
mentre all'interno delle "call 4479F0" succede (finalmente!) questo:
004479F0 push ebx 004479F1 push esi 004479F2 mov ebx, 40h ; contatore elementi lista 004479F7 mov ecx, eax ; indirizzo base lista 004479F9 004479F9 loc_4479F9: ; CODE XREF: sub_4479F0+11 j 004479F9 mov esi, ecx ; puntatore elemento lista 004479FB xor [esi], edx ; XORing codice controllo con elemento lista 004479FD add ecx, 4 ; punta al successivo 00447A00 dec ebx ; decrementa contatore 00447A01 jnz short loc_4479F9 ; chiudi loop 00447A03 pop esi 00447A04 pop ebx 00447A05 retn 00447A05 sub_4479F0 endp
Come si vede, questa routine viene chiamata tre volte, ed esegue lo XORing tra il codice di controllo e gli elementi di tre liste da 64 DWORD, il cui indirizzo base si trova rispettivamente in 44FE1C, 44FF1C e 45001C. Dove vengono utilizzate le liste? Il solito BPM in lettura sul primo elemento della prima lista (ma anche una banale ricerca di stringa all'interno del listato) ci fa approdare qui:
00447C78 xor al, byte ptr ds:unk_44FE1C[edx]
Questa istruzione si trova all'interno di una procedura che inizia in 447AE8; un BPX a
questo indirizzo ci mostrerà come essa venga invocata quando si chiede al programma di
inziare la ricerca di password su qualche tabella Paradox. EDX è l'offset all'interno
della lista, e nella procedura sono contenuti i riferimenti alle altre due liste. Bene, ma
a che servono queste liste? Per rispondere a questa domanda dovremmo reversare l'algoritmo
che si occupa della decrittazione delle password Paradox, non quello di protezione J
Comunque, ciò che avviene è abbastanza chiaro: il programma utilizza, per la
decrittazione delle password, delle liste che sono a loro volta criptate, e la chiave di
decrittazione viene ricavata dai caratteri del codice di sblocco inserito. L'algoritmo
utilizzato per la criptazione-decriptazione è una variante dell'algoritmo di Vernam.
[professional_mode on]
Se qualcuno fosse interessato ad una trattazione (leggermente) più approfondita della
problematica relativa all'implementazione dell'algoritmo di Vernam per questi usi può
leggere il mio tut sulla decrittazione dell password di InstallShield Package For The Web
presente sul sito di RingZero, o che potrei inviare a Que se lo ritiene opportuno (vi
sento, vi sento; non è giusto pronunciare queste frasi e soprattutto emettere questi
suoni indirizzandoli verso una signora). In pratica questo problema, se non si conoscono i
contenuti della lista, non ha soluzione matematica.
[professional_mode off]
L'algoritmo di validazione e quello di decrittazione agiscono sepatamente. Furbetto il
trucchetto, eh?
Ma il nocciolo della questione è appunto questo: il problema non ha soluzione matematica,
ma ha sicuramente una soluzione logica; anzi, la soluzione è persino banale,
risiedendo appunto in quel se non si conoscono i contenuti della lista. Quindi, se
leggere fin qui vi ha annoiato, e se invece risolvere i problemi logici vi affascina,
mettete via questo stupido tutorial e trovate da soli il modo di ricavare gli otto
caratteri che devono essere inseriti in ogni codice di sblocco affinchè il programma
funzioni. Se invece non vi frega nulla di tutto ciò, continuate pure fino alla fine.
La Soluzione
Devo essere sincera: all'inizio pensai che la lista non fosse affatto criptata. Questa
sarebbe stata la soluzione più logica, o quantomeno quella che avrei adottato io; ciò
infatti non avrebbe reso meno sicura la protezione, ma avrebbe reso più breve il
programma, ed adesso ve lo dimostrerò. La soluzione "logica" parte da un dato
di fatto: la versione shareware del programma trova effettivamente le password.
L'algoritmo è verosimilmente lo stesso, sebbene contenente la limitazione nel numero:
quindi almeno in parte le liste devono essere presenti in chiaro quando il
programma viene utilizzato in versione shareware.
Ora, questo renderebbe inutile la presenza di liste criptate; partendo dall'assunto che
l'utente non conosce l'algoritmo utilizzato per lo sblocco tramite codice (anzi,
più specificatamente, non conosce il valore 77h utilizzato per ricavare la chiave),
sarebbe bastato implementare semplicemente una routine che eseguisse comunque lo XORing
sulla lista non criptata quando il programma fosse stato avviato in versione registrata;
il codice corretto avrebbe contenuto i caratteri che alla fine avrebbero eseguito uno
XORing con zero, mentre lo XORing con qualunque altro valore avrebbe "corrotto"
la lista rendendo impossibile l'utilizzo del programma.
Quindi pensai che le cose potessero andare proprio così; in questo caso i caratteri
inseriti avrebbero dovuto essere quelli di numero d'ordine alfabetico "7", e
cioè "H", cosicchè lo XORing con "77" avrebbe dato luogo al DWORD
"00000000" e la lista non sarebbe stata corrotta. La stringa corrispondente,
corretta, con una coppia di "AA" all'inizio sarebbe allora stata
"AAHHHHHHHHHD"; ma provando ad inserirla nell'editbox per la registrazione, il
programma non funzionava ugualmente. L'approccio doveva essere diverso.
Domanda numero 1: la procedura in 447AE8 viene chiamata dalla versione shareware
del programma?
Risposta: no, mai. Inserendo un BPX in 447AE8, SoftICE arresta l'esecuzione della
sola versione "registrata"
Domanda numero 2: cosa viene chiamato nel programma shareware in luogo della
procedura 447AE8?
Risposta: boh...è proprio quello che dovremmo scoprire. Allora
Domanda numero 3: da dove viene chiamata la 447AE8?
Risposta: SoftICE (analisi dello stack, stepping, quello che volete voi
) ci
consente di stabilire che l'istruzione è questa:
00411C79 call dword ptr [edx+4]
OK, poniamo un BPX 411C79 ed avviamo il programma "registrato"; SoftICE
arresterà l'esecuzione due volte in 411C79; la prima volta per EDX+4 = 4487D0, la seconda
per EDX+4=447AE8, da dove dovrebbe cominciare il lavoro vero e proprio del programma.
Ora, "deregistriamo" (cancellando l'apposita chiave dal registro di sistema) il
programma, e ripetiamo il processo. SoftICE arresterà l'esecuzione sempre due volte, e
per la prima volta il valore di EDX+4 risulterà sempre 4487D0, mentre la seconda il
valore di EDX+4 sarà 44CBF0.
Se analizzato ("analizzato" non è né una volgare parolaccia né un termine
medico, ma è una contrazione di "analizziamo il listato"...analizziamo
invece è un termine medico vero? Ma come si fa ad analizzare un listato? Mah! :),
il codice rivelerà che l'intera procedura è del tutto simile a quella che parte in
447AE8; e per la precisione:
0044CD75 xor al, byte ptr ds:unk_4501D0[edx]
risulta analoga a
00447C78 xor al, byte ptr ds:unk_44FE1C[edx]
mentre
0044CD5C mov cl, byte ptr ds:unk_4502D0[eax]
risulta analoga a
00447C5A mov cl, byte ptr ds:unk_44FF1C[eax]
In pratica, nella versione shareware dovrebbero venire utilizzate delle liste in chiaro
poste agli indirizzi base 4501D0 e 4501D0, mentre nella versione registrata l'intero
flusso del programma verrebbe redirezionato verso una procedura che adopera versioni
crittate delle stesse liste e che vengono decrittate ad ogni avvio del programma
servendosi degli otto caratteri "suppletivi".
Se quest'ipotesi è vera, lo XORing di ogni elemento della lista in chiaro con il
corrispondente elemento di quella crittata dovrebbe fornire sempre lo stesso valore, dal
quale sarebbe a sua volta possibile risalire ai caratteri "suppletivi".
Di fatto abbiamo
Contenuto locazione 4501D0=B2 A5 0C DD Contenuto locazione 44FE1C=2B BC B7 77
XOR=99 19 BB AA
Contenuto locazione 4501D4=38 FE CB 5B Contenuto locazione 44FE20=A1 E7 70 F1 XOR=99
19 BB AA
Contenuto locazione 4502D0=61 A7 79 02 Contenuto locazione 44FF1C=F8 BE C2 A8
XOR=99 19 BB AA
Contenuto locazione 4502D4=61 A7 79 02 Contenuto locazione 44FF20=AE 2D D4 2B
XOR=99 19 BB AA
Bè, ogni ulteriore verifica sarebbe una perdita di tempo. Eseguendo lo XORing di ogni
byte del risultato con 77 si ha la sequenza EE 6E CC DD, che corrisponde ai numeri
d'ordine alfabetico, in base zero, dei caratteri OO GO MM NN.
Riepilogando:
- consideriamo i caratteri del nome che vogliamo venga visualizzato sul pannello come
utente registrato
- generiamo per ogni carattere la coppia data da: ((55*nr_d'ordine_nella_stringa) MOD256)
XOR (ASCII carattere_con_quel_numero_d'ordine) = ASCII(MSN+65) ASCII (LSN+65).
- aggiungiamo alla sequenza ottenuta la stringa "NNMMGOOO"
- eseguiamo lo XOring del primo carattere con 155d
- eseguiamo lo XOring del carattere seguente con il risultato, e così via
- aggiungiamo alla stringa due caratteri corrispondenti ai numeri d'ordine dei nibble del
risultato
La stringa corretta per "Alga" è ad esempio: "HGACMCLNNNMMGOOOHK".
Inserendola nell'editbox per la registrazione il programma risulta (ovviamente)
registrato, e funziona tutto. Fine.
|
Ah, e la password della tabella Paradox all'inizio? Bè, Ultimate Paradox
CryptoExplorer ha trovato un'infinità di password funzionanti, tra le quali
"antonino". Già, "antonino"...il nome di chi aveva generato la
tabella! Se non avessi tralasciato una delle regole BASILARI dell'hacking avrei fatto una
bella figura "istantanea".
Le alghe non sono poi così cattive, ma stupide sì!
Disclaimer |
UIC's page of reverse engineering, scegli dove andare: |
Home Anonimato Assembly ContactMe CrackMe Forum Iscrizione |
Lezioni Links Linux NewBies News Playstation |
Search Tools Tutorial UIC Faq XXX Page |
UIC |