格式化字符串的漏洞利用(Part 1)

格式化字符串漏洞是一类允许攻击者在任意内存地址执行读或者写操作的软件缺陷。本教程主要关注C编程程序以及对格式化字符串函数的利用。

在我们开始理解软件缺陷之前,我们必须得先知道什么是格式化字符串。一个格式化字符串也就是一个ASCII字符串,其包括了文本和格式参数。例如,

printf("My name is: %s", "nops");

该函数调用将返回字符串

My name is: nops

该printf函数的第一个参数就是格式化字符串,它主要是依靠一个用来告诉程序如何进行格式化输出的说明符。在C程序中我们有许多用来格式化字符串的说明符,在这些说明符后面我们可以填充我们的内容。记住,说明符的前缀总是“%”字符,另外说明符存在许多不同的数据类型,最常见的包括:

%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数

可能会存在格式化字符串漏洞的函数包括(但不局限于)fprintf, printf, sprintf, snprintf,等

该漏洞的存在主要是程序员对于用户的输入没有进行好过滤造成的,下面我们通过一个例子进行说明:

  #include <stdio.h>
    int main(int argc, char * argv[])
    {
        char a[1024];
        strcpy(a, argv[1]);
        printf(a);
        printf("\n");
    }

这段代码将把接收到的字符串作为参数,创建一个1024字符串缓冲区,接着将字符串复制到缓冲区,最后调用两个printf函数格式化输出。在正常情况下编译并运行,程序获取到的第一个参数是可预期的(如果你关注过缓冲区溢出漏洞就十分清楚)。

root@localhost:~/#gcc test.c -o test
root@localhost:~/# ./test blah
blah

但是如果我们仔细查看printf文档,我们了解到调用的第一个参数是一个特殊的格式化字符串说明符。在我们这个简单的测试代码中,我们可以看到argv[1]将作为参数传递给printf函数。所以,我们有用户提供(即黑客提供)的数据将被解释成格式化字符串,这是十分危险的。接下来我们就看看这类攻击的示例

    root@localhost:~/# ./test %s
    TERM_PROGRAM=Apple_Terminal

我们键入%s作为攻击参数,它就会爆出一些有关我们终端的信息。为何会这样?这是因为printf函数认为它将要打印下一个堆栈地址,便将数据理解成了字符串。这是因为我们将%s作为格式化字符串(在代码中它仅仅是一个变量),接下来我们在漏洞程序中添加更多的格式化字符串,看看会发生什么。

    root@localhost:~/# ./test %s.%s
    TERM_PROGRAM=Apple_Terminal.(null)

目前,我们添加了第二个格式化字符串参数,通过“.”进行分隔,下一个堆栈的值为null。为第二个参数增加null的值,我们获取到与之前相同的终端信息。这种攻击是在堆栈中直接读取值,堆栈中如果存储有密码,密钥等那就想当危险了。我们尝试使用这项技术读取更多的信息,程序会爆分段错误。

    root@localhost:~/# ./test %s.%s.%s
    Segmentation fault: 11

但是我们可以进一步利用该漏洞,将值压入堆栈中。为了更好理解的这个工作原理,我们必须搞清楚printf规范中的两个特性,"%n"可以用来存储到目前为止所写的字符数,在相应的参数中用整数来表明变量名。

    int i;
    printf("ABCDE%n", &i);

printf函数会将5(刚刚写入的字符数)写入i变量么?

我们需要了解的第二个特性便是“$”操作符,其允许我们从格式化字符串中选取一个作为特定的参数。例如,

printf("%3$s", 1, "b", "c", 4);

最终会显示结果“c”。这是因为格式化字符串“%3$s”,它告诉计算机“把格式化字符串后面第三个参数告诉我然后将参数解释为字符串”。所以,我们也可以这样做

printf("AAA%3$n");

printf函数将值“3”(输入的A的数量)写入第三个参数指向的地址。等等,我们这里没有第三个参数啊!记住咯,printf会使用堆栈上连续的参数。不论怎样,printf都会将“3”写入堆栈中某一个地址中。

好吧,我个人觉得是很酷的。我们获取堆栈中泄漏的数据,以及一个不受控制的原始任意写(write-what-where)。为了利用这个漏洞执行代码我们可以控制写入的内容,我们可以控制在哪里写入内容。我们只能在堆栈中的任意位置进行写操作,并非万能的。在下一节内容中我们会讲到ShellCode的开发,并给我们这个BUG添加攻击载荷,让我们可以控制数据。

 


本文转自:http://www.freebuf.com/

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: