printf格式化字符串漏洞

printf格式化字符串漏洞

0x01 简介

在C语言中,我们常用的输出函数有printf、 fprintf、 vprintf、 vfprintf、 sprint等。对于这些输出函数,Format String是其第一个参数,我们一般称之为格式化字符串。

printf 接受变长的参数,其中第一个参数为格式化字符串,后面的参数在实际运行时将与格式化字符串中特定格式的子字符串进行对应,将格式化字符串中的特定子串,解析为相应的参数值。

在格式化字符串中,”%s”、”%d” 等类型的符号叫符号说明,这些符号说明的基本格式为 %parameterfield width[length]type。相信大家对于简单的符号说明并不陌生,但如果要利用格式化字符串漏洞,我们还需要用到几个比较冷门的符号说明,如:

format_string

格式化字符串漏洞:

在了解printf变长参数的特性之后,我们能够发现一些这个函数可能存在的漏洞。

我们已经知道,printf函数在执行时,首先进行格式化字符串的解析——从栈(或者寄存器)获取参数并与符号说明进行匹配,然后将匹配的结果输出到屏幕上。那么,如果格式化字符串中的符号声明与栈上参数不能正确匹配,比如参数个数少于符号声明个数时,就有可能造成泄露。

另外,printf也是一项有力的攻击武器,我们可以通过控制格式化字符串的值来实现更多的泄露或者完成更高级的利用(以上资料来源于网络)。

0x02 环境

ubuntu 16.04(已安装好pwn所需的各种工具)

0x03 实验

首先,还是利用之前的简单测试代码(可以看到格式化字符串后面没有给参数),如下:

#include<stdio.h>
int main()
{
    char* name = "Sunny";
    printf("My name is %s");
    return 0;
}

然后编译一下:

gcc -no-pie -fstack-protector-all -m32 -o canary canary.c

编译完成后直接使用gdb进行调试,先看下main函数反汇编:

canary_main

这里在0x080484ae处下断,然后单步执行到0x80484c3的位置:

esp+4

可以看到%s是将要输出的,因为后面的参数程序没有给,但是系统是认为有的,也就会去找之前压入栈的参数(正常在call之前还会压入一个参数,压入的这个参数在’My name is %s’之前),也就是esp+4的地方(这里是0xffffd034)。那么我们就尝试使用set命令改下esp+4的值(因为现在是空的):

set

之后再运行一下,看一下效果:

c

发现输出了”My name is Sunny”,然后我们不进行修改再执行一下这个程序,看下效果:

null

可以看到这个程序正常情况下是输出的”My name is null”,说明我们已经成功修改了输出的内容。

下面我们再看一个简单的测试程序:

#include<stdio.h>
int main()
{
    char str[0x10];
    read(0,str,0x20);
    printf("Sunny %s.");
    return 0;
}

直接编译一下:

gcc -no-pie -fstack-protector-all -m32 -o printf1 printf1.c

然后gdb调试,看下main函数反汇编:

printf_main

在0x080484de处下断,然后直接运行,单步执行到0x80484e9处:

read

这时,提示输入,这里输入”hello”,然后继续单步执行到0x8048500处,然后查看下输出的内容:

printf_hello

可以看到输出了”Sunny hello”,说明%s处成功输出了esp+4位置的参数。

0x04 总结

由于格式化字符串变长参数的特性,在实际运行中,如果Format String的符号说明个数超过待匹配的参数个数,即有更多的符号说明需要被匹配时,printf会根据解析结果和调用约定去取栈上(reg)相应的值并输出。