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 :
% ./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.
(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.
(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 :
(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.
(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.
(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 :
% ./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) :
% 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.