P0pR0cK5's Blog

Pentest, Challenges, Tests and more ...

View on GitHub

IOLI Crackme

Retour

IOLI

Dans ce billet, un peu de reverse engineering avec les challenges IOLI. Ces challenges se divisent en 3 catégories :

  • bin-linux avec des exécutables ELF32
  • Bin-win32 avec des exécutables PE32 x86
  • bin_pocketPC avec des exécutables PE32 ARM

Je vais ici débuter par la partie Linux.

Pourquoi ? Parce que je suis véritablement nul dans le domaine du reverse engineering. J’ai donc trouvé plusieurs livres sur l’assembleur dont celui ci.

Ce livre écrit par Dennis Yurichev offre une base étonnamment complète sur le reverse engineering pour les machines ARM, x86 et MIPS (j’en avais déjà parlé dans un billet précédent). Pour ma part j’ai simplement dévoré la partie x86 car je suis débutant dans le domaine du reverse engineering (ou RE).

Avant de débuter, il est tout de même recommandé d’avoir de bonnes bases en langage de “bas niveau”, ici le C.

Note : Je parle de bas niveau en rapport aux langages modernes dont le paradigme ainsi que le typage est bien plus flexibles et accessibles que ceux du C.

Au cas où vous avez quelques lacunes, notre ami Dennis Yurichev propose un rappel de C assez bien écrit à cette adresse .

Pour le reste, je vous recommande chaudement de dévorer les articles de Hackndo qui expliquent avec une simplicitée enfantine le fonctionnement de l’assembleur x86 (32 bits malheureusement).

La description du challenge est la suivante :

======================= IOLI CRACKME v1.2 by pof =======================

[Goal]
	Crack the executable files to accept any password


[Hints]
	Level 0x00: strings is your friend
	Level 0x01: 5274
	Level 0x02: ((10*9)+(123*4))^2
	Level 0x03: same password as in level 0x02
	Level 0x04: 9+6=15 - try 69
	Level 0x05: 9+7=16 but one is not enough - try 970
	Level 0x06: same algorithm as in 5, but envp LOLO=1
	Level 0x07: if you find this level too difficult, try the next
	            one and then come back
	Level 0x08: same algorithm as in 0x06


[Tools]
	strings
		Displays printable strings in files
		included in all linux distributions
		http://sources.redhat.com/binutils/
	
	HT Editor
		File editor/viewer/analyzer for executables.
		sudo apt-get install ht
		http://hte.sourceforge.net/

	radare
		Complete toolchain for working with binary files
		http://radare.nopcode.org/

	IDA Pro
		Interactive multi-processor disassembler and debuger
		http://www.datarescue.com/idabase/

========================================================================
                                 [EOF]

Pour ce billet, je souhaite affûter mes talents de reverse engineering, je vais donc éviter au maximum de patcher les exécutables et me concentrer sur la compréhension des algorithmes.

bin-linux

crackme0x00

Depuis Radare2, je lance une analyse complète avec aaa. Je liste ensuite les fonctions avec afl et désassemble le main avec pdf @sym.main:

ioli-linux-00.png

Comme nous pouvons le voir, un call vers strcmp est fait à l’adresse 0x08048469. Radare2 étant assez futé, il trouve directement la chaîne de caractères str.250382 dont la valeur est :

[0x08048492]> psz @str.250382
250382

Testons :

[0x08048492]> ! ./crackme0x00
IOLI Crackme Level 0x00
Password: 250382
Password OK :)

crackme0x01

Même démarche qu’avant, je désassemble le main :

IOLI01

Je remarque une comparaison avec la valeur ` 0x149a à l’adresse 0x0804842b`:

[0x08048330]> ? 0x149a
hex     0x149a
octal   012232
unit    5.2K
segment 0000:049a
int32   5274
string  "\x9a\x14"
binary  0b0001010010011010
fvalue: 5274.0
float:  0.000000f
double: 0.000000
trits   0t21020100

Essayons donc de saisir cette valeur :

IOLI Crackme Level 0x01
Password: 5274
Password OK :)
root@kali:~/IOLI/bin-linux#

crackme0x02

Je reprends le même principe avec une décompilation du main :

crackme002

La partie comparaison semble un poil plus compliquée, lisons la :

