pwn format
漏洞成因
格式化字符串(Format String)漏洞是 pwn 题中的经典类型。它的核心成因通常是程序中直接将用户输入作为格式化字符串参数传给了 printf、sprintf、fprintf 等函数,例如:
1 | // 漏洞代码 |
当程序这样写时,如果我们输入格式化占位符(如 %p, %x, %n),printf 就会误把栈上的数据当作参数来解析,从而导致任意内存读取或任意内存写入。
解题技巧
确定偏移量(Offset)
做题的第一步,是找出你的输入数据在 printf 函数栈上的第几个参数位置。
- 测试方法: 输入一串标志性字符(如
AAAA或A的 ASCII 码0x41414141),后面接一串%p。 - 示例输入:
AAAA.%p.%p.%p.%p.%p.%p.%p.%p - 观察输出: 寻找输出中哪一个位置出现了
0x41414141(64位系统下是0x4141414141414141,或者是指向它的指针)。 - 快捷定位: 如果算出来是第 6 个位置,后续可以用
%6$p直接打印该位置的值,不用写一长串%p。
任意内存读取(Leak 信息)
有了偏移量后,我们可以利用 %s 或 %p 来泄露敏感信息,从而绕过安全防护。
泄露 Canary 或 libc 地址
绕过 Canary: Canary 通常存在于栈上固定位置。找到对应的偏移量后,用
%X$p(X为偏移量)就可以直接读出 Canary 的值,防止本地溢出时触发报错。泄露 libc 基地址(绕过 ASLR): 栈上通常会残留
__libc_start_main或其他 libc 函数的返回地址。通过%X$p读出这个地址,减去它在 libc 中的固定偏移,就能算出 libc 的基地址,进而拿到system函数和/bin/sh的地址。
任意地址读取
如果你想读取一个已知明文地址
0xabcdef00的内容:- 构造 Payload:
[目标地址] + %Offset$s printf解析到%Offset$s时,会把前面的目标地址当作指针,并打印出该指针指向的字符串内容。
- 构造 Payload:
任意内存写入(Modify 数据)
这是格式化字符串威力最大的地方,主要利用 %n 占位符。
关键原理:
%n不打印任何东西,而是把前面已经打印的字符总数,写入到对应的指针变量中。
由于 %n 一次性写入 4 字节,数值太大(比如要写一个地址 0x08049000,需要打印几千万个字符,会导致网络超时或程序崩溃),所以实际打 payload 时通常用:
%hn:写入 2 字节(short)%hhn:写入 1 字节(char)
常用修改目标
- 修改 GOT 表(最常见): 将某个常用函数(如
puts或printf自身)的 GOT 表地址,修改为system函数的地址。下一次程序调用puts(input)时,实际上执行的就是system(input)。 - 修改返回地址: 修改栈上的返回地址(Return Address),直接劫持控制流到后门函数或 ROP 链。
- 修改全局变量: 有些题目会检查一个
flag变量是否等于某个特定值,直接用%n把它改成目标值即可。
自动化原理解题:使用 pwntools
手动去算 %hn 需要多少个字符非常痛苦,好在 pwntools 提供了强大的自动化工具 fmtstr_payload。
模板:
1 | from pwn import * |
总结做题思维导图
- Checksec: 看程序开启了什么保护(如果是 Full RELRO,则无法修改 GOT 表,需要考虑修改栈上的返回地址或
__malloc_hook)。 - 找偏移: 传入
AAAA确定输入点在栈上的位置。 - Leak 地址: 打印出 Canary 和 libc 地址,计算出真实函数的绝对地址。
- 改写劫持: 利用
fmtstr_payload篡改 GOT 表或返回地址,打出system('/bin/sh')拿到 shell。