Kali-linux distribution GNU/Linux spécialisée dans l'audit et le pentest.
Kali-linux.fr Communauté française de kali-linux
tutoriel crackme kali linux

Dans cet article nous allons voir comment cracker notre 1er binaire ELF32 avec le decompileur gdb.

NIVEAU : DÉBUTANT

PRÉ-REQUIS :

  • Des bases en C
  • Des bases en assembleur
  • Une boite de Doliprane si pas ces bases
  • Une distribution Linux

Pour démarrer créons notre binaire en C qui nous servira d’exemple. N’importe quel éditeur de texte fera l’affaire.

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int main()
{

char saisie[50] = "";
printf("Entrez le mot de passe...: ");
scanf("%s",&saisie);


if (strcmp(saisie,"MonSuperMotDePasse")==0)
{
printf("Mot de passe correct \n");

}
else
{
printf("Mot de passe incorrect \n");

}

return 0;
} 

Rien de très compliqué dans ces quelques lignes de code. Nous définissons une variable saisie qui sera comparée au mot de passe à trouver, à savoir MonSuperMotDePasse.

Enregistrons ce bout de code et compilons le pour le rendre exécutable :

root@kali:/home/crackme# gcc -m32 -o crackme crackme.c

On le teste :

root@kali:/home/crackme# ./crackme
Entrez le mot de passe…: toto
Mot de passe incorrect
root@kali:/home/crackme# ./crackme
Entrez le mot de passe…: MonSuperMotDePasse
Mot de passe correct
root@kali:/home/crackme#

Le programme fonctionne correctement. Il nous renvoie Mot de passe incorrect si le mot de passe saisit n’est pas MonSuperMotDePasse.

Allez il est temps de contourner cette sécurité !

1ère Méthode: Afficher les strings

La 1ère méthode est celle des fainénants ! Elle ne fonctionne pratiquement jamais mais pour débuter le cracking c’est un réflexe que vous devrez avoir.

On fait donc un strings sur notre binaire. Pour ceux qui ne saurait pas ce qu’est la commande strings, elle permet d’afficher les chaines imprimables d’un fichier. Pour plus de renseignement je vous laisse consulter le MAN de strings.

root@kali:/home/crackme# strings crackme
tdh
/lib/ld-linux.so.2
libc.so.6
IO_stdin_used
isoc99_scanf
puts
printf
__cxa_finalize
strcmp
__libc_start_main GLIBC_2.7
GLIBC_2.1.3
GLIBC_2.0
_ITM_deregisterTMCloneTable
__gmon_start
_ITM_registerTMCloneTable WVSQ Y[^]
UWVS
[^]
Entrez le mot de passe…:
MonSuperMotDePasse
Mot de passe correct
Mot de passe incorrect
;*2$"
GCC: (Debian 8.3.0-6) 8.3.0
crtstuff.c deregister_tm_clones
do_global_dtors_aux
completed.6886
__do_global_dtors_aux_fini_array_entry
frame_dummy __frame_dummy_init_array_entry
crackme.c
__FRAME_END init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE
__libc_csu_fini
strcmp@@GLIBC_2.0
_ITM_deregisterTMCloneTable
__x86.get_pc_thunk.bx
printf@@GLIBC_2.0
_edata
__x86.get_pc_thunk.dx
__cxa_finalize@@GLIBC_2.1.3
__data_start
puts@@GLIBC_2.0
__gmon_start
dso_handle
_IO_stdin_used
__libc_start_main@@GLIBC_2.0
__libc_csu_init
_fp_hw
__bss_start
main
__isoc99_scanf@@GLIBC_2.7
__TMC_END
_ITM_registerTMCloneTable
.symtab
.strtab
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rel.dyn
.rel.plt
.init
.plt.got
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.got.plt
.data
.bss
.comment

Et là que voit-on parmi ces strings (je l’ai mis en gras), notre mot de passe !

Bon cette technique ne fonctionne que lorsque les variables sont non obfusquées mais ca vaut le coup d’essayer.

2ème méthode: debugger avec gdb.

