%c:输出字符,配上%n可用于向指定地址写数据。
%d:输出十进制整数,配上%n可用于向指定地址写数据。
%x:输出16进制数据,如%ilx表示要泄漏偏移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已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10hn表示写入的地址空间为2字节,%lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%hhn来适时调整。
%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
0x01 printf-读-简述
格式化字符串漏洞常见的标志为printf(&str),其中str中的内容是可控的。printf在解析format参数时,会自动从栈上format字符串结束的位置,按顺序读取格式化字符串对应的参数。
如图所示,执行的命令为printf(“%s %d %d %d %x”,buf, 1, 2, 3),紧随格式化字串后压入栈上的参数为4个,但格式化字串有五个参数,printf在解析第五个参数%x时,会继续往栈上读取,造成了信息泄露:
0x02 实战一
IDA中主函数逻辑如下:首先判断用户名是否为root,然后从系统中读取一个随机数,判断用户的输入与随机数是否相等。随机数输入的长度限制为0x50,告别了栈溢出的可能,随机数输入错误1次后exit_flag会置0,在下一次输入错误后程序会退出。
唯一的利用点在于程序中存在printf(&s),而s是可控的,因此可以用格式化字符串的任意地址读功能获取随机数:
.data:0000000000202098 exit_flag db 1 |
在输入中输入格式化字符串%x,程序会打印栈上的信息:
Hi, please input your name:root |
泄露出来的数据依次为RSI RDX RCX R8 R9 RSP+0x8 RSP+0x10 RSP+0x18的内容,在64位系统中,函数前5个参数通过寄存器传参,对应RSI RDX RCX R8 R9,函数不会泄露RDI,即格式化字符串本身的地址内容。
当寄存器的内容不足以填满格式化字符串的参数时,printf会继续往栈上索引,从RSP+0x8,即main函数的栈基址开始读取,刚好在第8个参数泄露了位于rsp+10h的随机数0x6db2adca20d558ab。
现在我们知道怎么计算偏移来读取任意地址的信息了,如果读取离当前RSP很远的信息,比如偏移了100个%p,可以使用的含义为输出对应位置的参数,比如%8$p输出第8个%p的数据:
Hi, please input your name:root |
pwndbg提供了一种方便的函数fmtarg,使用格式为fmtarg addr。在进入printf函数时断下,调用fmtarg后可以自动计算格式化参数与addr的偏移。fmtarg在计算index时将RDI也算了进去,后面会自动减一作为%$p的参数:
|
0x03 printf-写-简述
printf除了能将数据输出至标准输出,还能将数据输出至某一地址。printf通过%n、%hn、%hhn三个参数将已打印的字符个数输出至格式化参数对应的地址中,如:
|
可以通过格式化串中的输出占位符来调整输出字符串的个数:
|
%n一次写入四个字节,%hn一次写入两个字节,%hhn一次只写入一个字节。如果写入的字节数大于格式化字符串所对应的最大字节数,则发生溢出置0。
在空间足够的情况下,推荐使用%hhn进行写入,一来可以避免sprintf等函数末尾自动填充\0,二来通过溢出修改写入字节(如0x64 -> 0x32)所需的字符数较少,不会卡死。如果空间有限,则需酌情考虑使用其他格式字串或更换方法:
|
0x04 示例
与其他格式化字符串一样,%n系列也可以通过 $运算符来进行偏移,从而实现任意地址写的功能。下面我们通过一个简单的实例来康康如何进行写入,demo源码如下:
|
程序执行到printf前,栈上的数据分布如下:
rsp 0x7fffffffdb50 <- 0xa /* '\n' */ |
我们的目标是修改位于0x7fffffffdb50变量的值,注意,%n参数对应的是指针,我们需要借用一层跳板来执行解引用后修改操作,即传入0x7fffffffdb58这一指向0x7fffffffdb50的指针。
使用fmtarg得出该地址与格式化字符串的偏移为7:fmtarg 0x7fffffffdb58 The index of format argument : 8 ("%7hnn;%65c将打印栈上的垃圾数据,用于控制输出长度,进而控制修改的值。程序执行完后,t的值被修改成65: