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

Overflow ? Comment ça marche ?

Niveau: Débutant

Cet article est le premier d’une série dédié à l’exploitation de binaire (plus communément nommé pwn).

Vous vous demandez peut-être ce que c’est ? Eh bien c’est l’art d’exploiter des vulnérabilités liées à une mauvaise utilisation d’un langage de bas-niveau comme le C/C++

(note: Parfois on dit que le pwn est toute exploitation d’une faille liée à une mauvaise utilisation d’un langage quelconque comme les injections de code ou autre mais on va rester sur la première définition).

Une des vulnérabilité bien connue de cette dicipline est le buffer overflow.

Aujourd’hui ont va essayer de comprendre les bases de cette vulnérabilité. Et au fil des parties de ce tutoriel, nous allons exécuter nos premiers exploits. C’est parti!

Quelque Bases pour Contrôler EIP

Un peu d’assembleur

Ok, il va falloir voir quelques bases. Quand vous codez un programme en C ou en C++ et que vous le compilez avec gcc en utilisant la commande:

gcc -o programme programme.c 

Eh bien savez-vous comment fait gcc pour transformer votre code en C vers du langage machine exécutable par l’ordinateur ?

Cela ce fait en 3 étapes:

d’abord le code en C est transformé en langage assembleur (qui est le langage le plus bas niveau après le binaire), ensuite le code assembleur est traduit en binaire, et enfin l’exécutable est “linké”, c’est à dire que les liens sont fais avec les libraires (par exemple si votre code fait un printf il est nécessaire que votre programme sois attaché à la libs.so.6 qui contient la fonction printf).

Très bien maintenant voyons les bases de l’assembleur, langage qui est incontournable dans le domaine de l’exploitation. Vous verrez pourquoi plus tard.

Regardons d’abord à quoi ressemble l’assembleur:

section .text
    global _start

_start:
    push rdx
    mov rdi, 0x4444444444444444 ; v_addr
    mov rsi, 0x5555555555555555 ; len
    mov rdx, 0x7 ; RWX
    mov rax, 10 ; mprotect0x80483dc
    syscall
    mov rcx, 0x2222222222222222
    mov rsi, 0x3333333333333333
    mov rdx, 0x6666666666666666 ; random_int
    mov rdi, rsi
    jmp _loop

_loop:
    cmp rcx, 0x0
    je _end
    lodsb
    not al
    xor al, dl
    stosb
    loop _loopon va passer sur 

_end:
    pop rdx
    mov rax, 0x1111111111111111
    jmp rax

Il est normal que vous ne compreniez pas grand chose ! C’était juste pour vous montrer à quoi ça ressemble. Donc vous voyez votre code est formé d’instruction comme cmp et call ect .. On va voir les instruction de base:

; Note, en assembleur tout ce qui ai derrière un point virgule est un commentaire

call 0x80483dc; Appelle la fonction à l'addresse 0x80483dc

push 0x0; met la valeur 0x0 sur la stack

pop ebx; met ce qu'il y a en haut de la stack dans ebx 

mov eax, 0x1; met 0x1 dans eax

Ça sera tout ! Pas bien compliqué (vous verrez encore beaucoup d’assembleur si vous continuer le pwn ).

Les registres x86 et 64bits

Maintenant c’est quoi eax, ebx ?

Eh bien ce sont ce qu’on nomme des registres, des sortes de blocs ou l’on stocke de petites choses (adresse, nombre, ect …).

Pour un processeur x86 32 bits, vos registres eax, ebx, ecx, edx, ebp, esp, eip et enfin edi ont une taille de 32 bits (soit 4 bytes) .

Il existe d’autres registres qui ne vont pas nous intéresser maintenant.

Dernier petit truc avant de voir à quoi correspondent ces registres: en 64 bits vous aurez les mêmes registres sauf qu’ils feront 64 bits et que à la place du “e” vous aurez “r”, (rbx, rip, par exemple).

Ok, passons au choses sérieuses, que contient chacun de ces registres ?

eax -> registre accumulateur (accumulator register). Utilisé pour les opérations arithmétiques et le stockage de la valeur de retour des appels systèmes.

