WindowsAPI编程核心技术-注入技术
全局钩子注入
全局钩子作用于整个系统的基于消息的应用,需要使用DLL文件。
SetWindowsHookEx
将程序定义的钩子函数安装到挂钩链中。
1 2 3 4 5 6 7 8 9 10
| HHOOK WINAPI SetWindowsHookEx( _In_ int idHook, _In_ HOOKPROC lpfn, _In_ HINSTANCE hMod, _In_ DWORD dwThreadId )
|
例子
为了使DLL注入到所有进程中,需要设置WH_GETMESSAGE
消息的全局钩子。该类型钩子会监视消息队列,且Windows是基于消息驱动的,所有进程都会有自己的一个消息队列,都会加载该类型的全局钩子DLL。
为了将钩子句柄传递给其他进程,采用共享内存方法。突破进程独立性,相当于多个进程共享一个内存,只要一个进程修改了该变量,其他DLL中的这个值也会改变。
将以下代码编写到DLL中,再写个控制台工程,加载调用该DLL,拿Process Explorer看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| extern HMODULE g_hDllModule; #pragma data_seg("mydata") HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker,"/SECTION:mydata,RWS") LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam) { return ::CallNextHookEx(g_hHook, code, wParam, lParam); }; BOOL SetGlobalHook(VOID) { g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0); if (NULL == g_hHook) return FALSE; return TRUE; }; BOOL UnsetGlobalHook(VOID) { if (g_hHook) ::UnhookWindowsHookEx(g_hHook); return TRUE; }; int main(void) { return 0; };
|
DLL主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| HMODULE g_hDllModule = NULL; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){ switch (ul_reason_for_call){ case DLL_PROCESS_ATTACH:{ g_hDllModule = hModule; break; }; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; }; return TRUE; };
|
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <Windows.h> int _tmain(int argc, _TCHAR* argv[]){ typedef BOOL(*typedef_SetGlobalHook)(); typedef BOOL(*typedef_UnsetGlobalHook)(); HMODULE hDll = NULL; typedef_SetGlobalHook SetGlobalHook = NULL; typedef_UnsetGlobalHook UnsetGlobalHook = NULL; BOOL bRet = FALSE; do{ hDll = ::LoadLibrary("GlobalHook_Test.dll"); if (NULL == hDll) break; SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook"); if (NULL == SetGlobalHook) break; bRet = SetGlobalHook(); if (bRet) printf("SetGlobalHook OK.\n"); else printf("SetGlobalHook ERROR.\n"); system("pause"); UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook"); if (NULL == UnsetGlobalHook) break; UnsetGlobalHook(); printf("UnsetGlobalHook OK.\n"); }while(FALSE); system("pause"); return 0; };
|
远线程注入DLL
在其他进程空间创建一个进程。使用LoadLibrary
动态调用DLL,字符串参数用VirtualAllocEx
在目标进程空间申请一块内存,用WriteProcessMemory
写入。虽然Windows开启了ASLR,系统DLL装载基地址每次重启后都会变化,但在一次开机后,所有程序中系统DLL的装载基地址是相同的,所以看看本地程序空间的系统DLL基地址即可。
OpenProcess
打开现有的本地进程对象(进程是不活泼的,线程才是活泼的)。(这玩意儿得要Administrator权限)
1 2 3 4 5 6 7 8
| HANDLE WINAPI OpenProcess( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId )
|
VirtualAllocEx
在指定进程的虚拟地址空间内保留、提交或更改内存的状态。
1 2 3 4 5 6 7 8 9 10 11 12
| LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect )
|
WriteProcessMemory
在指定进程中将数据写入内存区域,该区域必须可访问。
1 2 3 4 5 6 7 8 9 10 11 12
| BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten )
|
CreateRemoteThread
在另一个进程的虚拟地址空间中创建运行的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_ LPDWORD lpThreadId )
|
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, WCHAR* pszDllFileName) { HANDLE hProcess = NULL; DWORD dwSize = 0; LPVOID pDllAddr = NULL; FARPROC pFuncProcAddr = NULL; hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) return FALSE; dwSize = 1 + ::lstrlen(pszDllFileName); pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (NULL == pDllAddr) return FALSE; if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) return FALSE; pFuncProcAddr = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (NULL == pFuncProcAddr) return FALSE; HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL); if (NULL == hRemoteThread) return FALSE; ::CloseHandle(hProcess); return TRUE; };
|
突破SESSION0隔离的远线程注入
由于SESSION0隔离机制,上面那个方法没法往系统服务进程中注。这里用CreateRemoteThread
的底层ZwCreateThreadEx
。使用CreateRemoteThread
往系统服务进程中注时ZwCreateThreadEx
的第七个参数CreateSuspended
(x86)或CreateThreadFlags
(x64)为1。在内核6.0(Windows VISTA、7、8)以后采用会话隔离机制,创建一个进程后不立即执行,先挂起,在查看要运行的进程所在会话层之后再决定是否恢复进程运行。
ZwCreateThreadEx
这玩意儿传参复杂得很,常用的跟CreateRemoteThread
差不多,不写了。它在ntdll.dll中没导出,得用GetProcAddress
手动导出。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| typedef DWORD(WINAPI* typedef_ZwCreateThreadEx_32)( PHANDLE, ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE, LPVOID, BOOL, DWORD, DWORD, DWORD, LPVOID ); typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE, ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE, LPVOID, ULONG, SIZE_T, SIZE_T, SIZE_T, LPVOID ); BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, WCHAR* pszDllFileName) { HANDLE hProcess = NULL; DWORD dwSize = 0; LPVOID pDllAddr = NULL; FARPROC pFuncProcAddr = NULL; hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) return FALSE; dwSize = 1 + ::lstrlen(pszDllFileName); pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); if (NULL == pDllAddr) return FALSE; if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)) return FALSE; HMODULE hNtdllDll = ::LoadLibrary(L"ntdll.dll"); if (NULL == hNtdllDll) return FALSE; pFuncProcAddr = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (pFuncProcAddr == NULL) return FALSE; typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx"); if (NULL == ZwCreateThreadEx) return FALSE; HANDLE hRemoteThread; DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL); if (hRemoteThread == NULL) return FALSE; CloseHandle(hProcess); FreeLibrary(hNtdllDll); return TRUE; };
|
APC异步过程调用注入
每个线程都有自己的APC队列,用QueueUserAPC
把一个APC函数压入APC队列。设计一个APC函数地址设置LoadLibraryA
,参数设置DLL路径,调用时装载指定DLL。
不是插入APC队列后立即执行,在用户模式下当线程处在可警告状态才会执行(把自己挂起)。为了保证能顺利装载DLL,应该向该进程的所有线程都插入相同的APC。
QueueUserAPC
将用户模式中的异步过程调用APC对象添加到指定进程的APC队列中。
1 2 3 4 5 6 7 8
| DWORD WINAPI QueueUserAPI( _In_ PAPCFUNC pfnAPC, _In_ HANDLE hThread, _In_ ULONG_PTR dwData )
|
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
| #include <TlHelp32.h> DWORD GetProcessIdByProcessName(WCHAR* pszProcessName){ DWORD dwProcessId = 0; PROCESSENTRY32 pe32 = { 0 }; HANDLE hSnapshot = NULL; BOOL bRet = FALSE; ::RtlZeroMemory(&pe32, sizeof(pe32)); pe32.dwSize = sizeof(pe32); hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (NULL == hSnapshot) return dwProcessId; bRet = ::Process32First(hSnapshot, &pe32); while (bRet) { if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName)) { dwProcessId = pe32.th32ProcessID; break; }; bRet = ::Process32Next(hSnapshot, &pe32); }; return dwProcessId; };
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength){ DWORD* pThreadId = NULL; DWORD dwThreadIdLength = 0; DWORD dwBufferLength = 1000; THREADENTRY32 te32 = { 0 }; HANDLE hSnapshot = NULL; BOOL bRet = TRUE; do{ pThreadId = new DWORD[dwBufferLength]; if (NULL == pThreadId) { bRet = FALSE; break; }; ::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD))); ::RtlZeroMemory(&te32, sizeof(te32)); te32.dwSize = sizeof(te32); hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (NULL == hSnapshot) { bRet = FALSE; break; }; bRet = ::Thread32First(hSnapshot, &te32); while (bRet) { if (te32.th32OwnerProcessID == dwProcessId) { pThreadId[dwThreadIdLength] = te32.th32ThreadID; dwThreadIdLength++; }; bRet = ::Thread32Next(hSnapshot, &te32); }; *ppThreadId = pThreadId; *pdwThreadIdLength = dwThreadIdLength; bRet = TRUE; } while (FALSE); if (FALSE == bRet) if (pThreadId) { delete[]pThreadId; pThreadId = NULL; }; return bRet; }
BOOL ApcInjectDll(WCHAR* pszProcessName, WCHAR* pszDllName) { BOOL bRet = FALSE; DWORD dwProcessId = 0; DWORD* pThreadId = NULL; DWORD dwThreadIdLength = 0; HANDLE hProcess = NULL, hThread = NULL; PVOID pBaseAddress = NULL; PVOID pLoadLibraryAFunc = NULL; SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName); DWORD i = 0; do { dwProcessId = GetProcessIdByProcessName(pszProcessName); if (0 >= dwProcessId) { bRet = FALSE; break; }; bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength); if (FALSE == bRet) { bRet = FALSE; break; }; hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { bRet = FALSE; break; }; pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == pBaseAddress) { bRet = FALSE; break; }; ::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet); if (dwRet != dwDllPathLen) { bRet = FALSE; break; }; pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (NULL == pLoadLibraryAFunc) { bRet = FALSE; break; }; for (i = 0; i < dwThreadIdLength; i++) { hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]); if (hThread) { ::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress); ::CloseHandle(hThread); hThread = NULL; }; }; bRet = TRUE; } while (FALSE); if (hProcess) { ::CloseHandle(hProcess); hProcess = NULL; }; if (pThreadId) { delete[]pThreadId; pThreadId = NULL; }; return bRet; };
|