我正在涉足网络安全,特别是缓冲区溢出主题。为此,我正在考虑一个示例,其中缓冲区溢出允许攻击者从静态分配的消息缓冲区(即数据段中的某处)执行任意代码。遗憾的是,我的努力因分段错误而失败,几乎可以保证,因为缺少该数据段上的执行权(我的攻击确实在可执行堆栈上正确运行)。
我很清楚有一些选项可以将程序标记为需要可执行堆栈,内核在加载二进制文件时会注意到这些选项。也就是说,您可以使用
创建这样的二进制文件gcc -z execstack <source> -o <binary>
在过去,这实际上足以在 Linux 中接收可执行堆,因为显然所有可读页面都被视为可执行页面。但对于更现代的内核,几乎只有堆栈可以通过此选项执行。
我还想提一下 Stackexchange 网络中与此主题相关的两个先前问题:
mprotect
系统调用,它允许从程序内手动重新定义页面的特征。不幸的是,这种方法不适用于我的目的,因为我对程序没有任何此类控制权,并且我首先尝试建立执行代码的能力。-z execstack
链接器选项二进制文件。不幸的是,问题本身(导致这种变化的原因)仍然没有答案。那么:在现代Linux中是否有任何方法可以运行一个没有段上可执行性保护的程序,而不必更改程序代码本身?如果做不到这一点,有没有办法至少使堆/数据段可执行?或者是否可以构成为仅堆栈可以被设为一般可执行?
确实没有办法很好地做到这一点。现代 Linux 几年前在 v5.8 中与 read-implies-exec 分道扬镳(请参阅此处补丁)。在不触及二进制文件(无论是其 ELF 标头还是其代码)的情况下,没有一种“通用”方法可以恢复旧的行为。某些技巧可能仍然有效,例如32 位二进制文件、旧 CPU 或奇怪的架构,仅此而已。一个例子是 x86 32 位 ELF 缺乏显式堆栈保护标志,今天仍然具有 read-implies-exec(请参见表此处),尽管现代编译器通常不应默认省略堆栈保护标志。 我一直在教授和撰写针对不同级别的二进制利用的网络安全课程的挑战,这一变化绝对需要对入门级二进制利用的整个教学设置进行更新。如今,采用允许跳转到任何地方的 shellcode 的“一切都是可执行的”假设是不现实的。当然,在某些情况下,读取意味着执行或完整的 RWX/未受保护的内存仍然存在,例如嵌入式环境,但通常不能再假设这一点。值得庆幸的是,剥削变得更加困难。
好的,但是如果我真的想要怎么办?
如果你想玩得开心,这里有一个我刚刚编写的内核模块,插入时通过
kprobes为整个系统重新启用read-implies-exec(你需要一个具有默认CONFIG_KPROBES=y
的内核)为了这个工作):
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/**
* Restore old read-implies-exec kernel behavior via a kprobes hack: hook into
* setup_new_exec() to set the READ_IMPLIES_EXEC personality flag for the
* current task and into setup_arg_pages() to force executable_stack=EXSTACK_ENABLE_X.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/binfmts.h>
#ifndef CONFIG_X86_64
#error "This module only supports x86-64"
#endif
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
static int kp_setup_new_exec_pre(struct kprobe *kp, struct pt_regs *regs)
{
current->personality |= READ_IMPLIES_EXEC;
return 0;
}
static int kp_setup_arg_pages_pre(struct kprobe *kp, struct pt_regs *regs)
{
regs->dx = EXSTACK_ENABLE_X;
return 0;
}
static struct kprobe kps[] = {
{ .pre_handler = kp_setup_arg_pages_pre, .symbol_name = "setup_arg_pages" },
{ .pre_handler = kp_setup_new_exec_pre, .symbol_name = "setup_new_exec" },
};
static int __init modinit(void)
{
int ret;
for (unsigned i = 0; i < ARRAY_SIZE(kps); i++) {
ret = register_kprobe(&kps[i]);
if (ret < 0) {
pr_err("Failed to register kprobe for %s: %d\n",
kps[i].symbol_name, ret);
return -1;
}
pr_info("Registered kprobe for %s\n", kps[i].symbol_name);
}
pr_warn("Your system now runs with old read-implies-exec semantics!\n");
return 0;
}
static void __exit modexit(void)
{
for (unsigned i = 0; i < ARRAY_SIZE(kps); i++) {
unregister_kprobe(&kps[i]);
pr_info("Unregistered kprobe for %s\n", kps[i].symbol_name);
}
}
module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Restore old read-implies-exec behavior via a kprobes hack");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("Dual MIT/GPL");
使用 busybox 在 x86-64 QEMU VM 上的 Linux v6.12 上进行测试:
/ # cat /proc/self/maps
00400000-00401000 r--p 00000000 00:02 6 /bin/busybox
00401000-005bc000 r-xp 00001000 00:02 6 /bin/busybox
005bc000-0067d000 r--p 001bc000 00:02 6 /bin/busybox
0067e000-00688000 rw-p 0027d000 00:02 6 /bin/busybox
00688000-0068b000 rw-p 00000000 00:00 0
30b03000-30b26000 rw-p 00000000 00:00 0 [heap]
7f845aeed000-7f845aef1000 r--p 00000000 00:00 0 [vvar]
7f845aef1000-7f845aef3000 r-xp 00000000 00:00 0 [vdso]
7fffe91a8000-7fffe91c9000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
/ # insmod read_implies_exec.ko
[ 8.195897] read_implies_exec: loading out-of-tree module taints kernel.
[ 8.200370] read_implies_exec: Registered kprobe for setup_arg_pages
[ 8.201108] read_implies_exec: Registered kprobe for setup_new_exec
[ 8.201497] read_implies_exec: Your system now runs with old read-implies-exec semantics!READ_IMPLIES_EXEC personality.
/ # cat /proc/self/maps
00400000-0067d000 r-xp 00000000 00:02 6 /bin/busybox
0067e000-00688000 rwxp 0027d000 00:02 6 /bin/busybox
00688000-0068b000 rwxp 00000000 00:00 0
26519000-2653c000 rwxp 00000000 00:00 0 [heap]
7f75b7d91000-7f75b7d95000 r--p 00000000 00:00 0 [vvar]
7f75b7d95000-7f75b7d97000 r-xp 00000000 00:00 0 [vdso]
7fff29ba3000-7fff29bc4000 rwxp 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]