gdb est un debugger pour linux permettant de debugger un binaire en cours d’execution via une analyse des registres de la mémoire. Il dispose d’une multitudes d’options qui sortent du cadre de cet article. Pour notre part nous n’en utiliserons que 4 que nous verrons plus tard

Lançons gdb :

root@kali:/home/crackme# gdb crackme
GNU gdb (Debian 8.2.1-2) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.

gdb-peda$

Comme vous pouvez le voir mon prompt m’affiche gdb-peda$. En effet j’ai rajouté à gdb une extension, très pratique et qui offre plus de visibilité lors de l’analyse des registres d’un binaire.

Pour installer peda (optionnel) :

git clone https://github.com/longld/peda.git ~/peda 
echo "source ~/peda/peda.py" >> ~/.gdbinit

Il est temps de désassembler notre crackme : Pour ce faire nous allons utiliser la commande disas, en précisant la fonction que nous souhaitons désassembler : la fonction main (c’est la que vous sortez le Doliprane)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x000011c9 <+0>:	lea    ecx,[esp+0x4]
   0x000011cd <+4>:	and    esp,0xfffffff0
   0x000011d0 <+7>:	push   DWORD PTR [ecx-0x4]
   0x000011d3 <+10>:	push   ebp
   0x000011d4 <+11>:	mov    ebp,esp
   0x000011d6 <+13>:	push   edi
   0x000011d7 <+14>:	push   esi
   0x000011d8 <+15>:	push   ebx
   0x000011d9 <+16>:	push   ecx
   0x000011da <+17>:	sub    esp,0x48
   0x000011dd <+20>:	call   0x10d0 <__x86.get_pc_thunk.bx>
   0x000011e2 <+25>:	add    ebx,0x2e1e
   0x000011e8 <+31>:	mov    DWORD PTR [ebp-0x4a],0x0
   0x000011ef <+38>:	lea    eax,[ebp-0x46]
   0x000011f2 <+41>:	mov    ecx,0x2e
   0x000011f7 <+46>:	mov    esi,0x0
   0x000011fc <+51>:	mov    DWORD PTR [eax],esi
   0x000011fe <+53>:	mov    DWORD PTR [eax+ecx*1-0x4],esi
   0x00001202 <+57>:	lea    edx,[eax+0x4]
   0x00001205 <+60>:	and    edx,0xfffffffc
   0x00001208 <+63>:	sub    eax,edx
   0x0000120a <+65>:	add    ecx,eax
   0x0000120c <+67>:	and    ecx,0xfffffffc
   0x0000120f <+70>:	shr    ecx,0x2
   0x00001212 <+73>:	mov    edi,edx
   0x00001214 <+75>:	mov    eax,esi
   0x00001216 <+77>:	rep stos DWORD PTR es:[edi],eax
   0x00001218 <+79>:	sub    esp,0xc
   0x0000121b <+82>:	lea    eax,[ebx-0x1ff8]
   0x00001221 <+88>:	push   eax
   0x00001222 <+89>:	call   0x1040 <printf@plt>
   0x00001227 <+94>:	add    esp,0x10
   0x0000122a <+97>:	sub    esp,0x8
   0x0000122d <+100>:	lea    eax,[ebp-0x4a]
   0x00001230 <+103>:	push   eax
   0x00001231 <+104>:	lea    eax,[ebx-0x1fdc]
   0x00001237 <+110>:	push   eax
   0x00001238 <+111>:	call   0x1070 <__isoc99_scanf@plt>
   0x0000123d <+116>:	add    esp,0x10
   0x00001240 <+119>:	sub    esp,0x8
   0x00001243 <+122>:	lea    eax,[ebx-0x1fd9]
   0x00001249 <+128>:	push   eax
   0x0000124a <+129>:	lea    eax,[ebp-0x4a]
   0x0000124d <+132>:	push   eax
   0x0000124e <+133>:	call   0x1030 <strcmp@plt>
   0x00001253 <+138>:	add    esp,0x10
   0x00001256 <+141>:	test   eax,eax
   0x00001258 <+143>:	jne    0x126e <main+165>
   0x0000125a <+145>:	sub    esp,0xc
   0x0000125d <+148>:	lea    eax,[ebx-0x1fc6]
   0x00001263 <+154>:	push   eax
   0x00001264 <+155>:	call   0x1050 <puts@plt>
   0x00001269 <+160>:	add    esp,0x10
   0x0000126c <+163>:	jmp    0x1280 <main+183>
   0x0000126e <+165>:	sub    esp,0xc
   0x00001271 <+168>:	lea    eax,[ebx-0x1fb0]
   0x00001277 <+174>:	push   eax
   0x00001278 <+175>:	call   0x1050 <puts@plt>
   0x0000127d <+180>:	add    esp,0x10
   0x00001280 <+183>:	mov    eax,0x0
   0x00001285 <+188>:	lea    esp,[ebp-0x10]
   0x00001288 <+191>:	pop    ecx
   0x00001289 <+192>:	pop    ebx
   0x0000128a <+193>:	pop    esi
   0x0000128b <+194>:	pop    edi
   0x0000128c <+195>:	pop    ebp
   0x0000128d <+196>:	lea    esp,[ecx-0x4]
   0x00001290 <+199>:	ret    
End of assembler dump.

    

Pour ceux qui découvrent l’assembleur rassurez vous, seules quelques lignes nous intéressent. Le désassemblage de notre fonction main nous a retourné 4 colonnes :

  • L’adresse mémoire de l’instruction. Exemple : 0x00001280
  • Une adresse relative dans la fonction en cours. Exemple <+188> qui est égal à main+188 (nous nous en servirons plus tard)
  • Un OPCODE. Exemple call
  • Une opération à effectuer avec les registres. Exemple esp,[ebp-0x10]

Pour plus de précisions sur les OPCODES je vous invite à suivre ce lien

https://www.gladir.com/CODER/ASM8086/referenceeopcode.htm

Il est temps de regarder notre programme de plus près. Rappelez vous, que fait notre crackme ? il compare une chaine saisie par l’utilisateur avec une variable. Ca tombe bien car si on regarde nos lignes assembleur une instruction nous saute aux yeux !

0x0000124e <+133>:	call   0x1030 <strcmp@plt>

strcmp@plt, = string compare. Ok c’est donc là que notre programme vérifie notre entrée pour la comparer. Continuons :

0x00001256 <+141>:	test   eax,eax
0x00001258 <+143>:	jne    0x126e <main+165>

test eax,eax. Ici il teste si le registre EAX est égal à 0. Il vérifie donc que la variable saisie est égale à la variable attendue. Et si ce n’est pas le cas (mauvais mot de passe) il saute en main+165 avec un jne 0x126e <main+165> (Jump If Not Equal)

Le but du jeu dans cette 2ème méthode va donc être de sauter par dessus ce test pour que le programme ignore ce jne. Pour ce faire nous allons utiliser 3 commandes :

  • run : Permet de lancer le programme
  • b* : (raccourcis pour break). Permet d’inserer un breakpoint à un endroit du programme
  • jump : permet de sauter à l’instruction voulue.

Tout d’abord créons un breakpoint sur notre test :

gdb-peda$ b* main+133
Breakpoint 1 at 0x124e

OK. Lançons maintenant notre programme :

gdb-peda$ run
Starting program: /home/crackme/crackme 
Entrez le mot de passe...: toto

