dimanche 13 décembre 2009

protection de la stack et du heap dans Ubuntu 9.10

Ubuntu 9.10 protège la stack et le heap contre les bufferoverflows en les rendant non exécutables. Cet article illustre cette fonctionnalité et propose une méthode pour tester un shellcode en local malgré cela.



  1. protection de la Stack
  2. protection du Heap
  3. le NX bit
  4. return to libc attack
  5. test en local de notre shellcode


outils

- gcc et gdb
- Ubuntu 9.10

protection de la Stack

Nous voulons tester un shellcode. Cependant, nous allons voir que la protection en exécution de la Stack et du Heap sous Ubuntu nous l'interdisent.

Voici notre shellcode helloworld:

"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\xeb\x05\x59\xb2\x0b\xcd\x80\xe8\xf6\xff\xff\xffhelloworld!"

$ objdump -d asm

08048054
:
8048054: 31 c0 xor %eax,%eax
8048056: 31 db xor %ebx,%ebx
8048058: 31 c9 xor %ecx,%ecx
804805a: 31 d2 xor %edx,%edx
804805c: b0 04 mov $0x4,%al
804805e: b3 01 mov $0x1,%bl
8048060: eb 05 jmp 8048067

08048062 :
8048062: 59 pop %ecx
8048063: b2 0b mov $0xb,%dl
8048065: cd 80 int $0x80

08048067 :
8048067: e8 f6 ff ff ff
804806c: 68 65 6c 6c 6f
8048071: 77 6f
8048073: 72 6c
8048075: 64 21 00

Nous le placons dans notre programme de test:

