利用格式化字符串漏洞改写返回地址执行指定函数
doSomething()的实现方案 没问题,咱们一步步搞定这个格式化字符串漏洞,实现在程序里调用doSomething()的目标。既然ASLR关了,还知道函数的精确地址,又能定位返回地址的位置,这事儿就好办多了。
核心原理
你的程序里vuln函数直接用printf(buf)处理用户输入,这就导致了格式化字符串漏洞——printf会把buf里的内容当成格式化指令解析,咱们可以利用%hhn(精确写入单个字节的格式化符)来修改栈上的main函数返回地址,把它改成doSomething()的地址,这样程序从main返回时就会跳转到doSomething()执行。
具体步骤
1. 确定输入内容在格式化参数中的偏移
首先得搞清楚咱们输入的内容在栈上对应格式化参数的起始位置。你可以输入这样的测试串:
AAAA%1$p %2$p %3$p %4$p %5$p %6$p ...
然后看输出里哪个位置显示0x41414141(也就是AAAA的十六进制值),假设是第6个,那就说明输入内容的前4字节对应第6个格式化参数,接下来的4字节是第7个,以此类推。
同时,你已经能打印返回地址,结合调试工具(比如gdb)可以确认main函数返回地址在栈上的存储位置,记为ret_addr_ptr(这是个指针,指向栈里存返回地址的地方)。
2. 拆分目标地址和构造地址段
把已知的doSomething()地址拆成4个字节(x86是小端序,所以低字节在前)。比如doSomething()地址是0x0804857F,拆分后就是:
- 低字节:
0x7F - 次低字节:
0x85 - 次高字节:
0x04 - 高字节:
0x08
然后,咱们需要在payload开头放四个连续的地址:ret_addr_ptr、ret_addr_ptr+1、ret_addr_ptr+2、ret_addr_ptr+3
每个地址按小端序写入(比如ret_addr_ptr是0xffffd000,就写成\x00\xd0\xff\xff)。这四个地址分别对应要修改返回地址的四个字节位置。
3. 构造格式化控制段
用%hhn来精确写入单个字节(%hhn会把当前printf输出的总字节数写入到对应的指针指向的内存字节),配合%<num>x来填充字节,凑到我们需要的数值:
- 第一个
%hhn(对应第6个参数,也就是ret_addr_ptr)要写入0x7F:前面已经有16字节的地址段,所以需要填充0x7F - 16 = 111个字节,用%111x,然后跟上%6$hhn - 第二个
%hhn(对应第7个参数,ret_addr_ptr+1)要写入0x85:此时总输出已经到0x7F,还需要填充0x85 - 0x7F = 6个字节,用%6x,然后%7$hhn - 第三个
%hhn(对应第8个参数,ret_addr_ptr+2)要写入0x04:这个值比当前总输出0x85小,所以要填充到0x04 + 0x100 = 260字节,差值是260 - 133 = 127(0x85是133),用%127x,然后%8$hhn - 第四个
%hhn(对应第9个参数,ret_addr_ptr+3)要写入0x08:需要填充到0x08 + 0x200 = 520字节,差值是520 - 260 = 260,用%260x,然后%9$hhn
4. 完整payload示例
把地址段和控制段拼起来,比如ret_addr_ptr是0xffffd000,doSomething()地址是0x0804857F,payload就是:
\x00\xd0\xff\xff\x01\xd0\xff\xff\x02\xd0\xff\xff\x03\xd0\xff\xff%111x%6$hhn%6x%7$hhn%127x%8$hhn%260x%9$hhn
5. 特殊情况处理
- 如果
doSomething()的地址里包含\x00字节,printf遇到\x00会停止解析,这时候可以调整写入顺序(比如先写高字节),或者用%n写入整个4字节地址(需要计算更大的填充数)。 - 测试时可以先用
%k$p代替%k$hhn,确认每个参数输出的是咱们预设的地址,确保偏移没搞错。
验证
把构造好的payload作为程序输入运行,程序执行完main后就会跳转到doSomething()执行了。
内容的提问来源于stack exchange,提问作者Mandar Juvekar

