Soluzione del crackme 6
matematicamente

Data

by Artemis

 

2-3-4-5 Marzo 2003

UIC's Home Page

Published by Quequero


Ho risolto il corso... e cosa voglio di più dalla vita? Un pò di metano!!!

Ebbravo artemis, allora... Come ti ho commentato l'algoritmo e' un hash basato su RSA ma e' l'ho creato per collidere un pochetto, l'analisi che tu ne fai e' corretta, a voi resterebbe solo dover bruteforzare lo spazio delle chiavi (32-bit a pezzo) cercando di trovare le condizioni di collisione che nessuno ha cercato... Si anche dopo che in mailinglist postai la forma del seriale che ripeto essere: "Xxxxxxx xx xxxx!" ;p a buon intenditor....

Ho risolto il corso... e cosa voglio di più dalla vita? Un pò di metano!!!

«~{Å}~»

Home page: http://artemislab.da.ru
E-mail: atlantys@freemail.it
Artemis23 su Ircnet nei canali #scorza, #hackeritaly, ICQ: 27580498

«~{Å}~»

Difficoltà

( )NewBies ( )Intermedio ( )Avanzato (x)Master

 

E' un semplice (all'apparenza) innocente (poi questa!) crackme che richiede di trovare il seriale e sono concessi tutti i tools esistenti nel mondo del reversing. :) Ah ovviamente se bisogna trovare il seriale, non dobbiamo patcharlo!


Soluzione del crackme 6
matematicamente
Written by Artemis

Introduzione

Questo corso, molto semplice in apparenza, mi ha procurato bei grattacapi per quattro giorni ma poi comunque ne è valsa la pena perché mi ha invogliato a dedicarmi agli altri corsi della UIC (per newbies e non) e mi ha anche spinto a studiare algoritmi di crittografia. Il corso non so quando è stato fatto (suppongo un annetto fa) ed io lo sto risolvendo a marzo 2003... non vogliatemente, ho letto le altre soluzioni quando mi sono visto perso, ma ho notato che neppure le soluzioni erano vere e proprie soluzioni e tante cose le avevo capite anche io, quindi (Què questa arringa è per te) giuro che quello che ho scritto è tutta farina del mio sacco e che non ricordo nemmeno tutti i calcoli e le formule degli altri. Mmha... passiamo avanti.

Tools usati

Come tools ho utilizzato Wdasm e Ida, il primo per debuggare il crackme nella prima fase dei miei controlli, il secondo per controllare lo stato di alcune locazioni di memoria e per scrivere questo tutorial. Potete trovare entrambi i tools su questo sito o nella mia home page che è scritta all'inizio di questo tutorial.

URL o FTP del programma

http://quequero.org :)

Notizie sul programma

Il crackme sfrutta un metodo di crittografia simile all'RSA ed accetta serial di 16 caratteri. Secondo me si chiama bugged perché non fa il controllo della lunghezza dei caratteri e non perché l'hai scritto nel 1999 quasi a capodanno. :) Infatti nel crackme è presente la chiamata a GetWindowTextLengthA, ma non la usa... doveva avere proprio una gran fretta quel giorno Què eh... eh beh... capodanno :)

Essay

Apriamo il programma con IDA o Wdasm e sotto la chiamata GetWindowTextA giungiamo immediatamente alla routine del codice. Ovviamente non mi voglio dilungare in particolari e scrivere 50 righe di codice, ma ho preferito lavorare non con i programmi ma con due finestre di notepad (che poi sono finito per reversare pure quello, e poi que, ti mando il tute ;).
Ho estrapolato tutta la routine di codice, ed ho controllato ad una ad una le locazioni di memoria dove venivano salvati i files, ho verificato che le locazioni di memoria non si incrociassero, del tipo:

       mov eax, 29832382h
       mov 00401000, eax
       mov ebx, 29832782h
       mov 00401003, ebx               <- attenti a quest'ultima

come vedete ebx poi va a sovrascrivere una parte della locazione precedente, che necessita di 4 bytes e se vedete, la distanza tra la prima e la seconda locazione è 3 bytes!

