日期:2008-10-05  浏览次数:20917 次

翻译:   xuzq@chinasafer.com

内容:
        介绍

        什么是格式化字符串攻击?
        Printf-学校忘记教给你的东西
        简单的例子
        来格式化吧!(Format Me!)
        X MARKS THE SPOT(X是本文示例程序中我们试图重写的一个变量,这句我不知
道如何翻译)
        怎么着(So what)?

摘要

本文讨论格式化字符串漏洞的成因和含义,并给出实际的例子来解释原理。

介绍

我知道在某些时候对于你我和我们大家而言,下面这种情况总会发生。在一个时下流行的
晚餐会上,夹杂在同事们大呼小叫的声音里,你听到了"格式化字符串攻击"这只言片语。
"格式化字符串攻击?什么是格式化字符串攻击?"你心说。由于害怕在同事们面前显露出
自己的无知,你决定停
止不自然的微笑,而频频点头以示自己对这玩艺了如指掌。如果一切顺利,大家会共饮鸡
尾酒,谈话仍将继续,但是没人明白这究竟是怎么回事。现在不用再害怕什么了,本文会
提供你想知道而又不好意思问的所有内容。

什么是格式化字符串攻击?

格式化字符串漏洞同其他许多安全漏洞一样是由于程序员的懒惰造成的。当你正在阅读本
文的时候,也许有个程序员正在编写代码,他的任务是:打印输出一个字符串或者把这个
串拷贝到某缓冲区内。他可以写出如下的代码:

    printf("%s", str);

但是为了节约时间和提高效率,并在源码中少输入6个字节,他会这样写:

    printf(str);

为什么不呢?干嘛要和多余的printf参数打交道,干嘛要花时间分解那些愚蠢的格式?
printf的第一个参数无论如何都会输出的!程序员在不知不觉中打开了一个安全漏洞,可
以让攻击者控制程序的执行,这就是不能偷懒的原因所在。

为什么程序员写的是错误的呢?他传入了一个他想要逐字打印的字符串。实际上该字符串
被printf函数解释为一个格式化字符串(format  
string)。函数在其中寻找特殊的格式字符比如"%d"。如果碰到格式字符,一个变量的参
数值就从堆栈中取出。很明显,攻击者至少可以通过打印出堆栈中的这些值来偷看程序的
内存。但是有些事情就不那么明显了,这个简单的错误允许向运行中程序的内存里写入任
意值。

Printf-学校忘记教给你的东西

在说明如何为了自己的目的滥用printf之前,我们应该深入领会printf提供的特性。假定
读者以前用过printf函数并且知道普通的格式化特性,比如如何打印整型和字符串,如何
指定最大和最小字符串宽度等。除了这些普通的特性之外,还有一些深奥和鲜为人知的特
性。在这些特性当中,
下面介绍的对我们比较有用:
        *在格式化字符串中任何位置都可以得到输出字符的个数。当在格式化字符串中
碰到"%n"的时候,在%n域之前输出的字符个数会保存到下一个参数里。例如,为了获取在
两个格式化的数字之间空间的偏量:

      int pos, x = 235, y = 93;
      printf("%d %n%d\n", x, &pos, y);
      printf("The offset was %d\n", pos);

* %n格式返回应该被输出的字符数目,而不是实际输出的字符数目。当把一个字符串格式
化输出到一个定长缓冲区内时,输出字符串可能被截短。不考虑截短的影响,%n格式表示
如果不被截短的偏量值(输出字符数目)。为了说明这一点,下面的代码会输出100而不
是20:

      char buf[20];
      int pos, x = 0;
      snprintf(buf, sizeof buf, "%.100d%n", x, &pos);
      printf("position: %d\n", pos);

简单的例子

除了讨论抽象和复杂的理论,我们将会使用一个具体的例子来说明我们刚才讨论的原理。
下面这个简单的程序能满足这个要求:
    /*
     * fmtme.c
     *       Format a value into a fixed-size buffer
     */

    #include <stdio.h>

    int
    main(int argc, char **argv)
    {
        char buf[100];
        int x;
        if(argc != 2)
            exit(1);
        x = 1;
        snprintf(bu