20/06/2010

Write up ndh crackme level 2

Allez, c'est reparti ! On ouvre notre exécutable avec ollydbg sans se poser de questions. Ici, même technique que le précédent on va aller chercher nos références à GetWindowTextA :

References in cm2:.text to <&USER32.GetWindowTextA>, item 0

Address=00401885
Disassembly=CALL DWORD PTR DS:<&USER32.GetWindowTextA>
Comment=USER32.GetWindowTextA

On vient poser notre breakpoint habituel en :

00401885   .  FF15 50814000 CALL DWORD PTR DS:[<&USER32.GetWindowTex>; \GetWindowTextA

On step over et on voit directement :

004018A9   .  83F8 08       CMP EAX,8
004018AC   .  75 7C         JNZ SHORT cm2.0040192A

Donc le password ( serial ) devra être d'une longueur de 8 caractères. Repartons de zéro et prenons comme password de test : "12345678". Plus loin on peut voir une routine qui est appelée :

004018BC   .  E8 3FF7FFFF   CALL cm2.00401000
004018C1   .  83C4 04       ADD ESP,4
004018C4   .  85C0          TEST EAX,EAX
004018C6   .  74 62         JE SHORT cm2.0040192A

Avec un saut conditionnel pour le code de retour qui sera testé et nous dira si le password est valide ou non.

Elle fait quoi cette routine en 00401000 ?

00401000   $  55            PUSH EBP
00401001   .  8BEC          MOV EBP,ESP
00401003   .  53            PUSH EBX
00401004   .  33D2          XOR EDX,EDX
00401006   .  33C0          XOR EAX,EAX
00401008   .  33DB          XOR EBX,EBX
0040100A   .  33C9          XOR ECX,ECX
0040100C   .  A1 B4B64000   MOV EAX,DWORD PTR DS:[40B6B4]
00401011   .  8BDA          MOV EBX,EDX
00401013   .  C1E3 0C       SHL EBX,0C
00401016   .  03C3          ADD EAX,EBX
00401018   .  8BDD          MOV EBX,EBP
0040101A   .  83C3 08       ADD EBX,8
0040101D   .  8B1B          MOV EBX,DWORD PTR DS:[EBX]
0040101F   .  03DA          ADD EBX,EDX
00401021   .  8A0B          MOV CL,BYTE PTR DS:[EBX]
00401023   .  33DB          XOR EBX,EBX
00401025   .  03D9          ADD EBX,ECX
00401027   .  03D9          ADD EBX,ECX
00401029   .  03D9          ADD EBX,ECX
0040102B   .  03D9          ADD EBX,ECX
0040102D   .  03D9          ADD EBX,ECX
0040102F   .  03C3          ADD EAX,EBX
00401031   .  42            INC EDX
00401032   .- FFE0          JMP EAX
00401034   .  33C0          XOR EAX,EAX
00401036   .  5B            POP EBX
00401037   .  5D            POP EBP
00401038   .  C3            RET

La première chose à voir est la construction du registre EAX à l'aide des différents opérandes ADD vers lesquels on jumpera ( JMP EAX ).

Si on se balade dans la routine on se rend compte qu'elle construit EAX à partir d'un pointeur en 40B6B4 ( 003D000 : sa valeur initiale ) auquel on ajoute 5 fois la valeur du caractère courant. On voit plus loin que lorsque l'on va revenir ( return ) de notre jump il y aura un XOR EAX qui fera merder le test conditionnel vu ci-dessus.

Le but serait donc d'éviter de revenir sur le XOR. Regardons où on jump.

003D014F   /E9 AC2E0000     JMP 003D3000
003D0154   |E9 A73E0000     JMP 003D4000
003D0159   |E9 A24E0000     JMP 003D5000
003D015E   |E9 9D5E0000     JMP 003D6000
003D0163   |E9 986E0000     JMP 003D7000
003D0168  ^|E9 93FEFFFF     JMP 003D0000
003D016D   |E9 8E0E0000     JMP 003D1000

C'est pas beau à voir le disas par ici.Bon ! Sautons en 003D3000 , enchaînons les sauts consécutifs jusqu'à arriver à :

003D7000  - E9 2FA00200     JMP cm2.00401034
003D7005  - E9 2AA00200     JMP cm2.00401034
003D700A  - E9 25A00200     JMP cm2.00401034
003D700F  - E9 20A00200     JMP cm2.00401034

Tiens, tiens ! Des sauts vers notre XOR qui fera tout merder. En regardant en détail tous les sauts qu'il y avait dans la liste sur laquelle on vient de tomber, on en voit 1 différent des autres :

003D70FF  - E9 329F0200     JMP cm2.00401036

Ce saut nous emmènerait juste après le XOR en question, c'est exactement ce qu'il nous faut. Là je me suis dit qu'en construisant EAX de manière à avoir EAX=003D70FF ce serait parfait !

Mais c'est impossible en un passage ( à partir de 5 additions du code hexa du premier caractère de la chaîne de passer de 0x0003D0000 à 0x003D70FF ).

Je relance les tests et je pose un breakpoint sur le JMP EAX en question; On step into et on voit :

003D0145   /E9 B60E0000     JMP 003D1000
003D014A  -|E9 B70E0300     JMP cm2.00401006
003D014F   |E9 AC2E0000     JMP 003D3000

Si EAX avait été égal à 0x003D014A au premier passage, on serait retombé sur la routine et peut-être arrivé à créer la valeur de EAX que l'on souhaite ( 0x003D70FF )

Bien cherchons tous les références à ce jump ( JMP cm2.00401006 ), logiquement il devrait être référencé 7 fois.

References in 003D0000..003D9FFF to cm2.00401006
Address    Disassembly
003D014A   JMP cm2.00401006
003D120D   JMP cm2.00401006
003D2226   JMP cm2.00401006
003D31E5   JMP cm2.00401006
003D423A   JMP cm2.00401006
003D525D   JMP cm2.00401006
003D612C   JMP cm2.00401006

Si on remarque bien à chaque saut l'addresse est incrementée de ( 0x1000 ) en plus des 5 additions de la valeur du caractère. Remontez et regardez le code en 00401011 : A chaque tour la valeur de EAX avant les additions est incrémentée de 0x1000 à chaque passage dans la routine. 1er passage : 0x003D0000 2ème passage : 0x003D1000 etc..

Logiquement il ne nous reste quasi rien à faire pour trouver notre password, on va calculer à la main la valeur que devrait avoir chaque caractère pour bien répondre aux critères des différents sauts qu'il va falloir enchaîner.

>>> chr(0x14a/5)
'B'
>>> chr(0x20d/5)
'i'
>>> chr(0x226/5)
'n'
>>> chr(0x1e5/5)
'a'
>>> chr(0x23a/5)
'r'
>>> chr(0x25d/5)
'y'
>>> chr(0x12c/5)
'<'

Mais nous n'avons que 7 caracatères pour l'instant souvenez vous :

003D70FF - E9 329F0200 JMP cm2.00401036

C'est l'adresse sur laquelle nous voulions jumper :]

>>> chr(0x0ff/5)
'3'

On teste le password "Binary<3" et c'est dans la poche.