mov dword [local_8h], 0x5a  ; 90 	8h = 90
mov dword [local_ch], 0x1ec ; 492	ch = 492 
mov edx, dword [local_ch]			edx = 492
lea eax, dword [local_8h]			eax = &8h // eax = addresse de 8h 
add dword [eax], edx				8h = 8h + 492 // [eax] est un passage par reference
mov eax, dword [local_8h]			eax = 582
imul eax, dword [local_8h]			eax = 582 * 582
mov dword [local_ch], eax			ch = 338724
mov eax, dword [local_4h]			eax = entrée clavier
cmp eax, dword [local_ch]			si eax == 338724 

La partie la plus complexe pour moi est celle-ci :

lea eax, dword [local_8h]		
add dword [eax], edx				

La première ligne charge l’adresse de la variable [local_8h] dans le registre eax et la deuxième place, dans la case mémoire pointée par eax, le résultat de l’addition de l’espace mémoire pointée par eax et la valeur de edx. ( ici 90 + 492 ).

Les [] permettent de spécifier la valeur contenue dans un espace mémoire, un peu comme le * permet de déréférencer un pointeur en C. Ainsi mov dword [local_ch], 0x1ec déplace la valeur 0x1ec dans la case mémoire pointée par l’adresse local_ch.

Note : Radare remplace automatiquement les adresses mémoires par des noms pour en simplifier le décodage.

crackme0x03

Pour cette fois, je vais analyser le contenu de la fonction main:

mov dword [local_8h], 0x5a  ; 90	8h = 90
mov dword [local_ch], 0x1ec ; 492	ch = 492 
mov edx, dword [local_ch]			edx = ch = 492
lea eax, dword [local_8h]			eax = &8h 
add dword [eax], edx				8h = 8h + 492 = 582 
mov eax, dword [local_8h]			eax = 582
imul eax, dword [local_8h]			eax = 582 * 582 
mov dword [local_ch], eax			ch = eax = 338724
mov eax, dword [local_ch]			eax = ch = 338724
mov dword [local_4h_2], eax			4h_2 = eax = 338724
mov eax, dword [local_4h]			eax = 4h
mov dword [esp], eax				&esp = eax 
call sym.test						appel de la fonction test

Le programme fait globalement le même calcul que le précédent mais il rajoute une nouvelle fonction test

Note : J’ai dû lancer un debug avec GDB pour comprendre le fonctionnement de la fonction test. Pour cela j’ai mis des points d’arrêt a l’appel de la fonction test dans le main et à la fin du prologue de cette même fonction. En comparant les valeurs des différents offset vers ebp j’ai découvert que la fonction test était appelée avec les paramètres suivants : test([val_scanf],338724)

Voici son désassemblage :

    push ebp
    mov ebp, esp
    sub esp, 8
    mov eax, dword [arg_8h]		     
    cmp eax, dword [arg_ch]     
