fmtstr_payload() in pwntools
from pwn import *
payload = fmtstr_payload(offset, writes, numbwritten='byte')
+offset is the stack offset of where you want to do the payload
+writes is a pair value { address_to_write_to: value_to_write }
+numbwritten (optional) is the number of byte that have been printed before the payload is processed
Example
Let’s say you are doing a CTF challenge where:
You found a format string vulnerability.
You used a debugger (like GDB) and found that your payload starts at offset 7.
You want to overwrite a variable called is_admin located at address 0x0804a02c with the value 1 to get admin privileges.
from pwn import *
# The address of the 'is_admin' variable
admin_variable_address = 0x0804a02c
# The value we want to write (1)
new_value = 1
# The stack offset we found
offset = 7
# Generate the payload
payload = fmtstr_payload(offset, {admin_variable_address: new_value})
# You would then send this payload to the vulnerable program
# p = process('./vulnerable_program')
# p.sendline(payload)
When the vulnerable program executes printf(payload), this specially crafted string will cause it to write the value 1 into the address 0x0804a02c. fmtstr_payload() automatically handles all the %n, %hn, %hhn specifiers and padding (%…c) to get the exact value you want written to the exact address.
Why this function fmtstr_payload() actually help
This function help yout to write payload which control exactly what value gets written to which address.
Why This Is a Lifesaver: The Manual Method
Our Goal: Write the 4-byte value 0xDEADBEEF to the address 0x12345678.
Assumptions:
- We are on a little-endian system (most x86/ARM CPUs).
- This means
0xDEADBEEFat0x12345678is stored in memory as:0xEFat0x1234567B0xBEat0x1234567A0xADat0x123456790xDEat0x12345678- We will use
%hhn(which writes 1 byte) for our writes.- The
printfbyte counter (which%hhnuses) starts at0.
Step 0: The Sort
The printf counter can only increase. We can’t write 222 bytes and then write 173 bytes, because the counter would already be past 173.
Therefore, we must sort our writes from the lowest byte value to the highest:
- Write
0xAD(173) -> to address0x12345679 - Write
0xBE(190) -> to address0x1234567A - Write
0xDE(222) -> to address0x12345678 - Write
0xEF(239) -> to address0x1234567B
Our payload will perform 4 writes in this specific order.
Step 1: Write the value 173 (0xAD)
- Goal: Write
173to0x12345679. - Current Counter:
0 - Action: We need the counter to become
173. We do this by printing173padding characters. - Payload Part:
%173c...(followed by a%...$hhnspecifier pointing to0x12345679) - Result:
*
printfprints173characters. * The counter increases from0to173. * The%hhntriggers and writes the value173(which is0xAD) to the address0x12345679.
Step 2: Write the value 190 (0xBE)
- Goal: Write
190to0x1234567A. - Current Counter:
173 - Action: We need the counter to become
190. It’s already at173, so we only need to print an additional190 - 173 = 17characters. - Payload Part:
%17c...(followed by a%...$hhnspecifier pointing to0x1234567A) - Result:
*
printfprints an additional17characters. * The counter increases from173to190. * The%hhntriggers and writes the value190(which is0xBE) to the address0x1234567A.
Step 3: Write the value 222 (0xDE)
- Goal: Write
222to0x12345678. - Current Counter:
190 - Action: We need the counter to become
222. We need to print an additional222 - 190 = 32characters. - Payload Part:
%32c...(followed by a%...$hhnspecifier pointing to0x12345678) - Result:
*
printfprints an additional32characters. * The counter increases from190to222. * The%hhntriggers and writes the value222(which is0xDE) to the address0x12345678.
Step 4: Write the value 239 (0xEF)
- Goal: Write
239to0x1234567B. - Current Counter:
222 - Action: We need the counter to become
239. We need to print an additional239 - 222 = 17characters. - Payload Part:
%17c...(followed by a%...$hhnspecifier pointing to0x1234567B) - Result:
*
printfprints an additional17characters. * The counter increases from222to239. * The%hhntriggers and writes the value239(which is0xEF) to the address0x1D234567B.
Summary
After 4 complex, ordered steps, we have performed 4 writes:
0x12345678now contains0xDE0x12345679now contains0xAD0x1234567Anow contains0xBE0x1234567Bnow contains0xEF
All 4 of these bytes combine to form the value 0xDEADBEEF at the address 0x12345678.
This is the extremely complex logic (sorting, calculating differences, handling padding, and managing addresses) that fmtstr_payload() automates for you in a single, clean line of code.