You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非PIE二进制ASLR环境下,printf@plt泄露GOT条目输出空串的原因

为什么调用printf@plt后输出空字符串?

这完全是printf和puts的核心行为差异导致的:

  • puts()的逻辑非常直白:它接收一个指针参数,从该指针指向的位置开始输出字节,直到碰到\x00空字节才停止——不管内容是字符串还是二进制值。所以你直接把GOT条目的地址传给puts,它会把GOT里存的libc地址(二进制数)当作字节流输出,哪怕包含不可打印字符,至少能把地址的原始字节打出来(可以通过管道捕获后分析)。
  • printf()是格式化输出函数,它的第一个参数必须是格式字符串。如果你直接把GOT条目的地址作为第一个参数传给printf,它会把这个地址指向的内容(也就是libc里printf的真实地址,是一串二进制值)当作格式字符串解析。这串二进制值里几乎不可能包含%s这类有效的格式占位符,甚至可能自带\x00字节(比如地址里有0的话)直接让printf停止解析,结果自然就是输出空字符串了。

举个32位系统的例子:假设printf@GOT里存的地址是0x08041234,对应的字节是0x34 0x12 0x04 0x08,printf把这四个字节当作格式字符串,里面没有任何%开头的格式符,所以它什么都不会输出。

还能用printf实现地址泄露吗?当然可以!

关键是给printf传递正确的参数组合,让它把你要泄露的地址当作字符串输出。你需要构造类似这样的调用逻辑:

printf("%s", &printf@GOT);

也就是让printf的第一个参数是包含%s的格式字符串,第二个参数是你要泄露的GOT条目地址。

具体在栈溢出场景下的实现思路:

  1. 在溢出的payload里嵌入"%s"字符串(不需要刻意加\x00结尾,printf遇到格式符就会处理后续逻辑);
  2. 控制栈/寄存器来构造printf的参数:
    • 32位系统(栈传参):按顺序压栈:先压printf的返回地址(比如后续ROP链或exit@plt),再压GOT条目地址(第二个参数),最后压栈上"%s"的地址(第一个参数),然后跳转到printf@plt;
    • 64位系统(寄存器传参):用ROP gadget把"%s"的地址放到rdi寄存器,把GOT条目地址放到rsi寄存器,再跳转到printf@plt。

这样printf会按照%s的要求,输出第二个参数指向的内存内容(也就是GOT里的libc地址),直到碰到空字节,你就能拿到libc的基地址了。

另外还有一种变通技巧:如果栈上或内存里已经存在可利用的格式符(比如环境变量里的字符串),你也可以用%x这类格式符来逐字节泄露栈上的内容,间接拿到GOT地址,但这种方式不如直接用%s直观高效。

内容的提问来源于stack exchange,提问作者awerv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:21:55