最近我意识到你可以用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位模式下执行的?这类电话是否有任何实际用途(现有的图书馆利用它的软件,或至少不那么不切实际的可能性)?
答案 0 :(得分:2)
在x86中,32位和64位指令编码大部分相同。
最大的例外是16个单字节INC
和DEC
指令操作码。在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位值)。