2

最近我意识到你可以在 64 位代码中做到这一点:

  const size_t kLowStackSize = 1024UL * 1024UL * 4UL;
  void *low_stack = mmap(NULL, kLowStackSize, PROT_READ | PROT_WRITE,
      MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
  struct __attribute__((packed, aligned(16))) {
    int32_t address;
    int16_t segment;
  } target = {(uint32_t) (uint64_t) code, 0x23};
  asm volatile(
      "mov %%rsp, %%r8\n"
      "mov %[stack], %%rsp\n"
      "push %%r8\n"
      "lcall *(%[target])\n"
      "pop %%rsp"
      :
      : [stack] "r" (low_stack + kLowStackSize), [target] "r" (&target)
      : "r8");

wherecode指向位于地址空间较低 4GiB 的可执行页面上的一段 32 位代码,并且是Linux x86 标头中段选择器0x23的值。__USER32_CS我不知道跳跃目标是否需要属性,但我添加了for good measure。当然,为了使远程返回成为可能,这个调用代码本身必须位于虚拟地址空间的低 4 GiB 中的某个位置。我发现把它放进main去就足够了。

我知道这几乎是无用的(没有加载 32 位库,调用约定不同等)并且容易损坏( 的值__USER32_CS不是 Linux 面向用户空间的 API 的一部分)。

我的问题:有没有一种简单的方法来证明调用的目标确实是在 32 位模式下执行的?这种调用是否有任何实际用途(现有的库软件利用它,或者至少不那么不切实际的可能性)?

4

1 回答 1

2

在 x86 中,32 位和 64 位指令编码大部分是相同的。

最大的例外是 16 个单字节INCDEC指令操作码。在 64 位模式下,这 16 个字节已重新用于REX前缀系列,它允许指定 64 位操作数大小以及在 64 位模式下使用新寄存器。

这意味着 64 位代码,例如:

    xorl %eax, %eax
    .byte 0x48, 0xff, 0xc8
; this is the same as:
;   decq %rax         ; opcode: 0x48 0xff 0xc8
    lret $0

是有效的 32 位代码,但将执行为:

    xorl %eax, %eax
    decl %eax         ; opcode: 0x48
    decl %eax         ; opcode: 0xff 0xc8
    lret $0

所以你可以ljmp对这段代码,并测试(32bit)返回值;-1如果在 64 位模式下-2执行,但如果在 32 位模式下执行,它将是。

我不知道从 32 位到 64 位模式远返回的前提条件是什么。我怀疑您可能必须同时设置一个“低内存”64 位堆栈指针以及一个低内存 64 位代码地址“蹦床”(以便远调用帧中的返回 EIP 和返回 ESP 都是 32 位值)。

于 2013-08-19T09:07:52.337 回答