WinZip 10.0 Keygenning
(RSA Defeating)

Data

by "Graftal"

 

gg/mese/aaaa

UIC's Home Page

Published by Quequero

Because the music do!

Graft, bravo come al solito, grazie mille!

Because the music do!

....

E-mail: [email protected]

Graftal @ irc.azzurra.org #crack-it #cryptorev #asm

....

Difficoltà

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

 

Introduzione

Salve a tutti. In questo tutorial ci occuperemo di un programma storico quale WinZip: arrivato alla decima incarnazione, i programmatori hanno deciso di cambiare (finalmente) la routine

di protezione: ed è qui che entriamo in gioco noi. Sedetevi comodi e godetevi il viaggio. =)

Allegato

Tools usati

Tool usati:

OllyDbg

Visual Studio 2003 + OpenSSL per codare il keygen.

RSA Tool v2 reperibile facilmente su goooogle =)

URL o FTP del programma

Beh non ci va tanta fantasia per capirlo.

Essay


Benissimo, cominciamo subito ad aprire il programma con ollydbg e notare che non è né packato né niente. La nag ci accoglie in modo a dir poco caloroso, e noi ricambiandolo andiamo su Enter Registration Code e inseriamo Graftal come username e 1234567890 come serial. Soliti bp quali GetWindowText e GetDlgItemText e poi facciamo OK. Il secondo di questi bp ci fa atterrare alla zona di codice seguente:

004136FF  |> PUSH 101                                 ; /Count = 101 (257.); Case 1 of switch 0041368F

00413704  |. MOV EDI,WINZIP32.0054F350                ; |ASCII "Graftal"

00413709  |. PUSH EDI                                 ; |Buffer => WINZIP32.0054F350

0041370A  |. PUSH 0C80                                ; |ControlID = C80 (3200.)

0041370F  |. PUSH EBX                                 ; |hWnd

00413710  |. CALL DWORD PTR DS:[<&USER32.GetDlgItemTe>; \GetDlgItemTextA

------------------- ~snip~ ------------------

00413724  |. PUSH 27                                  ; /Count = 27 (39.)

00413726  |. MOV ESI,WINZIP32.0054F454                ; |ASCII "1234567890"

0041372B  |. PUSH ESI                                 ; |Buffer => WINZIP32.0054F454

0041372C  |. PUSH 0C81                                ; |ControlID = C81 (3201.)

00413731  |. PUSH EBX                                 ; |hWnd

00413732  |. CALL DWORD PTR DS:[<&USER32.GetDlgItemTe>; \GetDlgItemTextA

Ciò che a noi realmente interessa qui sono le variabili Count che ci possono dare una POSSIBILE indicazione anche sulla lunghezza del serial. Di sicuro poi ci servono per fare il keygen, se non altro per quanto riguarda la lunghezza massima del nickname. Adesso ci tocca trovare il punto di check del serial: facciamo però attenzione qua, siccome il programma è molto dispersivo e ci ho messo un attimino a trovare la giusta locazione da andare a studiare e reversare. La cosa migliore da fare in questi casi, e steppare oltre tutto e tutti, per vedere se ci sono qualche variabili o qualche check che possono attirare la nostra attenzione: a quel punto, andremo più nel dettaglio. Da dove siamo ora nel codice, cominciamo a steppare fino ad arrivare ad un codice simile a questo:

0041383D  |. CALL WINZIP32.004133BB

00413842  |. TEST AL,AL

00413844  |. POP ECX

00413845  |. JE WINZIP32.004138E3                    ; salta molto più giù mostrandoci la beggar off

0041384B  |. PUSH EDI

0041384C  |. PUSH WINZIP32.0051A8F4                   ;  ASCII "Name1"

00413851  |. MOV EDI,WINZIP32.0051A808                ;  ASCII "WinZip"

00413856  |. PUSH EDI

00413857  |. CALL WINZIP32.00499F4B

0041385C  |. PUSH ESI

0041385D  |. PUSH WINZIP32.0051A8F0                   ;  ASCII "SN1"

00413862  |. PUSH EDI

00413863  |. CALL WINZIP32.00499F4B

00413868  |. CALL WINZIP32.00412B2A

0041386D  |. PUSH WINZIP32.0051A810                   ;  ASCII "winzip32.ini"

00413872  |. PUSH 0

00413874  |. PUSH 0

00413876  |. PUSH WINZIP32.0051A830                   ;  ASCII "rrs"

Possiamo facilmente intuire che il grosso del check DOVREBBE avvenire nella call che ho sopra scritto: infatti quel je nel caso dei dati che abbiamo immesso salta e quindi il nome e il serial non vengono salvati, se non altro non come verrebbero salvati dei serial corretti. Facciamo F9 e rifacciamo OK, steppiamo e questa volta arrivati alla call 004133BB (quella qua sopra insomma) steppiamoci dentro con F7. A questo punto ci ritroviamo di fronte una marea di codice, per la maggior parte assolutamente inutile. Come capirlo? Beh, è importante steppare oltre tutto il codice la prima volta per poi trovare il posto in cui andare a “scavare” =). Facendo cosi, arriviamo dopo un po’ a queste istruzioni:

00413514  |. PUSH EDI                                 ; /String

00413515  |. CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \lstrlenA

0041351B  |. CMP EAX,2

0041351E  |. JL SHORT WINZIP32.0041352B

00413520  |. CMP EAX,100

00413525  |. JG SHORT WINZIP32.0041352B

Altra cosa da ricordarsi codando il keygen: la lunghezza dell’username deve essere compresa tra 2 e 0x100. Se continuiamo a steppare noteremo comparire anche il nostro serial number, e poi basta, la call finisce. Adesso ci tocca trovare la giusta call da steppare e analizzare. Una prima scelta da fare, potrebbe essere quella di analizzare questa call:

0041348E  |. MOV EDI,WINZIP32.0054F350                ;  ASCII "Graftal"

00413493  |. LEA EAX,DWORD PTR SS:[EBP-438]

00413499  |. MOV ECX,EDI

0041349B  |. CALL WINZIP32.004124D2

Poiché il nostro username lo troviamo in giro, è possibile che nella call subito sotto, o in quelle ad esso successive, siano presenti istruzioni interessanti da analizzare. La prima call, tuttavia, anche solo ad un colpo d’occhio molto superficiale, ci fa capire che là dentro non accade niente di importante. Nella call dopo, troviamo:

004E6980  /MOV EAX,DWORD PTR DS:[ECX]

004E6982  |MOV EDX,7EFEFEFF

004E6987  |ADD EDX,EAX

004E6989  |XOR EAX,FFFFFFFF

004E698C  |XOR EAX,EDX

004E698E  |ADD ECX,4

004E6991  |TEST EAX,81010100

004E6996  |JE SHORT WINZIP32.004E6980

004E6998  |MOV EAX,DWORD PTR DS:[ECX-4]

004E699B  |TEST AL,AL

004E699D  |JE SHORT WINZIP32.004E69D1

004E699F  |TEST AH,AH

004E69A1  |JE SHORT WINZIP32.004E69C7

004E69A3  |TEST EAX,0FF0000

004E69A8  |JE SHORT WINZIP32.004E69BD

004E69AA  |TEST EAX,FF000000

004E69AF  |JE SHORT WINZIP32.004E69B3

004E69B1  \JMP SHORT WINZIP32.004E6980

Dopo aver reversato per un po’ di tempo, si comincia a capire che valori quali 0x7EFEFEFF e simili, soprattutto in questi contesti, sono call inutili ai nostri fini. Ciò è un ottimo criterio per eliminare un po’ delle call che si incontrano nel cammino =). Detto questo (trovo importante spiegare certi modi con cui “eliminare” il junk code, soprattutto perché è uno dei motivi che inducono il reverser a lasciar perdere il programma, o che comunque gli fanno perdere una miriade di tempo), usiamo un secondino e per pochissimo il cervello: se il check della lunghezza dell’username immesso è dopo, è altamente probabile che tutto il codice che lo precede non serva. Supposto ciò, saltiamo tutto fino ad arrivare all’strlen che vi avevo scritto prima e in particolare, quando siamo su questa call, senza fare step-into, vediamo quanto vale eax (ovvero uno dei parametri pushati alla call):

