Trucchetto Furbetto


2-09-2000

by Alga

 

 

UIC's Home Page

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)




Ultimate Paradox CryptoExplorer 1.7

Trucchetto Furbetto

Written by Alga

Introduzione


Le alghe sono cattive. Non di sapore (almeno, a quanto sostengono i Giapponesi), ma di animo (almeno, a quanto sostiene mia madre). E forse ciò che vi racconterò nel seguito è l'espressione di questa cattiveria: non si sproteggono i programmi altrui, vergogna! E soprattutto non si racconta in giro come si fa! E questo soprattutto quando il programma è (almeno, a quanto sostengono le recensioni) migliore del diretto concorrente presente sul mercato e costa meno. Ma allora Alga è proprio cattiva? No, non è questo. Vi racconto, come sempre, i fatti miei dei quali, come sempre, non ve ne importa un accidente e poi giudicherete voi.



Tools usati

In generale vi occorrono

- un debugger (tipicamente, SoftICE)
- un disassemblatore (qualunque andrà bene, ma IDA andrà meglio)
- un editor esadecimale (non vi serve se usate IDA)
- una tavola dei caratteri ASCII
- un convertitore di base/calcolatrice




URL o FTP del programma

http://www.cryptoexplorer.com

La demo del programma può venire scaricata dal sito.

Notizie sul programma 

Il programma è in grado di ricavare le password necessarie ad accedere a tabelle Paradox protette.
Come? Non vi importa un fico secco di accedere a tabelle Paradox protette da password?
Meglio così! Vorrà dire che sarete assolutamente disinteressati nel seguire questo tutorial e la vostra unica motivazione sarà la conoscenza!

Essay


Qualche giorno fa, al lavoro, si presentarono ben tre colleghi in delegazione, con fare tra il questuante ed il minaccioso:

"C'è un programma del quale non riusciamo a trovare la password"
"Embè? Perché lo venite a raccontare a me?"
"Perché sappiamo che tu sei in grado di trovarla, se vuoi"
"Primo: cosa sapete? Secondo: come lo sapete? Terzo: e se non fosse possibile? Quarto: quand'anche non fosse così, dite bene: "sei in grado di trovarla se vuoi"; chi vi dice che io voglia? Quinto (e più importante di tutti): non si chiedono certe cose"
"Dài, non farti pregare troppo. Ci serve che tu faccia questa cosa".

Qui l'atteggiamento di semi-minaccia era più chiaro. Volevano dire: invece di stare qui a non fare niente come sempre (cosa che, VI GIURO, non è affatto vera! Io lavoro duramente J ) cerca almeno di renderti utile facendo una cosa che ci serve (magari anche a livello personale), e checcefrega se tu la giudichi illegale o immorale

"Bè, vediamo almeno di che si tratta"

Si erano espressi male. Desideravano che trovassi le password di accesso ad una certa tabella Paradox per uso interno; chi aveva generato la tabella la aveva protetta, poi era andato in ferie rendendosi irreperibile e portandosi dietro la password.
Bisogna dire che la cosa cambiava completamente aspetto; non c'era violazione dei diritti di chicchessia, mi si forniva l'opportunità di fare per qualche tempo qualcosa che fondamentalmente mi piace, anziché il solito noioso e ripetitivo lavoro, nonché anche quella di fare bella figura. Solo che…il mio unico punto di contatto con Paradox era la presenza, su uno scaffale di casa, di un vecchio CD allegato al numero di settembre '95 di PCPlus contenente la versione 4.5, versione installata (ai tempi) e subito disinstallata; non avevo idea come funzionasse la password. Però pensai che, poiché Paradox era nato tanti e tanti anni fa, e poichè le versioni più recenti erano l'evoluzione di quelle vecchie, la protezione sarebbe stata relativamente semplice da aggirare. Così benedii mentalmente chi aveva generato la tabella ed accettai con entusiasmo.


Povera Alga!

