内联汇编中的 C++ SYSENTER x86 调用

C++ SYSENTER x86 calls in inline assembly

我即将了解 x86 上的 sysenter 是如何工作的。我在 x86 平台上创建了一个简单的控制台应用程序,它应该在内联汇编中手动调用 NtWriteVirtualMemory 函数。

我从这里开始使用这段代码,但似乎编译器不理解操作码“sysenter”,所以我决定 _emit 使用 sysenter 的字节。(也许我需要更改我的一些东西项目设置?)它编译但是当它即将调用函数时 visual studio 给我一个错误,我的 ret 是执行时的非法指令,程序停止。

有人知道如何正确地做到这一点吗?

#include <windows.h>
#include <iostream>



__declspec(naked) void __KiFastSystemCall()
{
    __asm
    {
        mov edx, esp

        // need to emit "sysenter" because of syntaxerrors, "Opcode"; "newline"
        _emit 0x0F
        _emit 0x34
        
        ret // illegal instructiona after execute?

    }

}

void Test_NtWriteVirtualMemory(HANDLE hProcess, PVOID BaseAddress, PVOID Buffer, SIZE_T sizeToWrite, SIZE_T* NumberOfBytesWritten)
{
    __asm
    {
        push NumberOfBytesWritten
        push sizeToWrite
        push Buffer
        push BaseAddress
        push hProcess

        mov eax, 0x3A // Syscall ID NtWriteVirtualMemory in Windows10


        mov edx, __KiFastSystemCall
        call edx

        add esp, 0x14 // 5 push *  4 bytes 20 dec

        retn
    }
}

void Test_NtWriteVirtualMemory(HANDLE hProcess, PVOID BaseAddress, PVOID Buffer, SIZE_T sizeToWrite, SIZE_T* NumberOfBytesWritten)
{
    __asm
    {
        push NumberOfBytesWritten
        push sizeToWrite
        push Buffer
        push BaseAddress
        push hProcess


        mov eax, 0x3a // Syscall ID NtWriteVirtualMemory in Windows10
        mov edx, 0x76F88E00
        call edx
        ret 0x14
    }
}



int main()
{
    std::cout << "Test Hello World\n";

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessId("MyGame.exe"));
    if (hProcess == NULL)
        return false;

    DWORD TestAddress = 0x87A0B4; // harcoded
    DWORD TestValue = 4;
    Test_NtWriteVirtualMemory(hProcess, (PVOID)TestAddress, (PVOID)TestValue, sizeof(DWORD), NULL);


    CloseHandle(hProcess);

    return 0;
}

由于您在 ret 指令而不是 sysenter 指令上得到了非法指令,您知道 sysenter 指令已被 CPU 正确解码。您的调用进入内核模式,但内核不喜欢您的系统调用调用。

可能是依靠 user-space 来帮助保存一些寄存器,因为 sysenter 非常小。在让 ret 执行之前 single-step 从内核返回后检查堆栈指针。

我只是推测问题所在,但在我看来,将系统调用门包装在另一个函数调用中看起来不对。正如我在评论中所说,不要这样做,因为系统调用号可能会改变。

在Linux下,32位进程通过VDSO(内核注入其地址space的库)调用得到最优的system-call指令,用于匹配内核想要的方式。 (sysenter 不保留堆栈指针,因此 user-space 必须提供帮助。)

也许如果你想玩这个指令你最好写一个玩具OS。

抱歉,答案不是很多,但并非完全不合理。

你有只有版本的Windows吗?

sysenterint 2eh 的“继承者”,在 Windows XP 时代引入。
Windows 的 64 位版本不使用它,事实上它 已被删除 因为:

  1. sysentersysret 在 AMD CPU 的长模式下是非法的(与兼容模式无关)。
  2. 64 位版本的 Windows1.
    IA32_SYSENTER_CS MSR 保留为零 这将在执行 sysenter.
    时导致#GP 错误 如果你 single-step 通过你的 __KiFastSystemCall 你应该看到调试器在执行 sysenter.
  3. 时捕获代码 0xc0000005 的异常

因此,为了使用 sysenter,您必须拥有真正的 32 位版本 Windows。
运行 64 位版本的 Windows 上的 32 位程序将无法运行,这是兼容模式(通过 WOW64 机器完成)。
如果,除了 Windows 的 64 位版本之外,您还有 AMD CPU,那么它将无法双倍工作。


Windows 64 位使用 syscall 用于 64 位程序或间接调用 TEB[ 的 WOW32Reserved 字段=61=]2,你应该使用那些。
请注意,64 位系统调用约定与通常的调用约定略有不同:特别是它假设 syscall 在它自己的 函数中 ,因此它期望参数堆栈向上移动 8。 另外,第一个参数必须在 r10 中,而不是 rcx.

例如,如果您内联 syscall 指令,则堆栈上的第一个参数(如果有)必须位于 rsp + 28h 而不是 rsp + 20h.

32位兼容模式系统调用约定也不同,需要将eaxecx都设置为特定值。
我没有深入了解 ecx 的具体用途,但它可能与称为 Turbo thunks 的优化有关,必须设置为特定值。
请注意,虽然系统调用编号非常不稳定,但 turbo thunk 甚至更多,因为它们可以被管理员禁用。


1我没有这方面的确切来源,在 Windows 的 my 版本上它只是零这会造成 sysenter 错误。

2即a call DWORD [fs:0c0h],这将指向一个代码,该代码将跳转到 64 位代码段的门描述符,该代码段依次执行 syscall

在 x86 中进行系统调用 Windows 与 x64 不同。您需要在 ret 中指定正确的参数长度,否则您将得到非法指令 and/or runtime esp.

此外,我不建议您使用内联汇编,而是在 .asm 文件中或作为 shellcode 使用它。

在 x86 Windows 上进行正确的 x86 系统调用:

mov eax, SYSCALL_INDEX
call sysentry
ret ARGUMENTS_LENGTH_SIZE
mov edx,esp
sysenter
retn

要在 x64 Windows 上进行正确的 x64 系统调用:

mov eax, SYSCALL_INDEX
mov r10,rcx
syscall
retn

以上将在任何 x86 和 x64 Windows(已测试)上 100% 正确工作。但是无法帮助您进行内联汇编,因为我从来没有那样使用过它。

尽情享受吧。