How to Execute 32-Bit Code in 64-Bit Process by Doing Mode-Switching

Running 32-bit code in 64-bit process on Linux - memory access

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.

Running code in 32bit mode while the app is 64bit, is it possible?

You have the following constraints:

  • You cannot execute 32 bit and 64 bit code in the same process.
  • The third party code is supplied in binary form, as 32 bit modules.

From this you can conclude that the third party code must run in a 32 bit process. This means you have the following options:

  1. Convert your process to 32 bit if that is possible. This is far and away the easiest solution.
  2. Leave your process as 64 bit, and run the third party code as a separate process.

The ugly solution you describe in the question is one way to implement option 2. But there are less ugly ways to do that. Instead of communicating using external files, you can use a remote procedure call mechanism (RPC).

There are plenty of options but the most obvious one is COM. Put the 32 bit code into an out-of-process COM server and consume it from your 64 bit application. This will let you write clean code that communicates to the third party library using method calls. The underlying RPC mechanism does all the low-level heavy lifting involved with getting information between the two processes.

Switch from 32bit mode to 64 bit (long mode) on 64bit linux

Contrary to the other answers, I assert that in principle the short answer is YES. This is likely not supported officially in any way, but it appears to work. At the end of this answer I present a demo.

On Linux-x86_64, a 32 bit (and X32 too, according to GDB sources) process gets CS register equal to 0x23 — a selector of 32-bit ring 3 code segment defined in GDT (its base is 0). And 64 bit processes get another selector: 0x33 — a selector of long mode (i.e. 64 bit) ring 3 code segment (bases for ES, CS, SS, DS are treated unconditionally as zeros in 64 bit mode). Thus if we do far jump, far call or something similar with target segment selector of 0x33, we'll load the corresponding descriptor to the shadow part of CS and will end up in a 64 bit segment.

The demo at the bottom of this answer uses jmp far instruction to jump to 64 bit code. Note that I've chosen a special constant to load into rax, so that for 32 bit code that instruction looks like

dec eax
mov eax, 0xfafafafa
ud2
cli ; these two are unnecessary, but leaving them here for fun :)
hlt

This must fail if we execute it having 32 bit descriptor in CS shadow part (will raise SIGILL on ud2 instruction).

Now here's the demo (compile it with fasm).

format ELF executable
segment readable executable

SYS_EXIT_32BIT=1
SYS_EXIT_64BIT=60
SYS_WRITE=4
STDERR=2

entry $
mov ax,cs
cmp ax,0x23 ; 32 bit process on 64 bit kernel has this selector in CS
jne kernelIs32Bit
jmp 0x33:start64 ; switch to 64-bit segment
start64:
use64
mov rax, qword 0xf4fa0b0ffafafafa ; would crash inside this if executed as 32 bit code
xor rdi,rdi
mov eax, SYS_EXIT_64BIT
syscall
ud2

use32
kernelIs32Bit:
mov edx, msgLen
mov ecx, msg
mov ebx, STDERR
mov eax, SYS_WRITE
int 0x80
dec ebx
mov eax, SYS_EXIT_32BIT
int 0x80
msg:
db "Kernel appears to be 32 bit, can't jump to long mode segment",10
msgLen = $-msg

How to start a 64-bit process from a 32-bit process

You can temporarily disable filesystem redirection around the call to Process.Start, the appropriate API's to P/Invoke are Wow64DisableWow64FsRedirection and Wow64RevertWow64FsRedirection.

Another option is to use %windir%\sysnative, which is available on Windows Vista and above.

Is it possible to use both 64 bit and 32 bit instructions in the same executable in 64 bit Linux?

Switching between long mode and compatibility mode is done by changing CS. User mode code cannot modify the descriptor table, but it can perform a far jump or far call to a code segment that is already present in the descriptor table. I think that in Linux (for example) the required compatibility mode descriptor is present.

Here is sample code for Linux (Ubuntu). Build with

$ gcc -no-pie switch_mode.c switch_cs.s

switch_mode.c:

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

extern bool switch_cs(int cs, bool (*f)());
extern bool check_mode();

int main(int argc, char **argv)
{
int cs = 0x23;
if (argc > 1)
cs = strtoull(argv[1], 0, 16);
printf("switch to CS=%02x\n", cs);

bool r = switch_cs(cs, check_mode);

if (r)
printf("cs=%02x: 64-bit mode\n", cs);
else
printf("cs=%02x: 32-bit mode\n", cs);

return 0;
}

switch_cs.s:

        .intel_syntax noprefix
.code64
.text
.globl switch_cs
switch_cs:
push rbx
push rbp
mov rbp, rsp
sub rsp, 0x18

mov rbx, rsp
movq [rbx], offset .L1
mov [rbx+4], edi

// Before the lcall, switch to a stack below 4GB.
// This assumes that the data segment is below 4GB.
mov rsp, offset stack+0xf0
lcall [rbx]

// restore rsp to the original stack
leave
pop rbx
ret

.code32
.L1:
call esi
lret

.code64
.globl check_mode
// returns false for 32-bit mode; true for 64-bit mode
check_mode:
xor eax, eax
// In 32-bit mode, this instruction is executed as
// inc eax; test eax, eax
test rax, rax
setz al
ret

.data
.align 16
stack: .space 0x100

Calling 32bit Code from 64bit Process

You'll need to have the 32-bit dll loaded into a separate 32-bit process, and have your 64 bit process communicate with it via interprocess communication. I don't think there is any way a 32-bit dll can be loaded into a 64 bit process otherwise.

There is a pretty good article here:

Accessing 32-bit DLLs from 64-bit code



Related Topics



Leave a reply



Submit