PWN 泄露输出与接收总结
在 Pwn 实战中,拿到 Shell 的前提通常是完美地“泄露 (Leak)”出内存中的地址(如 Libc 基址、Canary、PIE 基址)。
然而,C 语言的不同输出函数有着截然不同的底层,如果不摸透它们,你的 Pwntools 脚本就会频繁遭遇 EOFError、struct.error 或死锁卡死。
一、 C 语言三大输出函数特性对比
在寻找泄露 Gadget 或分析题目原本的输出漏洞时,必须一眼看穿这三个函数的本质区别:
| 输出函数 | 遇到 \x00 是否停止 |
是否自动追加 \n |
实战泄露评估 | 接收核心技巧 |
|---|---|---|---|---|
puts(addr) |
是 (绝对截断) | 是 | 最常见,但有坑。如果内存中相邻字节是 \x00,它就读不出来了。 |
接收后必须剔除末尾的 \n,且接收长度不固定。 |
printf(addr) |
是 (绝对截断) | 否 | 神级武器。支持 %s、%p,是格式化字符串漏洞的根源。 |
用 %p 泄露时需提防前导 0 被吞;用 %s 泄露同 puts 怕 \x00。 |
write(1, addr, len) |
否 (无视截断) | 否 | 最完美的泄露函数!你让它输出多少字节,它就死死输出多少,连 \x00 也会原样打出来。 |
根据参数 len 定长接收 recv(len),最为稳定。 |
二、 printf 格式化输出的四大底层陷阱
当我们利用 %p 或 %s 进行盲打或泄露时,常常被眼前的输出欺骗。以下是四大经典误区:
前导零消失术 (%p / %x)
现象:64位地址本应是 16 个十六进制字符(8字节),但 %p 输出往往只有 12 个字符(如 0x7ffff7a00000)甚至更少。
底层原因:%p 会把地址当成整数解析,抹杀所有无意义的前导 0。
极端情况:如果泄露的是一个空指针(0x0000000000000000),glibc 会直接输出字符串 (nil),而不是 0x0!
避坑方案:绝不要用定长 recv(12) 去接低位地址。在 Payload 尾部加上固定分隔符(如 -),使用 recvuntil(b"-") 动态截取。
解引用截断陷阱 (%s)
现象:用 %s 泄露 GOT 表时,有时只能泄露出一两个乱码字节,甚至什么都没有。
底层原因:%s 会去读指针指向的内存,一旦在内存中读到 \x00(零字节),它立刻认为字符串结束,停止打印!
实战影响:很多 GOT 表地址中间包含 \x00(尤其是高位全 0 时),导致泄露不完整。必须用 .ljust(8, b'\x00') 在 Python 端强行补齐 8 字节。
Canary 泄露的 \x00 覆盖
现象:Canary 保护机制的最低位固定是 \x00(用来防 puts 和 %s 泄露的)。
泄露手法:必须先用栈溢出刚好覆盖掉那个 \x00(比如写个 a),然后再调用 puts 或 printf。这样它才会顺着往下把整个 Canary 带出来。接收后,记得把最低位手动改回 \x00。
缓冲池死锁 (No setvbuf)
现象:脚本写得很完美,但在本地或远程跑时,终端卡死,什么输出都收不到。
底层原因:题目在 main 函数开头没有写 setvbuf(stdout, 0, 2, 0);。这导致 printf 的输出被缓存在了系统缓冲区里,没有带 \n 换行符就不会刷新到屏幕上。
避坑方案:在 Payload 末尾强制加上 \n,或者使用不需要缓冲的 write 函数。
三、 Pwntools 完美接收泄露地址模板
不要每次都去重新想怎么解包地址,直接粘贴以下三套标准模板
模板一:基于 %p 的通用接收(防前导 0 缺失)
适用场景:格式化字符串漏洞,用 %p 泄露栈或 libc。
核心思路:在 Payload 植入“结束标志符”,动态截取。
1 |
|
模板二:基于 puts 的 64 位接收(防 \n 和 \x00 截断)
适用场景:ROP 链调用 puts(got_addr) 泄露地址。
核心思路:剥离末尾的换行符,并用 \x00 将缺失的高位补齐到完整的 8 字节。
1 | # 假设程序执行了 puts(puts_got) |
模板三:基于 write 或 recv(定长) 的接收
适用场景:程序使用 write(1, addr, 8) 输出,或者你确信 %p 打印的是 64 位 Libc 地址(必是 12 个有效字符)。
核心思路:不依赖任何分隔符,直接数数。
1 | # 场景 A:确信 %p 输出的是 0x7f 开头的 libc 地址 |
四、排错法则
当你在 u64() 这一步遭遇报错时,立刻按照以下步骤排错:
开天眼:在 Python 脚本开头加上 context.log_level = 'debug'。
看长度:观察终端里红色的 [DEBUG] Received 0x... bytes:。
对症下药:
如果收到了 6 个字节,说明是 \x00 被吃掉了,加上 .ljust(8, b'\x00')。
如果收到了 7 个字节,且最后一个字节是 0x0a,说明把 puts 的换行符吃进去了,先 .strip(b'\n') 再补齐。
如果收到了乱码字符串,说明没加 int(..., 16),把十六进制字符串当成了字节流解析
- 本文链接: http://example.com/2026/03/14/PWN/pwn_attack/PWN通用问题/输出格式/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
欢迎关注我的其它发布渠道