Questi giochetti fortunatamente Què non li ha inseriti. Poi vedendo tanti nop nel codice mi sono preoccupato che non ci fosse SMC, o routines che copiavano codice lì dentro. E l'insicurezza è sparita leggendo in un altro tute un commento di Què che diceva di non avere inserito lui quei nop ma il il compilatore, per non incasinare il processore.

Così dopo questi controlli, niente packer e niente trucchetti descritti sopra, ho inserito delle variabili come key1, key2 etc. al posto delle locazioni di memoria, ho sostituito i salti di alcuni cicli inserendo While (così si leggono meglio) e ho messo tutto nella prima finestra di notepad. Ecco tutta la prima finestra ben commentata (poi lavoreremo solo con la seconda finestra):


<<<<<<<<<< FIRST WINDOW OF NOTEPAD >>>>>>>>>>

Il numero seriale deve essere di 16 caratteri

variabili:
key1 = 004020CC                        <- i primi 4 caratteri del seriale al contrario, cioè ABCD diventa DCBA
key2 = 004020D0                        <- 4 caratteri del seriale a partire dal 5°, messi al contrario
key3 = 004020D4                        <- 4 caratteri del seriale a partire dal 9°, messi al contrario
key4 = 004020D8                        <- gli ultimi 4 caratteri del seriale, messi al contrario

Ovviamente key1, key2, key3 e key4 sono le parti spezzettate del nostro seriale.

temp1 = 0040145D
temp2 = 00401461
play1 = 00401465
play2 = 00401469
play3 = 0040146D
play4 = 00401471
temp3 = 00401475
temp4 = 00401479

start:
       mov eax, key1
       imul eax, 93572678
       mov ebx, key2
       imul ebx, 26547305
       mov ecx, key3
       imul ecx, 37569814
       mov edx, key4
       imul edx, 89653715
       add eax, ebx
       add eax, ecx
       add eax, edx
       push eax                        <- da qui in poi lo stack non viene usato
       mov temp1, eax                  <- temp1 non viene più richiamata da nessuna parte
       xor eax, eax                    <- eax viene azzerato!!! Il codice sopra non serve!
       mov eax, key1                   <- il calcolo del seriale inizia da qui
       add eax, key2
       mov temp2, eax                  <- temp2 = key1 + key2
       mov eax, key3
       add eax, key4
       mov temp3, eax                  <- temp3 = key3 + key4
       mov eax, temp2
       dec eax                         <- occhio eh! E' come se sottraesse 1 a temp2 (vedi sotto)
       mov ebx, temp3                  <- mette temp3 in ebx
       dec temp3                       <- non serve, temp3 non verrà più richiamato!
       imul eax, ebx                   <- moltiplica (temp2 - 1) * temp3
       mov temp4, eax                  <- mette tutto in temp4
       call elabora

       call ExitProcess


elabora PROC

       cmp eax, FFFFFFFF               <- confronta se eax è -1, perché la imul di sopra era signed!
       jg otherop
       nop
       nop
       nop
       nop
       neg eax                         <- se è min o uguale a 0, moltiplica per -1

otherop:
       mov ecx, 14
       cdq                             <- trasforma una dword in una qword (quadword)
       idiv ecx                        <- divide eax per 14 (ahaha gioco di parole... divide "per" :)

While (eax!=0) {
       mov ebx, key1                   <- completamente obsoleto da qui...
       imul ecx, ebx
       mov ecx, key3                   (forse Què non voleva usare Sleep per ritardare il calcolo =)
       imul ecx, ecx                   <- ...a qui; perché ogni ciclo sovrascrive sia ebx che ecx
       dec eax
       rol key1, 05                    <- qui la cosa è importante... rotate left di 5 bit della key1
       ror key3, 04                    <- rotate "right" della key3 di b bit a destra.
}

       cmp temp4, FFFFFFFF
       jg sub_and_check                <- solito check del -1, insomma què vuole num >= a 0
       nop
       nop
       nop
       nop
       neg temp4


sub_and_check:
       sub temp4, 0321CCFA             <- attenti sottrae 0321CCFA a temp4
       cmp temp4, FFFFFFFF             <- e verifica se siamo andati sul negativo :)
       jg passa_ad_algoritmo
       nop
       nop
       nop
       nop
       neg temp4


passa_ad_algoritmo:
       call algoritmo                  <- ora inizia l'ultima parte
       ret

elabora ENDP


algoritmo PROC                         <- Da qui in poi lasciate perdere le variabili play, sono
                                          solo trucchi... appunto "play" giochi.
       mov eax, key1                   <- modifica "il valore" della key1
       call chiamata1
       call chiamata2
       sub key1, ebx                   <- e dopo aver elaborato lo sottrale alla key1 stessa
       mov eax, key1                   <- falso!
       mov play1, eax                  <- falso! Le play non vengono mai usate!
       mov eax, key2                   <- come sopra, ma usa la key2
       call chiamata1
       call chiamata2
       sub key2, ebx
       mov eax, key2                   <- falso!
       mov play2, eax                  <- falso!
       mov eax, key3                   <- come sopra, ma usa la key3
       call chiamata1
       call chiamata2
       sub key3, ebx
       mov eax, key3                   <- falso!
       mov play3, eax                  <- falso!
       mov eax, key4                   <- come sopra, ma usa la key4
       call chiamata1
       call chiamata2
       sub key4, ebx
       mov eax, key4                   <- falso!
       mov play4, eax                  <- falso!
       call check_seriale              <- inizia il confronto!
       ret

algoritmo ENDP


chiamata2 PROC

       mov ebx, temp4                  <- ricordo che temp4 = (key1 + key2 -1) * (key3 + key4)

While (ebx > 255) {
       sub ebx, 000000FF               <- in pratica sarebbe (ebx mod 255)
}

       cdq
       idiv ebx                        <- divide eax per il valore ottenuto con il mod
       imul eax, temp4                 <- e moltiplica il tutto per temp4
       mov ebx, eax                    <- ecco il valore che esce dalla seconda elaborazione
       ret

chiamata2 ENDP


chiamata1 PROC

       imul eax, eax                   <- eleva al quadrato la key in eax
       cmp eax, FFFFFFFF               <- solito check!
       jg esci
       nop
       nop
       nop
       nop
       neg eax

esci:
       ret

chiamata1 ENDP


check_seriale PROC
                                       Non mi fate spiegare pure qui... verifica se i valori
       mov eax, key1                   delle key elaborate corrispondono a quei valori!
       cmp eax, 0C185C58
       jne seriale_errato
       nop
       nop
       nop
       nop
       mov eax, key2
       cmp eax, 55F2600F
       jne seriale_errato
       nop
       nop
       nop
       nop
       mov eax, key3
       cmp eax, 35CE68B2
       jne seriale_errato
       nop
       nop
       nop
       nop
       mov eax, key4
       cmp eax, 1F3EECFA
       jne seriale_errato
       nop
       nop
       nop
       nop

* Possible StringData Ref from Data Obj ->"Bravo!"

{...}

       ret

check_seriale ENDP

<<<<<<<<<< EOF-IRST WINDOW OF NOTEPAD >>>>>>>>>>


Adesso possiamo estrapolare dall'analisi, in formule matematiche quello che fa il programma nel dettaglio:


<<<<<<<<<< SECOND WINDOW OF NOTEPAD >>>>>>>>>>

temp2 = key1 + key2 - 1
temp3 = key3 + key4
temp4 = temp2 * temp3 ;fino ad elabora


temp4 = eax

if (eax <= 0) { neg eax }

eax = eax / 14h


k = eax * 5

rol key1, k

(il ciclo, per eax volte, deve fare rol 5. Bene 5 * eax e ti dice quanti bit scorre)

k = eax * 4

ror key3, k

if (temp4 <= 0) { neg temp4 } ;obsoleto secondo me

temp4 = temp4 - 0321CCFA

if (temp4 <= 0) { neg temp4 }

eax = key1 * key1 (sarebbe key1 ^ 2)

if (eax <= 0) { neg eax }

ebx = temp4
ebx = ebx - ff fin quando ebx è > ff

quindi ebx = temp4 mod 255

key1 = key1 - ((eax/ebx) * temp4)



eax = key2 * key2 (sarebbe key2 ^ 2)

if (eax <= 0) { neg eax }

ebx = temp4
ebx = ebx - ff fin quando ebx è > ff

quindi ebx = temp4 mod 255

key2 = key2 - ((eax/ebx) * temp4)


eax = key3 * key3 (sarebbe key3 ^ 2)

if (eax <= 0) { neg eax }

ebx = temp4
ebx = ebx - ff fin quando ebx è > ff

quindi ebx = temp4 mod 255

key3 = key3 - ((eax/ebx) * temp4)



eax = key4 * key4 (sarebbe key4 ^ 2)

if (eax <= 0) { neg eax }

ebx = temp4
ebx = ebx - ff fin quando ebx è > ff

quindi ebx = temp4 mod 255

key4 = key4 - ((eax/ebx) * temp4)


key1 == 0C185C58 ?
key2 == 55F2600F ?
key3 == 35CE68B2 ?
key4 == 1F3EECFA ?

<<<<<<<<<< EOF SECOND WINDOW OF NOTEPAD >>>>>>>>>>


Adesso apriamo una terza finestra, ma nella nostra testa!
Ho letto negli altri tutorial che sul forum o in mailing list (non ricordo) si parlava del corso (non ho potuto seguire nemmeno quello che si diceva) e qualcuno parlava di collisioni di algoritmo di cifratura.
Non ho fatto nè una ricerca nè so cos'è, ma ho ipotizzato che la mia soluzione di questo corso è appunto la collisione, ossia trovare una chiave alternativa che rincretinisca l'algoritmo (potrebbe anche essere la chiave giusta eh!).

Innanzitutto devo fare delle considerazioni su alcune cose:

Quando Què disse nel tutorial che non aveva avuto il tempo per crackare il corso, significa che non ha scelto un seriale per com'è fatto l'algoritmo, ma ha preso un seriale qualsiasi dalla sua testa di Què (eheheh) e glielo ha sparato dentro, ha debuggato i valori finali di un algoritmo preso chissà dove, ha aggiunto i trucchetti ed ha messo il confronto alla fine :)

E poi le istruzioni:

 rol eax, 5
 ror eax, 4

garantiscono proprio la casualità dei numeri. Perché se fossero state tutte e due "rol" allora si sarebbe ipotizzato una ruotazione diversa di eax bit della key3... ma mettere una a destra e l'altra a manca, vuole proprio qualcosa di casuale. Questo è quello che in 3 giorni di studio dedicate interamente a questo corso (credetemi) mi ha portato a fare. Avevo abbandonato tutto quando nell'intento di trovare info sull'algoritmo ZIP del corso 12 (Què prometto che ci lavoro dopo questo tutorial), mi imbatto in un sito dove spiega la codifica del PGP, quindi fa riferimento anche all'RSA!

Vi riporto le parole (sono in italiano):

<-------------------------------------------------------------------->

In questo tipo di cifrari hanno grande importanza le cosidette funzioni unidirezionali: si tratta di funzioni invertibili tali che il calcolo della funzione diretta sia facile, mentre quello della inversa sia difficile per tutti coloro che non sono in possesso della chiave corretta. Un esempio di tale funzione è il logaritmo in base B dove l'inverso è appunto l'esponenziale. Se la base B è un numero ragionevolmente piccolo, il calcolo sia della funzione diretta che della inversa non risulta particolarmente difficile; diversa sarebbe la situazione se il modulo o base fosse un numero primo enorme, poiché la complessità di calcolo sarebbe a dir poco proibitiva.

<-------------------------------------------------------------------->

Potete leggere il testo qui: http://telemat.det.unifi.it/book/1997/cryptography/cryasy2.htm#rsa

Ebbene quindi si dovrebbe lavorare o con i plain text attack o... con quella famigerata complessità di calcolo proibitiva... in effetti il metodo che propongo è matematicamente lungo e complesso, ma teoricamente dovrebbe funzionare. Si ma voi direte... quello è l'RSA... che c'entra?
Ve lo faccio vedere subito, ecco quali sono i primi 3 punti (sono 5 in tutto) dell'RSA:

1.Trovare due numeri primi molto grandi P e Q tale che il prodotto o modulo è detto N=PQ;

2.Scegliere E minore di N tale che sia primo con (P-1)(Q-1), il che significa non avere fattori primi in comune. E deve essere dispari. (P-1)(Q-1) non può essere primo perché è pari.

3.Calcolare l'inverso di E, D, tale che DE=1modulo(P-1)(Q-1);
N.B. Il modulo esegue una operazione di divisione intera tra DE e (P-1)(Q-1) con resto 1.

mmmm... non vi torna qualcosa alla mente? Tralasciando i numeri molto grandi e i numeri primi... temp4 = (key1 + key2 "-1") * (key3 + key4) ???
Modulo di temp4?

Sembra l'RSA leggermente modificato con numeri più piccoli ma con addizioni, sottrazioni etc... e ROL, ROR RAR ZIP etc.!!! Che volevi farci forzare qualcosa di inforzabile? Pure col corso 12... 12 è multiplo di 6, sempre crittografia... vorresti diventare un crittografo??? Mmha... Quecrittografo... non ti ci vedo :P Hihihi si cmq l'algo e' un hash che ho creato sull'impronta dell'RSA e l'ho fatto apposta per avere qualche collisione... NdQue

Comunque al di là di questo forse ho trovato una soluzione a questo corso... iniziamo!

innanzitutto dobbiamo riuscire a neutralizzare (è una figura retorica... già stavano aprendo l'hex editor pronti a noppare!) quei rol e ror... essendo funzioni periodiche, utilizzando un registro a 32bit, il numero neutralizzante è appunto il 32.

Ritorno alle scuole elementari:

MCM tra:

rol 5
ror 4
bit 32

4 = 2^2
5 = 5
32 = 2^5

MCM = 2^5 * 5 = 32 * 5 = 160

Perché questo? Semplice, perché se al momento del ciclo vi è un numero divisibile sia per 4 che per 5 che per 32, allora vediamo le due formule di prima:

k = eax * 5

rol eax, k -> k = 160 * 5 = 800

rol eax, 800 -> 800 / 32 = 25 ehehehe il primo lo abbiamo fregato! :)

eax si fa 25 giretti di bit e poi torna come prima. Vediamo se funge anche col 4?

k = 160 * 4 = 640

rol eax, 640 -> 640 / 32 = 20

wow funge!

ok però c'è un problema, prima dei rol, il temp4 viene diviso per 14h... quindi, temp4 deve avere un numero che divisibile per 14h dia 160 (decimale). Allora:

14h = 20 in decimale

160 * 20 = 3200

temp4 deve essere multiplo di 3200.

3200 = (key1 + key2 - 1) (key3 + key4)

Ok. Ipotizzando che il seriale sia senza simboli, numerico e case sensitive, l'intervallo di valori inseribili e possibili nella textbox sono compresi tra i valori ascii:

48 e 90 (decimali) e sono numeri e lettere maiuscole... poi abbiamo:

da 97 a 122 che sono le lettere minuscole. Vabbè ja inseriamo anche i simboli che stanno al centro, che non sono tanti quindi abbiamo:

tra 48 e 122 che in hex sono: 30 e A7

quindi inserendo i valori tutti caratteri 0, si avrà 30303030, e inserendo tutti i valori z invece A7A7A7A7. Il numero più grande che temp4 potrà assumere è:

temp4 = ((A7A7A7A7 *2)-1) * (A7A7A7A7 *2)

perché faccio questo? Perché voglio trovare un numero compreso nell'intervallo dei caratteri ascii, che se gli sottraiamo -1 e lo eleviamo al quadrato sia divisibile per 3200... semplice algoritmo sfruttando il numero di una sola delle 4 key (una qualsiasi):

A7A7A7A7 -
30303030 =
-----------
77777777

77777777 = 2004318071 in decimale

il numero che devo trovare è la somma di due key "momentaneamente nel calcolo uguali" (dopo chiarisco):

0x30303030 * 2 = 0x60606060

for(i=0x60606060; i< 0x7777777; i++) {

a = (i-1) * i

s = a mod 3200

if s = 0 { inserisci numero in una lista }

}