,=< je 0x804848a				
|   mov dword [esp], str.Lqydolg_Sdvvzrug ; [0x80485ec:4]=0x6479714c ; "Lqydolg#Sdvvzrug$"
|   call sym.shift
|   jmp 0x8048496
`-> mov dword [esp], str.Sdvvzrug_RN ; [0x80485fe:4]=0x76766453 ; "Sdvvzrug#RN$$$#=,"
    call sym.shift
    leave
    ret

Pour simplifier voici un pseudo-code :

int test(int a,int b)
{
    if(a == b)
    {
        shift("Sdvvzrug#RN$$$#=,");
    }
    else
    {
        shift("Lqydolg#Sdvvzrug$")
    }
}

int main(int argc, char **argv)
{
    int a;
    int b = 90;
    int c = 492; 
    printf("IOLI Crackme Level 0x03\n");
    printf("Password: ");
    scanf("%d", &a);
    b = b + c;
    c = b * b;
    test(a,338724);
}

Nous pourrions nous arrêter ici car l’entrée clavier est comparée avec 338724. Mais l’objectif est d’apprendre à lire du code assembleur.

La fonction test appelle elle-même une autre fonction, shift :

push ebp
mov ebp, esp
sub esp, 0x98
mov dword [local_7ch], 0		7ch = 0
<Début de boucle>----------------------------------------------------------------------
mov eax, dword [arg_8h]			eax = adresse de la string (argument de la fonction)
mov dword [esp], eax			esp = eax 
call sym.imp.strlen         	Taille de la string 
cmp dword [local_7ch], eax		(eax = 17) == (7ch = 0) 
jae 0x8048450					si supérieur ou égal à zéro aller à 0x8048450
lea eax, dword [local_78h]		adresse de 78h dans eax
mov edx, eax					edx = eax 
add edx, dword [local_7ch]		edx = edx + 7ch 
mov eax, dword [local_7ch]		eax = 7ch 
add eax, dword [arg_8h]			eax = 8h (adresse de la string)
movzx eax, byte [eax]			eax = première lettre de la string 
sub al, 3						al = al - 3 (al = premier octet eax) décaler la lettre de 3
mov byte [edx], al				edx = al (premier octet seulement)
lea eax, dword [local_7ch]		charger addr 7ch dans eax
inc dword [eax]					incrément de 7ch 
jmp 0x8048424					retour à mov eax, dword [arg_8h]
<Fin de boucle>
lea eax, dword [local_78h]		charge adresse de 78h dans eax
add eax, dword [local_7ch]		eax = eax + valeur de 7ch
mov byte [eax], 0				eax = 0 (premier octet) 
lea eax, dword [local_78h]		eax = adresse de 78h
mov dword [local_4h], eax		4h = eax = adresse de 78h
mov dword [esp], 0x80485e8  	pointeur vers %s pour printf
call sym.imp.printf         	appel de printf 
leave
ret

Fort de nos infos, la fonction shift décale chaque caractère de 3 valeurs. Ainsi les deux codes étranges de la fonction test donnent ceci :

Lqydolg#Sdvvzrug$ ===> Invalid Password!
Sdvvzrug#RN$$$#=, ===> Password OK !!! :)

Donc pour résumer le mot de passe est encore une fois 338724

Note : J’ai décidé ici de ne pas exploiter la fonction de debugger de Radare2. J’utiliserais cette fonction dans le prochain crackme .

crackme0x04

Comme pour les fois précédentes, je débute par l’affichage de la fonction main :

IOLI_4

A l’adresse 0x08048559 se trouve un appel à la fonction sym.check voyons voir à quoi elle ressemble :

IOLI_Check_4

Voici un pseudo-code de la fonction check:

passlen = strlen(password);
if(passlen >= 0)
{
    int addition = 0; 
    for(i=0; i <= passlen ; i++)
    {
        int val = atoi(password[i]);
        if(val + addition == 15)
        {
            printf("Password OK !");
        }
        addition += val;
    }
    printf("Password Incorrect !");
}
else
{
    printf("Password Incorrect !")
}

Il faut en effet que les valeurs du mot de passe additionnées donnent 15 :

root@kali:~/Challenges/IOLI# bin-linux/crackme0x04
IOLI Crackme Level 0x04
Password: 555
Password OK!
root@kali:~/Challenges/IOLI# bin-linux/crackme0x04
IOLI Crackme Level 0x04
Password: 12345
Password OK!
root@kali:~/Challenges/IOLI# bin-linux/crackme0x04
IOLI Crackme Level 0x04
Password: 96
Password OK!
root@kali:~/Challenges/IOLI# bin-linux/crackme0x04
IOLI Crackme Level 0x04
Password: 69
Password OK!

crackme0x05

Ici l’algorithme ressemble pas mal à celui que nous avons déjà décortiqué avant :

IOLI_5

Il semble que cette fois, il faille avoir un mot de passe dont la somme des chiffres donne 16, néanmoins une nouvelle fonction fait son apparition :

IOLI_5_parell

Sans entrer dans les détails comme nous l’avons fait avant, nous allons faire une analyse plus globale du programme en s’aidant des parties remarquables de ce dernier ainsi que de son comportement.

La fonction parell teste tout simplement si les nombres saisis sont pairs, voici une preuve :

p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 745
Password Incorrect!
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 754
Password OK!
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 88
Password OK!
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 853
Password Incorrect!
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 835
Password Incorrect!
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 8431
Password Incorrect!
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x05 
IOLI Crackme Level 0x05
Password: 8134
Password OK!

Note : J’ai l’impression que seul le dernier chiffre est vérifié, car seul les nombres finissant par un chiffre pair sont acceptés.

La partie intéressante de la fonction est la suivante :

mov eax dword[var_4h]  <-- Déplacement de la valeur à tester dans EAX
and eax 1			   <-- ET logique sur le nombre retourne 0 si pair, 1 si impair
test eax eax		   <-- équivalent a cmp eax 0
jne 0x800484c6		   <-- Jump if Not Equal (ZF=0)

Pourquoi ce code bizarre ? Pour des raisons d’optimisation, le CPU effectue plus rapidement une opération basique comme ET plutôt qu’une comparaison (cmp).

CMP effectue les opérations suivantes :

  • Soustraction des deux opérandes
  • Si le résultat est 0 , les valeurs sont égales
    • Mise en place du drapeau ZF

TEST est plus subtile :

  • Et logique sur les deux opérandes
  • Si le résultat est 0 , les valeurs sont égales
    • Mise en place du drapeau ZF

Un nombre pair ET 1 donnera toujours zéro

Le drapeau ZF est ensuite utilisé pour les sauts conditionnels. Dans notre cas, JNE saute vers l’adresse 0x800484c6 si ZF n’est pas levé (ZF=0).

Rappelez-vous que le CPU n’est qu’un tas de portes logiques miniatures

Il faut donc saisir un nombre dont la somme est égale à 16 et dont le dernier chiffre est pair.

crackme0x06

Cette fois, le nombre de fonctions change avec l’apparition de dummy:

[0x08048400]> afl
0x08048000    3 72   -> 73   fcn.08048000
0x08048048    1 221          fcn.08048048
0x08048125    8 478  -> 479  fcn.08048125
0x08048303    1 93           fcn.08048303
0x08048360    1 23           sym._init
0x08048388    1 6            sym.imp.__libc_start_main
0x08048398    1 6            sym.imp.scanf
0x080483a8    1 6            sym.imp.strlen
0x080483b8    1 6            sym.imp.printf
0x080483c8    1 6            sym.imp.sscanf
0x080483d8    1 6            sym.imp.strncmp
0x080483e8    1 6            sym.imp.exit
0x08048400    1 33           entry0
0x08048424    3 33           fcn.08048424
0x08048450    6 47           sym.__do_global_dtors_aux
0x08048480    4 50           sym.frame_dummy
0x080484b4    6 102          sym.dummy
0x0804851a    7 110          sym.parell
0x08048588    6 127          sym.check
0x08048607    1 99           sym.main
0x08048670    4 99           sym.__libc_csu_init
0x080486e0    1 5            sym.__libc_csu_fini
0x080486e5    1 4            sym.__i686.get_pc_thunk.bx
0x080486f0    4 35           sym.__do_global_ctors_aux
0x08048714    1 26           sym._fini

Étant donné que le programme précédent ressemble beaucoup à celui-ci, je décide de comparer directement l’appel de la fonction check avec celle du challenge précédent :

IOLI_6_compare

Il semble que dans le crackme06 à gauche) deux paramètres sont envoyés à la fonction check contre seulement 1 dans le crackme05 (à droite).

Étant donné que je n’y comprends pas grand-chose, je vais lancer le programme et voir ce que contiennent ces variables en utilisant le mode debug de Radare2 :

p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ radare2 -d crackme0x06
Process with PID 8840 started...
= attach 8840 8840
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
[0xf7f49c70]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 8840 8840
8840
[0xf7f49c70]> pdf @sym.check
/ (fcn) sym.check 127
|   sym.check (int arg_8h, int arg_ch);
|           ; var int local_dh @ ebp-0xd
|           ; var int local_ch @ ebp-0xc
|           ; var int local_8h @ ebp-0x8
|           ; var int local_4h @ ebp-0x4
|           ; arg int arg_8h @ ebp+0x8
|           ; arg int arg_ch @ ebp+0xc
|           ; var int local_4h_2 @ esp+0x4
|           ; var int local_8h_2 @ esp+0x8
|              ; CALL XREF from 0x0804865e (sym.main)
|           0x08048588      55             push ebp
|           0x08048589      89e5           mov ebp, esp
|           0x0804858b      83ec28         sub esp, 0x28               ; '('
|           0x0804858e      c745f8000000.  mov dword [local_8h], 0
|           0x08048595      c745f4000000.  mov dword [local_ch], 0
|              ; JMP XREF from 0x080485f7 (sym.check)
|       .-> 0x0804859c      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|       :   0x0804859f      890424         mov dword [esp], eax        ; const char * s
|       :   0x080485a2      e801feffff     call sym.imp.strlen         
|       :   0x080485a7      3945f4         cmp dword [local_ch], eax   ; [0x13:4]=-1 ; 19
|      ,==< 0x080485aa      734d           jae 0x80485f9
|      |:   0x080485ac      8b45f4         mov eax, dword [local_ch]
|      |:   0x080485af      034508         add eax, dword [arg_8h]
|      |:   0x080485b2      0fb600         movzx eax, byte [eax]
|      |:   0x080485b5      8845f3         mov byte [local_dh], al
|      |:   0x080485b8      8d45fc         lea eax, dword [local_4h]
|      |:   0x080485bb      89442408       mov dword [local_8h_2], eax
|      |:   0x080485bf      c74424043d87.  mov dword [local_4h_2], 0x804873d ; 
|      |:   0x080485c7      8d45f3         lea eax, dword [local_dh]
|      |:   0x080485ca      890424         mov dword [esp], eax        ; ...
|      |:   0x080485cd      e8f6fdffff     call sym.imp.sscanf         
|      |:   0x080485d2      8b55fc         mov edx, dword [local_4h]
|      |:   0x080485d5      8d45f8         lea eax, dword [local_8h]
|      |:   0x080485d8      0110           add dword [eax], edx
|      |:   0x080485da      837df810       cmp dword [local_8h], 0x10  ; [0x10:4]=-1 ; 16
|     ,===< 0x080485de      7512           jne 0x80485f2
|     ||:   0x080485e0      8b450c         mov eax, dword [arg_ch]     ; [0xc:4]=-1 ; 12
|     ||:   0x080485e3      89442404       mov dword [local_4h_2], eax
|     ||:   0x080485e7      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|     ||:   0x080485ea      890424         mov dword [esp], eax
|     ||:   0x080485ed      e828ffffff     call sym.parell
|     `---> 0x080485f2      8d45f4         lea eax, dword [local_ch]
|      |:   0x080485f5      ff00           inc dword [eax]
|      |`=< 0x080485f7      eba3           jmp 0x804859c
|      `--> 0x080485f9      c704244e8704.  mov dword [esp], str.Password_Incorrect ; 
|           0x08048600      e8b3fdffff     call sym.imp.printf        
|           0x08048605      c9             leave
\           0x08048606      c3             ret
[0xf7f49c70]> dcu 0x080485cd ## Je demande à radare de s'arrêter avant l'appel de sscanf 
Continue until 0x080485cd using 1 bpsize
IOLI Crackme Level 0x06
Password: 666
hit breakpoint at: 80485cd
[0x080485cd]> afvd ## J'affiche les valeurs des variables locale à la fonction 
var local_8h = 0xffcd5310  0x00000006  .... edx
var local_ch = 0xffcd530c  0x00000001  ....
var arg_8h = 0xffcd5320  0xffcd5340  @S.. eax stack R W 0x363636 (666) -->  ascii
var local_dh = 0xffcd530b  0x00000136  6...
var local_4h = 0xffcd5314  0x00000006  .... edx
var arg_ch = 0xffcd5324  0xffcd545c  \T.. stack R W 0xffcd625f -->  stack R W 0x4c5f5347 (GS_LIB=/home/p0pr0ck5/.fonts) -->  ascii

