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。