grim7reaper

A Code Craftsman

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 :

1
2
3
4
5
6
7
############################################################
##        Bienvennue dans ce challenge de cracking        ##
############################################################

Password : AAAAAAAA

Wrong password.

Maintenant, avec notre ami GDB :

1
2
3
4
(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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
(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 :

1
2
3
4
5
   0x08048408 <+24>:    push   $0x0
   0x0804840a <+26>:    push   $0x1
   0x0804840c <+28>:    push   $0x0
   0x0804840e <+30>:    push   $0x0
   0x08048410 <+32>:    call   0x8058a70 <ptrace>

correspond à :

1
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.

1
2
3
4
5
6
7
(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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   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 ou inc)
  • on ramène eax sur un octet.
  • on compare al avec dl
  • 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(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 :

1
2
3
4
5
6
7
(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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(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 :

1
2
3
4
5
6
7
8
% ./ch3.bin
############################################################
##        Bienvennue dans ce challenge de cracking        ##
############################################################

Password : easy

Good password !!!

Gagné !
Ça commence à devenir intéressant :-)