格式化字符串漏洞

0x00 格式化参数

参数 输入类型 输出类型
%d 十进制整数
%u 无符号十进制整数
%x 十六进制整数
%s 指针 字符串
%n 指针 到目前位置为止,已写的字节个数

%n格式化参数比较独特,因为它在写数据时没有任何输出,与读数据然后显示相反,格式化函数遇到格式化参数%n时,它将输出已经被存放到对应函数参数地址的字节数。

0x01 格式化字符漏洞

当程序员用printf(string)语句代替printf("%s",string)语句输出字符串时,就会造成格式化字符串漏洞。也许程序员的本意只是单纯打印一段字符(如“hello world”),但如果这段字符串来源于外部用户可控的输入,则该用户完全可以在字符串中嵌入格式化字符(如%s)。那么,由于printf允许参数个数不固定,故printf会自动将这段字符当作format参数,而用其后内存中的数据匹配formate参数。

格式化字符串漏洞

以上图为例,假设调用printf(str)时的栈是这样的。

1)如str就是“hello world”,则直接输出“hello world”;

2)如str是format,比如是%2$x,则输出偏移2处的16进制数据。

通过组合变换格式化字符串参数,我们可以读取任意偏移处的数据或向任意偏移处写数据,从而达到利用格式化字符串漏洞的作用。

0x02 读取任意存储地址的内容

可以用格式化参数%s来读取任意存储位置的内容。

格式化字符串漏洞

要是我们的输入可以覆盖偏移2的内容为0x41414141,载通过给printf(formate)的formate赋值%2$s,那我们就可以读取地址0x41414141指向的值

0x03 向任意存储地址写入

可以使用%n来对任意存储地址进行写入操作。

格式化字符串漏洞

如图,通过向printf(formate)的formate赋值%20s$2n就可以将0x41414141所指向的值修改为0x14(16进制)

0x04 直接参数访问

顾名思义,直接参数访问允许通过使用美元符号$直接存取参数。

例如:用%N$d可以访问第N个参数,并且把它以十进制输出。

printf("7th:%7$d, 4th:%4$05d 
",10,20,30,40,50,60,70,80);
上述print调用的输出显示如下:
7th: 70, 4th:0040

0x05 附加

32位:
读
'%{}$x'.format(index)           // 读4个字节
'%{}$p'.format(index)           // 同上面
'${}$s'.format(index)

'%{}$n'.format(index) // 解引用,写入四个字节 '%{}$hn'.format(index) // 解引用,写入两个字节 '%{}$hhn'.format(index) // 解引用,写入一个字节 '%{}$lln'.format(index) // 解引用,写入八个字节
64位: 读 '%{}$x'.format(index, num) // 读4个字节 '%{}$lx'.format(index, num) // 读8个字节 '%{}$p'.format(index) // 读8个字节 '${}$s'.format(index)
'%{}$n'.format(index) // 解引用,写入四个字节 '%{}$hn'.format(index) // 解引用,写入两个字节 '%{}$hhn'.format(index) // 解引用,写入一个字节 '%{}$lln'.format(index) // 解引用,写入八个字节 %1$lx: RSI %2$lx: RDX %3$lx: RCX %4$lx: R8 %5$lx: R9 %6$lx: 栈上的第一个QWORD

0x06 参考链接

printf 格式化输出符号详细说明

格式化字符串漏洞利用小结

Linux下的格式化字符串漏洞利用姿势