从Linux上的64位代码远程调用__USER32_CS

时间:2013-08-16 11:32:24

标签: linux x86-64 inline-assembly memory-segmentation

最近我意识到你可以用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");

其中code指向位于地址空间下部4GiB中的可执行页面上的32位代码,0x23__USER32_CS段选择器的值在Linux的x86头文件中。我不知道跳转目标是否需要这些属性,但我添加了for good措施。当然,为了使远程返回成为可能,这个调用代码本身必须位于虚拟地址空间的较低4 GiB中的某个位置。我发现将它放入main就足够了。

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

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

1 个答案:

答案 0 :(得分: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这段代码,并测试(32位)返回值;如果在64位模式下执行,则为-1,如果在32位模式下执行,则为-2

我不知道从32位到64位模式的远程返回的先决条件是什么。我怀疑你可能不得不设置一个“低mem”64位堆栈指针开始以及低mem 64位代码地址“trampoline”(这样远程调用帧中的返回EIP和返回ESP都是32位值)。