在Linux上以64位进程运行32位代码 - 内存访问

时间:2017-01-29 14:01:44

标签: linux assembly x86-64

我正在尝试在64位Linux进程中运行32位代码。 32位代码是完全独立的,它可以自行进行直接的IA32系统调用。如果我要在32位进程中加载​​此代码,它将运行正常。

最初,我以为我可以为32位代码分配一个堆栈,切换到它并且一切都会正常工作,但这并不是那么顺利。主要是因为与堆栈相关的指令(POP / PUSH / ...)正在进行8字节移位而不是4字节。

通过谷歌搜索,我了解到我可以通过切换到段选择器0x23来转换到32位模式。不幸的是,细分是我所知甚少的。

我可以使用类似的内容(内联AT& T程序集)转换到32位模式:

movl $0x23, 4(%%rsp) // segment selector 0x23
movq %0, %%rax
movl %%eax, (%%rsp) // target 32-bit address to jump to
lret

其中%0包含代码映射位置的32位地址。代码开始运行,我可以看到PUSH / POP现在以它应该的方式工作,但它比我在64位模式下运行代码看似无害的指令时更早崩溃:

0x8fe48201      mov    0xa483c(%rbx),%ecx

%rbx(或更像%ebx,因为此代码已经是32位,GDB只是不知道)包含0x8fe48200。它尝试从(0x8feeca3c)读取的地址是有效且可读的(根据/proc/XXX/maps),当我从GDB中读取它时,它包含期望值。

然而,Linux在此指令的进程中发送SIGSEGV,错误地址为0(由stracep $_siginfo._sifields._sigfault.si_addr gdb报告)。不知怎的,似乎0x8feeca3c不是32位域中的有效地址。

任何想法如何进行?

更新:我写了一个最小的例子,即segfaults读取地址0,虽然地址0并未真正被引用。看起来读取内存中的任何地址都失败了(甚至读取刚刚执行的指令的地址!),尽管堆栈操作正常。

#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

// 32-bit code we're executing
const unsigned char instructions[] = {
        0x6a, 0, // push 0
        0x58, // popl %eax
        0xe8, 0, 0, 0, 0, // call the next line to get our location in memory
        0x5b, // pop %ebx
        // THE FOLLOWING mov SEGFAULTS, but it is well within the mapped area (which has size 0x3000)
        // A simpler "mov (%ebx), %eax" (0x8b, 0x03) would fail as well
        0x8b, 0x83, 0, 0x20, 0, 0, // mov 0x2000(%ebx), %eax
        0xf4 // hlt, not reached
};

int main()
{
        void* area;
        void* stack;

        area = mmap(NULL, 3*4096, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
        memcpy(area, instructions, sizeof(instructions));

        stack = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
        stack = (void*) (((uint64_t) stack) + 4096 - 4);

        memset(((char*)area) + 2*4096, 0xab, 100); // Place 0xAB in the area we mov from in 32-bit instructions

        // Switch to 32-bit mode and jump into the code
        __asm__ volatile("movq %1, %%rsp;" \
                         "subq $8, %%rsp;" \
                         "movl $0x23, 4(%%rsp);" \
                         "movq %0, %%rax;" \
                         "movl %%eax, (%%rsp);" \
                         "lret" :: "m"(area), "r"(stack) :);
}

1 个答案:

答案 0 :(得分:4)

好问题:)

问题是ds仍然设置为零,在64位模式下它没有被使用。所以,你需要重新加载它,它会工作。将初始测试推送/弹出更改为push $0x2b; pop %ds将起到作用:

const unsigned char instructions[] = {
        0x6a, 0x2b, // push $0x2b
        0x1f, // pop %ds

我从32位程序中提取0x2b值。我只是想知道为什么push有效。仔细看,ss也设置为64位模式,因此将其复制到ds以及es可能更安全。