Day 05 格式化字符串漏洞
格式化字符串漏洞
1. 漏洞原理
在 C 语言中,printf 这一类函数会根据传入的 格式化字符串(format string)解析参数,例如:
1 | printf("hello %s\n", name); |
但如果程序员直接写成:
1 | printf(input); |
而没有提供额外参数,用户输入就会直接被当成格式化字符串解析。这时攻击者可以通过特殊的格式符(如 %p、%s、%n)来读取或写入内存,从而造成严重漏洞。
2. 基本利用方式
(1) %p —— 打印栈上的地址
%p会从栈上依次取出参数并以指针形式打印,不会解引用。- 在没有参数的情况下,
printf还是会从栈上取值,因此我们能通过连续输入多个%p来泄露栈上的内容。
示例:
1 | 输入: %p #打印二参的地址 |
(2) %s —— 打印指定地址的字符串
%s会把栈上的值当作 指针,然后尝试打印这个地址对应的字符串(解引用)。
示例:
1 | 输入: %4$s #打印第五个参数解引用后的值 |
表示取栈上第 5 个参数的值作为地址,并打印字符串。
如果这个地址正好是 flag 所在位置,就能直接泄露内容。
(3) %n —— 写入内存
%n会把 当前已输出的字符数 写入到对应的参数地址中。- 这意味着攻击者可以 往任意地址写入整数,从而修改内存关键位置(如 GOT 表)。
示例:
1 | 输入: AAAA%n |
会将输出的字符数(4)写入栈上取出的地址。
配合偏移控制:
1 | 输入: %10$n |
会将输出字符数写入栈上第 11个参数所指向的地址。
用途:
- 任意地址写(最关键的利用手段)。
- 可用来覆盖 GOT 表条目(got表可写时,即partial RELRO),把函数指针劫持到
system。
3. 常见利用流程
信息泄露
- 用
%p打印大量内容,找到栈上或 GOT/PIE 地址。 - 用
%s配合偏移泄露内存,获取 libc 地址。
- 用
任意写
用
%n向 GOT 表写入system地址,可以用fmtstr_payload(offset, writes, numbwritten, write_size)这个函数改写got表。offset是字符串偏移值,使用pwndbg调试找到输入所在的参数位置,writes是键值对,形如{printf.got:system.plt},键是要改写的地址,值是目标函数的plt调用地址。后面两个参数一般默认。示例:
payload=fmtstr_payload(6,{0x601030:0x40085D})0x601030指printf的.got.plt地址,0x40085D指system的.plt地址。
拿 shell
- 覆盖
printf的 GOT 为system,然后再次调用时输入/bin/sh。
- 覆盖
4. 小技巧
指定参数位置:
%7$p→ 打印第 8个参数;
%12$s→ 打印第 13个参数指向的字符串。
这样能精确控制泄露和写入。控制写入的值:
%n写入的是“已经打印的字符数”,所以需要通过%xxx的方式控制输出长度。例如:1
AAAA%1234c%10$n
会在输出
AAAA后打印 1234 个字符,总共 1238 个字符,然后把 1238 写入第 11 个参数的地址。关于offset和%x$的取值:

offset=x=6 + 上图最左边那列的编号数
5. 总结
%p→ 泄露地址。%s→ 任意地址解引用。%n→ 任意地址写(修改 GOT、函数指针)。
利用链:
信息泄露 → 找 libc → 任意写 → 劫持控制流 → getshell。




