WindowsAPI编程核心技术-注入技术

全局钩子注入

全局钩子作用于整个系统的基于消息的应用,需要使用DLL文件。

SetWindowsHookEx

将程序定义的钩子函数安装到挂钩链中。

1
2
3
4
5
6
7
8
9
10
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
//钩子程序的类型 具体可选参数自己查
_In_ HOOKPROC lpfn,
//指向钩子程序的指针,当dwThreadId为0或指定由不同进程创建线程标识符,则lpfn必须指向DLL中钩子过程 否则可指向与当前进程关联的代码中的钩子过程
_In_ HINSTANCE hMod,
//lpfn指向的钩子过程的DLL句柄 当dwThreadId指定由当前进程创建线程且钩子过程位于与当前进程关联的代码中时hMod必须为NULL
_In_ DWORD dwThreadId
//与钩子程序关联的线程标识符 为0时钩子过程与系统中所有线程相关联
)//成功返回钩子过程的句柄 失败返回NULL

例子

为了使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,
//访问线程对象 检查进程的安全描述符 如果调用者启用SeDebugPrivilege权限则授予请求的访问权限
_In_ BOOL bInheritHandle,
//此进程创建的进程是否继承该句柄
_In_ DWORD dwProcessId
//要打开的本地进程PID
)//成功返回指定进程的句柄 失败返回NULL

VirtualAllocEx

在指定进程的虚拟地址空间内保留、提交或更改内存的状态。

1
2
3
4
5
6
7
8
9
10
11
12
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
//过程句柄
_In_opt_ LPVOID lpAddress,
//分配页面所需起始地址指针 NULL则自动分配
_In_ SIZE_T dwSize,
//要分配的内存大小
_In_ DWORD flAllocationType,
//内存分配类型 4个值自己去找
_In_ DWORD flProtect
//要分配的页面区域内存保护
)//成功给你分配页面基址 失败NULL

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,
//安全描述符+子进程是否可继承 NULL为默认安全描述符+子进程不可继承
_In_ SIZE_T dwStackSize,
//堆栈初始大小 0默认
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
//远程进程中线程起始地址
_In_ LPVOID lpParameter,
//传参的指针
_In_ DWORD dwCreationFlags,
//控制线程创建 0在创建后立即运行
_Out_ LPDWORD lpThreadId
//接收线程标识符
)//成功新线程句柄 失败NULL

例子

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); //创建远程线程 实现DLL注入
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)( //这32位版的ZwCreateThreadEx 这里没用上
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"); //获取LoadLibraryA
if (pFuncProcAddr == NULL)
return FALSE;
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx"); //获取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,
//APC函数指针
_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;
};
// 根据PID获取所有的相应线程ID
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) {
// 获取进程对应的线程ID
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;
}
// APC注入
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 {
// 根据进程名称获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (0 >= dwProcessId) {
bRet = FALSE;
break;
};
// 根据PID获取所有的相应线程ID
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;
};
// 向申请的空间中写入DLL路径数据
::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
if (dwRet != dwDllPathLen) {
bRet = FALSE;
break;
};
// 获取 LoadLibrary 地址
pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (NULL == pLoadLibraryAFunc) {
bRet = FALSE;
break;
};
// 遍历线程, 插入APC
for (i = 0; i < dwThreadIdLength; i++) {
// 打开线程
hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hThread) {
// 插入APC
::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;
};