00413532  |. PUSH 0

00413534  |. PUSH EAX

00413535  |. CALL WINZIP32.004E68F0      ; questa call

-------------------- ~snip~ --------------------------

00413554  |> MOV ESI,12C

00413559  |. PUSH ESI

0041355A  |. LEA EAX,DWORD PTR SS:[EBP-144]

00413560  |. PUSH EAX

00413561  |. PUSH EDI

00413562  |. CALL WINZIP32.00411EA8            ; poi analizziamo questa

EAX vale “bcom”, stringa che troviamo relativamente spesso in queste istruzioni: non facciamoci ingannare, non serve a niente sta stringa => non serve a niente nemmeno la call. Se volete analizzarla bene, ma sappiate che questo è uno dei modi in cui il programma ci fa perdere tempo prezioso: dobbiamo essere rapidi a trovare l’algo, altrimenti ci si annoia e si lascia perdere. E ciò non va bene =). Tuttavia, la seconda call già contiene qualcosa che può attirare l’attenzione: dando uno sguardo d’insieme, notiamo anche una costante, 0x1021. Se qualcuno di voi aveva già reversato il winzip 8 o il 9, avrà sicuramente notato che questa era la mask usata dal programma per poi generare il serial. Già qui dovrebbe accendersi una lampadina.. però se uno non ne sa niente della tecnica di protezione delle versioni vecchie come fa a capire? No problem, steppiamo nella call e vediamo che serial viene generato:

---------- ~snip dei loop precedenti, ci interessa solo il serial già generato e completato -------------------

00411EF9  |. PUSH ECX                                 ; /Arg5

00411EFA  |. MOVZX EAX,AX                             ; |

00411EFD  |. PUSH EAX                                 ; |Arg4

00411EFE  |. PUSH WINZIP32.0051D9E8                   ; |Arg3 = 0051D9E8 ASCII "%04X%04X"

00411F03  |. PUSH DWORD PTR SS:[EBP+10]               ; |Arg2

00411F06  |. MOV BYTE PTR DS:[54F4C8],1               ; |

00411F0D  |. PUSH DWORD PTR SS:[EBP+C]                ; |Arg1

00411F10  |. CALL WINZIP32.0050755E                   ; \WINZIP32.0050755E

---------- ~snip~ ------------------------

Ed ecco nello stack, subito dopo la call di cui (nota per i più sgrammaticati: non è un errore, “cui” non “qui”) sopra – che in realtà è un semplicissimo sprintf – troviamo il nostro bel serial:
"56AE08A3"

Che effettivamente è il mio serial per le versioni precedenti di winzip.. se non lo si sapesse, si potrebbe sempre provare a inserirlo come serial per ottenere una bella beggar off, che magari ti dice anche che il tuo serial è vecchio e che dovresti aggiornarlo per la versione 10 del programma. Usciti dalla call, vediamo che alcune cosette vengono fatte al serial generato poco fa, e altre call si susseguono, inutili, le une dopo le altre, fino ad arrivare finalmente al nostro serial:

004135CB  |. MOV EBX,WINZIP32.0054F454         ; ASCII "1234567890"

004135D0  |. PUSH EBX

004135D1  |. PUSH EDI

004135D2  |. CALL WINZIP32.004120FB            ; ci conviene dare un’occhiata qua =)

Ora, esattamente come abbiamo fatto prima con il nostro username, ora cominciamo a vedere se le call presenti subito sotto il nostro serial sono interessanti o meno. Cominciamo con la prima:

00412118  |. MOV DWORD PTR SS:[EBP-150],EAX

0041211E  |. MOV DWORD PTR SS:[EBP-14C],EAX

00412124  |. MOV DWORD PTR SS:[EBP-154],EAX

0041212A  |. MOV DWORD PTR SS:[EBP-158],0FFFF

00412134  |. CALL WINZIP32.004E8F04            ; lasciamo perdere questa call, troppo complicata =)

------------- ~snip~ ---------------------

0041214B  |> /MOVZX EAX,BYTE PTR DS:[EDI]      ; prende il primo char dell’username

0041214E  |. |PUSH EAX

0041214F  |. |CALL WINZIP32.004E768E           ; fa un qualche tipo di check che non ci interessa

00412154  |. |TEST EAX,EAX                     ; se eax == 1

00412156  |. |POP ECX

00412157  |. |JE SHORT WINZIP32.0041215E

00412159  |. |MOV AL,BYTE PTR DS:[EDI]         ; il char va bene e..

0041215B  |. |MOV BYTE PTR DS:[ESI],AL         ; ..viene storato in un’altra variabile

0041215D  |. |INC ESI

0041215E  |> |INC EDI

0041215F  |. |CMP BYTE PTR DS:[EDI],0

00412162  |.^\JNZ SHORT WINZIP32.0041214B      ; si rifà fino alla fine dell’username

00412164  |> LEA EAX,DWORD PTR SS:[EBP-148]    ; username in eax

0041216A  |. PUSH EAX

0041216B  |. MOV BYTE PTR DS:[ESI],0

0041216E  |. CALL WINZIP32.004E6950            ; ritorna strlen(username)

00412173  |. CMP EAX,12C                       ; se minore di 0x12C..

00412178  |. POP ECX

00412179  |. JB SHORT WINZIP32.0041218C        ; ..allora ok

00412192  |. PUSH EAX                          ; eax = username

00412193  |. CALL WINZIP32.00507CB3            ; CharLower(username)

--------------------- ~blabla~ ------------------

Allora questo è un po’ di codice commentato: vi ho messo solo l’inizio della call siccome è molto lunga. Se steppate anche un po’ superficialmente nel resto del codice, noterete che vi vengono riproposte altre istruzioni molto simili o uguali a quelle che abbiamo analizzato prima, mentre altre totalmente nuove e che ad una prima occhiata potrebbero sembrare importanti. Ma come facciamo a capire che la call è pressoché inutile? Diciamo che prima di tutto o ci va un po’ di culo, o si può vedere che vengono eseguiti tanti tanti calcoli, ma alla fine non compaiono più né il nostro serial, né il nostro username, né lunghezze che possano corrispondergli né niente di niente. E’ a questo punto che bisogna andare ad analizzare un’altra call per vedere se almeno là succede qualcosa: in caso contrario, la call tralasciata era probabilmente importante =). Ecco la prossima call:

004135D7  |. LEA EAX,DWORD PTR SS:[EBP-438]

004135DD  |. PUSH EBX

004135DE  |. PUSH EAX

004135DF  |. CALL WINZIP32.00411E19

Steppiamoci dentro:

00411E19  /$ PUSH EBP

00411E1A  |. MOV EBP,ESP

00411E1C  |. PUSH DWORD PTR SS:[EBP+8]

00411E1F  |. CALL WINZIP32.00507CB3                  ;  CharLower(username) è importante!

00411E24  |. POP ECX

00411E25  |. PUSH DWORD PTR SS:[EBP+C]

00411E28  |. MOV ECX,DWORD PTR DS:[55CE00]

00411E2E  |. PUSH DWORD PTR SS:[EBP+8]

00411E31  |. PUSH WINZIP32.0051D9E0                   ;  ASCII "SU_STD"

00411E36  |. CALL WINZIP32.004975F8                  ;  dobbiamo assolutamente steppare qua dentro

00411E3B  |. TEST AL,AL

00411E3D  |. JE SHORT WINZIP32.00411E48

00411E3F  |. MOV BYTE PTR DS:[54E5FB],1

00411E46  |. JMP SHORT WINZIP32.00411E79

00411E48  |> PUSH DWORD PTR SS:[EBP+C]

00411E4B  |. MOV ECX,DWORD PTR DS:[55CE00]

00411E51  |. PUSH DWORD PTR SS:[EBP+8]

00411E54  |. PUSH WINZIP32.0051D9D8                   ;  ASCII "SU_PRO"

00411E59  |. CALL WINZIP32.004975F8                  ;  e poi qua dentro

00411E5E  |. TEST AL,AL

00411E60  |. JE SHORT WINZIP32.00411E72

00411E62  |. MOV BYTE PTR DS:[54E5FB],1

00411E69  |. MOV BYTE PTR DS:[54E5FD],1

00411E70  |. POP EBP

00411E71  |. RETN

00411E72  |> MOV BYTE PTR DS:[54E5FB],0

00411E79  |> MOV BYTE PTR DS:[54E5FD],0

00411E80  |. POP EBP

00411E81  \. RETN

Bene bene bene.. SU_STD = STANDARD e SU_PRO = PROFESSIONAL.. almeno teoricamente =). Tra un po’ scopriremo se è veramente così o no.
Allora, cominciamo con lo steppare dentro alla prima call segnalata, quella subito sotto SU_STD:

-------------------- ~snip~ -------------------------

00497641  |. FF70 08        PUSH DWORD PTR DS:[EAX+8]