Nous avons ici découvert une chose importante, lorsque nous codons en C sous linux voici à quoi ressemble une fonction main :

int main(int argc, char **argv, char** envp)
{
    *** du code ***
}

Comme nous le constatons, la variable envp est en fait un pointeur de pointeur ( les deux petites étoiles). Lorsque nous avons affiché les variables courantes de la fonction dummy nous avons trouvé ceci :

var arg_ch = 0xffcd5324  0xffcd545c  \T.. stack R W 0xffcd625f -->  stack R W 0x4c5f5347 (GS_LIB=/home/p0pr0ck5/.fonts) -->  ascii

Cette variable contient plusieurs adresses mémoires, c’est potentiellement notre pointeur de pointeur envp qui contient les variables d’environnements, affichons-la durant le debuggage :

:> pxw @0xffcd625f ## Affichage du contenu de ce que pointe arg_ch 
0xffcd625f  0x4c5f5347 0x2f3d4249 0x656d6f68 0x7030702f  GS_LIB=/home/p0p
0xffcd626f  0x6b633072 0x662e2f35 0x73746e6f 0x45444b00  r0ck5/.fonts.KDE
0xffcd627f  0x4c55465f 0x45535f4c 0x4f495353 0x72743d4e  _FULL_SESSION=tr
0xffcd628f  0x4c006575 0x4f435f53 0x53524f4c 0x3d73723d  ue.LS_COLORS=rs=
0xffcd629f  0x69643a30 0x3b31303d 0x6c3a3433 0x31303d6e  0:di=01;34:ln=01
0xffcd62af  0x3a36333b 0x303d686d 0x69703a30 0x3b30343d  ;36:mh=00:pi=40;
0xffcd62bf  0x733a3333 0x31303d6f 0x3a35333b 0x303d6f64  33:so=01;35:do=0
0xffcd62cf  0x35333b31 0x3d64623a 0x333b3034 0x31303b33  1;35:bd=40;33;01
0xffcd62df  0x3d64633a 0x333b3034 0x31303b33 0x3d726f3a  :cd=40;33;01:or=
0xffcd62ef  0x333b3034 0x31303b31 0x3d696d3a 0x733a3030  40;31;01:mi=00:s
0xffcd62ff  0x37333d75 0x3a31343b 0x333d6773 0x33343b30  u=37;41:sg=30;43
0xffcd630f  0x3d61633a 0x343b3033 0x77743a31 0x3b30333d  :ca=30;41:tw=30;
0xffcd631f  0x6f3a3234 0x34333d77 0x3a32343b 0x333d7473  42:ow=34;42:st=3
0xffcd632f  0x34343b37 0x3d78653a 0x333b3130 0x2e2a3a32  7;44:ex=01;32:*.
0xffcd633f  0x3d726174 0x333b3130 0x2e2a3a31 0x3d7a6774  tar=01;31:*.tgz=
0xffcd634f  0x333b3130 0x2e2a3a31 0x3d637261 0x333b3130  01;31:*.arc=01;3
0xffcd635f  0x2e2a3a31 0x3d6a7261 0x333b3130 0x2e2a3a31  1:*.arj=01;31:*.
0xffcd636f  0x3d7a6174 0x333b3130 0x2e2a3a31 0x3d61686c  taz=01;31:*.lha=

