Format String Vulnerability

FSV(格式字符串漏洞)是一类特殊的漏洞,它是由对格式化输出函数(例如C语言中的printf函数家族)的不当使用而造成的。如果攻击者能够控制格式化输出函数的输入,并且应用程序没有进行适当的验证,那么攻击者就能:通过把格式化参数(例如’%x’)插入到数据中,来改变格式化输出函数的行为,让它误认为有更多的参数。而这些参数却并不存在,于是乎攻击者就能够对栈上的数据进行读写了。

Capability

格式化输出函数本身并没有漏洞,但FSV给了攻击者控制它们的能力。那么攻击者能够通过FSV做些什么呢?最简单的攻击就是利用FSV让程序崩溃,例如printf ("%s%s%s%s%s%s%s%s%s%s%s%s");极有可能读取到没有映射的地址,造成程序崩溃。再者,攻击者也可以通过%n来篡改栈上的地址来达到同样的效果。

information leakage

如果格式化字符串的输出是可见的(或者间接可见的,例如返回给一个字符串给remote attacker),那么FSV就可能导致信息的泄露。和前面所说的类似,printf ("%08x.%08x.%08x.%08x.%08x\n");能够揭露栈上的一部分数据(在64位下揭露的是寄存器和栈中的数据)。但某些情况下,FSV能够被用来揭露任何地址的数据,而不仅仅是栈上的数据。

通常,格式化字符串被放在栈上,而格式化输出函数内部通常会有一个内部指针,它指向当前的格式化字符串参数。如果能够把目标地址嵌入到格式化字符串当中去,那么目标地址就会被保存在栈上了,这样一来就可以通过操纵内部指针的方式,把目标地址的数据给输出。下面这个例子中,user_input保存了一个格式化字符串。在32位下,printf ("\x10\x01\x48\x08 %x %x %x %x %s");能够揭露0x10014808地址的值。

int main(int argc, char *argv[])
{
    char user_input[100];
    ... ... /* other variable definitions and statements */
    scanf("%s", user_input); /* getting a string from user */
    printf(user_input); /* Vulnerable place */
    return 0;
}

fsv1

那么64-bit下有什么区别呢?我也自己做了一个实验。与32-bit不同的是,只有第七个之后的参数会被放在栈上,而前六个参数是保存在栈中的。但类似的攻击依然可以成功。printf(%p.%p.%p.%p.%p.%p)会输出0x70252e70252e7025(也即user_input),这说明用户的输入自身也被输出了。那么如何实现一个任意的读呢?值得注意的是在64-bit下,地址不可避免的含有\x00,所以这里我把\x00放在字符串的最后。例如,printf("%7s$lx\x00\x00\x00\x00\x00\x40\x00\x00");输出的结果是400000。如果要输出任意地址的数据,就要注意对齐:printf("%7$s\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00")会按照%s输出地址0x400000地址的内容。

overwrite memory

FSV不仅能够用来泄露内存中的信息,还能被攻击者用来篡改内存中的数据。要达到这个目标,需要借助较为特殊的格式指定符号%n。它并不会打印任何东西,但它会存储它之前所打印的所有字符数。因此只要采用和information leakage相似的方法,只需要把%s替换为%n,就能够实现对任意地址的读写。例如在32-bit下,printf (“\x10\x01\x48\x08 %x %x %x %x %n”)就能够覆写0x10014808中的地址。然而,这个值是由%n之前的打印的字符数来决定的,那么用这种方法能够写一个任意值到指定的地址中去吗?通过指定宽度,可以节省字符串的长度,但是如果要设置一个较大的值,就需要进行多次写操作,例如利用%hn把一次%n分解成两次,这样一来printf就只用输出一小部分数据了。在64-bit下,我也做了一个类似的试验,printf("123%7$n\x00\x50\x10\x60\x00\x00\x00\x00\x00")会把123写到0x605010处。如果要写多个数,则需要按照从小到大的顺序进行输入,因为%n获取的值是累加的。值得注意的是,64-bit下地址会不可避免的包含\x00,它同时也是print家族函数的终止符,所以\x00只能放在最后。这也同时限制了64-bit下FSV的能力,使其在许多情况下并不能实现指定地址的读写了。