格式字符串是包含格式说明符的字符串。它们被用于C语言和许多其他编程语言的格式函数中。例如,以下代码示例显示了C中printf()的工作方式。根据变量名中包含的内容,该语句将输出不同的句子。
printf(“Hello, my name is %s.”, name);
如果变量名称包含字符串“ 连云小李”,则printf()语句将输出:
Hello, my name is 连云小李.
注:这里特意用汉字,因为有的编译器汉字显示编码有问题,需要特别注意修改类似Unicode和utf-8
接着是格式化函数和参数
除了printf() 以外,还有许多格式函数,它们使用格式字符串来产生输出。例如在C语言中,有printf(), fprintf(), sprintf(), snprintf(), printf_s(), fprintf_s(), sprintf_s(), snprintf_s()等。
而除了上面代码使用的%s外,还有许多不同的格式说明符。不同的格式说明符指示应将其替换为哪种数据类型:简单举几个例子
%d用于带符号的十进制整数,
%u代表十进制的无符号整数,
%x是十六进制的无符号整数,
%s表示数据指向字符串的指针。
根据格式说明符规定的数据格式,格式函数检索从堆栈中请求的数据。
printf(“A is the number %d, B is the string %s”, A, &B);
上面的printf() 函数将尝试从堆栈中检索A的值和字符串B的地址。
格式化字符串一文入门到实战
那到底怎么利用格式函数呢?
当攻击者可以直接控制输入给函数的格式字符串时,就可以利用格式函数:看下面的示例,是不是很清晰? printf(“If the attacker can control me, you’re in trouble.”, A, B); 当字符串中的格式说明符数量与用于填充这些位置的函数参数(如上面的A和B)数量不匹配时,将发生此漏洞。如果攻击者提供的占位符超过了参数个数,则可以使用格式函数来读取或写入堆栈。 这里还需要补充一点关于堆栈的说明: 堆栈涉及到数据结构的知识,不在本文的讨论范围内,这里便仅作简单叙述。 现在只需要记住,局部变量和函数参数存储在堆栈中。这意味着,当声明局部变量或函数参数时,它将被压入堆栈。而当调用函数时,该函数也会从堆栈中获取数据。 我们正式开始使用格式函数尝试泄漏程序信息: 当攻击者提供的格式说明符多于函数参数来填充其位置时,想象一下会发生什么情况?当有两个格式说明符,但只有一个函数参数提供值时,printf() 会做什么? printf(“A is the number %d, reading stack data: %x”, A); printf() 仍将尝试从堆栈中检索两个值。但是由于堆栈上只有一个实际的函数参数(A)占据了这些位置,因此另一个值将被堆栈上下一个值替换。在这种情况下,printf() 将检索堆栈中的下一个值,并以十六进制格式显示它。 因此,攻击者只需提供以下字符串即可读取堆栈上的大量数据: printf(“%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x”); 看起来乱七八糟的,可能有点像WEB渗透中的SQL攻击的效果,这句代码将在堆栈上打印接下来的20个数据 攻击者甚至可以通过使用特殊情况格式说明符直接访问堆栈上的第i个参数: printf("%10$x"); 这句代码将在堆栈上打印第十个元素 很明显,这将导致堆栈数据泄漏:包括返回地址,局部变量和其他函数的参数。 那再升级一下,如何在内存中的任何位置读取数据呢? 当%s用作格式说明符时,该函数会将堆栈上的数据视为要从中获取字符串的地址。这称为引用传递。这意味着即使数据不在堆栈中,攻击者也有可能使用%s从任何地址读取。 但是,具体又如何控制%s访问的地址?攻击者需要在堆栈上放置一个地址,并使%s取消引用该地址! 更简便一点的情况下,格式字符串将会完全由攻击者控制存储在堆栈中!因此,如果攻击者可以将地址植入格式字符串中并让%s取消引用,则甚至可以访问堆栈之外的数据。 printf(“\xef\xbe\xad\xde%x%x%x%s”, A, B, C); 例如,上面的代码将导致printf() 打印位于地址0xdeadbeef的字符串。%x系列用于将堆栈遍历到格式字符串的位置,所需的%x数量会因情况而异。%s告诉printf() 处理的前四个字节的格式字符串作为指针指向打印的字符串。