我即将学习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位版本不使用它,事实上它被删除了因为:
sysenter
和sysret
在AMD CPU的长模式下是非法的(与兼容模式无关)IA32_SYSENTER_CS
MSR被64位版本的Windows1保留为零
执行sysenter
时,这将导致#GP故障
如果您单步执行__KiFastSystemCall
,那么在执行_emit
0时,调试器应该会捕获一个异常,代码为0xc0000005
因此,为了使用sysenter
,您必须拥有真正的32位版本的Windows
在64位版本的Windows上运行32位程序不起作用,这是兼容模式(通过WOW64机制完成)
如果您除了拥有64位版本的Windows之外,还拥有AMD CPU,那么它不会双倍工作。
Windows 64位使用syscall
作为64位程序或间接调用TEB2的WOW32Reserved
字段,您应该使用这些
请注意,64位系统调用约定与通常的约定略有不同:特别是它假设syscall
位于自己的函数中,因此它希望堆栈上的参数上移8。另外,第一个参数必须在r10
中,而不是在rcx
中。
例如,如果内联syscall
指令,则堆栈上的第一个参数(如果有)必须位于rsp + 28h
,而不是rsp + 20h
。
32位兼容模式系统调用约定也不同,您需要将eax
和ecx
都设置为特定值
我没有挖掘ecx
的确切用途,但它可能与一个名为Turbo thunks
的优化有关,必须设置为特定值
请注意,虽然系统调用编号非常不稳定,但turbo thunk更不稳定,因为管理员可以禁用它们。
1我没有确切的来源,它在我的版本的Windows上只是零,它使sysenter
出错。
2即call 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%正确工作(已测试)。但不能帮助你进行内联汇编,因为我从来没有用过它。
享受吧。