dovrebbe essere abbastanza veloce e dovremmo ottenere circa un numero per ogni 100 e passa combinazioni.

A questo punto questi numeri sono divisibili sia per 14, per 32, per 4 e per 5 e oltretutto sono compresi in un intervallo ascii alfanumerico (e comprendono anche simboli alcuni :)

At this point (come dice la mia proff d'inglese) ogni numero trovato ha questa filosofia:

(esempio) temp4 = (30303030 + 30303030 - 1) (30303030 + 30303030)

sintetizzando:

j = key1 + key2

temp4 = (j-1) * j

temp4 = j^2 - j

alla fine poi vedremo che se il quadrato di j è:

16, non è detto che la key sia 8 + 8... perché può essere:

1 + 15
15 + 1 (da notare la posizione delle key influisce sul codice)
2 + 14
14 + 2 etc... l'importante è che al momento abbiamo un neutralizzatore della divisione per 14h, e dei ro(l/r).

bene ora passiamo alla parte più rompicoglioni dell'algoritmo:

-------------------------->
temp4 = temp4 - 0321CCFA
-------------------------->

per ogni key la situazione deve essere:

key = key - (key^2/temp4 mod 255) * temp4

if (key = que_code) { giusto } else { vai_a_cagare_tu_e_il_tuo_tute }

bene qui arriviamo ad un equazione dove abbiamo una lista di valori di temp4, quindi per ogni temp4 dobbiamo provare l'equazione:

k = temp4 - 0321CCFA (k è la costante)

a - (a^2/k mod 255) * k = que_code

la sola incognita è che potrebbe essere qualsiasi delle 4 key (a patto che ad ogni key per cui si fa l'equazione, ci sia il que_code corrispondente).
Non mi dite che non avete capito cos'è il que_code... il que_code è il valore del confronto della parte finale dell'algoritmo:

       mov eax, key2
       cmp eax, 55F2600F
       jne seriale_errato

una volta trovato ad esempio il valore di key1 per quest'equazione, la cosa diventa:

key2 = rad(temp4)+1 - key1

cosa diversa per key3 e 4... una volta trovato key3 la formula diventa:

key4 = rad(temp4) - key3

perché ho messo sopra +1? Perché l'algo del crackme alla somma key1key2 sottrae -1 e poi moltiplica per la somma delle altre due key, quindi temp4 è un quadrato di un numero che è la somma di due key. Il quadrato fa questo:

(key1 + key2 - 1) = (key3 + key4)

key1 + key2 - 1 = j

key3 + key4 = j

temp4 = (key1 + key2 - 1) * (key3 + key4)

quindi temp4 = j * j = j^2

rad(j^2) ci da (key3 + key4)

quindi j = (key3 + key4) formula inversa key4 = j - key3

ok poi se j = (key1 + key2 - 1)

formula inversa:

key2 = j - key1 + 1

Forse questo metodo dovrebbe essere applicato con una variante, perché potrebbe dare disturbo questa istruzione:

temp4 = temp4 - 0321CCFA

dovrebbero essere inseriti in quella lista di prove, anche tutti gli stessi numeri ma con aggiunto il numero 0321CCFA. Allora si che l'algo sarebbe perfetto!

Ah dimenticavo... se dopo trovate le chiavi, esse sono per esempio: ABCD FGHI JKLM NOPQ il seriale sarà: DCBA IHGF MLKJ QPON cioè ogni key al contrario... perché? Studiatevi GetWindowTextA e monitorate i caratteri quando inserite il seriale e passano dalla memoria ai registri- Fiuuuu... finalmente è finito :)


                                                                                                                 bye by Artemis 23am

Note finali

Un enorme ringraziamento va a DIO, Gesù, la Madonna, tutti i santi e il cielo in generale (non è una bestemmia idioti! Sono molto religioso!) perché mi accompagnano da quando sono nato e il saluto va anche a tutti i dardi della UIC, e... alla piccola operazione (estrazione di un neo all'addome) che ho subito, che mi ha permesso di stare un bel pò di giorni a casa e quindi mi ha dato modo di scrivere questo tutorial, senza pensare alla scuola!