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

Lub*_*osD 6 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
Run Code Online (Sandbox Code Playgroud)

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

0x8fe48201      mov    0xa483c(%rbx),%ecx
Run Code Online (Sandbox Code Playgroud)

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

但是,Linux会SIGSEGV根据此指令将a发送到进程,并且错误地址是0(由stracep $_siginfo._sifields._sigfault.si_addr内部报告gdb)。在某种程度上,这似乎0x8feeca3c不是32位域中的有效地址。

任何想法如何进行?

更新:我已经写了一个最小的示例,该段会读取地址0,尽管并未真正引用地址0。尽管堆栈操作可以正常工作,但似乎读取内存中的任何地址都将失败(甚至无法读取刚刚执行的指令的地址!)。

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

Jes*_*ter 4

好问题:)

问题是它ds仍然设置为零,在 64 位模式下它不被使用。因此,您需要重新加载它,它就会起作用。将初始测试推送/弹出更改为push $0x2b; pop %ds即可解决问题:

const unsigned char instructions[] = {
        0x6a, 0x2b, // push $0x2b
        0x1f, // pop %ds
Run Code Online (Sandbox Code Playgroud)

0x2b从 32 位程序中提取了该值。我只是一直想知道为什么push有效。仔细观察,ss也设置为 64 位模式,因此将其复制到ds以及es.