grim7reaper

A Code Craftsman

Crackme — GDB

Le crackme étudié dans cet article provient d’ici.

Ce second crackme est un peu moins simple que le précédent, mais il est tout de même trivial. En revanche, je me suis compliqué la vie : la solution était en fait bien plus simple que ce que l’on peut penser. Mais au moins, ça me permet de présenter l’usage de GDB ^^

Comme la dernière fois, on va commencer par lancer strings sur notre binaire. Cependant, cette fois le nombre de chaîne de caractères est beaucoup plus important (1506 chaînes). Ayant la flemme de regarder cela en détail (pourtant j’aurai dû, comme vous le verrez par la suite), je décide de passer à autre chose.

Déjà, on peut remarquer (via la commande file ou ldd) que l’exécutable est compilé en mode statique (ce qui explique le grand nombre de chaîne de caractères), ça signifie que l’on peut oublier d’office les bidouilles à base de ltrace et autres LD_PRELOAD (ne vous inquiétez pas, on aura l’occasion de s’amuser avec ça dans d’autres défis).

Partant de ces premières observations, je décide de partir sur GDB. Mais avant cela, voyons une exécution normale :

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

username: foo
Bad username

Ok, il faut donc fournir un nom d’utilisateur avant de pouvoir fournir le mot de passe.

Maintenant, passons à GDB. Première chose à faire : placer un point d’arrêt sur la fonction main, puis regarder le code assembleur.

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
(gdb) b main
Breakpoint 1 at 0x8048317
(gdb) r
Starting program: ch2.bin

Breakpoint 1, 0x08048317 in main ()
(gdb) disass
Dump of assembler code for function main:
   0x08048309 <+0>:     lea    0x4(%esp),%ecx
   0x0804830d <+4>:     and    $0xfffffff0,%esp
   0x08048310 <+7>:     pushl  -0x4(%ecx)
   0x08048313 <+10>:    push   %ebp
   0x08048314 <+11>:    mov    %esp,%ebp
   0x08048316 <+13>:    push   %ecx
=> 0x08048317 <+14>:    sub    $0x24,%esp
   0x0804831a <+17>:    movl   $0x80a6b19,-0xc(%ebp)
   0x08048321 <+24>:    movl   $0x80a6b1e,-0x10(%ebp)
   0x08048328 <+31>:    movl   $0x80a6b2c,(%esp)
   0x0804832f <+38>:    call   0x8048de0 <puts>
   0x08048334 <+43>:    movl   $0x80a6b6c,(%esp)
   0x0804833b <+50>:    call   0x8048de0 <puts>
   0x08048340 <+55>:    movl   $0x80a6bac,(%esp)
   0x08048347 <+62>:    call   0x8048de0 <puts>
   0x0804834c <+67>:    movl   $0x80a6bea,(%esp)
   0x08048353 <+74>:    call   0x8048db0 <printf>
   0x08048358 <+79>:    mov    -0x8(%ebp),%eax
   0x0804835b <+82>:    mov    %eax,(%esp)
   0x0804835e <+85>:    call   0x804826a <getString>
   0x08048363 <+90>:    mov    %eax,-0x8(%ebp)
   0x08048366 <+93>:    mov    -0xc(%ebp),%eax
   0x08048369 <+96>:    mov    %eax,0x4(%esp)
   0x0804836d <+100>:   mov    -0x8(%ebp),%eax
   0x08048370 <+103>:   mov    %eax,(%esp)
   0x08048373 <+106>:   call   0x80502f0 <strcmp>
   0x08048378 <+111>:   test   %eax,%eax
   0x0804837a <+113>:   jne    0x80483d0 <main+199>
   0x0804837c <+115>:   movl   $0x80a6bf5,(%esp)
   0x08048383 <+122>:   call   0x8048db0 <printf>
   0x08048388 <+127>:   mov    -0x8(%ebp),%eax
   0x0804838b <+130>:   mov    %eax,(%esp)
   0x0804838e <+133>:   call   0x804826a <getString>
   0x08048393 <+138>:   mov    %eax,-0x8(%ebp)
   0x08048396 <+141>:   mov    -0x10(%ebp),%eax
   0x08048399 <+144>:   mov    %eax,0x4(%esp)
   0x0804839d <+148>:   mov    -0x8(%ebp),%eax
   0x080483a0 <+151>:   mov    %eax,(%esp)
   0x080483a3 <+154>:   call   0x80502f0 <strcmp>
   0x080483a8 <+159>:   test   %eax,%eax
   0x080483aa <+161>:   jne    0x80483c2 <main+185>
   0x080483ac <+163>:   movl   $0x80a6c00,0x4(%esp)
   0x080483b4 <+171>:   movl   $0x80a6c0c,(%esp)
   0x080483bb <+178>:   call   0x8048db0 <printf>
   0x080483c0 <+183>:   jmp    0x80483dc <main+211>
   0x080483c2 <+185>:   movl   $0x80a6c52,(%esp)
   0x080483c9 <+192>:   call   0x8048de0 <puts>
   0x080483ce <+197>:   jmp    0x80483dc <main+211>
   0x080483d0 <+199>:   movl   $0x80a6c5f,(%esp)
   0x080483d7 <+206>:   call   0x8048de0 <puts>
   0x080483dc <+211>:   mov    $0x0,%eax
   0x080483e1 <+216>:   add    $0x24,%esp
   0x080483e4 <+219>:   pop    %ecx
   0x080483e5 <+220>:   pop    %ebp
   0x080483e6 <+221>:   lea    -0x4(%ecx),%esp
   0x080483e9 <+224>:   ret
End of assembler dump.

