免杀断链

前言

在这篇文章中我们介绍PEB断链(R3层和R0层)

R3层隐藏模块

首先我们介绍一下PEB断链,相信各位读者大大对PEB这个结构体可以说相当熟悉了,这里我们简单描述一下,做一个介绍

在开始使用TEB/PEB 获取进程的模块信息之前,我想有必要解释一下这两个名词:PEB 指的是进程环境块(Process Environment Block),用于存储进程状态信息和进程所需的各种数据。每个进程都有一个对应的 PEB 结构体。TEB 指的是线程环境块(Thread Environment Block),用于存储线程状态信息和线程所需的各种数据。每个线程同样都有一个对应的 TEB 结构体。

PEB 中包含了进程的代码、数据段指针、进程的环境变量、进程启动参数信息以及加载的模块信息等。在 x86-32 体系下,FS 段寄存器偏移 0x30 处存放了索引,索引查找的指针指向当前进程的 PEB 结构体,在 x86-64 下该指针位于 FS 段寄存器偏移 0x60 处。其他进程可以通过访问自己的 PEB 结构体来获取自己的状态和信息。

TEB 中包含了线程的堆栈指针、TLS(线程本地存储)指针、异常处理链表指针、用户模式分页表指针等信息。在 x86-32体系下,FS 段寄存器偏移 0x18 处通常为指向 TEB 结构体的指针,在 x86-64 下该指针位于 FS 段寄存器偏移 0x30 处。其他线程可以通过访问自己的 TEB 结构体来获取自己的状态和信息。

而对于 PEB 结构体,微软是没有公开文档的,需要自己进行重定义(原始结构体定义中缺少我们需要的部分),经查阅逆向文献,得到如下的结构体定义(部分不需要用到的成员已经被截断):

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

typedef struct _PEB_LDR_DATA32

{

ULONG Length; // +0x00

BOOLEAN Initialized; // +0x04

PVOID SsHandle; // +0x08

LIST_ENTRY InLoadOrderModuleList; // +0x0c

LIST_ENTRY InMemoryOrderModuleList; // +0x14

LIST_ENTRY InInitializationOrderModuleList;// +0x1c

} PEB_LDR_DATA32, * PPEB_LDR_DATA32; // +0x24





typedef struct _PEB32

{

UCHAR InheritedAddressSpace; //0x0

UCHAR ReadImageFileExecOptions; //0x1

UCHAR BeingDebugged; //0x2

union

{

UCHAR BitField; //0x3

struct

{

UCHAR ImageUsesLargePages : 1; //0x3

UCHAR IsProtectedProcess : 1; //0x3

UCHAR IsImageDynamicallyRelocated : 1; //0x3

UCHAR SkipPatchingUser32Forwarders : 1; //0x3

UCHAR IsPackagedProcess : 1; //0x3

UCHAR IsAppContainer : 1; //0x3

UCHAR IsProtectedProcessLight : 1; //0x3

UCHAR IsLongPathAwareProcess : 1; //0x3

};

};

PVOID Mutant; //0x4

PVOID ImageBaseAddress; //0x8

PEB_LDR_DATA32* Ldr; //0xc

RTL_USER_PROCESS_PARAMETERS* ProcessParameters; //0x10

PVOID SubSystemData; //0x14

PVOID ProcessHeap; //0x18

RTL_CRITICAL_SECTION* FastPebLock; //0x1c

SLIST_HEADER* volatile AtlThunkSListPtr; //0x20

PVOID IFEOKey; //0x24

} PEB32, * PPEB32;



typedef struct _STRING64

{

USHORT Length; //0x0

USHORT MaximumLength; //0x2

ULONGLONG Buffer; //0x8

}STRING64, * LPSTRING64;



typedef struct _PEB_LDR_DATA64

{

ULONG Length; //0x0

UCHAR Initialized; //0x4

PVOID SsHandle; //0x8

LIST_ENTRY InLoadOrderModuleList; //0x10

LIST_ENTRY InMemoryOrderModuleList; //0x20

LIST_ENTRY InInitializationOrderModuleList; //0x30

PVOID EntryInProgress; //0x40

UCHAR ShutdownInProgress; //0x48

PVOID ShutdownThreadId; //0x50

}PEB_LDR_DATA64, *PPEB_LDR_DATA64;



typedef struct _PEB64

{

UCHAR InheritedAddressSpace; //0x0

UCHAR ReadImageFileExecOptions; //0x1

UCHAR BeingDebugged; //0x2

union

{

UCHAR BitField; //0x3

struct

{

UCHAR ImageUsesLargePages : 1; //0x3

UCHAR IsProtectedProcess : 1; //0x3

UCHAR IsImageDynamicallyRelocated : 1; //0x3

UCHAR SkipPatchingUser32Forwarders : 1; //0x3

UCHAR IsPackagedProcess : 1; //0x3

UCHAR IsAppContainer : 1; //0x3

UCHAR IsProtectedProcessLight : 1; //0x3

UCHAR IsLongPathAwareProcess : 1; //0x3

};

};

UCHAR Padding0[4]; //0x4

ULONGLONG Mutant; //0x8

ULONGLONG ImageBaseAddress; //0x10

PEB_LDR_DATA64* Ldr; //0x18

ULONGLONG ProcessParameters; //0x20

ULONGLONG SubSystemData; //0x28

ULONGLONG ProcessHeap; //0x30

ULONGLONG FastPebLock; //0x38

ULONGLONG AtlThunkSListPtr; //0x40

ULONGLONG IFEOKey; //0x48

}PEB64, *PPEB64;

PEB_LDR_DATA结构体

以 x86-32 为例:PEB 结构体中偏移为 0xC 的成员变量是 Ldr 该变量是一个指向 PEB_LDR_DATA 结构体的指针。

该结构体的第一个变量 Length 表示链表长度信息,大小是结点数乘以当前范围的大小(x32 是0x4,x64 是 0x8),最后再减去 1。

然后从偏移 0xC 开始,就是三个 LIST_ENTRY 链表的头结点,链表中结点数据类型都是 LIST_ENTRY,只是链表的排序模式不同。

LIST_ENTRY结构体

LIST_ENTRY 结构是模块链表结构里的结点数据结构,它包含两个成员指针, Flink 指向下一个链表结点,Blink 指向前一个链表结点。模块链表属于一种双向链表的数据结构。

LDR_DATA_TABLE_ENTRY结构体

对于每一个指针,它实际指向的数据结构并不是 LIST_ENTRY 结构体,而是 LDR_DATA_TABLE_ENTRY结构体,这是每一个结点指向的模块信息数据结构。

对于该结构中的 DllBase 是当前模块的基址,EntryPoint 是模块的入口地址,SizeOfImage 是映像大小,FullDllName 是模块完整路径字符串。

可以通过遍历链表结点的方式获取所有模块的信息:LDR_DATA_TABLE_ENTRY 结构中的 LIST_ENTRY 结构对应下一个 LDR_DATA_TABLE_ENTRY 结点中的 LIST_ENTRY 结构。

这些信息引用于其他大佬的文章下面是原文链接,这里只复制了我们需要的信息,想要了解的更全面,就可以阅读那篇文章

原文链接:https://blog.csdn.net/qq_59075481/article/details/135281393

这里我们了解到每一个结构体都有两个指针,分别指向其前后的结构体,并且每个结构体都带有该模块的信息,所以我们通过获取到需要的信息再进行判读,如果满足条件,就执行模块隐藏操作(让其前后指针都指向自己)。

实现的效果:图片这里我们可以看到已经隐藏成功。这是效果图图片
R0层隐藏进程****注:基于R0层进行隐藏进程,需要使用驱动,但是有个问题就是加载驱动需要带有有效签名,如果没有签名windows是不会将驱动加载起来的。获取进程模块的步骤为:获取 _KPCR -> _KPRCB -> KTHREAD -> _KAPC_STATE -> _KPROCESS -> _EPROCESS(1)KPCR 的获取方法为:fs:[0](2)KPCR 结构体中偏移0x120处有一个成员 _KPRCB 结构体,该结构体中偏移0x4处(即 fs:[0x124])有一个 CurrentThread 指针,指向 _KTHREAD 结构体(3)_KTHREAD 结构体偏移0x34处有一个 ApcState 结构体,该结构体偏移0x10处有一个 Process 指针,指向 _KPROCESS(4)_EPROCESS 结构体的第一个成员是 Pcb,是一个 _KPROCESS 结构体(5)(3)中的 Process 指针指向的就是 _EPROCESS 中的一个成员 _KPROCESS 结构体,也是 _EPROCESS 结构体的首地址。这一点和PEB断链中,ldr->Flink 与 _LDR_DATA_TABLE_ENTRY 首地址相等是一样的(6)_EPROCESS 结构体中偏移0x88有一个 _LIST_ENTRY 结构体,该结构体保存了当前进程在进程链表中的位置信息(7)节点指针指向的是 _LIST_ENTRY 结构体,该结构体存储了模块在链表中的位置信息(8)通过 _EPROCESS 结构体中偏移0x174的 ImageFileName 确定所属进程, 并通过该结构体中的 _LIST_ENTRY结构体删除该节点在链表中的位置信息(双向循环链表删除节点),即可达到0环下的进程隐藏。
PPID Spoofing**
PPID spoofing是一种攻击技术,用于欺骗系统和安全工具,使其认为恶意进程的父进程ID(PPID)是合法的。通过伪装父进程ID,攻击者可以隐藏恶意进程的真实来源,绕过安全监控和检测。这种技术可能被用于实施恶意活动,如隐藏恶意进程、绕过安全防护、进行横向移动等。在安全领域中,需要采取相应的防御措施来防范PPID spoofing攻击。
PPID Spoofing 原理概述

PPID Spoofing 是通过在 STARTUPINFOEXW 结构体中的 PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员中,使用 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 来告诉最终调用调用的 CreateProcess 函数,将即将创建的进程,归入到指定的父进程之下。PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员是通过 InitializeProcThreadAttributeList分配内存,并由 UpdateProcThreadAttribute 函数设置其属性(设置成 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS),来达到偷换父进程的目的。
PPID Spoofing 原理概述

PPID Spoofing 是通过在 STARTUPINFOEXW 结构体中的 PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员中,使用 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 来告诉最终调用调用的 CreateProcess 函数,将即将创建的进程,归入到指定的父进程之下。PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员是通过 InitializeProcThreadAttributeList分配内存,并由 UpdateProcThreadAttribute 函数设置其属性(设置成 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS),来达到偷换父进程的目的。
InitializeProcThreadAttributeList 函数

初始化用于创建进程和线程的指定属性列表。BOOL InitializeProcThreadAttributeList( [out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, [in] DWORD dwAttributeCount, DWORD dwFlags, [in, out] PSIZE_T lpSize );[out, optional] lpAttributeList属性列表。此参数可以为 NULL,以确定支持指定数量的属性所需的缓冲区大小。[in] dwAttributeCount要添加到列表的属性计数。//这里我们只需要一个就行了,那就是PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性,用于创建指定的父进程。dwFlags此参数是保留的,必须为零。[in, out] lpSize如果 lpAttributeList 不为 NULL,则此参数指定输入时 lpAttributeList 缓冲区的大小(以字节为单位)。输出时,此参数接收初始化的属性列表的大小(以字节为单位)。如果 lpAttributeList 为 NULL,则此参数接收所需的缓冲区大小(以字节为单位)。UpdateProcThreadAttribute函数BOOL UpdateProcThreadAttribute( [in, out] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, [in] DWORD dwFlags, [in] DWORD_PTR Attribute, [in] PVOID lpValue, [in] SIZE_T cbSize, [out, optional] PVOID lpPreviousValue, [in, optional] PSIZE_T lpReturnSize );这里我们主要注意第三个参数,给定一个属性,这里有很多我们只使用其中的一个。PROC_THREAD_ATTRIBUTE_PARENT_PROCESS:**lpValue参数是指向要使用的进程的句柄的指针,而不是作为所创建进程的父进程的调用进程。要使用的进程必须具有PROCESS_CREATE_PROCESS访问权限。从指定进程继承的属性包括句柄、设备映射、处理器相关性、优先级、配额、进程令牌和作业对象。(请注意,某些属性(如调试端口)将来自创建进程,而不是此 handle 指定的进程。)那么思路明确,我们先声明结构体,然后对结构体第二个值进行大小以及属性的初始化。
接下来上代码:图片

下面是使用的方法和效果。图片图片图片那么这里我就实验成功了,多研究研究就行,需要原文加VX:TZjx86