内联程序集中的C++SYSENTER x86调用



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

我从这里开始写这段代码,但编译器似乎不理解操作码"sysenter";所以我决定用sysenter的字节CCD_ 1它们。(也许我需要更改项目设置中的某些内容?)它进行了编译,但当它即将调用函数visualstudio时,会给我一个错误,即我的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 Worldn";
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;
}

您有32位版本的Windows吗?

CCD_ 3是;继任者";CCD_ 4,并在Windows XP时代引入
Windows的64位版本不使用它,事实上它被删除了因为:

  1. sysentersysret在AMD CPU的长模式下是非法的(与兼容模式无关)
  2. IA32_SYSENTER_CSMSR被64位版本的Windows1保留为零
    执行sysenter时,这将导致#GP故障
    如果您单步执行__KiFastSystemCall,那么在执行_emit0时,调试器应该会捕获一个异常,代码为0xc0000005

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


Windows 64位使用syscall作为64位程序或间接调用TEB2WOW32Reserved字段,您应该使用这些
请注意,64位系统调用约定与通常的约定略有不同:特别是它假设syscall位于自己的函数中,因此它希望堆栈上的参数上移8。另外,第一个参数必须在r10中,而不是在rcx中。

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

32位兼容模式系统调用约定也不同,您需要将eaxecx都设置为特定值
我没有挖掘ecx的确切用途,但它可能与一个名为Turbo thunks的优化有关,必须设置为特定值
请注意,虽然系统调用编号非常不稳定,但turbo thunk更不稳定,因为管理员可以禁用它们。


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

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

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

可能这取决于用户空间来帮助保存一些寄存器,因为sysenter非常小。在让ret执行之前,从内核返回后检查堆栈指针。

我只是猜测这个问题,但在我看来,将系统调用门封装在另一个函数调用中是错误的。正如我在评论中所说,不要这样做,因为系统调用编号可能会在您身上发生更改。

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

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

对不起,这不是一个很大的答案,但也不是完全没有道理。

在x86 Windows中进行系统调用与x64不同。您需要在ret中指定正确的参数长度,否则您将获得非法指令和/或运行时特别是

此外,我不建议您使用内联程序集,而是在.asm文件中或作为shell代码使用它。

要在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%正确工作(已测试)。但不能帮助你进行内联汇编,因为我从来没有用过它。

享受吧。

最新更新