Information intéressante : il y a deux appels à strcmp. Ça pourrait bien correspondre aux vérifications du nom d’utilisateur et du mot de passe. On va donc placer un point d’arrêt sur strcmp afin de pouvoir examiner son code.

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
(gdb) b *0x80502f0
Breakpoint 2 at 0x80502f0
(gdb) c
Continuing.
############################################################
##        Bienvennue dans ce challenge de cracking        ##
############################################################

username: AAAAAAAA

Breakpoint 2, 0x080502f0 in strcmp ()
(gdb) disass
Dump of assembler code for function strcmp:
=> 0x080502f0 <+0>:     push   %ebp
   0x080502f1 <+1>:     xor    %edx,%edx
   0x080502f3 <+3>:     mov    %esp,%ebp
   0x080502f5 <+5>:     push   %esi
   0x080502f6 <+6>:     push   %ebx
   0x080502f7 <+7>:     mov    0x8(%ebp),%esi
   0x080502fa <+10>:    mov    0xc(%ebp),%ebx
   0x080502fd <+13>:    lea    0x0(%esi),%esi
   0x08050300 <+16>:    movzbl (%esi,%edx,1),%eax
   0x08050304 <+20>:    movzbl (%ebx,%edx,1),%ecx
   0x08050308 <+24>:    test   %al,%al
   0x0805030a <+26>:    je     0x8050328 <strcmp+56>
   0x0805030c <+28>:    add    $0x1,%edx
   0x0805030f <+31>:    cmp    %cl,%al
   0x08050311 <+33>:    je     0x8050300 <strcmp+16>
   0x08050313 <+35>:    movzbl %al,%edx
   0x08050316 <+38>:    movzbl %cl,%eax
   0x08050319 <+41>:    sub    %eax,%edx
   0x0805031b <+43>:    mov    %edx,%eax
   0x0805031d <+45>:    pop    %ebx
   0x0805031e <+46>:    pop    %esi
   0x0805031f <+47>:    pop    %ebp
   0x08050320 <+48>:    ret
   0x08050321 <+49>:    lea    0x0(%esi,%eiz,1),%esi
   0x08050328 <+56>:    movzbl %cl,%edx
   0x0805032b <+59>:    neg    %edx
   0x0805032d <+61>:    mov    %edx,%eax
   0x0805032f <+63>:    pop    %ebx
   0x08050330 <+64>:    pop    %esi
   0x08050331 <+65>:    pop    %ebp
   0x08050332 <+66>:    ret
End of assembler dump.

On prend soin d’utiliser une chaîne très facilement reconnaissable en tant que nom d’utilisateur, ça peut être utile par la suite.
En analysant le code on voit que la comparaison (instruction cmp à l’adresse 0x0805030f) se fait sur le contenu des registres cl et al. Ces registres correspondent en fait aux registres 32-bit ecx et eax traités comme des registres 8-bit. Or, on voit que les valeurs de ces registres sont chargées à partir des adresses contenues dans esi (pour eax) et ebx (pour ecx) via des instructions movzbl. Il suffit donc de mettre un point d’arrêt après le chargement des adresses dans esi et ebx afin de pouvoir afficher les chaînes de caractères :

1
2
3
4
5
6
7
8
9
10
(gdb) b *0x08050300
Breakpoint 3 at 0x8050300
(gdb) c
Continuing.

Breakpoint 3, 0x08050300 in strcmp ()
(gdb) p (char*)$esi
$1 = 0x80c8688 "AAAAAAAA"
(gdb) p (char*)$ebx
$2 = 0x80a6b19 "john"

On reconnaît notre chaîne AAAAAAAA dans esi, c’est donc ebx qui contient le nom d’utilisateur. On découvre alors que le nom d’utilisateur est john.

Comme nous avons donné un mauvais mot de passe, le programme va se terminer avant que nous puissions découvrir le mot de passe. Pour éviter cela, nous allons modifier la valeur de retour de strcmp. On commence par ajouter un point d’arrêt juste après le premier appel de strcmp dans main, puis on placera 0 (valeur de retour de strcmp lorsque les deux chaînes sont identiques) dans le registre eax (registre utilisé pour stocker la valeur de retour, ce comportement est défini par la convention d’appel utilisée) avant de continuer l’exécution.

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) b *0x08048378
Breakpoint 4 at 0x8048378
(gdb) c
Continuing.

Breakpoint 4, 0x08048378 in main ()
(gdb) set $eax = 0
(gdb) c
Continuing.
password: BBBBBBBB

Breakpoint 2, 0x080502f0 in strcmp ()

Comme prévu, il nous demande le mot de passe et l’on se retrouve à nouveau dans strcmp. En appliquant le même principe que précédemment on obtient le mot de passe.

1
2
3
4
5
6
(gdb) c
Continuing.

Breakpoint 3, 0x08050300 in strcmp ()
(gdb) p (char*)$ebx
$3 = 0x80a6b1e "the ripper"

Étant maintenant en possession des identifiants, on peut récupérer le code secret :

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

username: john
password: the ripper
Bien joue, vous pouvez valider l'epreuve avec le mot de passe : 987654321 !

Gagné !
Le mot de passe était donc 987654321.

Le mot de la fin

Pour information, tout cela était inutile. En effet, la sortie de strings nous donnait déjà toutes les informations nécessaires (pour peu que l’on prenne le temps de la regarder de plus près) :

1
2
3
4
5
6
7
% strings ch2.bin
[…]
john
the ripper
[…]
987654321
[…]

On voit donc que le nom d’utilisateur et le mot de passe était en clair dans le binaire. Pis encore : le code à obtenir (987654321) est lui aussi en clair dans le binaire. On n’avait même pas besoin de connaître le nom d’utilisateur et le mot de passe pour y accéder.