【发布时间】:2013-08-18 19:19:26
【问题描述】:
最近我意识到你可以在 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 是Linux x86 标头中__USER32_CS 段选择器的值。我不知道跳跃目标是否需要属性,但我添加了for good measure。当然,要使远程返回成为可能,这个调用代码本身必须位于虚拟地址空间的低 4 GiB 中的某个位置。我发现把它放到main 就足够了。
我知道这几乎是无用的(没有加载 32 位库,调用约定不同等)并且容易损坏(__USER32_CS 的值不是 Linux 面向用户空间的 API 的一部分)。
我的问题:有没有一种简单的方法来证明调用的目标确实是在 32 位模式下执行的?这种调用是否有任何实际用途(现有的库软件利用它,或者至少不是那么不切实际的可能性)?
【问题讨论】:
-
只需将一些代码放入您的目标中,在 64 位和 32 位模式下执行不同。我会为 64 位序列
XOR EAX, EAX; DEC RAX; RAX; LRET使用二进制代码,因为在 32 位模式下,由于 REX 前缀被解释为DEC EAX,因此计算为XOR EAX, EAX; DEC EAX; DEC EAX; LRET(第一个DEC来自前缀,第二个 @987654331 @实际的两字节操作码),因此给出不同的结果。 -
这是一个非常棒的想法(实际上我可以手工组装)。也许您应该将其发布为答案。如果没有人提到一些在用户空间中在
__USER_CS和__USER32_CS之间进行远调用的实际软件,我想我会接受你的。
标签: linux x86-64 inline-assembly memory-segmentation