00497644  |. FF70 04        PUSH DWORD PTR DS:[EAX+4]            ; "-----BEGIN RSA PUBLIC KEY-----MBcCEADjFVf2ZJjxOFl+bqOr3O8CAwEAAQ==-----END RSA PUBLIC KEY-----"

00497647  |. FF37           PUSH DWORD PTR DS:[EDI]

00497649  |. E8 A9C80600    CALL WINZIP32.00503EF7

Ehi ehi, cosa vedono le mie pupille! RSA? Ma che bello! =)
Quello che viene pushato è il contenuto di un PEM Key. Per alcuni, una domanda sorgerà spontanea: ma cos’è un PEM Key? Diciamo che è un file .pem che viene usato per storare le chiavi rsa necessarie per criptare e decriptare dati. In questo caso, abbiamo un bel public key (per informazioni particolareggiate riguardo all’rsa, vi rimando al tutorial di evilcry sull’argomento, veramente bello). Teniamo quindi gli occhi aperti per eventuali funzioni inerenti RSA.
Se diamo un’occhiata all’interno della call, vedremo che la libreria del winzip chiamata wzeay32 contiene le funzioni che riguardano l’rsa, e, tanto per fare un esempio, ci sono due call a funzioni chiamate d2i_RSAPublicKey e PEM_ASN1_read_bio. Facendo una piccolissima ricerca su internet, poiché avevo notato la netta similitudine tra librerie quali libeay32 e wzeay32, si può facilmente scoprire che wzeay32 è adibito alla gestione delle funzioni OpenSSL all’interno del winzip =). Teniamolo a mente che ci servirà dopo nel fare il keygen (sebbene non sia indispensabile).

Adesso cominciamo a steppare, tenendo a mente che nel momento in cui incontriamo del codice dove viene pushata una stringa che ha come contenuto "..\..\common\WzReg.c" o qualcosa di simile, la call subito precedente è inutile e quindi non perdiamo tempo ad analizzarla. Detto questo, proseguiamo a steppare, diamo un’occhiata premendo Enter quando siamo su una call per vedere se può servire a qualcosa, e in caso contrario continuiamo fino a che non arriviamo qua:

00497760  |. PUSH EAX

00497761  |. PUSH DWORD PTR SS:[EBP-44]

00497764  |. PUSH DWORD PTR DS:[EDI]

00497766  |. CALL WINZIP32.00503FC6     ; ed ecco qua la parte importante!

Steppiamo e vediamo cosa ci aspetta:

00503FEC  |. CMP DWORD PTR SS:[EBP+C],0     ; username = 0?

00503FF0  |. JE WINZIP32.005041EB           ; se sì, beggar off

00503FF6  |. TEST BYTE PTR DS:[ESI+8],2

00503FFA  |. JE WINZIP32.00504197

00504000  |. MOV EDI,DWORD PTR SS:[EBP+14]  ; serial = 0?

00504003  |. TEST EDI,EDI

00504005  |. JE WINZIP32.005041BB           ; se sì, beggar off

0050400B  |. CMP DWORD PTR SS:[EBP+18],1E   ; lunghezza serial = 1E?

0050400F  |. JNZ WINZIP32.005041BB

Ecco qua una cosa fondamentale: il nostro serial è lungo 0x0B (perché viene contato anche lo zero finale) mentre dovrebbe essere lungo 0x1E caratteri. Facciamo F9 e inseriamo questo serial: 12345678901234567890123456789. Ok, ritorniamo dove eravamo prima, sorbiamoci un paio di altri check che vanno a buon fine, fino a raggiungere questo loop:

00504039  |> /CMP EDX,100

0050403F  |. |JNB SHORT WINZIP32.00504056

00504041  |. |MOV AL,BYTE PTR DS:[ECX+EDI]

00504044  |. |CMP AL,2D

00504046  |. |JE SHORT WINZIP32.00504050

00504048  |. |MOV BYTE PTR SS:[EBP+EDX-138],AL

0050404F  |. |INC EDX

00504050  |> |INC ECX

00504051  |. |CMP ECX,1D

00504054  |.^\JL SHORT WINZIP32.00504039

Non l’ho commentato perché c’è poco da capire: semplicemente toglie dal serial tutti i trattini che dovrebbero esserci, ma che noi ancora non abbiamo messo, e riposiziona i caratteri nella stringa in modo che non ci siano “buchi” (abbiamo appena tolto i trattini, quindi..). Tenete sempre presente che 0x2D = ‘-‘. Subito dopo questo loop, c’è una bella call che andiamo ad analizzare a fondo perché è fondamentale. Vediamo come inizia:

00504FDD  |> /MOV EAX,EDI             ; lunghezza serial senza trattini in eax

00504FDF  |. |SHL EAX,3               ; eax * 8

00504FE2  |. |PUSH 5

00504FE4  |. |CDQ

00504FE5  |. |POP ECX

00504FE6  |. |IDIV ECX                ; eax / 5 con resto in edx

00504FE8  |. |TEST EDX,EDX            ; resto = 0? (quindi eax multiplo di 5?)

00504FEA  |. |JNZ WINZIP32.005050F5   ; se no, salta e beggar off

Bene, quindi dobbiamo mettere tanti trattini finché il nostro 29 diventa un multiplo di 5. Mettiamo quindi 4 bei trattini e rendiamo così il nostro serial: 12345-78901-34567-90123-56789
Attenzione a SOSTITUIRE i caratteri che c’erano prima con i trattini, e non ad aggiungerli, se no il check che abbiamo analizzato prima dà beggar off =). Il codice che vi mostro ora è molto lungo, ma non fatevi spaventare, le cose veramente importanti le spiego là dentro e subito dopo vi chiarisco il tutto spiegandovelo senza i commenti, siccome verrebbe un pasticcio mostruoso fare le due cose assieme:

00505011  |> /MOV EAX,DWORD PTR SS:[EBP+8]

00505014  |. |MOVZX EAX,BYTE PTR DS:[EAX]

00505017  |. |PUSH EAX

00505018  |. |CALL WINZIP32.004E9127                 ; lasciamo perdere, inutile

0050501D  |. |CMP AL,41                              ; al = char del serial

0050501F  |. |POP ECX                                ; in base al carattere che trova..

00505020  |. |JNZ SHORT WINZIP32.00505026            ; ..lo lavora in modo da dargli un valore..

00505022  |. |XOR CL,CL                              ; ..in modo che questo sia sempre formato..

00505024  |. |JMP SHORT WINZIP32.00505062            ; ..da soli 5 bit significativi.

00505026  |> |CMP AL,42

00505028  |. |JBE SHORT WINZIP32.00505032

0050502A  |. |CMP AL,49

0050502C  |. |JNB SHORT WINZIP32.00505034

0050502E  |. |SUB AL,42

00505030  |. |JMP SHORT WINZIP32.00505060

00505032  |> |CMP AL,49

00505034  |> |JBE SHORT WINZIP32.0050503E

00505036  |. |CMP AL,4F

00505038  |. |JNB SHORT WINZIP32.00505040

0050503A  |. |SUB AL,43

0050503C  |. |JMP SHORT WINZIP32.00505060

0050503E  |> |CMP AL,4F

00505040  |> |JBE SHORT WINZIP32.0050504A

00505042  |. |CMP AL,53

00505044  |. |JNB SHORT WINZIP32.0050504C

00505046  |. |SUB AL,44

00505048  |. |JMP SHORT WINZIP32.00505060

0050504A  |> |CMP AL,53

0050504C  |> |JBE SHORT WINZIP32.00505056

0050504E  |. |CMP AL,5A

00505050  |. |JA SHORT WINZIP32.00505056

00505052  |. |SUB AL,45

00505054  |. |JMP SHORT WINZIP32.00505060

00505056  |> |CMP AL,30                              ;  Switch (cases 30..39)

00505058  |. |JB SHORT WINZIP32.005050D3

0050505A  |. |CMP AL,39

0050505C  |. |JA SHORT WINZIP32.005050D3

0050505E  |. |SUB AL,1A                              ;  Cases 30 ('0'),31 ('1'),32 ('2')..

00505060  |> |MOV CL,AL

00505062  |> |MOV EAX,DWORD PTR SS:[EBP+C]           ; dopodichè tutti i valori ottenuti..

00505065  |. |PUSH 8                                 ; sono “mischiati” come spiegato dopo

00505067  |. |CDQ

00505068  |. |POP EBX

00505069  |. |IDIV EBX

0050506B  |. |CMP EDX,7                              ;  Switch (cases 0..7)

0050506E  |. |JA SHORT WINZIP32.005050BF

00505070  |. |JMP DWORD PTR DS:[EDX*4+505104]

00505077  |> |SHL CL,3                               ;  Case 0 of switch 0050506B

0050507A  |. |JMP SHORT WINZIP32.005050B8

0050507C  |> |MOV AL,CL                              ;  Case 1 of switch 0050506B

0050507E  |. |SHR AL,2

00505081  |. |OR BYTE PTR DS:[ESI],AL

00505083  |. |INC ESI

00505084  |. |SHL CL,6

00505087  |. |JMP SHORT WINZIP32.005050B8

00505089  |> |SHL CL,1                               ;  Case 2 of switch 0050506B

0050508B  |. |JMP SHORT WINZIP32.005050A9

0050508D  |> |MOV AL,CL                              ;  Case 3 of switch 0050506B

0050508F  |. |SHR AL,4

00505092  |. |OR BYTE PTR DS:[ESI],AL

00505094  |. |INC ESI

00505095  |. |SHL CL,4

00505098  |. |JMP SHORT WINZIP32.005050B8

0050509A  |> |MOV AL,CL                              ;  Case 4 of switch 0050506B

0050509C  |. |SHR AL,1

0050509E  |. |OR BYTE PTR DS:[ESI],AL

005050A0  |. |INC ESI

005050A1  |. |SHL CL,7

005050A4  |. |JMP SHORT WINZIP32.005050B8

005050A6  |> |SHL CL,2                               ;  Case 5 of switch 0050506B

005050A9  |> |OR BYTE PTR DS:[ESI],CL

005050AB  |. |JMP SHORT WINZIP32.005050BF

005050AD  |> |MOV AL,CL                              ;  Case 6 of switch 0050506B

005050AF  |. |SHR AL,3

005050B2  |. |OR BYTE PTR DS:[ESI],AL

005050B4  |. |INC ESI

005050B5  |. |SHL CL,5

005050B8  |> |MOV BYTE PTR DS:[ESI],CL

005050BA  |. |JMP SHORT WINZIP32.005050BF

005050BC  |> |OR BYTE PTR DS:[ESI],CL                ;  Case 7 of switch 0050506B

005050BE  |. |INC ESI

005050BF  |> |MOV EBX,DWORD PTR SS:[EBP-4]           ;  Default case of switch 0050506B

005050C2  |. |DEC EDI

005050C3  |. |INC DWORD PTR SS:[EBP+8]

005050C6  |. |INC DWORD PTR SS:[EBP+C]

005050C9  |> |TEST EDI,EDI

005050CB  |.^\JG WINZIP32.00505011

