用户层下API的逆向分析及重构
字数 1356 2025-08-07 08:22:23

Windows用户层API逆向分析与重构技术详解

0x00 前言

Windows操作系统提供给用户层(R3)的API实际上是对操作系统内核(R0)接口的封装。恶意程序常通过钩取这些API来实现数据截取和修改。本文以ReadProcessMemory为例,详细分析其在用户层的实现机制,并展示如何绕过用户层钩子直接调用系统服务。

0x01 API调用链分析

1.1 调用流程跟踪

ReadProcessMemory的完整调用链如下:

  1. 应用程序调用kernel32.ReadProcessMemory
  2. 跳转到API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory
  3. 调用KernelBase.ReadProcessMemory
  4. 调用ntdll.NtReadVirtualMemory
  5. 最终通过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钩子。需要模拟调用过程中的堆栈操作:

  1. 参数入栈
  2. 设置系统调用号(eax)
  3. 调用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的方式

  1. 快速调用(KiFastSystemCall)

    7C92E4F0 8BD4 mov edx,esp
    7C92E4F2 0F34 sysenter
    7C92E4F4 C3 ret
    
  2. 中断调用(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 技术原理总结

  1. API调用本质:用户层API最终都会通过系统调用进入内核
  2. 进入ring0的两种方式
    • 中断门(int 0x2E):通过IDT表查找CS/EIP,TSS提供SS/ESP
    • 快速调用(sysenter):MSR寄存器提供CS/SS/ESP/EIP
  3. 绕过钩子:通过直接系统调用可以绕过用户层的API钩子
  4. 兼容性考虑:需要检测CPU是否支持sysenter指令

0x06 防御与检测

  1. 检测直接系统调用
    • 监控非标准路径的系统调用
    • 检查系统调用号是否正常
  2. 防护措施
    • 内核钩子检测
    • 系统调用表保护
    • 关键API调用栈验证

通过深入理解Windows API的调用机制,安全研究人员可以更好地分析恶意软件行为,同时开发更有效的防护方案。

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 调用: 进入kernel32.dll后: 跳转到KernelBase.dll: 最终调用ntdll中的实现: 1.3 系统调用机制 NtReadVirtualMemory 通过以下方式进入内核: KiFastSystemCall 的实现: 0x02 直接系统调用实现 2.1 绕过用户层钩子 通过直接调用 ntdll.KiFastSystemCall 可以绕过用户层的API钩子。需要模拟调用过程中的堆栈操作: 参数入栈 设置系统调用号(eax) 调用 KiFastSystemCall 2.2 ReadProcessMemory重构 2.3 WriteProcessMemory重构 0x03 系统调用机制深入 3.1 _ KUSER_ SHARED_ DATA结构 用户层和内核层共享的数据结构: 用户层地址:0x7ffe0000 内核层地址:0xffdf0000 在0x30偏移处存放了进入ring0的实现方法指针。 3.2 两种进入ring0的方式 快速调用(KiFastSystemCall) : 中断调用(KiIntSystemCall) : 3.3 CPU支持检测 通过CPUID指令检测是否支持sysenter/sysexit: 检查edx寄存器的第11位(SEP位),若为1则表示支持。 0x04 进阶实现 4.1 直接使用中断门 4.2 直接使用sysenter 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的调用机制,安全研究人员可以更好地分析恶意软件行为,同时开发更有效的防护方案。