Crackme — Anti-Debug
Le crackme étudié dans cet article provient d’ici.
Quatrième crackme, et cette fois nous allons rencontrer notre premier mécanisme anti-débogueur.
Nous avons donc à faire à un exécutable statique avec un strings
de 2069 lignes qui ne contient pas de mot de passe (cette fois j’ai vérifié
^^")
Ok, sortons le débogueur (comme pour le crackme 2, sauf que cette fois c’est justifié :-P). Mais avant cela, voyons d’abord une exécution normale :
############################################################
## Bienvennue dans ce challenge de cracking ##
############################################################
Password : AAAAAAAA
Wrong password.
Maintenant, avec notre ami GDB :
(gdb) r
Starting program: /home/slaperche/Téléchargements/crackme/ch3.bin
Debugger detecté ... Exit
[Inferior 1 (process 5244) exited with code 01]
Hoho, voyez-vous ça ? Monsieur n’aime pas être surveillé. Bon, on va y aller un peu plus doucement cette fois :
(gdb) b main
Breakpoint 1 at 0x80483fe
(gdb) r
Starting program: /home/slaperche/Téléchargements/crackme/ch3.bin
Breakpoint 1, 0x080483fe in main ()
(gdb) disass
Dump of assembler code for function main:
0x080483f0 <+0>: lea 0x4(%esp),%ecx
0x080483f4 <+4>: and $0xfffffff0,%esp
0x080483f7 <+7>: pushl -0x4(%ecx)
0x080483fa <+10>: push %ebp
0x080483fb <+11>: mov %esp,%ebp
0x080483fd <+13>: push %ecx
=> 0x080483fe <+14>: sub $0x14,%esp
0x08048401 <+17>: movl $0x80c2888,-0xc(%ebp)
0x08048408 <+24>: push $0x0
0x0804840a <+26>: push $0x1
0x0804840c <+28>: push $0x0
0x0804840e <+30>: push $0x0
0x08048410 <+32>: call 0x8058a70 <ptrace>
0x08048415 <+37>: add $0x10,%esp
0x08048418 <+40>: test %eax,%eax
0x0804841a <+42>: jns 0x8048436 <main+70>
0x0804841c <+44>: sub $0xc,%esp
0x0804841f <+47>: push $0x80c2894
0x08048424 <+52>: call 0x80492d0 <puts>
0x08048429 <+57>: add $0x10,%esp
0x0804842c <+60>: mov $0x1,%eax
0x08048431 <+65>: jmp 0x80484f9 <main+265>
0x08048436 <+70>: sub $0xc,%esp
0x08048439 <+73>: push $0x80c28b0
0x0804843e <+78>: call 0x80492d0 <puts>
0x08048443 <+83>: add $0x10,%esp
0x08048446 <+86>: sub $0xc,%esp
0x08048449 <+89>: push $0x80c28f0
0x0804844e <+94>: call 0x80492d0 <puts>
0x08048453 <+99>: add $0x10,%esp
0x08048456 <+102>: sub $0xc,%esp
0x08048459 <+105>: push $0x80c2930
0x0804845e <+110>: call 0x80492d0 <puts>
0x08048463 <+115>: add $0x10,%esp
0x08048466 <+118>: mov $0x80c296e,%eax
0x0804846b <+123>: sub $0xc,%esp
0x0804846e <+126>: push %eax
0x0804846f <+127>: call 0x8048f60 <printf>
0x08048474 <+132>: add $0x10,%esp
0x08048477 <+135>: mov 0x80e549c,%eax
0x0804847c <+140>: sub $0x4,%esp
0x0804847f <+143>: push %eax
0x08048480 <+144>: push $0x9
0x08048482 <+146>: lea -0x16(%ebp),%eax
0x08048485 <+149>: push %eax
0x08048486 <+150>: call 0x8048f90 <fgets>
0x0804848b <+155>: add $0x10,%esp
0x0804848e <+158>: lea 0x8048497,%eax
0x08048494 <+164>: inc %eax
0x08048495 <+165>: jmp *%eax
0x08048497 <+167>: mov $0x8bea558a,%eax
0x0804849c <+172>: inc %ebp
0x0804849d <+173>: hlt
0x0804849e <+174>: add $0x4,%eax
0x080484a1 <+177>: mov (%eax),%al
0x080484a3 <+179>: cmp %al,%dl
0x080484a5 <+181>: jne 0x80484e4 <main+244>
0x080484a7 <+183>: mov -0x15(%ebp),%dl
0x080484aa <+186>: mov -0xc(%ebp),%eax
0x080484ad <+189>: add $0x5,%eax
0x080484b0 <+192>: mov (%eax),%al
0x080484b2 <+194>: cmp %al,%dl
0x080484b4 <+196>: jne 0x80484e4 <main+244>
0x080484b6 <+198>: mov -0x14(%ebp),%dl
0x080484b9 <+201>: mov -0xc(%ebp),%eax
0x080484bc <+204>: inc %eax
0x080484bd <+205>: mov (%eax),%al
0x080484bf <+207>: cmp %al,%dl
0x080484c1 <+209>: jne 0x80484e4 <main+244>
0x080484c3 <+211>: mov -0x13(%ebp),%dl
0x080484c6 <+214>: mov -0xc(%ebp),%eax
0x080484c9 <+217>: add $0xa,%eax
0x080484cc <+220>: mov (%eax),%al
0x080484ce <+222>: cmp %al,%dl
0x080484d0 <+224>: jne 0x80484e4 <main+244>
0x080484d2 <+226>: sub $0xc,%esp
0x080484d5 <+229>: push $0x80c297a
0x080484da <+234>: call 0x80492d0 <puts>
0x080484df <+239>: add $0x10,%esp
0x080484e2 <+242>: jmp 0x80484f4 <main+260>
0x080484e4 <+244>: sub $0xc,%esp
0x080484e7 <+247>: push $0x80c298e
0x080484ec <+252>: call 0x80492d0 <puts>
0x080484f1 <+257>: add $0x10,%esp
0x080484f4 <+260>: mov $0x0,%eax
0x080484f9 <+265>: mov -0x4(%ebp),%ecx
0x080484fc <+268>: leave
0x080484fd <+269>: lea -0x4(%ecx),%esp
0x08048500 <+272>: ret
End of assembler dump.
Tiens, tiens, mais que vois-je ? Un appel à
ptrace à l’adresse
0x08048410. Il faut savoir que, sous Linux du moins, ptrace
est
l’appel système à la base des débogueurs. Il permet de mettre en pause un
programme, de faire du pas à pas, de lire et écrire sa mémoire et ses
registres. Il permet également de détecter si un programme est sous la
surveillance de ptrace
(et donc, potentiellement sour la
surveillance d’un débogueur). Et c’est ce qui est fait ici. Le code :
0x08048408 <+24>: push $0x0
0x0804840a <+26>: push $0x1
0x0804840c <+28>: push $0x0
0x0804840e <+30>: push $0x0
0x08048410 <+32>: call 0x8058a70 <ptrace>
correspond à :
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
Qui permet de savoir si le processus courant est sous surveillance.
Maintenant que l’on sait comment le binaire se défend, nous allons pouvoir
contourner cette protection. Pour cela, nous pouvons placer un point d’arrêt
sur l’instruction qui teste la valeur de retour de ptrace
et la
changer (sachant que l’appel renvoi -1 si le processus est tracé et 0 si ce
n’est pas le cas) avant de continuer l’exécution.
(gdb) b *0x08048418
Breakpoint 2 at 0x8048418
(gdb) c
Continuing.
Breakpoint 2, 0x08048418 in main ()
(gdb) set $eax = 0
Avant de continuer, regardons le code de plus près. Bon déjà, il n’y a pas
d’appel à strcmp
. Cependant, en examinant le code avec attention
on remarque cette partie :
0x0804849e <+174>: add $0x4,%eax
0x080484a1 <+177>: mov (%eax),%al
0x080484a3 <+179>: cmp %al,%dl
0x080484a5 <+181>: jne 0x80484e4 <main+244>
0x080484a7 <+183>: mov -0x15(%ebp),%dl
0x080484aa <+186>: mov -0xc(%ebp),%eax
0x080484ad <+189>: add $0x5,%eax
0x080484b0 <+192>: mov (%eax),%al
0x080484b2 <+194>: cmp %al,%dl
0x080484b4 <+196>: jne 0x80484e4 <main+244>
0x080484b6 <+198>: mov -0x14(%ebp),%dl
0x080484b9 <+201>: mov -0xc(%ebp),%eax
0x080484bc <+204>: inc %eax
0x080484bd <+205>: mov (%eax),%al
0x080484bf <+207>: cmp %al,%dl
0x080484c1 <+209>: jne 0x80484e4 <main+244>
0x080484c3 <+211>: mov -0x13(%ebp),%dl
0x080484c6 <+214>: mov -0xc(%ebp),%eax
0x080484c9 <+217>: add $0xa,%eax
0x080484cc <+220>: mov (%eax),%al
0x080484ce <+222>: cmp %al,%dl
0x080484d0 <+224>: jne 0x80484e4 <main+244>
Tiens tiens, on dirait bien qu’il y a un pattern ici :
-
on charge un octet dans
dl
-
on charge une valeur dans
eax
-
on fait une opération arithmétique sur
eax
(add
ouinc
) -
on ramène
eax
sur un octet. -
on compare
al
avecdl
- on saute à l’adresse 0x80484e4 si la comparaison précédente génère un résultat différent de 0 (ce qui signifie que les deux opérandes étaient différents).
Cela ressemble étrangement à une boucle (boucle déroulée certes, mais boucle quand même) pour comparer deux chaînes de caractères. Et comme ce pattern se répète quatre fois, on pourrait émettre l’hypothèse que le mot de passe recherché a une longueur de 4 caractères.
Vérifions nos hypothèses en examinant les valeurs de al
et dl
à chaque cmp
. Commençons par poser un point
d’arrêt sur le premier cmp
.
(gdb) b *0x080484a3
Breakpoint 3 at 0x80484a3
(gdb) c
Continuing.
############################################################
## Bienvennue dans ce challenge de cracking ##
############################################################
Password : AAAA
Breakpoint 3, 0x080484a3 in main ()
(gdb) p (char)$al
$1 = 101 'e'
(gdb) p (char)$dl
$2 = 65 'A'
Ok, dl
semble contenir notre chaîne (on reconnaît notre A), c’est
donc al
qui va contenir les caractères du mot de passe à trouver
(le premier caractère du mot de passe est donc e
).
Bien, maintenant que l’on sait quel registre surveiller, continuons avec les
autres cmp
. Mais avant cela, il va falloir bidouiller un peu. Et
oui, le cmp
ne va pas produire le résultat attendu et la
prochaine instruction va nous faire sauter (ce qui va nous empêcher d’analyser
les autres cmp
). La prochaine instruction est un jne
ce qui signifie Jump if Not Equal, qui est le parfait équivalent de
l’instruction jnz
(qui signifie Jump if Not Zero). Or le
second nom (jnz
) nous met la puce à l’oreille : cette instruction
se base sur la valeur du flag z
. Pour éviter le saut, il va donc
falloir mettre ce flag à 1 après l’instruction cmp
.
Allons-y :
(gdb) ni
0x080484a5 in main ()
(gdb) p $eflags
$3 = [ CF AF SF IF ]
(gdb) set $eflags = $eflags | 64
(gdb) p $eflags
$4 = [ CF AF ZF SF IF ]
Et voilà, le flag z
est positionné (il y a peut-être une syntaxe
plus simple pour faire ça, mais je n’ai pas trouvé). D’où sort le 64 ? Et bien
c’est tout simple : d’après
cette page,
le flag z
correspond au bit 6, et comme chacun sait 26
= 64. S’en suit un petit OU bit à bit pour positionner le flag voulu. Et voilà
le travail.
Allez, continuons et découvrons ce fichu mot de passe :
(gdb) b *0x080484b2
Breakpoint 4 at 0x80484b2
(gdb) c
Continuing.
Breakpoint 4, 0x080484b2 in main ()
(gdb) p (char)$al
$5 = 97 'a'
(gdb) ni
0x080484b4 in main ()
(gdb) set $eflags = $eflags | 64
(gdb) b *0x080484bf
Breakpoint 5 at 0x80484bf
(gdb) c
Continuing.
Breakpoint 5, 0x080484bf in main ()
(gdb) p (char)$al
$6 = 115 's'
(gdb) ni
0x080484c1 in main ()
(gdb) set $eflags = $eflags | 64
(gdb) b *0x080484ce
Breakpoint 6 at 0x80484ce
(gdb) c
Continuing.
Breakpoint 6, 0x080484ce in main ()
(gdb) p (char)$al
$7 = 121 'y'
Ok, le mot de passe serait donc easy.
Voyons voir :
% ./ch3.bin
############################################################
## Bienvennue dans ce challenge de cracking ##
############################################################
Password : easy
Good password !!!
Gagné !
Ça commence à devenir intéressant :-)