Ok, cerchiamo di spiegare in modo organico: cosa succede all’inizio del loop? In pratica prende i caratteri dell’username e sottrae al codice ascii un valore predefinito in base al valore del carattere preso dal serial. Se per esempio il carattere è un numero (ed è quindi compreso tra 0x30 e 0x39) gli sottrae 0x1A e così via. Questo serve ad avere un valore dove i bit significativi sono massimo 5. Dopo di questo, nella seconda parte del loop se notate c’è uno switch con 8 case.. 8 come i bit che formano un byte no? =) Comunque, qua praticamente cosa fa: immaginate di eseguire la PRIMA parte del loop per tutti i caratteri del serial (quella che fa SOLO le sottrazioni), in modo da ottenere una serie di valori. Ora immaginate di mettere in un solo contenitore, diciamo una stringa per esempio, tutti gli 0 e gli 1 che formano i byte. Cosa vedreste? Beh, che i primi 3 bit di ogni byte valgono 0. A questo punto allora, il programma fa semplicemente che togliere questi 3 bit e spostare tutto in modo da avere una variabile che non contenga più questi 3 bit. Ma così quello che avevamo prima viene “distrutto” dato che ora i gruppi da 8 bit che formano 1 byte sono un insieme di bit presi ora da un carattere, ora da un altro. Tanto per essere chiari, eccovi un esempio molto semplice:

Abbiamo questi 3 byte

10011011 11110101 10111100

Tramite la prima parte del loop li trasformiamo in:

00011011 00010101 00011100

Tramite la seconda parte del loop, togliamo i 3 bit iniziali e trasformiamo il tutto in questo:

11011 10101 11100

Quindi ora il primo byte, tanto per fare un esempio, sarà:

11011101

E così via per tutti gli altri byte. Come vedete è ben diverso da quello che avevamo prima (100011011). Però i caratteri sono multipli di 5 (al contrario del mio esempio) e quindi alla fine togliendo 3 bit da ogni byte, si riesce a formare un numero “giusto” di byte (non rimangono dei bit in più che non raggiungono l’ottava insomma, come invece è accaduto nel mio esempio). Va bene, spero che sia chiaro, perché era una parte importante =) (in caso contrario rileggete il tutto o mandatemi una mail).

Eccoci arrivati ad un’altra parte importante: la generazione di un particolare byte. Fino a qui, abbiamo visto che il programma prende il nostro serial, lo modifica opportunamente per ottenere una stringa lunga 120bit (15 caratteri ASCII). Questa stringa, il nostro “serial numerico”, ha un byte finale che deve essere molto particolare. Il programma, nel pezzo di codice qui sotto, si prefigge il compito di controllare la sua effettiva validità; vediamo secondo quali parametri:

005040A5  |> /MOV EAX,DWORD PTR DS:[EDI]          ; vediamo quanto vale edi tra poco..

005040A7  |. |TEST EAX,EAX

005040A9  |. |MOV BL,1                           ; attenzione a questo mov, è da mettere nel keygen

005040AB  |. |JLE SHORT WINZIP32.005040E3

005040AD  |. |MOV DWORD PTR SS:[EBP-148],EAX

005040B3  |> |/MOV EAX,DWORD PTR SS:[EBP-140]

005040B9  |. ||PUSH 8

005040BB  |. ||CDQ

005040BC  |. ||POP ECX

005040BD  |. ||IDIV ECX

005040BF  |. ||MOV ESI,EDX

005040C1  |. ||MOV EDX,80                        ; 0x80 = 10000000 in bit

005040C6  |. ||MOV ECX,ESI

005040C8  |. ||SAR EDX,CL                        ; shr edx, cl

005040CA  |. ||TEST BYTE PTR SS:[EBP+EAX-14],DL  ; ebp+eax-14 = il nostro serial numerico

005040CE  |. ||JE SHORT WINZIP32.005040D2        ; jump se il bit = 0

005040D0  |. ||INC BL

005040D2  |> ||INC DWORD PTR SS:[EBP-140]

005040D8  |. ||DEC DWORD PTR SS:[EBP-148]

005040DE  |.^|\JNZ SHORT WINZIP32.005040B3

005040E0  |. |MOV ESI,DWORD PTR SS:[EBP+8]

005040E3  |> |MOVZX EAX,BL                     ; il numero di bit in eax

005040E6  |. |CDQ

005040E7  |. |PUSH 2

005040E9  |. |POP ECX

005040EA  |. |IDIV ECX                         ; dividilo per 2

005040EC  |. |MOV ECX,DWORD PTR SS:[EBP-144]   ; numero che vale 7 e che viene decrementato ad ogni loop

005040F2  |. |MOV AL,BYTE PTR SS:[EBP-5]       ; ultimo byte del serial numerico in al

005040F5  |. |SHL DL,CL

005040F7  |. |XOR AL,DL

005040F9  |. |TEST DL,AL                       ; (ultimo byte) and (valore di dl)

005040FB  |. |JNZ WINZIP32.0050418B            ; se salta, beggar off

00504101  |. |ADD EDI,4                        ; passa alla dword successiva

00504104  |. |DEC DWORD PTR SS:[EBP-144]       ; decrementa il numero di cui parlavo prima

0050410A  |. |CMP EDI,WINZIP32.0054B734        ; ASCII "WZRFWZUD"

00504110  |.^\JL SHORT WINZIP32.005040A5       ; quando il numero è minore di 3, smette il loop

