常见的格式化字符串函数有printf scanf等
这里我们给出一个简单的例子,其实相信大多数人都接触过printf函数之类的。之后我们再一个一个进行介绍。
Input: printf("Color %s, Number %d, Float %4.2f ", "red", 123456, 3.14)
Output: Color red, Number 123456, Float 3.14
printf为格式化字符串函数, Color %s为格式化字符串, red为参数
这里我们了解一下格式化字符串的格式,其基本格式如下
%[parameter][flags][field width][.precision][length]type
每一种pattern的含义请具体参考维基百科的格式化字符串。以下几个pattern中的对应选择需要重点关注
type:
d/i,有符号整数
u,无符号整数
x/X,16进制unsigned int 。x使用小写字母;X使用大写字母。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
o,8进制unsigned int 。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
s,如果没有用l标志,输出null结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了l标志,则对应函数参数指向wchar_t型的数组,输出时把每个宽字符转化为多字节字符,相当于调用wcrtomb 函数。
c,如果没有用l标志,把int参数转为unsigned char型输出;如果用了l标志,把wint_t参数转为包含两个元素的wchart_t数组,其中第一个元素包含要输出的字符,第二个元素为null宽字符。
p, void *型,输出对应变量的值。printf("%p",a)用地址的格式打印变量a的值,printf("%p", &a)打印变量a所在的地址。
n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
%, '%'字面值,不接受任何flags, width。
那么假设,此时我们在编写程序时候,写成了下面的样子
printf("Color %s, Number %d, Float %4.2f");
此时我们可以发现我们并没有提供参数,那么程序会如何运行呢?程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为
1. 解析其地址对应的字符串
2. 解析其内容对应的整形值
3. 解析其内容对应的浮点值
对于2,3来说倒还无妨,但是对于对于1来说,如果提供了一个不可访问地址,比如0,那么程序就会因此而崩溃。
这基本就是格式化字符串漏洞的基本原理了
格式化字符串漏洞利用
其实,在上一部分,我们展示了格式化字符串漏洞的两个利用手段
1. 使程序崩溃,因为%s对应的参数地址不合法的概率比较大。
2. 查看进程内容,根据%d,%f输出了栈上的内容。
下面我们会对于每一方面进行更加详细的解释。
程序崩溃
我们只要输入若干%s就可以达到目的了, 因为在栈上不可能都存储的是合法的地址(比如栈上的数值)
泄露内存
1. 泄露栈内存
获取某个变量的值 根据 C 语言的调用规则,格式化字符串函数会根据格式化字符串直接使用栈上自顶向上的变量作为其参数 (64 位会根据其传参的规则进行获取)。这里我们主要介绍 32 位。利用%x来获取栈的内存, 但建议用%p, %order$x获取指定参数的值, 其实就是函数不知道参数的个数, 所以说我们输入的格式化字符串才会泄露栈上的内容
获取某个变量对应地址的内存 利用%s来获取变量对应地址的内容, %order$s同上
2. 泄露任意地址内存
利用GOT表得到libc函数地址,进而获取libc,进而获取其它libc函数地址
盲打,dump整个程序,获取有用信息。
最后声明一下 此文章仅为摘抄笔记不是原创