1
votes

I was looking how NtDll works for x86 processes, and i debugged the function NtCreateFile using IDA PRO. The code for it is following:

mov     eax, 55h        ; NtCreateFile
mov     edx, offset _Wow64SystemServiceCall@0 ; 
call    edx ; Wow64SystemServiceCall() ; 
retn    2Ch

And the Wow64SystemServiceCall():

mov     edx, large fs:30h
mov     edx, [edx+464h]
test    edx, 2
jz      short loc_7738B5C8
int     2Eh             ; DOS 2+ internal - EXECUTE COMMAND
                        ; DS:SI -> counted CR-terminated command string
retn
loc_7738B5C8:                           ; CODE XREF: 
jmp     far ptr byte_7738B8FF

I looked up the command code for jmp far ptr byte_7738B8FF and it was EA CF B5 38 77 33 00 which is a jump to another segment, 0x33 jmp 0x33:0x7738b5cf . So from what i read in internet, this is the x64 segment base for processes on 64-bit systems, right? Unfortunately i cannot debug further, because, ida doesnt follow the jump. But i made another simple C application compiled for x64, and called CreateFile, attached x64 IDA PRO Remote debugger, and looked up disassembly, and NtCreateFile was looking like so:

x64_NtCreateFile proc near  
mov     r10, rcx
mov     eax, 55h
test    byte ptr ds:7FFE0308h, 1
jnz     short loc_7FFED6695B85
syscall
retn
loc_7FFED6695B85:                     
int     2Eh             ; DOS 2+ internal - EXECUTE COMMAND
                        ; DS:SI -> counted CR-terminated command string
retn

So i have a few question, how does the jump from x86 process attached ntdll far jump jmp 0x33:0x7738b5cf just lands into x64_NtCreateFile first instruction? How does the switching from x86 to x64 happens in this case exactly? Basically i can just make x86 application, and switch segment with jump, and just execute the x64 code in there, which i can create by just doing something like db (0x00) ; x64 machine code commands, is this right?

2
Reading-up about the way that an OS is bootstrapped may be beneficial to you here. First the 16bit -> 32bit transition and then later, the 32 --> 64 bit switch.enhzflep
Unfortunately i cannot debug further, because, ida doesnt follow the jump because ida is bad debugger. use windbg - it allow you enter into jumpRbMm
@RbMm thanks for Advice. I tried it and it was less comfortable, but better in terms of disassemblyVlad

2 Answers

3
votes

If you look at the bytes at address 0x7738b5cf, you would see something like

41 FF A7 F8 00 00 00 (at least if you're on Windows 8.1 or newer)

which corresponds to a single x86_64 instruction jmp QWORD PTR [r15+0xf8].

Right after switching from 32-bit to 64-bit code execution via the far jump, the R15 register will always point to a special jump table within wow64cpu.dll (the R15 register is setup to point to this table from 64-bit code that executes before the 32-bit entry point of your application).

[r15+0xf8] just happens to point to the CpupReturnFromSimulatedCode method within wow64cpu.dll, which will setup the right context and perform the actual system call (in your case for NtCreateFile) using the syscall instruction.

For some information that elaborates upon this, see:

2
votes

Yes, I can confirm that one can execute 64-bit code in a 32-bit Windows app running on 64-bit windows.

Mixing x86 with x64 code provides an explanation and an example of how to do it.

This is what I tried (adapted for and compiled with my Smaller C compiler):

#define EM(a) asm("db " #a);

#define X64_Start_with_CS(_cs) \
{ \
    EM(0x6A) EM(_cs)                     /*  push   _cs                   */ \
    EM(0xE8) EM(0) EM(0) EM(0) EM(0)     /*  call   $+5                   */ \
    EM(0x83) EM(4) EM(0x24) EM(5)        /*  add    dword [esp], 5        */ \
    EM(0xCB)                             /*  retf                         */ \
}

#define X64_End_with_CS(_cs) \
{ \
    EM(0xE8) EM(0) EM(0) EM(0) EM(0)     /*  call   $+5                   */ \
    EM(0xC7) EM(0x44) EM(0x24) EM(4)     /*                               */ \
    EM(_cs) EM(0) EM(0) EM(0)            /*  mov    dword [rsp + 4], _cs  */ \
    EM(0x83) EM(4) EM(0x24) EM(0xD)      /*  add    dword [rsp], 0xD      */ \
    EM(0xCB)                             /*  retf                         */ \
}

#define X64_Start() X64_Start_with_CS(0x33)
#define X64_End() X64_End_with_CS(0x23)

#define __break() asm("int3")

int main(void)
{
  __break();
  X64_Start();
  EM(0x48) EM(0x8D) EM(0x05) EM(0xF9) EM(0xFF) EM(0xFF) EM(0xFF) // lea rax, [$] ; rip-relative
  X64_End();
  __break();
}

I then ran it under a debugger and noticed that eax contained the address of the 64-bit instruction "lea rax, [$]" when the second break point was hit.