ebx -> registre de base (base register). Utilisé comme pointeur de donnée (située dans DS en mode segmenté).

ecx -> registre compteur (counter register)

edx -> registre de données (data register). Utilisé pour les opérations arithmétiques et les opérations d'entrée/sortie.

ebp -> (Extended Base Pointer) pointeur de base
/!\ important
esp -> Pointe vers le haut de la stack (ou pile), retenez bien !
eip -> Très important, pointe vers la prochaine instruction à executer

Les segments de mémoire

Pour l’instant, étant sur l’exploitation sur des systèmes Linux ont va plutôt s’intéresser au différents segments d’un binaire ELF (ou Executable and Linkable Format) qui est un fichier exécutable sous Linux, on va prendre un petit schéma:

Résultat de recherche d'images pour "executable and linkable format"

Hein ? Mais c’est quoi .text .rodata etc ??

Eh bien c’est simple ! C’est tout simplement nos segments, voyons ce que contient chacun:

  • .data: Segments ou sont stocké les variable globales (on verra plus tard)
  • .bss: Contient les variable statique
  • .text: Notre code

Ce n’est peut-être pas assez clair, nous allons donc prendre un petit programme pour illustrer cela:

#include <stdio.h>
#include <stdlib.h>

int a;
static int b;

int main(int argc; char **argv) {
  int c = 0;

  printf("Bonjour ! \n");
}

Eh bien ici nous avons:

  • a et b qui sont dans .bss
  • c qui est mis sur la stack (on verra plus bas)
  • notre programme (c’est a dire le main) qui est dans .text

Petite remarque, les “include” et les “define” font parti de ce que l’on nomme préprocesseur quand notre code est compilé ces parties sont effacées.

Regardons de plus près ce que ça donne. Ça nous permettra aussi au passage de voir comment désassembler un programme:

#define NOMBRE 15

int main() {
  int a = NOMBRE;
}

Compilons donc le code:

quasar@pwn:~/kali$ gcc -o code code.c
quasar@pwn:~/kali$ file code
code: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=eaac74af1d2565323c1ee8330fff4a208511831f, not stripped

Essayons de décompiler avec objdump qui est un désassembler linéaire

quasar@pwn:~/kali$ objdump -M intel -d code

code:     format de fichier elf64-x86-64


Déassemblage de la section .init :

0000000000400390 <_init>:
  400390:	48 83 ec 08          	sub    rsp,0x8
  400394:	48 8b 05 5d 0c 20 00 	mov    rax,QWORD PTR [rip+0x200c5d]        # 600ff8 <_DYNAMIC+0x1d0>
  40039b:	48 85 c0             	test   rax,rax
  40039e:	74 05                	je     4003a5 <_init+0x15>
  4003a0:	e8 2b 00 00 00       	call   4003d0 <__libc_start_main@plt+0x10>
  4003a5:	48 83 c4 08          	add    rsp,0x8
  4003a9:	c3                   	ret    

Déassemblage de la section .plt :

00000000004003b0 <__libc_start_main@plt-0x10>:
  4003b0:	ff 35 52 0c 20 00    	push   QWORD PTR [rip+0x200c52]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  4003b6:	ff 25 54 0c 20 00    	jmp    QWORD PTR [rip+0x200c54]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  4003bc:	0f 1f 40 00          	nop    DWORD PTR [rax+0x0]

00000000004003c0 <__libc_start_main@plt>:
  4003c0:	ff 25 52 0c 20 00    	jmp    QWORD PTR [rip+0x200c52]        # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  4003c6:	68 00 00 00 00       	push   0x0
  4003cb:	e9 e0 ff ff ff       	jmp    4003b0 <_init+0x20>

Déassemblage de la section .plt.got :

00000000004003d0 <.plt.got>:
  4003d0:	ff 25 22 0c 20 00    	jmp    QWORD PTR [rip+0x200c22]        # 600ff8 <_DYNAMIC+0x1d0>
  4003d6:	66 90                	xchg   ax,ax

Déassemblage de la section .text :

00000000004003e0 <_start>:
  4003e0:	31 ed                	xor    ebp,ebp
  4003e2:	49 89 d1             	mov    r9,rdx
  4003e5:	5e                   	pop    rsi
  4003e6:	48 89 e2             	mov    rdx,rsp
  4003e9:	48 83 e4 f0          	and    rsp,0xfffffffffffffff0
  4003ed:	50                   	push   rax
  4003ee:	54                   	push   rsp
  4003ef:	49 c7 c0 60 05 40 00 	mov    r8,0x400560
  4003f6:	48 c7 c1 f0 04 40 00 	mov    rcx,0x4004f0
  4003fd:	48 c7 c7 d6 04 40 00 	mov    rdi,0x4004d6
  400404:	e8 b7 ff ff ff       	call   4003c0 <__libc_start_main@plt>
  400409:	f4                   	hlt    
  40040a:	66 0f 1f 44 00 00    	nop    WORD PTR [rax+rax*1+0x0]

0000000000400410 <deregister_tm_clones>:
  400410:	b8 37 10 60 00       	mov    eax,0x601037
  400415:	55                   	push   rbp
  400416:	48 2d 30 10 60 00    	sub    rax,0x601030
  40041c:	48 83 f8 0e          	cmp    rax,0xe
  400420:	48 89 e5             	mov    rbp,rsp
  400423:	76 1b                	jbe    400440 <deregister_tm_clones+0x30>
  400425:	b8 00 00 00 00       	mov    eax,0x0
  40042a:	48 85 c0             	test   rax,rax
  40042d:	74 11                	je     400440 <deregister_tm_clones+0x30>
  40042f:	5d                   	pop    rbp
  400430:	bf 30 10 60 00       	mov    edi,0x601030
  400435:	ff e0                	jmp    rax
  400437:	66 0f 1f 84 00 00 00 	nop    WORD PTR [rax+rax*1+0x0]
  40043e:	00 00 
  400440:	5d                   	pop    rbp
  400441:	c3                   	ret    
  400442:	0f 1f 40 00          	nop    DWORD PTR [rax+0x0]
  400446:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
  40044d:	00 00 00 

0000000000400450 <register_tm_clones>:
  400450:	be 30 10 60 00       	mov    esi,0x601030
  400455:	55                   	push   rbp
  400456:	48 81 ee 30 10 60 00 	sub    rsi,0x601030
  40045d:	48 c1 fe 03          	sar    rsi,0x3
  400461:	48 89 e5             	mov    rbp,rsp
  400464:	48 89 f0             	mov    rax,rsi
  400467:	48 c1 e8 3f          	shr    rax,0x3f
  40046b:	48 01 c6             	add    rsi,rax
  40046e:	48 d1 fe             	sar    rsi,1
  400471:	74 15                	je     400488 <register_tm_clones+0x38>
  400473:	b8 00 00 00 00       	mov    eax,0x0
  400478:	48 85 c0             	test   rax,rax
  40047b:	74 0b                	je     400488 <register_tm_clones+0x38>
  40047d:	5d                   	pop    rbp
  40047e:	bf 30 10 60 00       	mov    edi,0x601030
  400483:	ff e0                	jmp    rax
  400485:	0f 1f 00             	nop    DWORD PTR [rax]
  400488:	5d                   	pop    rbp
  400489:	c3                   	ret    
  40048a:	66 0f 1f 44 00 00    	nop    WORD PTR [rax+rax*1+0x0]

0000000000400490 <__do_global_dtors_aux>:
  400490:	80 3d 99 0b 20 00 00 	cmp    BYTE PTR [rip+0x200b99],0x0        # 601030 <__TMC_END__>
  400497:	75 11                	jne    4004aa <__do_global_dtors_aux+0x1a>
  400499:	55                   	push   rbp
  40049a:	48 89 e5             	mov    rbp,rsp
  40049d:	e8 6e ff ff ff       	call   400410 <deregister_tm_clones>
  4004a2:	5d                   	pop    rbp
  4004a3:	c6 05 86 0b 20 00 01 	mov    BYTE PTR [rip+0x200b86],0x1        # 601030 <__TMC_END__>
  4004aa:	f3 c3                	repz ret 
  4004ac:	0f 1f 40 00          	nop    DWORD PTR [rax+0x0]