Allora, questo come vedete è un doppio loop. Vi spiego ora come funziona. Sostanzialmente, prende il numero 0x80 (che è un numero che trasformato in binario vale 1000000) e facendo un and (‘test’) controlla se il primo bit del primo byte del nostro “serial numerico” è uguale a 1. Se lo è, il je non salta e bl, che tiene il conto di tutti i bit che valgono 1 nei primi 25 bit del serial numerico, viene incrementato. Perché ho detto i primi 25 bit? Vediamo un po’ cosa contiene edi: le prime 4 dword sono uguali a 0x19 e l’ultima a 0x14. Se sommate tutto, vedete che viene 0x78, ovvero 120. Come i nostri bit =). Da questo capiamo che il winzip conta quanti bit = 1 ci sono nei primi 25 bit del serial numerico, poi nei successivi 25 e cosi via finché non ne rimangono solo più 20. Oltretutto, quel 0x80 viene dimezzato ad ogni loop: questo perché 0x40 vale 0100000 in binario, 0x20 vale 0010000 e cosi via fino ad 1. Questo permette di controllare tutti e 8 i bit. Alla fine di tutto questo, abbiamo 5 valori (come le 5 dword contenute in edi). La seconda parte del loop ci spiega cosa fa il programma con questi 5 valori: setta prima di tutto una variabile = 7, poi prende il primo valore, lo dimezza, fa uno shl tra il resto della divisione e questo valore inizialmente uguale a 7. Il risultato viene xorato con l’ultimo byte del serial numerico e viene effettuato un and tra i due valori: se il risultato è 0, allora va bene, se è 1 salta alla beggar off. Ad ogni loop, il 7 iniziale viene decrementato.

Ok, abbiamo quasi finito, tenete duro =). Se guardiamo il resto della call in cui siamo, vediamo due chiamate interessanti: RSA_public_decrypt e SHA1.

NAME 

RSA_private_encrypt, RSA_public_decrypt - low level signature operations

SYNOPSIS 

 #include <openssl/rsa.h>

 int RSA_private_encrypt(int flen, unsigned char *from,

    unsigned char *to, RSA *rsa, int padding);

 int RSA_public_decrypt(int flen, unsigned char *from,

    unsigned char *to, RSA *rsa, int padding);

DESCRIPTION 

These functions handle RSA signatures at a low level.

RSA_private_encrypt() signs the flen bytes at from (usually a message digest with an algorithm identifier) using the private key rsa and stores the signature in to. to must point to RSA_size(rsa) bytes of memory.

padding denotes one of the following modes:

RSA_PKCS1_PADDING

PKCS #1 v1.5 padding. This function does not handle the algorithmIdentifier specified in PKCS #1. When generating or verifying PKCS #1 signatures, rsa_sign(3) and rsa_verify(3) should be used.

RSA_NO_PADDING

Raw RSA signature. This mode should only be used to implement cryptographically sound padding modes in the application code. Signing user data directly with RSA is insecure.

RSA_public_decrypt() recovers the message digest from the flen bytes long signature at from using the signer's public key rsa. to must point to a memory section large enough to hold the message digest (which is smaller than RSA_size(rsa) - 11). padding is the padding mode that was used to sign the data.

RETURN VALUES 

RSA_private_encrypt() returns the size of the signature (i.e., RSA_size(rsa)). RSA_public_decrypt() returns the size of the recovered message digest.

On error, -1 is returned; the error codes can be obtained by err_get_error(3).

Questa è la descrizione delle funzioni RSA_public_decrypt e del complementare RSA_private_encrypt (quest’ultimo lo useremo nel keygen). Quindi il nostro famoso “serial numerico” deve essere un valore ritornato da RSA_private_encrypt, dopodichè il programma provvederà a decriptarlo e a ottenere un numero. Vediamo tutto questo, e vediamo pure tra poco cosa se ne fa di questo numero:

00504112  |. |PUSH 3

00504114  |. |PUSH DWORD PTR DS:[ESI+4]

00504117  |. |LEA EAX,DWORD PTR SS:[EBP-24]

0050411A  |. |PUSH EAX                         ; valore di ritorno, facciamo un Follow in dump su EAX

0050411B  |. |LEA EAX,DWORD PTR SS:[EBP-14]

0050411E  |. |PUSH EAX

0050411F  |. |PUSH 0F

00504121  |. |POP ESI

00504122  |. |PUSH ESI

00504123  |. |CALL DWORD PTR DS:[55EF28]       ; wzeay32.RSA_public_decrypt

Se avete fatto il Follow un dump, tenete presente il valore della prima dword, perché la ritroviamo a breve..

Andando poco più in giù nel listato, c’è la funzione di hashing SHA1. Fossi in voi, avrei già intuito cosa sta per succedere, ma non vi rovino ancora la sorpresina :P.

SYNOPSIS 

#include <openssl/sha.h>
 unsigned char *SHA1(const unsigned char *d, unsigned long n,
                  unsigned char *md);
 
DESCRIPTION

SHA1() computes the SHA-1 message digest of the n bytes at d and places it in md (which must have space for SHA_DIGEST_LENGTH == 20 bytes of output). If md is NULL, the digest is placed in a static array.

Ora che sappiamo che parametri prende la funzioni di hash, vediamo il disasm:

00504140  |. |PUSH EAX                         ; variabile dove contenere l’hash

00504141  |. |PUSH DWORD PTR SS:[EBP+10]       ; lunghezza dell’hash (7 nel caso di Graftal)

00504144  |. |PUSH DWORD PTR SS:[EBP+C]        ; stringa da hashare (graftal in LOWERCASE mi raccomando)

00504147  |. |CALL DWORD PTR DS:[55EF20]       ; call a SHA1 hash

0050414D  |. |PUSH ESI

0050414E  |. |LEA EAX,DWORD PTR SS:[EBP-14]

00504151  |. |PUSH 0

00504153  |. |PUSH EAX

00504154  |. |CALL WINZIP32.004E68F0          

00504159  |. |PUSH 0E                         

0050415B  |. |LEA EAX,DWORD PTR SS:[EBP-38]

0050415E  |. |PUSH EAX

0050415F  |. |LEA EAX,DWORD PTR SS:[EBP-13]

00504162  |. |PUSH EAX

