【windows】通过断链隐藏模块(DLL)

概述:主要通过 TEB + PEB 实现

1. 三环断链(EPB断链)

作用:3环断链通常用于在用户层隐藏dll

1.1. 步骤说明

获取dll模块的步骤:

  1. 获取TEB
  2. 获取PEB
  3. 获取ldr
  4. dll模块

详细步骤:

  1. TEB 的获取方法为: fs:[0]fs:[0]

  2. 通过 TEB 结构体发现, PPEB(PEB指针,只想PEB结构体首地址),位于 TEB 偏移 0x30 处,因此 PPEB==fs:[0x30]PPEB == fs:[0x30]

  3. 通过 PEB 结构体发现, ldr 位于 PEB 偏移 0xc 处,因此 ldr==PPEB+0xcldr==PPEB+0xc

  4. 通过 ldr 指向的结构体 _PEN_LDR_DATA 结构体找到3个双向循环链表 _LIST_ENTRY 结构体。

  5. _LIST_ENTRY 结构体中有两个成员,指向前驱模块节点的 _LIST_ENTRY、指向后继模块节点的 _LIST_ENTRY

  6. 节点指针指向的是 _LIST_ENTRY 结构体,该结构体存储了模块在链表中的位置信息

  7. 通过 _LDR_DATA_TATBLE_ENTRY 结构体中偏移 0x18DllBase 确定要隐藏的dll,并通过该结构体中的 _LIST_ENTRY 结构体删除该节点在链表中的位置信息(双向循环链表删除节点),即可达到3环下的dll隐藏。

    _LDR_DATA_TABLE_ENTRY_PEB_LDR_DATA 的关系:

    _LDR_DATA_TABLE_ENTRY_PEB_LDR_DATA 的关系是,_LDR_DATA_TABLE_ENTRY_PEB_LDR_DATA 结构的一部分,用于存储模块加载相关信息。

    具体来说,_PEB_LDR_DATA 结构包含三个链表排序方式,分别是模块加载顺序、模块内存顺序、模块初始化顺序。这些链表结构是通过_LDR_DATA_TABLE_ENTRY 来存储的,每个 _LDR_DATA_TABLE_ENTRY 都包含了模块的相关信息,如模块名称、模块大小、模块地址等。

    在调试信息中,可以通过断点和查看寄存器等方式来查看 _LDR_DATA_TABLE_ENTRY_PEB_LDR_DATA 的内容,进而分析模块的加载和运行情况。

1.2. 相关结构体

1.2.1. TEB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _TEB
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB // PPEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
+0x0c4 CurrentLocale : Uint4B
... ...

1.2.2. PEB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
kd> dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA // ldr
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : Ptr32 Void
+0x024 FastPebUnlockRoutine : Ptr32 Void
+0x028 EnvironmentUpdateCount : Uint4B
+0x02c KernelCallbackTable : Ptr32 Void
... ...

1.2.3. ldr的结构体:_PEB_LDR_DATA

1
2
3
4
5
6
7
8
9
kd> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY // 模块加载顺序链表
+0x014 InMemoryOrderModuleList : _LIST_ENTRY // 模块在内存中的顺序链表
+0x01c InInitializationOrderModuleList : _LIST_ENTRY // 模块初始化的顺序链表
+0x024 EntryInProgress : Ptr32 Void

1.2.4. _LIST_ENTRY 的结构体:

后文结构体强转就是因为 Blink 直接返回的是 _LDR_DATA_TABLE_ENTRY 的首地址。

1
2
3
4
kd> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY // 指向前驱节点的指针
+0x004 Blink : Ptr32 _LIST_ENTRY // 指向后继节点的指针

1.2.5. 补充:_LDR_DATA_TABLE_ENTRY 结构体

存储dll模块的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kd> dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY // _LIST_ENTRY结构体中Flink或Blink指向的位置
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void // dll基址
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING // dll名称
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 Void
+0x04c PatchInformation : Ptr32 Void

1.3. Demo

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
#include<stdio.h>
#include<Windows.h>

typedef struct _UNICODE_STRING
{
USHORT Length; //字符串长度
USHORT MaximumLength; //字符串最大长度
PWSTR Buffer; //双字节字符串指针
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList; //代表按加载顺序构成的模块列表
LIST_ENTRY InMemoryOrderModuleList; //代表按内存顺序构成的模块列表
LIST_ENTRY InInitializationOrderModuleList; //代表按初始化顺序构成的模块链表
}PEB_LDR_DATA, * PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderModuleList; //代表按加载顺序构成的模块列表
LIST_ENTRY InMemoryOrderModuleList; //代表按内存顺序构成的模块列表
LIST_ENTRY InInitializeationOrderModuleList; //代表按初始化顺序构成的模块链表
PVOID DllBase; //该模块的基地址
PVOID EntryPoint; //该模块的入口
ULONG SizeOfImage; //该模块的影像大小
UNICODE_STRING FullDllName; //模块的完整路径
UNICODE_STRING BaseDllName; //模块名
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDataStamp;
}LDR_MODULE, * PLDR_MODULE;

PEB_LDR_DATA* g_pPebLdr = NULL;
LDR_MODULE* g_pLdrModule = NULL;
LIST_ENTRY* g_pInLoadOrderModule;
LIST_ENTRY* g_pInMemoryOrderModule;
LIST_ENTRY* g_pInInitializeationOrderModule;

void ring3BrokenChains(HMODULE hModule)
{
LIST_ENTRY* pHead = g_pInLoadOrderModule;
LIST_ENTRY* pCur = pHead;

do {
pCur = pCur->Blink;
g_pLdrModule = (PLDR_MODULE)pCur; // 这里为什么可以直接将pCur转为PLDR_MODULE,见下面代码解释

// 这是因为 pCur 指向 _LIST_ENTRY 结构体,指向的地址刚好是_LDR_DATA_TABLE_ENTRY的首地址,因此二者在内存上刚好是对齐的。
// CONTAINING_RECORD这个宏返回成员变量所在结构体的基址,ldte == g_pLdrModule
// PLDR_MODULE ldte = CONTAINING_RECORD(pCur, _LDR_DATA_TABLE_ENTRY, InLoadOrderModuleList);


if (hModule == g_pLdrModule->DllBase)
{
g_pLdrModule->InLoadOrderModuleList.Blink->Flink = g_pLdrModule->InLoadOrderModuleList.Flink;
g_pLdrModule->InLoadOrderModuleList.Flink->Blink = g_pLdrModule->InLoadOrderModuleList.Blink;

g_pLdrModule->InInitializeationOrderModuleList.Blink->Flink = g_pLdrModule->InInitializeationOrderModuleList.Flink;
g_pLdrModule->InInitializeationOrderModuleList.Flink->Blink = g_pLdrModule->InInitializeationOrderModuleList.Blink;

g_pLdrModule->InMemoryOrderModuleList.Blink->Flink = g_pLdrModule->InMemoryOrderModuleList.Flink;
g_pLdrModule->InMemoryOrderModuleList.Flink->Blink = g_pLdrModule->InMemoryOrderModuleList.Blink;
break;
}
} while (pHead != pCur);
}

int main(int argc, char* argv[])
{
__asm
{
mov eax, fs: [0x30] ; // PPEB
mov ecx, [eax + 0xC]; // ldr
mov g_pPebLdr, ecx;

mov ebx, ecx;
add ebx, 0xC;
mov g_pInLoadOrderModule, ebx; // 第1个链表

mov ebx, ecx;
add ebx, 0x14;
mov g_pInMemoryOrderModule, ebx; // 第2个链表

mov ebx, ecx;
add ebx, 0x1C;
mov g_pInInitializeationOrderModule, ebx; // 第3个链表
}

printf("点任意按键开始断链");
getchar();
ring3BrokenChains(GetModuleHandleA("kernel32.dll"));
printf("断链成功\n");
getchar();
return 0;
}

2. 0环断链(EPROCESS断链)

作用:0环断链通常用于在内核层隐藏进程

2.1. 1 步骤说明

获取进程模块的步骤为:

  1. 获取 _KPCR
  2. 获取 _KPRCB
  3. 获取 _KTHREAD
  4. 获取 _KAPC_STATE
  5. 获取 _KPROCESS
  6. 获取 _EPROCESS

详细步骤说明: [todo]


【windows】通过断链隐藏模块(DLL)
https://hodlyounger.github.io/A_OS/Windows/win/【win】通过断链隐藏模块/
作者
mingming
发布于
2024年1月23日
许可协议