用户层下API的逆向分析及重构
字数 1356 2025-08-07 08:22:23
Windows用户层API逆向分析与重构技术详解
0x00 前言
Windows操作系统提供给用户层(R3)的API实际上是对操作系统内核(R0)接口的封装。恶意程序常通过钩取这些API来实现数据截取和修改。本文以ReadProcessMemory为例,详细分析其在用户层的实现机制,并展示如何绕过用户层钩子直接调用系统服务。
0x01 API调用链分析
1.1 调用流程跟踪
ReadProcessMemory的完整调用链如下:
- 应用程序调用
kernel32.ReadProcessMemory - 跳转到
API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory - 调用
KernelBase.ReadProcessMemory - 调用
ntdll.NtReadVirtualMemory - 最终通过
ntdll.KiFastSystemCall进入内核
1.2 关键代码分析
在OD中跟踪ReadProcessMemory调用:
01314E52 FF15 64B03100 call dword ptr ds:[<&KERNEL32.ReadProcessMemory>]
进入kernel32.dll后:
7622C1D4 E9 F45EFCFF jmp <jmp.&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory>
跳转到KernelBase.dll:
761F20CD FF25 0C191F7 jmp dword ptr ds:[<&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory>]
最终调用ntdll中的实现:
75DA9A1F FF15 C411DA7 call dword ptr ds:[<&ntdll.NtReadVirtualMemory>]
1.3 系统调用机制
NtReadVirtualMemory通过以下方式进入内核:
77A162F8 B8 15010000 mov eax, 0x115 ; 系统调用号
77A162FD BA 0003FE7F mov edx, 0x7FFE0300 ; 决定进入0环方式的函数指针
77A16302 FF12 call dword ptr ds:[edx] ; 调用KiFastSystemCall
KiFastSystemCall的实现:
77A170B0 8BD4 mov edx,esp
77A170B2 0F34 sysenter
77A170B4 C3 retn
0x02 直接系统调用实现
2.1 绕过用户层钩子
通过直接调用ntdll.KiFastSystemCall可以绕过用户层的API钩子。需要模拟调用过程中的堆栈操作:
- 参数入栈
- 设置系统调用号(eax)
- 调用
KiFastSystemCall
2.2 ReadProcessMemory重构
void MyReadMemory(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD *dwSizeRet)
{
_asm {
lea eax, [ebp + 0x14]
push eax // dwSizeRet
push [ebp + 0x14] // dwSize
push [ebp + 0x10] // pBuffer
push [ebp + 0xC] // pAddr
push [ebp + 0x8] // hProcess
sub esp, 4 // 平衡call指令的堆栈
mov eax, 0x115 // 系统调用号
mov edx, 0X7FFE0300
call dword ptr [edx]
add esp, 0x18 // 平衡堆栈
}
}
2.3 WriteProcessMemory重构
void MyWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten)
{
_asm {
lea eax,[ebp + 0x18]
push eax // lpNumberOfBytesWritten
push [ebp + 0x14] // nSize
push [ebp + 0x10] // lpBuffer
push [ebp + 0xC] // lpBaseAddress
push [ebp + 0x8] // hProcess
sub esp, 4 // 平衡call指令的堆栈
mov eax, 0x115 // 系统调用号
mov edx, 0x7FFE0300
call dword ptr [edx]
add esp, 0x18 // 平衡堆栈
}
}
0x03 系统调用机制深入
3.1 _KUSER_SHARED_DATA结构
用户层和内核层共享的数据结构:
- 用户层地址:0x7ffe0000
- 内核层地址:0xffdf0000
在0x30偏移处存放了进入ring0的实现方法指针。
3.2 两种进入ring0的方式
-
快速调用(KiFastSystemCall):
7C92E4F0 8BD4 mov edx,esp 7C92E4F2 0F34 sysenter 7C92E4F4 C3 ret -
中断调用(KiIntSystemCall):
7C92E500 8D542408 lea edx,[esp+8] 7C92E504 CD2E int 2Eh 7C92E506 C3 ret
3.3 CPU支持检测
通过CPUID指令检测是否支持sysenter/sysexit:
mov eax, 1
cpuid
检查edx寄存器的第11位(SEP位),若为1则表示支持。
0x04 进阶实现
4.1 直接使用中断门
BOOL __stdcall MyReadProcessMemory_IntGate(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD *dwSizeRet)
{
LONG NtStatus;
__asm {
lea edx,hProcess; // 参数指针
mov eax, 0xBA; // 系统调用号
int 0x2E; // 触发中断
mov NtStatus, eax;
}
// 错误处理和返回值处理...
}
4.2 直接使用sysenter
BOOL __stdcall MyReadProcessMemory_sysenter(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD *dwSizeRet)
{
LONG NtStatus;
__asm {
// 参数入栈
lea eax,[ebp + 0x18]
push eax
push [ebp + 0x14]
push [ebp + 0x10]
push [ebp + 0xC]
push [ebp + 0x8]
sub esp, 4
// 系统调用准备
mov eax, 0xBA;
push return_address; // 模拟call指令
// 执行sysenter
mov edx, esp;
_emit 0x0F; // sysenter
_emit 0x34;
return_address:
add esp, 0xBA;
mov NtStatus, eax;
}
// 错误处理和返回值处理...
}
0x05 技术原理总结
- API调用本质:用户层API最终都会通过系统调用进入内核
- 进入ring0的两种方式:
- 中断门(int 0x2E):通过IDT表查找CS/EIP,TSS提供SS/ESP
- 快速调用(sysenter):MSR寄存器提供CS/SS/ESP/EIP
- 绕过钩子:通过直接系统调用可以绕过用户层的API钩子
- 兼容性考虑:需要检测CPU是否支持sysenter指令
0x06 防御与检测
- 检测直接系统调用:
- 监控非标准路径的系统调用
- 检查系统调用号是否正常
- 防护措施:
- 内核钩子检测
- 系统调用表保护
- 关键API调用栈验证
通过深入理解Windows API的调用机制,安全研究人员可以更好地分析恶意软件行为,同时开发更有效的防护方案。