00504163  |. |CALL WINZIP32.004E7E20          

00504168  |. |PUSH ESI

00504169  |. |LEA EAX,DWORD PTR SS:[EBP-24]

0050416C  |. |PUSH EAX

0050416D  |. |LEA EAX,DWORD PTR SS:[EBP-14]

00504170  |. |PUSH EAX

00504171  |. |CALL WINZIP32.004E95C0           ; qui c’è il check vero e proprio

Tutto ciò non era proprio fondamentale, la parte importante la troviamo nella call che vi ho segnato e che ora andiamo ad analizzare, anche se non molto a fondo, siccome è inutile:

----- ~ snip ~ -----

004E961D  |. REPE CMPS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] ; esi = hash del nostro username, edi = hash ricavato dal serial

004E961F  |. JE SHORT WINZIP32.004E9648                      ; se sono uguali, salta e il serial è corretto, olè!

----- ~ snip ~ -----

Ehi ehi ehi! Che cosa vedo! =)

In edi è presente il valore trovato dalla funzione RSA_public_decrypt! Ma che bello eh? Ecco svelato il “segreto”: se la funzione RSA ritorna l’hash del nostro username, siamo registrati =).

Va bene, noi abbiamo parlato tanto di RSA ma.. non pensate manchi qualcosa ancora per completare il quadro? Eh già, e le chiavi? Dove le mettiamo? Quali sono? =). Tutte domande a cui risponderò a breve. Per prima cosa, vi ricordate che il programma aveva due modalità di registrazione, la SU_STD e la SU_PRO? Ecco, andiamo a vedere che chiave usa SU_STD e SU_PRO nella funzione RSA_public_decrypt:

----- ~ snip ~ -----

00504112  |. 6A 03          |PUSH 3

00504114  |. FF76 04        |PUSH DWORD PTR DS:[ESI+4]       ; Follow in dump su questa variabile

00504117  |. 8D45 DC        |LEA EAX,DWORD PTR SS:[EBP-24]

0050411A  |. 50             |PUSH EAX

0050411B  |. 8D45 EC        |LEA EAX,DWORD PTR SS:[EBP-14]

0050411E  |. 50             |PUSH EAX

0050411F  |. 6A 0F          |PUSH 0F

00504121  |. 5E             |POP ESI

00504122  |. 56             |PUSH ESI

00504123  |. FF15 28EF5500  |CALL DWORD PTR DS:[55EF28]      ; RSA_public_decrypt

----- ~ snip ~ -----


Siccome è una strutture di bignum, può confondere un po’ le idee all’inizio, ma guardando poco più in basso al nostro bel Follow in dump possiamo notare una bella fila di numeri lunghi 0x0F che sono pure puntati poco più in alto nella struttura. Guarda caso la funzione prende 0x0F byte.. Sarà un caso? Naa :P. Segnatevi, ovviamente al contrario, quei byte e ecco qua la prima chiave, il nostro N per il SU_STD: E31557F66498F138597E6EA3ABDCEF.

Ripetendo la stessa cosa per il SU_PRO: 9D845872E117501775DD51D9044945.

Adesso usate l’RSA Tool v2 (se non l’avete fate un bel google e trovatelo, è relativamente conosciuto) e ditegli di trovare p e q, senza preoccuparvi dell’esponente che è il solito 0x10001. Vi scrivo qua tutti i dati:

SU_STD:

n = E31557F66498F138597E6EA3ABDCEF

p = EC5A57BA9E77CFB

q = F5F5BE226C8F59D

e = 0x10001

d = 16B67CCCD53502F0870D775052FB49

SU_PRO:

n = 9D845872E117501775DD51D9044945

p = C2C17D26DBC6E83

q = CF0D008F6995E97

e = 0x10001

d = 1E84F8063A2AA42B29DD5578421E79

Sono stati tutti ricavati dal RSA Tool. Con questi dati, possiamo finalmente codare il keygen =).

~ KeyGen ~

Per quanto riguarda il keygen, non c’è molto da dire: vi riassumo qua i passi per il keygen e vi allego il src al keygen fatto da me. Non me ne vogliate, io avevo cominciato a scrivere il keygen in asm, ma quando ho deciso di implementare le OpenSSL ho preferito scriverlo in C: di conseguenza certi pezzi sono fatti in asm nel sorgente, ma si capiscono piuttosto bene; diciamo solo che è anti-estetico. E poi se non capite quelli, cosa vi mettete a reversare? :P

Ok allora, il nostro keygen deve fare esattamente questo:

1)     Prendere l’username e metterlo in lowercase.

2)     Fare l’hash della stringa ottenuta.

3)     Inizializzare le chiavi RSA.

4)     Fare un RSA_private_encrypt sul nostro hash.

5)     Creare l’ultimo byte contando i bit = 1 presenti negli altri.

6)     Creare il serial in caratteri facendo il processo inverso fatto dal programma e mettendo i trattini.

7)     Tutto questo per tutti e due i tipi di serial ovviamente, standard e professional.

Ecco, come compito a casa, codate un keygen bello pulito e inviatemi i src come al solito alla mail latfarg (at) gmail (dot) com =).

Graftal

Note finali

Questo era il tutorial, spero vi sia piaciuto :P. Ho messo un bel po’ a scriverlo per penuria (:P) di tempo, ma alla fine cel’ho fatta. Vorrei ringraziare tutti quelli che conosco, in particolare Eimiar, il mitico evilcry ;), il clan di UT2004 di cui faccio parte e come sempre il Que che pubblica molto gentilmente il mio materiale :P.

Se avete commenti, consigli, dubbi inviate tutto a latfarg(at)gmail(dot)com =)

Alla prossima.

Disclaimer

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

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza del linguaggio Assembly.