漏洞分析
万万没想到直到学习了pwn五个月以后 我才开始学习手写格式化字符串payload 先前都是习惯利用了fmstr_payload来构造了 但是直到遇到了一道题 要在一次格式化字符漏洞中 利用两次 fmstr_payload构造出来的payload无法达到预期的攻击效果 所以只能自己手写了
来复习一下格式化字符串任意写漏洞的原理
利用了%n可以根据已经输出的字节修改对应偏移处地址的值
不过先前我们学习过的任意写 只是简单的将一个地址处的值修改为个位数大小 所需要的字节数很小 如果我们想要修改got表的值为后门函数呢 这要如何实现 总不可能传输同等大小的字节数吧
这时候引入一个新的格式化字符 %c 其有什么效果呢 我们编写下面一段小程序
|
%c 可以输出单个字符 所以此时的运行结果应该是单个字符t
如果像%s之类的格式化字符 在前面加上数字呢 又有什么效果?
#include <stdio.h> |
可以看到最后的结果在实际输出的字符t前面 还加上了9字节的\x00 也就是会自动补全我们输出的字符
而其占用的字节数也很小 哪怕是%0x10000c 所占用的字节数也只为9
这就使得哪怕题目限制了我们利用格式化字符漏洞的payload的字节数 我们仍然可以保证任意写的攻击
但是这仍然不够完美 我们还有没有更好的办法来修改got表这样的地址其值
我们来看一下函数的got表 在32位情况下 其存储的值是如何占用这四个字节
可以看到是小端序存储 并且一个字节对应着两个数字
那么比如说printf函数中的got表 高位的0x08 对应的地址为0x804989c + 3
如果我们只需要修改高位的值 就可以往这个地址写入单字节 利用 ‘h’来构造格式化字符
payload = "%"+str(要修改的值).encode()+"c%偏移$hhn" |
实例分析
下面利用一道国赛题来帮助理解
[CISCN 2019西南]PWN1
查看一下保护机制
没有开启Partical RELRO 或者是Full RELRO 那么可以fini_array处就有可写的权限
ida看一下伪代码
int __cdecl main(int argc, const char **argv, const char **envp) |
同时还提供了system函数
只有一次格式化字符串的机会 既然可以修改fini_array 那么我们首先想到的就是利用格式化字符串漏洞 将fini_array修改为main函数的地址
不过实际攻击效果和我预期的有点不一样 在第二次执行完main函数以后就没有办法再次返回了 估计是栈空间不够的锅 那没办法 就只能在第一次格式化字符串的时候就同时修改fini_array和printf函数的got表
就是这里 利用fmstr_payload构造出来的payload无法达到预期的攻击效果 所以我们采用手写的方式
首先是计算一下偏移 这个就不详细展开了 最后发现的偏移是4
fini_addr = 0x804979C |
首先我们要清楚一点 如果单次格式化字符利用想要修改多个地址值 那么后面需要修改的值一定是要大于前面的
因为前面%c输出的空字符 也算到后面的总字节数里面的 为了防止修改的值超出预期 所以需要把较大的数值安排到后面
还有一点是为什么要用str().encode()的形式 是因为python3 byte型和字符型的要求
完整exp:
from pwn import* |