1. 三环断链(EPB断链)
作用:3环断链通常用于在用户层隐藏dll
1.1. 步骤说明
获取dll模块的步骤:
- 获取TEB
- 获取PEB
- 获取ldr
- dll模块
详细步骤:
-
TEB 的获取方法为:
-
通过 TEB 结构体发现, PPEB(PEB指针,只想PEB结构体首地址),位于 TEB 偏移 0x30 处,因此
-
通过 PEB 结构体发现, ldr 位于 PEB 偏移 0xc 处,因此
-
通过 ldr 指向的结构体
_PEN_LDR_DATA结构体找到3个双向循环链表_LIST_ENTRY结构体。 -
_LIST_ENTRY结构体中有两个成员,指向前驱模块节点的_LIST_ENTRY、指向后继模块节点的_LIST_ENTRY。 -
节点指针指向的是
_LIST_ENTRY结构体,该结构体存储了模块在链表中的位置信息 -
通过
_LDR_DATA_TATBLE_ENTRY结构体中偏移 0x18 的DllBase确定要隐藏的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
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
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
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 的首地址。
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模块的信息
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
#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 步骤说明
获取进程模块的步骤为:
- 获取
_KPCR- 获取
_KPRCB- 获取
_KTHREAD- 获取
_KAPC_STATE- 获取
_KPROCESS- 获取
_EPROCESS
详细步骤说明: [todo]