0

我即将学习 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;
}

4

3 回答 3

4

您有32 位版本的 Windows 吗?

sysenter是 Windows XP 时代的“继任者”,int 2eh并在 Windows XP 时代推出。
64 位版本的 Windows 不使用它,实际上它已被删除,因为:

  1. sysenter并且sysret在 AMD CPU 的长模式下是非法的(与兼容模式无关)。
  2. IA32_SYSENTER_CS64 位版本的 Windows 1将MSR 保留为零。
    这将在执行时导致#GP 错误sysenter
    如果您单步执行,__KiFastSystemCall您应该会看到调试器执行sysenter.

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


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

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

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


1我没有明确的来源,在的 Windows 版本上它只是零,它会sysenter出错。

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

于 2020-08-18T19:47:31.513 回答
1

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

可能它依赖于用户空间来帮助保存一些寄存器,因为sysenter它非常小。从内核返回后检查堆栈指针,就像您在执行前单步ret执行一样。

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

在 Linux 下,32 位进程通过 VDSO(内核注入到其地址空间的库)调用以获得最佳系统调用指令,以符合内核所需的方式使用。(sysenter不保留堆栈指针,因此用户空间必须提供帮助。)

也许如果你想玩这个指令,你最好编写一个玩具操作系统。

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

于 2020-08-18T15:38:52.853 回答
0

在 x86 Windows 中进行系统调用与 x64 不同。您需要在ret中指定正确的参数长度,否则您将获得非法指令和/或运行时 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% 正确工作。虽然不能帮助你进行内联汇编,因为我从来没有那样使用过它。

享受。

于 2020-09-24T05:09:20.100 回答