00000000004004b0 <frame_dummy>:
  4004b0:	bf 20 0e 60 00       	mov    edi,0x600e20
  4004b5:	48 83 3f 00          	cmp    QWORD PTR [rdi],0x0
  4004b9:	75 05                	jne    4004c0 <frame_dummy+0x10>
  4004bb:	eb 93                	jmp    400450 <register_tm_clones>
  4004bd:	0f 1f 00             	nop    DWORD PTR [rax]
  4004c0:	b8 00 00 00 00       	mov    eax,0x0
  4004c5:	48 85 c0             	test   rax,rax
  4004c8:	74 f1                	je     4004bb <frame_dummy+0xb>
  4004ca:	55                   	push   rbp
  4004cb:	48 89 e5             	mov    rbp,rsp
  4004ce:	ff d0                	call   rax
  4004d0:	5d                   	pop    rbp
  4004d1:	e9 7a ff ff ff       	jmp    400450 <register_tm_clones>

00000000004004d6 <main>:
  4004d6:	55                   	push   rbp
  4004d7:	48 89 e5             	mov    rbp,rsp
  4004da:	c7 45 fc 0f 00 00 00 	mov    DWORD PTR [rbp-0x4],0xf
  4004e1:	b8 00 00 00 00       	mov    eax,0x0
  4004e6:	5d                   	pop    rbp
  4004e7:	c3                   	ret    
  4004e8:	0f 1f 84 00 00 00 00 	nop    DWORD PTR [rax+rax*1+0x0]
  4004ef:	00 

00000000004004f0 <__libc_csu_init>:
  4004f0:	41 57                	push   r15
  4004f2:	41 56                	push   r14
  4004f4:	41 89 ff             	mov    r15d,edi
  4004f7:	41 55                	push   r13
  4004f9:	41 54                	push   r12
  4004fb:	4c 8d 25 0e 09 20 00 	lea    r12,[rip+0x20090e]        # 600e10 <__frame_dummy_init_array_entry>
  400502:	55                   	push   rbp
  400503:	48 8d 2d 0e 09 20 00 	lea    rbp,[rip+0x20090e]        # 600e18 <__init_array_end>
  40050a:	53                   	push   rbx
  40050b:	49 89 f6             	mov    r14,rsi
  40050e:	49 89 d5             	mov    r13,rdx
  400511:	4c 29 e5             	sub    rbp,r12
  400514:	48 83 ec 08          	sub    rsp,0x8
  400518:	48 c1 fd 03          	sar    rbp,0x3
  40051c:	e8 6f fe ff ff       	call   400390 <_init>
  400521:	48 85 ed             	test   rbp,rbp
  400524:	74 20                	je     400546 <__libc_csu_init+0x56>
  400526:	31 db                	xor    ebx,ebx
  400528:	0f 1f 84 00 00 00 00 	nop    DWORD PTR [rax+rax*1+0x0]
  40052f:	00 
  400530:	4c 89 ea             	mov    rdx,r13
  400533:	4c 89 f6             	mov    rsi,r14
  400536:	44 89 ff             	mov    edi,r15d
  400539:	41 ff 14 dc          	call   QWORD PTR [r12+rbx*8]
  40053d:	48 83 c3 01          	add    rbx,0x1
  400541:	48 39 eb             	cmp    rbx,rbp
  400544:	75 ea                	jne    400530 <__libc_csu_init+0x40>
  400546:	48 83 c4 08          	add    rsp,0x8
  40054a:	5b                   	pop    rbx
  40054b:	5d                   	pop    rbp
  40054c:	41 5c                	pop    r12
  40054e:	41 5d                	pop    r13
  400550:	41 5e                	pop    r14
  400552:	41 5f                	pop    r15
  400554:	c3                   	ret    
  400555:	90                   	nop
  400556:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
  40055d:	00 00 00 

0000000000400560 <__libc_csu_fini>:
  400560:	f3 c3                	repz ret 

Déassemblage de la section .fini :

0000000000400564 <_fini>:
  400564:	48 83 ec 08          	sub    rsp,0x8
  400568:	48 83 c4 08          	add    rsp,0x8
  40056c:	c3                   	ret    
quasar@pwn:~/kali$ 

Hein ? C’est quoi ça ? Faudrait que je vous en parle mais là c’est encore trop tôt ce qui nous intéresse c’est “main”, avant il y’a une technique plus pratique que voici:

quasar@pwn:~/kali$ gdb code
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 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.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from code...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel 
(gdb) disas main 
Dump of assembler code for function main:
   0x00000000004004d6 <+0>:	push   rbp
   0x00000000004004d7 <+1>:	mov    rbp,rsp
   0x00000000004004da <+4>:	mov    DWORD PTR [rbp-0x4],0xf
   0x00000000004004e1 <+11>:	mov    eax,0x0
   0x00000000004004e6 <+16>:	pop    rbp
   0x00000000004004e7 <+17>:	ret    
End of assembler dump.
(gdb) 

GDB est l’acronyme de GNU Debugger, un outil très important qu’on va apprendre à utiliser, patience !

Comme vous le voyez dans notre main:

   0x00000000004004d6 <+0>:	push   rbp
   0x00000000004004d7 <+1>:	mov    rbp,rsp
   0x00000000004004da <+4>:	mov    DWORD PTR [rbp-0x4],0xf
   0x00000000004004e1 <+11>:	mov    eax,0x0
   0x00000000004004e6 <+16>:	pop    rbp
   0x00000000004004e7 <+17>:	ret  

Vous rappelez-vous de rbp ? Non? retournez en haut !

Pour les autres, continuons… que fais notre programme ?

  • push rbp, met rbp sur la stack
  • mov rbp,rsp, met rsp dans rbp
  • mov DWORD PTR [rbp-0x4],0xf met 0xf (15 en haxadécimal) dans rbp
  • mov eax,0x0 met 0 dans eax
  • pop rbp met ce qu’il y a dans le haut de la stack dans rbp
  • ret quitte main

Comme vous le voyez on a pas de define (d’ailleurs ont n’a pas de nom de variable non plus) mais finalement on a bien 15 dans rbp !

On va remettre tout ça d’avantage au clair (il est important que vous compreniez bien !), prenons ce petit programme:

#include <stdio.h>

int main(void) {
    return 0;
}

Très bien, Compilons cela :

gcc test.c -o test

Et regardons la taille de chaque session via la commande size:

$ size test

text data bss dec  hex filename
1073 560  8   1641 669 test

Nous allons changer encore un peu notre code en rajoutant une variable globale !

#include <stdio.h>

int variableglobale;

int main(void) {
    return 0;
}
$ size test

text data bss dec  hex filename
1073 560  12   1641 669 test

Comme vous le remarquez bss est passé de 8 octets à 12 octets ! Notre variable globale est stockée dans bss !

#include <stdio.h>

int main(void) {
    static int variablestatique;
    return 0;
}
size test

text data bss dec  hex filename
1073 564  8   1641 669 test

Ahah ! Data passe de 560 à 564 notre variable statique est mise dans data

#include <stdio.h>

int global = 1;

int main(void) {
    return 0;
}
size test

text data bss dec  hex filename
1073 564  8   1641 669 test

Comme vous le remarquez une variable globale si elle est initialisée est mise dans data! Ça ira, j’espère que vous avez maintenant compris !

Comment fonctionne la mémoire ?

Dans cette partie on va pas parler de votre mémoire physique (Disque dur ou SSD) mais plutôt de la mémoire RAM et comment celle-ci est gérée par votre système d’exploitation ?

Nous allons nous attaquer d’abord à la mémoire virtuelle ! Un schéma vaut toujours mieux que de longs discours !

Schéma du principe de la mémoire virtuelle

Un processus qui tourne sur une machine nécessite de la mémoire, et dans un ordinateur, la quantité de mémoire est limitée. Il faut donc que les processus aillent chercher de la mémoire disponible pour pouvoir travailler.

Mais maintenant plusieurs processus s’exécutent en même temps. Que se passerait-il si deux processus voulaient accéder, à la même zone mémoire ? Et si jamais un processus écrivait dans une zone mémoire, puis qu’un autre processus écrase cette même zone mémoire avec ses propres données, alors le premier processus pensera retrouver ses données, mais il trouvera en fait les données du deuxième processus. Cela pose un très très gros problème ! !

C’est là qu’intervient une fonctionnalité majeure de votre système d’exploitation qui va résoudre le problème en allouant à chaque processus une plage de mémoire virtuelle (de 4Go sur les système 32 bits et de 8Go sur les systèmes 64 bits) chaque processus pourra utiliser les adresses mémoires dont il a besoin sans se soucier des autres processus, le noyau du système d’exploitation se débrouillera pour faire le lien entre la mémoire virtuelle et la mémoire réelle !

La Stack et la Heap

Nous allons maintenant passer à quelque chose de très important ! C’est quoi la stack et la heap (pile et tas):

La heap (ou tas pour les français) est manipulable par le programmeur. C’est la partie de la mémoire dans laquelle sont écrites les zones mémoires allouées dynamiquement (malloc() ou calloc()). Cette zone mémoire n’a pas de taille fixe. Elle augmente et diminue en fonction de ce qu’on lui demande, on peut réserver ou supprimer des blocs via des algorithmes d’allocation ou de libération pour une utilisation future. Plus la taille du tas augmente, plus les adresses mémoires augmentent, et s’approchent des adresses mémoires de la pile. La taille des variables dans le tas n’est pas limitée (sauf limite physique de la mémoire), contrairement à la pile.

Les variables stockées dans le tas sont accessibles partout dans le programme avec des pointeurs (vous avez sûrement vu ça quand vous avez appris le C) .

La stack (Pile en français) possède également une taille variable, mais plus sa taille augmente, plus les adresses mémoires diminuent, en s’approchant du haut du tas. En gros la stack grandi vers le bas. C’est ici qu’on retrouve les variables locales des fonctions . La stack frame d’une fonction est une zone mémoire, dans la pile, dans laquelle toutes les informations, nécessaires à l’appel de cette fonction, sont stockées. S’y trouvent également les variables locales de la fonction. Retenez cela très important.

Approfondir un peu la notion de Stack

Cela sera très important pour la suite !

Commençons par LIFO.

Hein ? Quoi LIFO

Rien de bien compliqué et on l’a déjas un peu vu , LIFO signifie Last In, First Out . C’est à dire que le dernier truc mis sur la stack est le premier qu’on va sortir on la vu notamment à travers pop et push

On fait un pop rax; rax est sur la stack

On fait un push rbp; on a rax dans rbp (rax est retiré de la stack)

En fait non, c’est pas totalement ça, rax reste à la même adresse sauf que maintenant rsp (qui est le stack pointer pointant vers le haut de la stack) pointe maintenant vers une adresse en plus bas.

Maintenant parlons de stack frame c’est simple et on doit juste savoir que c’est ce qui permet de passer le contexte d’exécution lorsqu’on appelle une fonction, en 32 bits tout les arguments d’une fonction sont passé par la stack

Dernier petit points vous vous rappelez les adresses de la stack diminuent ? On va expliquer ça un peu

img

tiré de Hackndo, blog de pixis.

Vous voyez la pile en haut ? imaginons que la première adresse de la stack après le mapping du noyau est 0xfffbbbbb par exemple et bien on va dire que c’est la plus basse (pourtant elle est plutôt vers le haut en fait !). En effet la stack grandie en se rapprochant de plus en plus de 0x00000000, c’est a dire l’adresse la plus basse

L’Overflow enfin !

Enfin, nous arrivons à la partie de l’exploitation ! Prenons un programme pour l’exemple !

#include <stdio.h>

int main() {
  char buffer[100];
  int a;

  scanf("%s", buffer);
}

Le code à l’air totalement normal, vous avez sûrement du utiliser la fonction “scanf “dans votre apprentissage du langage C. Mais et si on regarde à quoi ressemble la stack dans cet exemple ?? Eh bien suivez moi bien c’est très important !

[ buffer (100) ][int a][saved ebp][saved eip]

Vous vous demandez peut être c’est quoi saved ebp et saved eip ? Bon en fait on s’en fou un peu de saved ebp ce qui nous intéresse c’est saved eip, vous vous rappelez ce que contient eip ? Oui c’est ça ! L’adresse de la prochaine instruction a exécuter 🙂 Et si on changeait cette adresse on pourrait exécuter n’importe quoi !

