本文是关于格式化字符串漏洞的介绍,论坛中有很多类似优秀的文章,我也对格式化字符串漏洞做一个总结,大家互相学习进步。
fmtstr github地址(包含代码和pdf文档):
https://github.com/ylcangel/exploits/tree/master/fmtstr
该文章仅用于学习目的,不能用于商用,谢谢!由于本人能力有限,文章中可能会存在纰漏,欢迎大家指出,我及时斧正。
图片上传会变的模糊,这个我实在没办法,如果没办法看,请去github下载pdf。
系统:CentOS release 6.10 (Final)、32 位
内核版本:Linux 2.6.32-754.10.1.el6.i686 i686 i386 GNU/Linux
gcc 版本: 4.4.7 20120313 (Red Hat 4.4.7-23) (GCC)
gdb版本:GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)
libc版本:libc-2.12.so
程序中提供了参数可控(该格式化字符串参数来自外部输入)的printf族和scanf族函数或错误的参数类型或格式化字符串参数和传入参数个数不一致等情况,这导致我们可以控制程序行为或泄露一些信息。例如:
1、 可控的参数
2、 参数类型错误
3、 格式化字符串参数和传入参数不符
第一种危害最大,我们完全可以控制程序行为,第二、三种不能控制程序行为,但可以对信息泄露提供一些帮助。
格式化漏洞有两种利用方式:一种是实现任何地址读(可用于信息泄露),一种是实现任意地址写(可用于覆盖返回地址、got表、函数虚表等)。
我们以printf函数为例来说明几个主要的格式化字符串的参数:
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n – 把前面打印的字符长度输出到指定地址
%N$ - 第N个参数
程序执行结果:
这里面前5个格式化字符串都用于输出,第6个用于输入,第七个用于指定参数的位置,第几个参数。
现在我们来看看参数个数不一致会发生什么情况。参数不一致指的是格式化字符串参数个数和实际输入参数不一致。
1、没有输入参数
然我们看看程序运行结果:
从图中可以看到,程序把sp+4地址对应内存值作为第一个参数(靠近栈顶第二个)。
2、带有输入参数:
然我们看看程序运行结果:
程序中包含7个格式化字符串参数,但实际传入参数只有两个。我们看到这次程序把sp+12
地址处的内存值作为程序第三个格式化参数,以此类推(我们设定的格式化参数都是4字节输出)。在看一个例子:
程序运行结果:
运行结果和第二个例子唯一的不同的是第三个格式化参数是%s,输出也变成了sp+12地址处内存中对应的字符串“|=x”(该地址不一定是字符串)。
通过这三个例子,我们了解到printf处理格式化参数的原理是如果输入的格式化参数个数多于实际输入参数,它将会把sp+ N*4的内存值作为格式化参数对应的类型输出(N代表第N个参数,也可以理解为到达栈顶的步长)。在我测试的机器上第一个参数的地址是当前sp+4。以上面三个例子说明:
第一个例子:依据sp + N*4,此时N为1,所以对应第一个参数地址为sp + 1*4 即为sp + 4。第二个例子:依据sp+ N*4,此时N为3(有两个参数),所以对应格式化参数的参数地址为sp+ 3*4,即sp + 12,第三个同理。
通过%N$我们可以指定格式化参数对应的实际参数位置,如%2$d代表把第二个输入参数以十进制方式输出。
1、 栈上没有定义参数
运行结果:
从运行结果我们可以看到指定输出参数位置和不指定的运行结果是一致的,原理同上一节讲的。
2、栈上存在定义的参数
运行结果:
从运行结果中可以看出,如果存在实际参数%N$中的N就代表第几个参数(它永远指向真是的参数,而不是sp + N*4),否则它指向sp + N*4(可以理解为距离栈顶的步长)。
泄露内存的核心都是利用%N$,可以延伸出两种形式:泄露栈内存和泄露任意内存。泄露栈内存有以下两种方式:
1、printf("%N$#x", {arg1...argn}) - 打印第N个参数的二级制值,%N$表示第N个参数, N可以大于n,arg代表实际输入参数(N >= 1)。
2、printf("%N$#x") - 打印当前栈顶距离N的内存值(N >= 1)。
%#x根据实际需要可以被替代为%s、%p。
前面已经举例了,这里就不在单独举例了。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-11-7 10:53
被angelToms编辑
,原因: