是否可以在经典操作系统上在虚拟 0x0 处写入?

Arc*_*tek 2 c assembly gcc ld virtual-memory

我不确定我问的问题是否正确,但我想知道 C 或 ASM 程序是否可以在虚拟地址 0x0 处写入?

我知道内核不允许在虚拟 0x0 处写入/读取,但我想知道是否有一些奇怪的方法可以做到这一点。可能使用我不知道的 ld 或 gcc 标志?

设置如下:

  • 在 x86-64 上运行 Linux 的计算机
  • 如果需要可以使用root

fuz*_*fuz 6

为此需要三个步骤:

  1. 配置系统以允许将内存映射到地址零。这是通过将vm.mmap_min_addrsysctl 设置为 0 来完成的:

    sysctl vm.mmap_min_addr=0
    
    Run Code Online (Sandbox Code Playgroud)

    如果您在系统上使用 Wine,则该 sysctl 可能已经配置,因为 Wine 需要它才能执行 16 位代码。

    另一种方法vm.mmap_min_addr=0是使用 运行进程CAP_SYS_RAWIO,例如以 root 身份运行sudo strace ./a.out。(strace让您看到它进行的系统调用,这样您就可以看到mmap返回成功0而不是(void*)-1

  2. 在地址0处建立映射。为了让内核让你向地址0写入数据,你需要在那里映射一些内存。这可以通过调用mmap映射一些内存或安排二进制文件加载段来轻松完成。

    有关示例mmap,请参阅在 Linux 上使用 mmap 分配地址零失败

  3. 取消引用指向地址 0 的指针。这可以使用 C 代码(例如*(int *)0 = 42.
    这恰好可以使用 GCC 或禁用优化的 clang 编译为我们想要的 asm 1


脚注 1:
0是 C 中的空指针常量,取消引用它是未定义的行为。禁用优化后,GCC 和 clang 仅编译为存储到该地址 ( Godbolt ) 的 asm,但 clang 警告非易失性空指针的间接将被删除,而不是陷阱请考虑使用__builtin_trap()或限定指针与 '易失性'。这确实是启用优化后会发生的情况。GCC-O2或更高版本仍然会发出存储指令,但在 x86-64 上会出现ud2非法指令陷阱。

volatile int *volatile zero_ptr = 0; *zero_ptr = 42;在 GCC 和 clang 中工作,以向编译器隐藏 UB:Godbolt。(使指针对象本身volatile隐藏了它是空指针的事实。使其成为一个volatile int围绕 GCC bug(?)的指针,它优化了实际存储,也许假设它不能指向任何有生命周期的东西比这个函数更持久。)

  • `0` 是一个空指针常量,在 C 中取消引用它是 UB。 https://godbolt.org/z/hThG46P1e 显示 GCC 发出我们想要的存储指令,但后面跟着 `ud2` (非法指令)。Clang 根本不会为整个函数发出任何指令,警告您应该使用 `__builtin_trap()` 来代替,或者使用 `volatile int *` (它确实按照我们想要的方式编译)。您可以使用“易失性int *易失性zero_ptr = 0;” *zero_ptr = 0;` 对编译器隐藏 UB。(使它成为指向易失性的指针可以解决 GCC bug(?),它假设存储可以被优化掉。) (3认同)
  • 或者“int *ptr = mmap(0, ...)”作为获取编译器不知道将是“0”的运行时变量指针的便捷方法。(OTOH,这给怀疑者留下了怀疑 mmap 是否真的返回 0 的空间,你可以使用 `strace` 来验证)。顺便说一句,正如 Harold 在该问题下评论的那样,以 root 身份运行(`sudo`)可以让 `mmap` 工作,即使不更改 `vm.mmap_min_addr`。我刚刚在内核为 6.7 的 Arch Linux 系统上尝试过。 (3认同)
  • C 规范并没有说地址 0 不能被访问。它*确实*说(通过规定的组合)空指针常量不指向任何对象或函数,然后非常直接地得出评估“*(int *)0”的行为未定义。另一方面,`uintptr_t addr = 0; 也不一定如此;*(int *)地址;`。 (3认同)
  • 很好地提供了一个示例,说明访问地址 0 是有用的(“Wine 需要它才能执行 16 位代码。”)。 (2认同)