Dopo una mezzoretta di lavoro fu chiaro che non esisteva nessun flag all'interno della tabella e nessuna password in chiaro che il programma verificasse. "Non sapevi che le tabelle di Paradox sono criptate?" No, ignorante Alga, non lo sapevo; cioè, ora lo sapevo, ma ormai avevo accettato.

Cominciai a maturare l'idea che sarebbe stato più saggio ricorrere a dei decriptatori commerciali, piuttosto che studiarsi il formato e fare tutto da sola.
Diedi un'occhiata (metaforica: le alghe non hanno occhi) su Internet, e trovai due programmi che si occupano di password cracking su Paradox: ParadoxKey e Ultimate Paradox CryptoExplorer. Ambedue programmi commerciali, ed ambedue scaricabili come demo limitate. Il primo aveva un costo di circa 45$, e la demo era limitata a password di due lettere; il secondo (di gran lunga migliore, a dar retta a ciò che avevo letto in giro) richiedeva un prezzo di registrazione di 37$, e la demo era in grado di trovare password lunghe sino a cinque caratteri.

Scaricai il secondo. Le sue performances più brillanti lo rendevano preferibile, ed inoltre la possibilità di trovare password lunghe 5 caratteri lo rendeva adatto ai miei scopi. Era infatti materialmente possibile che chi doveva proteggere (ma da chi, poi?) una tabella per uso interno la proteggesse con password più lunghe di 5 caratteri?

Dopo aver osservato a lungo sul monitor il messaggio "The limitation of this demo is 5 letter in a password…" mi resi conto che si, era materialmente possibile. Era illogico (a che serviva?), inusuale (di solito le password per uso interno sono PIPPO, per l'appunto cinque caratteri), ma possibile. Maledii mentalmente chi aveva generato la tabella, e pensai "Povera Alga!"

"Povera" nel senso di "senza una lira in tasca". Difatti (non so se avete mai osservato un'alga da vicino) le alghe non hanno tasche. Così pensai che la cosa più saggia da fare fosse quella di…adattare il programma ai miei scopi, piuttosto che comprarlo. Trovare una password non giustificava infatti la spesa (personale) di 37$. E sono più che sicura che questa sarebbe stata la risposta che avrei ricevuto se avessi richiesto l'acquisto aziendale del prodotto. Sono davvero così cattiva?


Le alghe sono stupide ed ingenue, ma neanche la PhrozenCrew scherza!

Cosi iniziai la solita trafila che si usa per i programmi che possono venire sbloccati con un codice di registrazione.

1) Inserire caratteri a casaccio
2) Stare a guardare quello che succede
3) Cercare nel disassemblato riferimenti a quello che è successo

Nel caso specifico quello che succede dopo aver inserito caratteri a casaccio ed aver premuto il pulsante "Register" è la comparsa di un MessageBox che dice "The registration string you entered is bad".
L'unico riferimento è qui:
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

 

In W32Dasm (anche in IDA, se è per questo) è possibile verificare come la chiamata alla funzione di valutazione 4477A8 venga eseguita da due procedure diverse all'interno del programma. L'altra chiamata è in 44E400, all'interno di una procedura che scrive prima dal registro di sistema; in essa se il risultato della funzione è diverso da zero viene tra l'altro eseguita una call a 44E658, nel corso della quale viene settata un'altra variabile e viene visualizzata una la stringa "for", prima di visualizzare quindi la scritta "Thanks for support!…".:
 * 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]

[Que votazione On]
Que ritiene opportuno che gli venga spedito il tutorial se l'Alga lo desidera :)) NdQue (ma va? :)
[Que votazione 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.



Note finali

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

Considerato che:

- l'Autore del programma è russo (quindi, verosimilmente, non ricco)
- il programma costa meno (o anche MOLTO meno) dei concorrenti, ed è migliore
- che, in assoluto, la cifra richiesta è più che ragionevole (meno di un libro a contenuti tecnici)
- che non si trovano in giro, almeno per il momento, crack funzionanti di questo programma

vi pregherei CALDAMENTE di non adoperare le informazioni contenute in ciò che avete appena finito di leggere per scrivere keygenerators da spargere ai quattro venti e diffondere su tutto il Web. Grazie
J

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