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 :
1234567
% ./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.
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 :
12345678910
(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.
123456789101112
(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.
123456
(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 :
12345678
% ./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) :
1234567
% 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.