[----------------------------------registers-----------------------------------]
EAX: 0xffffd23e ("toto")
EBX: 0x56559000 --> 0x3efc 
ECX: 0x1 
EDX: 0xf7f8189c --> 0x0 
ESI: 0x0 
EDI: 0xffffd270 --> 0x1 
EBP: 0xffffd288 --> 0x0 
ESP: 0xffffd220 --> 0xffffd23e ("toto")
EIP: 0x5655624e (<main+133>:	call   0x56556030 <strcmp@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x56556249 <main+128>:	push   eax
   0x5655624a <main+129>:	lea    eax,[ebp-0x4a]
   0x5655624d <main+132>:	push   eax
=> 0x5655624e <main+133>:	call   0x56556030 <strcmp@plt>
   0x56556253 <main+138>:	add    esp,0x10
   0x56556256 <main+141>:	test   eax,eax
   0x56556258 <main+143>:	jne    0x5655626e <main+165>
   0x5655625a <main+145>:	sub    esp,0xc
Guessed arguments:
arg[0]: 0xffffd23e ("toto")
arg[1]: 0x56557027 ("MonSuperMotDePasse")
[------------------------------------stack-------------------------------------]
0000| 0xffffd220 --> 0xffffd23e ("toto")
0004| 0xffffd224 ("'pUV")
0008| 0xffffd228 --> 0xf7ffd000 --> 0x28f2c 
0012| 0xffffd22c --> 0x565561e2 (<main+25>:	add    ebx,0x2e1e)
0016| 0xffffd230 --> 0x0 
0020| 0xffffd234 --> 0xffffd334 --> 0xffffd4c4 ("/home/crackme/crackme")
0024| 0xffffd238 --> 0xf7f80000 --> 0x1d9d6c 
0028| 0xffffd23c --> 0x6f740000 ('')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x5655624e in main ()
gdb-peda$ 

Nous voyons ici l’utilité de peda qui nous affiche nos différents registres ! Comme prévu le programme à breaké sur l’instruction strcmp et nous rend la main.

[-------------------------------------code-------------------------------------]
   0x56556249 <main+128>:	push   eax
   0x5655624a <main+129>:	lea    eax,[ebp-0x4a]
   0x5655624d <main+132>:	push   eax
=> 0x5655624e <main+133>:	call   0x56556030 <strcmp@plt>
   0x56556253 <main+138>:	add    esp,0x10
   0x56556256 <main+141>:	test   eax,eax
   0x56556258 <main+143>:	jne    0x5655626e <main+165>
   0x5655625a <main+145>:	sub    esp,0xc

La fleche => nous indique que nous sommes à l’instruction main+133. Nous voyons qu’ensuite le programme ajoute 0x10 en esp puis teste eax. Et saute en main+165 si not equal. Nous allons donc le faire sauter juste après le jne pour qu’il ne puisse lire cette condition

gdb-peda$ jump* main+145
Continuing at 0x5655625a.
Mot de passe correct 
[Inferior 1 (process 11278) exited normally]

Et bingo ! malgré un mauvais de passe “toto”, notre programme l’a accepté. Bon c’est déjà bien mais ce qui nous intéresse nous c’est de connaitre le vrai mot de passe ! Pas de problème nous allons fouiller dans la stack ! 3ème méthode. relancons notre programe :

gdb-peda$ run
Starting program: /home/crackme/crackme 
Entrez le mot de passe...: toto

[----------------------------------registers-----------------------------------]
EAX: 0xffffd23e ("toto")
EBX: 0x56559000 --> 0x3efc 
ECX: 0x1 
EDX: 0xf7f8189c --> 0x0 
ESI: 0x0 
EDI: 0xffffd270 --> 0x1 
EBP: 0xffffd288 --> 0x0 
ESP: 0xffffd220 --> 0xffffd23e ("toto")
EIP: 0x5655624e (<main+133>:	call   0x56556030 <strcmp@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x56556249 <main+128>:	push   eax
   0x5655624a <main+129>:	lea    eax,[ebp-0x4a]
   0x5655624d <main+132>:	push   eax
=> 0x5655624e <main+133>:	call   0x56556030 <strcmp@plt>
   0x56556253 <main+138>:	add    esp,0x10
   0x56556256 <main+141>:	test   eax,eax
   0x56556258 <main+143>:	jne    0x5655626e <main+165>
   0x5655625a <main+145>:	sub    esp,0xc
Guessed arguments:
arg[0]: 0xffffd23e ("toto")
arg[1]: 0x56557027 ("MonSuperMotDePasse")
[------------------------------------stack-------------------------------------]
0000| 0xffffd220 --> 0xffffd23e ("toto")
0004| 0xffffd224 ("'pUV")
0008| 0xffffd228 --> 0xf7ffd000 --> 0x28f2c 
0012| 0xffffd22c --> 0x565561e2 (<main+25>:	add    ebx,0x2e1e)
0016| 0xffffd230 --> 0x0 
0020| 0xffffd234 --> 0xffffd334 --> 0xffffd4c4 ("/home/crackme/crackme")
0024| 0xffffd238 --> 0xf7f80000 --> 0x1d9d6c 
0028| 0xffffd23c --> 0x6f740000 ('')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x5655624e in main ()

Il break de nouveau sur le strcmp. Nous savons qu’a ce stade il va effectuer une comparaison. Il a donc empilé sur la stack les variables nécessaires à cette comparaison. Regardons un peu ce qui se trouve en esp (Extended Stack Pointer) :

gdb-peda$ x/2x $esp
0xffffd220:	0xffffd23e	0x56557027

Ok donc il nous a empilé des trucs en 0xffffd23e et 0x56557027. Allons voir ce qu’il y a mis :

gdb-peda$ x/2x $esp
0xffffd220:	0xffffd23e	0x56557027
gdb-peda$ x/s 0xffffd23e
0xffffd23e:	"toto"
gdb-peda$ x/s 0x56557027
0x56557027:	"MonSuperMotDePasse"

Bingo ! Nous retrouvons ce que nous avons saisi : toto et avec quoi il est comparé : MonSuperMotDePasse.

Comment patcher le binaire pour accepter n’importe quel mot de passe

Allez en bonus patchons notre binaire pour qu’il accepte n’importe quel mot de passe. Pour ce faire nous allons modifier avec un éditeur hexadecimal l’opcode jne par un NOP. En l’équivalent hexa de l’opcode jne s’ecrit 7514. et NOP qui signifie ne rien faire s’écrit 9090. Il nous suffit donc de remplacer dans un editeur hexa la chaine 7514 par 9090. J’utilise GHex.

Comment sais-ton que jne s’écrit 75 14 et NOP 90 90 ? Via objump entre autre :

root@kali:/home/crackme# objdump -d crackme | grep jne
    1189:	75 27                	jne    11b2 <__do_global_dtors_aux+0x42>
    1258:	75 14                	jne    126e <main+0xa5>
    12eb:	75 e3                	jne    12d0 <__libc_csu_init+0x30>
root@kali:/home/crackme# objdump -d crackme | grep nop
    1117:	90                   	nop
    129f:	90                   	nop

Le jne qui nous intéresse est celui du main donc sa transcription hexa est bien 75 14

On recherche la chaîne 75 14 et on la remplace place 90 90

On sauvegarde, on autorise l’exécution et on teste :

root@kali:/home/crackme# chmod +777 crackme2
root@kali:/home/crackme# ./crackme2
Entrez le mot de passe...: motdepassequidevraitpas marcher
Mot de passe correct 
root@kali:/home/crackme# 

Et voila. Maintenant notre programme acceptera n’importe quel mot de passe comme correct.


6 thoughts on “Débuter en CrackMe: contourner l’authentification en 3 méthodes”
  1. Super intéressant merci pour cet article.

    Je reste seulement sur ma faim par rapport aux moyen de protéger son binaire de ces attaques justement.
    Quelles auraient été les méthodes pour protéger notre application C de cette attaque ?
    Chiffrer le mdp avant comparaison ?
    Cela n’empêche pas le hack de l’instruction jne

  2. Bonjour, lu cet article par pure curiosité, mes quelques cours d’assembleurs remontent à plus de 30 ans (sur des 8086), mais j’ai trouvé ça super intéressant, et même génial ! Ça me donne presque envie de m’y remettre ! Merci beaucoup.

    1. Merci c’est sympa.
      c’est ce genre de commentaires qui donne envie de faire plus d’articles 😉

  3. Donc, en plus des couleurs, peda est réellement super pratique!update-alternatives –config x-session-manager

  4. Dans la partie desassemblage on voit les arguments de strcmp compare, donc “toto” et “MonSuperMotdePasse”

    Guessed arguments:
    arg[0]: 0xffffd23e (“toto”)
    arg[1]: 0x56557027 (“MonSuperMotDePasse”)

    C’est fiable aussi cette methode ?

    1. Hello,
      Oui c’est tout aussi fiable.
      En fait les arg[0] et arg[1] sont générés par PEDA qui regardent de lui meme dans les registres.
      Sur la version de base de GDB tu n’auras pas cette information.

Leave a Comment

Time limit is exhausted. Please reload CAPTCHA.