格式化字符串 ======================================== 概述 ---------------------------------------- 类printf函数的最大的特点就是,在函数定义的时候无法知道函数实参的数目和类型。对于这种情况,可以使用省略号指定参数表。 带有省略号的函数定义中,参数表分为两部分,前半部分是确定个数、确定类型的参数,第二部分就是省略号,代表数目和类型都不确定的参数表,省略号参数表中参数的个数和参数的类型是事先的约定计算出来的,每个实参的地址(指针)是根据确定参数表中最后一个实参的地址算出来的。 这里涉及到函数调用时的栈操作。函数栈的栈底是高地址,栈顶是低地址。在函数调用时函数实参是从最后一个参数(最右边的参数)到第一个参数(最左边的参数)依次被压入栈顶方向。也就是说函数调用时,函数实参的地址是相连的,并且从左到右地址是依次增加的。 攻击原理 ---------------------------------------- 因为类printf函数中省略号参数表中参数的个数和类型都是由类printf函数中的那个格式化字符串来决定的,所以攻击者可以利用编程者的疏忽或漏洞,巧妙构造格式化字符串,达到攻击目的。 举一个简单的例子,如果想输出一个字符串,可以使用 ``printf("%s", str)`` 或者 ``printf(str)`` 。而第二种写法是有漏洞的,如果攻击者输入 ``%d`` ``%x`` 等格式化字符,那么一个变量的参数值就从堆栈中取出。 常用字符 ---------------------------------------- - ``%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`` 来适时调整 sinks ---------------------------------------- - printf - vprintf - cprintf - dprintf - fprintf - asprintf - snprintf - vdprintf - vfprintf - vsprintf - vasprintf - vsnprintf