Je continue à debug la fonction dummy , voici son désassemblage :

/ (fcn) sym.dummy 102
|   sym.dummy (int arg_ch);
|           ; var int local_8h_2 @ ebp-0x8
|           ; var int local_4h @ ebp-0x4
|           ; arg int arg_ch @ ebp+0xc
|           ; var int local_4h_2 @ esp+0x4
|           ; var int local_8h @ esp+0x8
|              ; CALL XREF from 0x08048547 (sym.parell)
|           0x080484b4      55             push ebp
|           0x080484b5      89e5           mov ebp, esp
|           0x080484b7      83ec18         sub esp, 0x18
|           0x080484ba      c745fc000000.  mov dword [local_4h], 0
|           0x080484c1      8b45fc         mov eax, dword [local_4h]
|           0x080484c4      8d1485000000.  lea edx, dword [eax*4]
|           0x080484cb      8b450c         mov eax, dword [arg_ch]     ; [0xc:4]=-1 ; 12
|           0x080484ce      833c0200       cmp dword [edx + eax], 0
|           0x080484d2      743a           je 0x804850e
|           0x080484d4      8b45fc         mov eax, dword [local_4h]
|           0x080484d7      8d0c85000000.  lea ecx, dword [eax*4]
|           0x080484de      8b550c         mov edx, dword [arg_ch]     ; [0xc:4]=-1 ; 12
|           0x080484e1      8d45fc         lea eax, dword [local_4h]
|           0x080484e4      ff00           inc dword [eax]
|           0x080484e6      c74424080300.  mov dword [local_8h], 3
|           0x080484ee      c74424043887.  mov dword [local_4h_2], str.LOLO ; "LOLO"
|           0x080484f6      8b0411         mov eax, dword [ecx + edx]
|           0x080484f9      890424         mov dword [esp], eax        ; size_t n
|           ;-- eip:
|           0x080484fc      e8d7feffff     call sym.imp.strncmp 
|           0x08048501      85c0           test eax, eax
|           0x08048503      75bc           jne 0x80484c1
|           0x08048505      c745f8010000.  mov dword [local_8h_2], 1
|           0x0804850c      eb07           jmp 0x8048515
|           0x0804850e      c745f8000000.  mov dword [local_8h_2], 0
|              ; JMP XREF from 0x0804850c (sym.dummy)
|           0x08048515      8b45f8         mov eax, dword [local_8h_2]
|           0x08048518      c9             leave
\           0x08048519      c3             ret

Je remarque un inc, cela montre la présence d’une boucle dont le compteur est local_4h, Je décide donc de lancer le programme et d’en suivre le fonctionnement dans la fonction dummy :

radare2 -d ./crackme0x06
[0x080483d0]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[0x080483d0]>pdf @sym.parell
***
Je passe les détails
***
[0x080483d0]> V # passage en mode visuel + 3 x 'p' pour se mettre dans la bonne vue
:> dcu 0x08048547
Continue until 0x08048547 using 1 bpsize
IOLI Crackme Level 0x06
Password: 6666
 eip 0x080484fc      eflags 1I          oeax 0xffffffff
|      ,==< 0x080484d2      743a           je 0x804850e                ;[1]
|      |:   0x080484d4      8b45fc         mov eax, dword [local_4h]
|      |:   0x080484d7      8d0c85000000.  lea ecx, dword [eax*4]
|      |:   0x080484de      8b550c         mov edx, dword [arg_ch]     ; [0xc:4]=-1 ; 12
|      |:   0x080484e1      8d45fc         lea eax, dword [local_4h]
|      |:   0x080484e4      ff00           inc dword [eax]
|      |:   0x080484e6      c74424080300.  mov dword [local_8h], 3
|      |:   0x080484ee      c74424043887.  mov dword [local_4h_2], str.LOLO  ; "LOLO"
|      |:   0x080484f6      8b0411         mov eax, dword [ecx + edx]
|      |:   0x080484f9      890424         mov dword [esp], eax        ; size_t n
|      |:   ;-- eip:
|      |:   0x080484fc      e8d7feffff     call sym.imp.strncmp     
|      |:   0x08048501      85c0           test eax, eax
|      |`=< 0x08048503      75bc           jne 0x80484c1               ;[3]
|      |    0x08048505      c745f8010000.  mov dword [local_8h_2], 1
|      |,=< 0x0804850c      eb07           jmp 0x8048515               ;[4]
|      `--> 0x0804850e      c745f8000000.  mov dword [local_8h_2], 0
|       |      ; JMP XREF from 0x0804850c (sym.dummy)
|       `-> 0x08048515      8b45f8         mov eax, dword [local_8h_2]
|           0x08048518      c9             leave
\           0x08048519      c3             ret

Je décide d’afficher l’état de la variable au fur et à mesure des tours de boucles :

## Je demande à radare de continuer jusqu'à la ligne suivant STRCMP
:> dcu 0x08048501
Continue until 0x08048501 using 1 bpsize
hit breakpoint at: 8048501
## J'affiche le contenu de EAX et de la variable arg_ch
:> pxw @eax
0xffd9826d  0x435f534c 0x524f4c4f 0x73723d53 0x643a303d  LS_COLORS=rs=0:d
****
:> pxw @0xffd98257
0xffd98257  0x54554c43 0x5f524554 0x4d5f4d49 0x4c55444f  CLUTTER_IM_MODUL
****
## Je demande à radare de continuer jusqu'à la ligne suivant STRCMP
:> dcu 0x08048501
Continue until 0x08048501 using 1 bpsize
hit breakpoint at: 8048501
## J'affiche le contenu de EAX et de la variable arg_ch
:> pxw @eax
0xffd9826d  0x435f534c 0x524f4c4f 0x73723d53 0x643a303d  LS_COLORS=rs=0:d
****
:> pxw @0xffd98257
0xffd98859  0x5353454c 0x534f4c43 0x752f3d45 0x622f7273  LESSCLOSE=/usr/b
****

Comme nous le voyons, la boucle récupère toutes les variables d’environnement et les compare à LOLO, testons :

p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ export LOLO=1
p0pr0ck5@WORKSTATION:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x06
IOLI Crackme Level 0x06
Password: 88
Password OK!

La partie liée à la chaîne LOLO n’est que pure déduction, mais l’appel à strcmp laisse à penser que c’est une comparaison entre ces deux éléments.

crackme0x07

Cette fois, le challenge se corse un peu avec des fonctions réécrites portant des noms proches de celles de la libc. Voici un graphique des appels :

cracme0X07

Je décide donc de comprendre le fonctionnement de chaque fonction à l’aide de Radare2 en débutant par sub.strlen_5B9:

int addition = 0; 
for(i=0; i <= passlen ; i++)
{
    int val = atoi(password[i]);
    addition += val;
}

Même chose que sur les précédents crackmes, nous procédons à l’addition des caractères convertis en int .

Mais ce n’est pas tout :

## Comparaison de la somme précédente avec 16 (0x10)
0x0804860b      837df810       cmp dword [local_8h], 0x10
## Si non égale, sauter à l'adresse 0x8048623
0x0804860f      7512           jne 0x8048623
## Préparation des arguments
0x08048611      8b450c         mov eax, dword [arg_ch]    
0x08048614      89442404       mov dword [local_4h_2], eax
0x08048618      8b4508         mov eax, dword [arg_8h]    
0x0804861b      890424         mov dword [esp], eax   
## Appel de sub.sscanf_542
0x0804861e      e81fffffff     call sub.sscanf_542    

Dans cette partie de code, la valeur additionnée est comparée au chiffre 16. Si la valeur correspond, le code de la fonction sub.sscanf_542 est appelé et appelle directement sub.LOLO_4b4

Une fois dans la fonction, un premier test a lieu :

0x080484e6      c74424080300.  mov dword [esp + 8], 3                               0x080484ee      c7442404a887.  mov dword [esp + 4], str.LOLO                     0x080484f6      8b0411         mov eax, dword [ecx + edx]                     
0x080484f9      890424         mov dword [esp], eax                           
0x080484fc      e8d7feffff     call sym.imp.strncmp        

C’est en fait le même test que la dernière fois qui vérifie la présence de la variable d’environnement LOLO. Une fois de retour dans sub.sscanf_542 , un autre test qui m’est assez familier :

0x08048585      8b45fc         mov eax, dword [ebp - 4]                                 0x08048588      83e001         and eax, 1                                                 0x0804858b      85c0           test eax, eax                                               0x0804858d      7521           jne 0x80485b0               ;[8] 

C’est le fameux test de parité du nombre. Suite à ce dernier, nous obtenons ceci :

0x08048598      c70424c58704.  mov dword [esp], str.Password_OK                           0x0804859f      e814feffff     call sym.imp.printf                                         0x080485a4      c70424000000.  mov dword [esp], 0                                         0x080485ab      e838feffff     call sym.imp.exit           

C’est simplement le bout de code affichant la validation du mot de passe. Donc pour résumer, il nous faut un mot de passe répondant aux prérequis suivants :

  • Composés de chiffres dont l’addition donne 16
  • Composé de chiffres pairs

Et pour finir la variable d’environnement LOLO doit être mise en place. Je teste :

p0pr0ck5@p0pr0ck5-W0rK5t4t10n:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ export LOLO=1
p0pr0ck5@p0pr0ck5-W0rK5t4t10n:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x07
IOLI Crackme Level 0x07
Password: 88
Password OK!

J’ai découvert après coup la raison pour laquelle les fonctions ne retournaient pas des noms cohérents. C’est parce que le binaire est stripped et que ses symboles ont été retirés :

p0pr0ck5@p0pr0ck5-W0rK5t4t10n:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ rabin2 -I ./crackme0x07
arch     x86
binsz    4780
bintype  elf                                                                               bits     32                                                                               canary   false                                                                             class    ELF32                                                                             crypto   false                                                                             endian   little                                                                           havecode true                                                                             intrp    /lib/ld-linux.so.2                                                               lang     c                                                                                 linenum  false                                                                             lsyms    false                                                                             machine  Intel 80386                                                                       maxopsz  16                                                                               minopsz  1
nx       true                                                                             
os       linux                                                                             pcalign  0                                                                                 pic      false                                                                             relocs   false
relro    partial
rpath    NONE
static   false
stripped true <------------------------ICI !!!
subsys   linux
va       true

Crackme0x08

Suite à la lecture rapide de la structure du code, je découvre que le code est exactement le même que dans le challenge précédent. Seule différence, l’exécutable n’est pas stripped et les symboles sont donc disponibles.

En testant je confirme mon hypothèse :

p0pr0ck5@p0pr0ck5-W0rK5t4t10n:~/Documents/Reverse-eng/IOLI-crackme/bin-linux$ ./crackme0x08
IOLI Crackme Level 0x08
Password: 32
Password Incorrect!

Conclusion

Radare2 est un outil d’une puissance redoutable. Il dispose de fonctionnalités aussi utiles que variées et je compte bien continuer à m’en servir pour le développement d’exploits en remplacement (ou pas) de GDB-peda.

Il existe également une version graphique de Radare2 du nom de Cutter mais elle ne permet pour le moment pas de faire de debug complet. Je préfère donc continuer à utiliser la version CLI.

Sources


Written on April 10, 2020 by


Retour