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