断链隐藏进程及恢复(附代码)

一.前言:

​ 继Strider上次问了我如何隐藏一个进程之后,这周仔细研究了一下,首先去了解了一下隐藏进程的各种方法,其实有很多方法很多也很杂,然后有些隐藏只是能躲过任务管理器还有ntdll里面ZwQuerySystemInformation函数(枚举进程的函数),然后通过一些暴力的枚举方法还是会露馅,就总结一些常见的吧:

​ 1.比较简单的即是内核中映射的进程列表进行断链操作,此处是一个双链表(后文会详细赘述)

​ 2.其次就是修改内核映射中的EPROCESS中对应的pid,也能实现隐藏

​ 3.然后就是对于DriveObject信息的抹除(没看懂,系统学内核了再复现)

而本文也是就第一种方法展开复现,来进行进程的隐藏。

二.踩坑:

​ 首先,我们知道,进程体EPROCESS是被系统维护在一个双向链表LIST_ENTRY中的,那么,我们只要把进程的EPROCESS从这个链表中摘除,就可以实现进程隐藏了,当然,这只能瞒过进程管理器和ntdll中的zwQuerySystemInformation,暴力枚举依旧可以发现断链隐藏的进程,因为进程体还在内存中。

​ 然后开始进行断链操作,借助windbg实现操作,一直在纠结一个问题就是能否在自己的本机上调试本机的内核,然后问了一下鹏哥,确实是可以的,然后就有点踩坑地开始复现了,网上很多关于windbg使用的教学都是老版本的,然后微软好像22年就开始禁用了离线符号包的下载使用了,我在本机上下载了老版本的windbg,在配置符号文件的时候一直卡着过不去。

​ 一开始使用的时候,先附加了需要隐藏的一个进程,输入命令dt _eprocess,一开始看到了eprocess 的结构,没意识到犯了很严重的错误(后续文章中会讲述)。

image-20240420193537445

​ 直到后面查看内核映射中的进程列表信息的时候才意识到有点蠢,继续输入命令! process 0 0,开始出现最头疼的报错了

image-20240420194030938

​ 然后一直搜一直搜,搜到了问题是得修复一下ntdll的符号文件,然后这个符号文件一般是可以进行离线下载的,但是按照网上说的设置符号下载位置的方法srv*yourpath*http://msdl.microsoft.com/download/symbols,设置完成之后我们重启计算机,用windbg 附加一个进程来下载符号,然后还是不太好使!

image-20240420194800419

​ 然后我就搜到了这个。。。

image-20240420194948859

​ 不甘心没有,然后进了上述命令中的http://msdl.microsoft.com/download/symbols地址一探究竟,然后我就放弃了。。。

image-20240420195220939

​ 遇到困难了,由于辉哥这两天比较忙,所以求助了一下鹏哥,由于鹏哥之前是在XP上使用的windbg,然后就得到了鹏哥XP的克隆(真香),同时也得到了一份老版windbg的32位和64位安装包,以及最最重要的符号文件!!!

三.正文开始

1.严重错误剖析

​ 开始在XP上进行实验,正常的安装windbg和下载符号文件(打包成exe太爽了)之后,开始进行调试。

image-20240420200209949

​ ?!woc,换了个方式又报了个错,然后搜了一下,感觉有被自己蠢到,发现了上文中所说的严重错误,体会到了再没熟悉windbg使用就瞎敲命令的下场:

​ windbg命令分为标准命令(40个左右),元命令(一百多个)和扩展命令。

​ 标准命令提供最基本的调试功能,不区分大小写。如:bp g dt dv k等
​ 元命令提供标准命令没有提供的功能,也内建在调试引擎中,以.开头。如.sympath .reload等
​ 扩展命令用于扩展某一方面的调试功能,实现在动态加载的扩展模块中,以!开头。如!analyze等

​ 每个调试命令都各有使用范围,有些命令只能用于内核调试,有些命令只能用于用户调试,有些命令只能用于活动调试。但用户也不必记得这许多,一旦在某个环境下,使用了不被支持的命令,都会显示“No export XXX found”的字样。就拿!process命令来说吧,它显示进程信息,但只能用于内核调试中,如果在用户调试中使用,就是下面的情景:

1
2
0:001> ! process 0 0
No export process found

​ 所以,在使用windbg进行调试的时候,一定要搞清楚你是在那种模式下进行调试,还是那句老话,在使用之前,你要清楚你自己在干什么。关闭windbg之后,重新启动并切换windbg模式至内核debug

image-20240420201201218

​ 选至本地:

image-20240420201230115

然后你就会惊奇的发现左下角的开头变了:(lkd也算是内核调试的标志了)

image-20240420201313339

输入命令! process 0 0,即可看到进程列表

image-20240420201631737

2.真正的正文开始

首先我们遇到两个问题:

1.既然要遍历链表,我们就要知道每个结构的前后结构;

2.既然要对比进程名,我们就要知道进程名放在哪个地方;

先查看一手该版本系统下的eporcess结构体中各项数值的偏移量(基于windows的版本不同各有偏差,并不唯一)

​ 输入命令dt _eprocess,主要查看下图中标记的两处位置的偏移量

image-20240420202735543

(1). ActiveProcessLinks

​ EPROCESS块中有一个ActiveProcessLinks成员,它是一个PLIST_ENTRY结构的双向链表。当一个新进程建立的时候父进程负责完成EPROCESS块,然后把ActiveProcessLinks链接到一个全局内核变量PsActiveProcessHead链表中。(可以看成双链表中的一个节点地址,而此节点中分别存储的是该进程在链表中的上下进程的指针地址)

​ 紧接着输入命令dt _LIST_ENTRY,查看LIST_ENTRY的结构

image-20240420203819388

​ 可以看到变量FLink和BLink,FLink指向当前节点的后一个节点的ActiveProcessLinks地址,BLink指向当前结点的前一个节点的ActiveProcessLinks地址,这块儿的两个地址一定别记反,后面即是通过他们来找到锁定进程的上下进程的。

(2).ImageFileName

​ 在上上图中,我们可以看到在偏移0x174处,出现了ImageFileName,这里存的就是我们要找的进程名

​ 接着再输入一遍命令! process 0 0,随便选择一个进程,这里选择 vmtoolsd.exe,注意记住它的上下邻接进程

image-20240420204510507

​ 然后查看该进程的eprocess,输入命令dt _eprocess 85f5ab28,可看出 ActiveProcessLinks 处由两个数值相减,相比他们就是上文中提到的变量FLink和BLink吧,然后反复思考,一个进程的ActiveProcessLinks由两个变量组成,双指针,那尾减去头一定就是本身的地址了。故下图中0x85f42e28应该是BLink,而0x85f34ca0也就是FLink,分别指向当前结点的前后两个节点的ActiveProcessLinks地址。

image-20240420204947114

​ OK,那么事情已经明了了,进程被映射到内核之中后被列举成了这一串双链表,而我们只需要摘除掉其中的那个目标就行,也就是说需要通过检索到的目标进程之后,通过它的eprocess中的ActiveProcessLinks进而实现断链操作,所以我们现在要做的就是通过ActiveProcessLinks中存储的两个值,访问到应该在进程链表中排与目标进程前后的两个进程的eprocess。

​ 1.按着上图中的例子走,先试着访问一手目标进程后面的进程是啥,输入命令dd 0x85f34ca0,查看地址0x85f34ca0中的内容

image-20240420210418864

​ 已知0x85f34ca0中存储的是指向目标进程的下一个进程的ActiveProcessLinks信息,再由上文中总结出来的“后减前原则”,故可知

85f3b688中存储的是后一个进程再后一个进程的ActiveProcessLinks,而85f5abb0中存储的是前一个进程(此时也就是我们的目标进程的ActiveProcessLinks),不信可以输入命令dd 0x85f5abb0进行如下验证:(对照上上图中目标进程的ActiveProcessLinks的值即可)

image-20240420211025654

​ 注意,因为FLink指向当前节点的后一个节点的ActiveProcessLinks地址,所以后一个节点的EPROCESS地址为ActiveProcessLinks地址中的(0x85f34ca0)减去偏移0x88,成功。

image-20240420212657461

​ 2.同理,这块采用比较笨的方法进行运算,因为我对地址偏移的操作还不太那么熟悉,那么我只要拿到了目标进程中指向前一个进程ActiveProcessLinks的地址,然后输入命令dd 0x85f42e28,再拿出前一个进程中指向再前一个进程的ActiveProcessLinks的地址中的指向后一个进程的FLink,在用这个Flink地址-0x88,故可得到结果:

image-20240420213608682

image-20240420213639665

综上所述,我们已经由已知的目标进程求出了起前后两个进程的eprocess,接下来直接进行断链操作即可,然后实现对于进程的隐藏,贴出大佬的脚本如下:(我还没写过驱动程序,所以脚本的复现还没进行,不过指日可待)

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
#ifndef CXX_HIDEPROCESS_H
# include "HideProcess.h"
#endif


ULONG_PTR ActiveOffsetPre = 0;
ULONG_PTR ActiveOffsetNext = 0;
ULONG_PTR ImageName = 0;
WIN_VERSION WinVersion = WINDOWS_UNKNOW;

PLIST_ENTRY Temp = NULL;
PLIST_ENTRY HeadEntry = NULL;
NTSTATUS
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegisterPath)
{


DbgPrint("DriverEntry\r\n");

DriverObject->DriverUnload = UnloadDriver;


WinVersion = GetWindowsVersion();


switch(WinVersion)
{
case WINDOWS_XP: //32Bits
{

ActiveOffsetPre = 0x8c;
ActiveOffsetNext = 0x88;
ImageName = 0x174;
break;
}

case WINDOWS_7: //64Bits
{
ActiveOffsetPre = 0x190;
ActiveOffsetNext = 0x188;
ImageName = 0x2e0;
break;
}
}


HideProcess("notepad.exe");

HeadEntry = (PLIST_ENTRY)((ULONG_PTR)PsGetCurrentProcess()+ActiveOffsetNext);// 在DriverEntry中执行得到的才是System进程

return STATUS_SUCCESS;

}

VOID HideProcess(char* ProcessName)
{
PEPROCESS EProcessCurrent = NULL;
PEPROCESS EProcessPre = NULL;


EProcessCurrent = PsGetCurrentProcess(); //System EProcess



EProcessPre = (PEPROCESS)((ULONG_PTR)(*((ULONG_PTR*)((ULONG_PTR)EProcessCurrent+ActiveOffsetPre)))-ActiveOffsetNext);

//DbgPrint("EProcessCurrent: 0x%p\r\n",EProcessCurrent);

//DbgPrint("EProcessNext: 0x%p\r\n",EProcessNext);



while (EProcessCurrent!=EProcessPre)
{
// DbgPrint("%s\r\n",(char*)((ULONG_PTR)EProcessCurrent+ImageName));


if(strcmp((char*)((ULONG_PTR)EProcessCurrent+ImageName),ProcessName)==0)
{


Temp = (PLIST_ENTRY)((ULONG_PTR)EProcessCurrent+ActiveOffsetNext);

if (MmIsAddressValid(Temp))
{
// Temp->Blink->Flink = Temp->Flink;
// Temp->Flink->Blink = Temp->Blink; //数据结构 不稳定


RemoveEntryList(Temp);


}


break;
}

EProcessCurrent = (PEPROCESS)((ULONG_PTR)(*((ULONG_PTR*)((ULONG_PTR)EProcessCurrent+ActiveOffsetNext)))-ActiveOffsetNext);


}
}

VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
ResumeProcess();
DbgPrint("UnloadDriver\r\n");
}

VOID ResumeProcess()
{

if(Temp!=NULL)
{
InsertHeadList(HeadEntry,Temp);
}


}






WIN_VERSION GetWindowsVersion()
{
RTL_OSVERSIONINFOEXW osverInfo = {sizeof(osverInfo)};
pfnRtlGetVersion RtlGetVersion = NULL;
WIN_VERSION WinVersion;
WCHAR wzRtlGetVersion[] = L"RtlGetVersion";

RtlGetVersion = GetFunctionAddressByName(wzRtlGetVersion); //Ntoskrnl.exe 导出表
if (RtlGetVersion)
{
RtlGetVersion((PRTL_OSVERSIONINFOW)&osverInfo);
}
else
{
PsGetVersion(&osverInfo.dwMajorVersion, &osverInfo.dwMinorVersion, &osverInfo.dwBuildNumber, NULL); //Documet
}

DbgPrint("Build Number: %d\r\n", osverInfo.dwBuildNumber);

if (osverInfo.dwMajorVersion == 5 && osverInfo.dwMinorVersion == 1)
{
DbgPrint("WINDOWS_XP\r\n");
WinVersion = WINDOWS_XP;
}
else if (osverInfo.dwMajorVersion == 6 && osverInfo.dwMinorVersion == 1)
{
DbgPrint("WINDOWS 7\r\n");
WinVersion = WINDOWS_7;
}
else if (osverInfo.dwMajorVersion == 6 &&
osverInfo.dwMinorVersion == 2 &&
osverInfo.dwBuildNumber == 9200)
{
DbgPrint("WINDOWS 8\r\n");
WinVersion = WINDOWS_8;
}
else if (osverInfo.dwMajorVersion == 6 &&
osverInfo.dwMinorVersion == 3 &&
osverInfo.dwBuildNumber == 9600)
{
DbgPrint("WINDOWS 8.1\r\n");
WinVersion = WINDOWS_8_1;
}
else
{
DbgPrint("WINDOWS_UNKNOW\r\n");
WinVersion = WINDOWS_UNKNOW;
}

return WinVersion;
}


PVOID
GetFunctionAddressByName(WCHAR *wzFunction)
{
UNICODE_STRING uniFunction;
PVOID AddrBase = NULL;

if (wzFunction && wcslen(wzFunction) > 0)
{
RtlInitUnicodeString(&uniFunction, wzFunction); //常量指针
AddrBase = MmGetSystemRoutineAddress(&uniFunction); //在System 进程 第一个模块 Ntosknrl.exe ExportTable
}

return AddrBase;
}