12.1. 格式化字符串¶
12.1.1. 概述¶
类printf函数的最大的特点就是,在函数定义的时候无法知道函数实参的数目和类型。对于这种情况,可以使用省略号指定参数表。
带有省略号的函数定义中,参数表分为两部分,前半部分是确定个数、确定类型的参数,第二部分就是省略号,代表数目和类型都不确定的参数表,省略号参数表中参数的个数和参数的类型是事先的约定计算出来的,每个实参的地址(指针)是根据确定参数表中最后一个实参的地址算出来的。
这里涉及到函数调用时的栈操作。函数栈的栈底是高地址,栈顶是低地址。在函数调用时函数实参是从最后一个参数(最右边的参数)到第一个参数(最左边的参数)依次被压入栈顶方向。也就是说函数调用时,函数实参的地址是相连的,并且从左到右地址是依次增加的。
12.1.2. 攻击原理¶
因为类printf函数中省略号参数表中参数的个数和类型都是由类printf函数中的那个格式化字符串来决定的,所以攻击者可以利用编程者的疏忽或漏洞,巧妙构造格式化字符串,达到攻击目的。
举一个简单的例子,如果想输出一个字符串,可以使用 printf("%s", str)
或者 printf(str)
。而第二种写法是有漏洞的,如果攻击者输入 %d
%x
等格式化字符,那么一个变量的参数值就从堆栈中取出。
12.1.3. 常用字符¶
%c
输出字符,配上
%n
可用于向指定地址写数据
%d
输出十进制整数,配上
%n
可用于向指定地址写数据
%x
输出16进制数据
%i$x
表示要泄漏偏移i处4字节长的16进制数据%i$lx
表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样
%p
输出16进制数据,与
%x
基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit
%s
输出的内容是字符串,即将偏移处指针指向的字符串输出,如
%i$s
表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息
%n
将
%n
之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置%100x%10$n
表示将0x64写入偏移10处保存的指针所指向的地址(4字节)%$hn
表示写入的地址空间为2字节%$hhn
表示写入的地址空间为1字节%$lln
表示写入的地址空间为8字节,在32bit和64bit环境下一样有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过
%$hn
或%$hhn
来适时调整
12.1.4. sinks¶
printf
vprintf
cprintf
dprintf
fprintf
asprintf
snprintf
vdprintf
vfprintf
vsprintf
vasprintf
vsnprintf