Mais comment changer cette valeur ? Et bien c’est très simple ! Vous voyez scanf ne vérifie pas le nombre de caractères qu’elle reçois ! Prenons un autre programme :

#include <stdio.h>

int fonctionadmin() {
  printf("Vous avez réussi à sauter sur la fonction administrateur ! \n");
}
int vuln() {
  char buffer[10];
  scanf("%s", buffer);
}
int main() {
  vuln()
}

Dans ce cas nous avons la stack qui correspond plus ou moins à ça:

Stack

Si lors du scanf on donnait une valeur trop grande par exemple plusieurs “A” la stack ressemblera à cela:

https://beta.hackndo.com/assets/uploads/2015/03/stack3.png

Crédit: Ces deux schémas sont tiré de Hackndo, blog de pixis

Conclusion

Et voila EIP à été overflow ! A correspond à 0x41 en ASCII, EIP va donc sauter sur 0x41414141 qui n’est sûrement pas une valeur valide, le programme va donc nous retourner un segmentation fault ! Mais si on changeait les A par une adresse valide on pourrait sauter n’importe ou et notamment sur la fonction fonctionadmin()

C’est donc fini pour le coté théorique, on verra la pratique dans la deuxième partie. On se retrouve donc au prochain article!

À bientôt!

10 thoughts on “Overflow ? Comment ça marche ?”
  1. Oui en fait j’ai éssayé ceci :
    python -c “print ‘A’*24+’@_fonctionadmin'” , malheuresement ça m’affiche une erreur de segmentation , …

    au fait , j’ai bien debuger le code , esp diminue de 64 bits suivant votre shema

  2. Je ne suis pas un grand spécialiste de ce genre de détails mais :

    Tu dis que la pile grandit vers le bas. Que le tas grandit vers le bas.

    Mais en réalité est ce que ce ne serait pas plutôt :

    Le tas grandit vers le bas, la pile grandit vers le haut ? (Je suppose que c’est cela qui va permettre un écrasement de données et générer nos vuln enfin je suppose hein)

  3. Merci pour cet océan de connaissance partagé avec nous 😀 J’apprends plein de choses.

    Quelques coquilles orthographiques, mais je ne ferais l’affront à personne de te corriger en public, si tu veux, MP moi – Sinon pas grave – cela me va tout aussi bien.

    Petit détail qui semble important : La Stack et la Heap

    Tu as un smiley dans ton paragraphe où tu dis que “Stack” = “Tas”, je découvre mais d’après ce que j’ai compris le stack serait plutôt la pile, cela pourrait foutre le bordel dans la tête des gens 😉

    Merci encore X_X Je me suis gavé avec ton tuto 🙂

  4. > Pour un processeur 32 bits vos registres eax, ebx, ecx, edx, ebp, esp, eip et enfin edi ont une taille de 8 bits sois un byte (ou octet).

    C’est pas 32 bits (soit 4 bytes) plutôt ? 🙂

  5. “Pour un processeur 32 bits vos registres eax, ebx, ecx, edx, ebp, esp, eip et enfin edi ont une taille de 8 bits sois un byte (ou octet). Il existe d’autres registres qui ne vont pas nous intéresser maintenant. Dernier petit truc avant de voir à quoi corresponde ces registre: en 64 bits vous aurez les même registre sauf qu’ils feront 16 bits et que à la place due vous aurez r, (rbx, rip, par exemple).”

    Il faudrait corriger ces erreurs qui rendent les explications difficiles à comprendre pour les non-connaisseurs. Les registres x86 32 bits font… 32 bits et non pas 8. Et dans l’architecture 64 bits ils font… 64 bits et non pas 16. Certes, EAX contient aussi AX (16 bits), AL et AH (8 bits), mais c’est un registre 32 bits. D’ailleurs il n’est pas très cohérent d’écrire `mov rax, 0x1111111111111111` avant d’expliquer que RAX est un registre 16 bits.

    1. Merci Renaud d’être passé. Voyons si l’explication est plus claire maintenant. Est-ce que c’est validé?

Leave a Comment

Time limit is exhausted. Please reload CAPTCHA.