// helloworld.c
#include < stdio.h>
char sh[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\xeb\x05\x59\xb2\x0b\xcd\x80\xe8\xf6\xff\xff\xffhelloworld!";
int main()
{
void (*func)();
func = (void*) sh;
func();
return 0;
}

Nous compilons puis exécutons:
$ gcc -Wall -g -o shellcode shellcode.c
$ ./shellcode

Erreur de segmentation
$

Surpris, nous vérifions que le programme saute bien sur notre shellcode:
$ gdb ./shellcode
(gdb) list

1 #include
2 char sh[] = "\x90\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\xeb\x05\x59\xb2\x0b\xcd\x80\xe8\xf6\xff\xff\xffhelloworld!";
3 int main()
4 {
5 void (*func)();
6 func = (void*) sh;
7 func();
8 return 0;
9 }
10
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x080483c9 in main () at shellcode.c:7
7 func();

Où a lieu le mauvais accès mémoire? Voyons le programme en assembleur:

(gdb) disassemble main
Dump of assembler code for function main:
0x080483b4 : push %ebp
0x080483b5 : mov %esp,%ebp
0x080483b7 : and $0xfffffff0,%esp
0x080483ba : sub $0x10,%esp
0x080483bd : movl $0x804a040,0xc(%esp)
0x080483c5 : mov 0xc(%esp),%eax
0x080483c9 : call *%eax
0x080483cb : mov $0x0,%eax
0x080483d0 : leave
0x080483d1 : ret
End of assembler dump.
(gdb)

L'erreur arrive au call *eax. Qu'y a-t-il dans eax et qu'y a-t-il à l'adresse pointée par eax?

(gdb) info registers eax
eax 0x804a040 134520896
(gdb) x/16x 0x804a040
0x804a040 : 0x31c03190 0x31c931db 0xb304b0d2 0x5905eb01
0x804a050 : 0x80cd0bb2 0xfffff6e8 0x6c6568ff 0x6f776f6c
0x804a060 : 0x21646c72 0x00000000 0x00000000 0x00000000
0x804a070: 0x00000000 0x00000000 0x00000000 0x00000000

Nous retrouvons bien notre shellcode.

Vérifions que l'espace mémoire dans lequel se trouve notre shellcode est non exécutable. Gardons le programme débuggé et regardons les caractéristiques du processus associé. Dans un autre terminal:

$ pidof shellcode
3444
$ cat /proc/3444/maps
001e6000-00201000 r-xp 00000000 08:06 1215 /lib/ld-2.10.1.so
00201000-00202000 r--p 0001a000 08:06 1215 /lib/ld-2.10.1.so
00202000-00203000 rw-p 0001b000 08:06 1215 /lib/ld-2.10.1.so
003d9000-00517000 r-xp 00000000 08:06 2411 /lib/tls/i686/cmov/libc-2.10.1.so
00517000-00519000 r--p 0013e000 08:06 2411 /lib/tls/i686/cmov/libc-2.10.1.so
00519000-0051a000 rw-p 00140000 08:06 2411 /lib/tls/i686/cmov/libc-2.10.1.so
0051a000-0051d000 rw-p 00000000 00:00 0
00623000-00624000 r-xp 00000000 00:00 0 [vdso]
08048000-08049000 r-xp 00000000 08:06 2530 /home/shellcode
08049000-0804a000 r--p 00000000 08:06 2530 /home/shellcode
0804a000-0804b000 rw-p 00001000 08:06 2530 /home/shellcode
b7fef000-b7ff0000 rw-p 00000000 00:00 0
b7ffe000-b8000000 rw-p 00000000 00:00 0
bffeb000-c0000000 rw-p 00000000 00:00 0 [stack]
08048000-08049000
Nous remarquons que:
- l'adresse de notre shellcode (0x804a040) se trouve dans un espace mémoire rw-p non exécutable,
- notre programme est mappé dans trois espaces dont l'un (08048000-08049000) est exécutable. Nous reverrons cela plus tard.

Conclusion: Ubuntu 9.10 a rendu la stack non exécutable.


protection du Heap

Recommençons en placant notre shellcode dans le Heap:

// shellcode2.c
#include < stdio.h>
#include < stdlib.h>
int main()
{
char* chaine_stack;
char* chaine_heap;
int size,i;
chaine_stack = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\xeb\x05\x59\xb2\x0b\xcd\x80\xe8\xf6\xff\xff\xffhelloworld!";
size = 35;
chaine_heap = (char*) malloc (size*4);
for (i=0; i< size; i++)
chaine_heap[i]= chaine_stack[i];
void (*func)();
func = (void*) chaine_heap;
func();
return 0;
}
Compilons et exécutons:
$ gcc -Wall -g -o shellcode2 shellcode2.c
$ ./shellcode2

Erreur de segmentation

Nous avons à nouveau un problème d'accès mémoire. Débuggons:

$ gdb ./shellcode2
(gdb) run
Starting program: /home/shellcode2
Program received signal SIGSEGV, Segmentation fault.
0x0804844a in main () at shellcode2.c:16
16 func();
(gdb) disassemble main
Dump of assembler code for function main:
0x080483e4 : push %ebp
0x080483e5 : mov %esp,%ebp
0x080483e7 : and $0xfffffff0,%esp
0x080483ea : sub $0x30,%esp
0x080483ed : movl $0x8048520,0x2c(%esp)
0x080483f5 : movl $0x23,0x24(%esp)
0x080483fd : mov 0x24(%esp),%eax
0x08048401 : shl $0x2,%eax
0x08048404 : mov %eax,(%esp)
0x08048407 : call 0x804831c
0x0804840c : mov %eax,0x28(%esp)
0x08048410 : movl $0x0,0x20(%esp)
0x08048418 : jmp 0x8048434
0x0804841a : mov 0x20(%esp),%eax
0x0804841e : add 0x28(%esp),%eax
0x08048422 : mov 0x20(%esp),%edx
0x08048426 : add 0x2c(%esp),%edx
0x0804842a : movzbl (%edx),%edx
0x0804842d : mov %dl,(%eax)
0x0804842f : addl $0x1,0x20(%esp)
0x08048434 : mov 0x20(%esp),%eax
0x08048438 : cmp 0x24(%esp),%eax
---Type to continue, or q to quit---
0x0804843c : jl 0x804841a
0x0804843e : mov 0x28(%esp),%eax
0x08048442 : mov %eax,0x1c(%esp)
0x08048446 : mov 0x1c(%esp),%eax
0x0804844a : call *%eax
0x0804844c : mov $0x0,%eax
0x08048451 : leave
0x08048452 : ret
End

L'erreur a bien lieu au moment de l'appel call *%eax

(gdb) info registers eax
eax 0x804b008 134524936
(gdb) x/16x 0x804b008
0x804b008: 0xdb31c031 0xd231c931 0x01b304b0 0xb25905eb
0x804b018: 0xe880cd0b 0xfffffff6 0x6c6c6568 0x726f776f
0x804b028: 0x0021646c 0x00000000 0x00000000 0x00000000
0x804b038: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb)

Il s'agit à nouveau de l'appel à notre shellcode. Voyons les droits d'accès mémoire. Dans un autre Terminal:

$ pidof shellcode2
5818
$ cat /proc/5818/maps
0039a000-003b5000 r-xp 00000000 08:06 1215 /lib/ld-2.10.1.so
003b5000-003b6000 r--p 0001a000 08:06 1215 /lib/ld-2.10.1.so
003b6000-003b7000 rw-p 0001b000 08:06 1215 /lib/ld-2.10.1.so
00431000-0056f000 r-xp 00000000 08:06 2411 /lib/tls/i686/cmov/libc-2.10.1.so
0056f000-00571000 r--p 0013e000 08:06 2411 /lib/tls/i686/cmov/libc-2.10.1.so
00571000-00572000 rw-p 00140000 08:06 2411 /lib/tls/i686/cmov/libc-2.10.1.so
00572000-00575000 rw-p 00000000 00:00 0
00ef5000-00ef6000 r-xp 00000000 00:00 0 [vdso]
08048000-08049000 r-xp 00000000 08:06 2528 /home/shellcode2
08049000-0804a000 r--p 00000000 08:06 2528 /home/shellcode2
0804a000-0804b000 rw-p 00001000 08:06 2528 /home/shellcode2
0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
b7fef000-b7ff0000 rw-p 00000000 00:00 0
b7ffe000-b8000000 rw-p 00000000 00:00 0
bffeb000-c0000000 rw-p 00000000 00:00 0 [stack]
$

Notre shellcode est placé à l'adresse 0x804b008, donc placé dans le Heap, non exécutable.
0804b000-0806c000 rw-p 00000000 00:00 0 [heap]


le NX bit

Le NX-bit (No-eXecute), est une technologie implémentée dans les microprocesseurs récents. Il sert à marquer certaines zones mémoires comme non exécutables. Linux supporte le NX-bit depuis le noyau 2.6.8. (voir changelog kernel 2.6.8 référence n°4 et Wikipedia référence n°5 et référence n°6 ).


return to libc attack

Face à une stack (ou Heap) non exécutable, il est possible d'utiliser l'attaque "return to libc". Il s'agit de remplacer l'adresse de retour sur la pile par l'adresse d'une fonction placée en librairie, en ayant auparavant empilé les bons arguments.

Cette méthode est difficile à implémenter avec l'option (activée par défaut sous Ubuntu) Address Space Layout Randomization. En effet, l'adresse de la fonction cible est alors aléatoirement mappée en mémoire, donc non accessible via un pointeur fixe.

Pour davantage d'informations, voir Wikipedia (référence n°3) et l'article de Heurs (référence n°2).



test en local de notre shellcode

Comment pouvons nous tester notre shellcode?

Nous avons remarqué que notre programme était mappé dans trois zones mémoires, dont l'une est exécutable. Voyons ce que nous y trouvons. Nous retournons dans le programme shellcode2 que nous débuggons à nouveau:

$ gdb ./shellcode2
(gdb) run
Starting program: /home/shellcode2
Program received signal SIGSEGV, Segmentation fault.
0x0804844a in main () at shellcode2.c:16
16 func();
(gdb)

Nous observons la zone mémoire 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
(gdb) x/1000x 0x8048000
(...)

Nous copions-collons le listing dans gedit et recherchons la chaine de caractères 31c0. Nous retrouvons notre shellcode à l'adresse 0x8048520,
c'est à dire 0x8048000 + 0x520, donc, par rapport à l'adresse de notre shellcode dans le heap: 0x804b008 - 0x8 - 0x3000 + 0x520.

Nous modifions notre programme:

// shellcode3.c
#include < stdio.h>
#include < stdlib.h>
int main()
{
char* chaine_stack;
char* chaine_heap;
int size,i;
chaine_stack = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb3\x01\xeb\x05\x59\xb2\x0b\xcd\x80\xe8\xf6\xff\xff\xffhelloworld!";
size = 35;
chaine_heap = (char*) malloc (size*4);
for (i=0; i< size; i++)
chaine_heap[i]= chaine_stack[i];
void (*func)();
func = (void*) chaine_heap;
func = func - 0x8 -0x3000 + 0x520;
func();
return 0;
}
$ gcc -Wall -g -o shellcode3 shellcode3.c
$ gdb ./shellcode3
(gdb) run

Starting program: /home/shellcode3
helloworld!

CTRL-C
(gdb)

Cela fonctionne avec gdb. Pourtant:
$ ./shellcode3
Erreur de segmentation

Nous vous laissons méditer là dessus...

conclusion

Ubuntu 9.10 offre une protection intéressante: elle rend la Stack et le Heap non exécutables. Cette protection s'ajoute à l' Address Space Layout Randomization et à la Stack smashing protection.

références

1) ghosts in the stack - Trance - les Shellcodes - http://www.ghostsinthestack.org/article-5-les-shellcodes.html
2) ghosts in the stack - Heurs - ret onto ret - http://www.ghostsinthestack.org/article-6-les-ret-onto-ret.html
3) Wikipedia - return to libc attack - http://en.wikipedia.org/wiki/Return-to-libc
4) changelog Kernel 2.6.8 - paragraphe [PATCH] NX (No eXecute) support for x86 - http://www.kernel.org/pub/linux/kernel/v2.6/ChangeLog-2.6.8
5) Wikipedia - Executable space protection - http://en.wikipedia.org/wiki/Executable_space_protection
6) Wikipedia - NX bit - http://en.wikipedia.org/wiki/NX_bit

1 commentaire:

  1. Arrggggg, je médite je médite mais là, ça m'énerve sérieusement !!!!
    gdb jouerait-il dans l'espace mémoire exécutable? Comment dans ce cas rendre possible l'exécution de notre prog dans cet espace mémoire dans Ubuntu 9.10 !!! HELP !

    RépondreSupprimer