I'm experimenting with running 32-bit code inside a 64-bit Linux process. The 32-bit code is completely self-contained, it makes direct IA32 system calls on its own. If I were to load this code in a 32-bit process, it would run just fine.
Initially, I thought I could just allocate a stack for the 32-bit code, switch to it and everything would work fine, but that didn't go so well. Mainly because stack-related instructions (POP/PUSH/...) were doing 8-byte shifts instead of 4 bytes.
By googling, I learned that I can transition to 32-bit mode by switching to segment selector 0x23. Unfortunately, segments are something I know very little about.
I am able to transition to 32-bit mode with something like this (inline AT&T assembly):
movl $0x23, 4(%%rsp) // segment selector 0x23
movq %0, %%rax
movl %%eax, (%%rsp) // target 32-bit address to jump to
lret
Where %0 contains a 32-bit address of where the code is mapped. The code starts running, I can see that PUSH/POP now works the way it should, but it crashes even earlier than when I ran the code in 64-bit mode on a seemingly innocuous instruction:
0x8fe48201 mov 0xa483c(%rbx),%ecx
Where %rbx
(or more like %ebx
since this code is already 32-bit, GDB just doesn't know that) contains 0x8fe48200
. The address it's trying to read from (0x8feeca3c
) is valid and readable (according to /proc/XXX/maps
) and when I read it from within GDB, it contains the expect value.
Yet, Linux sends a SIGSEGV
to the process on this instruction and the faulting address is 0
(as reported by strace
or p $_siginfo._sifields._sigfault.si_addr
inside gdb
). Somehow it seems 0x8feeca3c
is not a valid address in the 32-bit realm.
Any ideas how to proceed?
UPDATE: I have written a minimal example that segfaults reading address 0, although address 0 is not really being referenced. It seems that reading any addresses in memory fails (even reading the address of the instruction that has just been executed!), although stack operations work OK.
#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) :);
}
While 64-bit apps couldn't work on 32-bit OS, 32-bit apps could work on 64-bit OS but they need some 32-bit libraries to run. Since Ubuntu 11.04 (Natty) and Debian 7.0 (Wheezy) there has been support for multiarch, when 32-bit and 64-bit libraries could live on the same OS.
You can't directly link to 32bit code inside of a 64bit program. The best option is to compile a 32bit (standalone) program that can run on your 64bit platform (using ia32), and then use a form of inter-process communication to communicate to it from your 64bit program.
To install 32-bit libraries on Ubuntu 13.04 (64-bit) or later, open Terminal and type: sudo apt-get install lib32z1 (you will need to enter your password). Then just for good measure, let's make sure your Ubuntu is up to date. Type sudo apt-get update and lastly, restart your computer.
You can install a 32 bit system on 64 bit hardware. The converse is not true. You cannot install a 64 bit system on 32 bit hardware.
Nice question :)
The problem is that ds
is still set to zero, in 64 bit mode it's not used. So, you need to reload that and it will work. Changing your initial test push/pop to push $0x2b; pop %ds
will do the trick:
const unsigned char instructions[] = {
0x6a, 0x2b, // push $0x2b
0x1f, // pop %ds
I extracted the 0x2b
value from a 32 bit program. I just kept wondering why push
worked. On closer look, ss
is set in 64 bit mode too, so it may be safer to copy that to ds
as well as to es
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With