内联汇编中的 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吗?
sysenter
是 int 2eh
的“继承者”,在 Windows XP 时代引入。
Windows 的 64 位版本不使用它,事实上它 已被删除 因为:
sysenter
和 sysret
在 AMD CPU 的长模式下是非法的(与兼容模式无关)。
- 64 位版本的 Windows1.
将 IA32_SYSENTER_CS
MSR 保留为零
这将在执行 sysenter
.
时导致#GP 错误
如果你 single-step 通过你的 __KiFastSystemCall
你应该看到调试器在执行 sysenter
. 时捕获代码 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位兼容模式系统调用约定也不同,需要将eax
和ecx
都设置为特定值。
我没有深入了解 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% 正确工作。但是无法帮助您进行内联汇编,因为我从来没有那样使用过它。
尽情享受吧。
我即将了解 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吗?
sysenter
是 int 2eh
的“继承者”,在 Windows XP 时代引入。
Windows 的 64 位版本不使用它,事实上它 已被删除 因为:
sysenter
和sysret
在 AMD CPU 的长模式下是非法的(与兼容模式无关)。- 64 位版本的 Windows1.
将IA32_SYSENTER_CS
MSR 保留为零 这将在执行sysenter
.
时导致#GP 错误 如果你 single-step 通过你的__KiFastSystemCall
你应该看到调试器在执行sysenter
. 时捕获代码 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位兼容模式系统调用约定也不同,需要将eax
和ecx
都设置为特定值。
我没有深入了解 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% 正确工作。但是无法帮助您进行内联汇编,因为我从来没有那样使用过它。
尽情享受吧。