格式化字符串漏洞
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 \n",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 参考链接
Always believe that good things will come.