3环和0环断链隐藏分析
2023-10-9 17:55:11 Author: mp.weixin.qq.com(查看原文) 阅读量:3 收藏

以下实验均在 windows xp系统下完成。

1、3环断链(PEB断链)

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

获取dll模块的步骤为:获取 TEB -> PEB -> ldr -> dll模块

(1)TEB的获取方法为:fs:[0]。
(2)通过TEB结构体发现,PPEB(PEB指针,指向PEB首地址)位于TEB偏移0x30处,因此PPEB == fs:[0x30]。
(3)通过PEB结构体发现,ldr位于PEB偏移0xc处,因此 ldr == PPEB + 0xc。
(4)通过ldr指向的结构体 _PEB_LDR_DATA 结构体找到3个双向循环链表 _LIST_ENTRY结构体。
(5)_LIST_ENTRY结构体有两个成员,指向前驱模块节点的 _LIST_ENTRY、指向后继模块节点的 _LIST_ENTRY。
(6)节点指针指向的是 _LIST_ENTRY 结构体,该结构体存储了模块在链表中的位置信息。
(7)通过 _LDR_DATA_TABLE_ENTRY 结构体中偏移 0x18的 DllBase 或 0x2c 的 BaseDllName 确定要隐藏的dll,并通过该结构体中的 _LIST_ENTRY结构体删除该节点在链表中的位置信息(双向循环链表删除节点),即可达到3环下的dll隐藏。

TEB结构体:3环下的线程环境块结构体

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
... ...

PEB结构体:3环下的进程环境块结构体

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
... ...

_PEB_LDR_DATA结构体:ldr的结构体

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

_LIST_ENTRY结构体:上述三个链表的结构体

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

_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

PEB断链源码:

#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,见下面代码解释

// 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;
}

关键代码解释:

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);

2、0环断链(EPROCESS断链)

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

获取进程模块的步骤为:获取 _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环下的进程隐藏。

KPRC(Kernel Processor Control Region, 内核处理器控制区域)结构体:

kd> dt _kpcr
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 DebugActive : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB // _KPRCB 结构体

_KPRCB 结构体:

kd> dt _KPRCB
ntdll!_KPRCB
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B
+0x004 CurrentThread : Ptr32 _KTHREAD // fs:[0x124]
+0x008 NextThread : Ptr32 _KTHREAD
+0x00c IdleThread : Ptr32 _KTHREAD
... ...

_KTHREAD 结构体:

kd> dt _KTHREAD
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 Teb : Ptr32 Void
+0x024 TlsArray : Ptr32 Void
+0x028 KernelStack : Ptr32 Void
+0x02c DebugActive : UChar
+0x02d State : UChar
+0x02e Alerted : [2] UChar
+0x030 Iopl : UChar
+0x031 NpxState : UChar
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE // _KAPC_STATE 结构体
+0x04c ContextSwitches : Uint4B
... ...

_KAPC_STATE 结构体:

kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY
+0x010 Process : Ptr32 _KPROCESS // _KTHREAD + 0x44 = _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar

kd> dt _eprocess
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY // 保存进程的双线循环链表结构体,与PEB中的ldr 结构体一样
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32 _ETHREAD
+0x118 HardwareTrigger : Uint4B
+0x11c VadRoot : Ptr32 Void
+0x120 VadHint : Ptr32 Void
+0x124 CloneRoot : Ptr32 Void
+0x128 NumberOfPrivatePages : Uint4B
+0x12c NumberOfLockedPages : Uint4B
+0x130 Win32Process : Ptr32 Void
+0x134 Job : Ptr32 _EJOB
+0x138 SectionObject : Ptr32 Void
+0x13c SectionBaseAddress : Ptr32 Void
+0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x148 Win32WindowStation : Ptr32 Void
+0x14c InheritedFromUniqueProcessId : Ptr32 Void
+0x150 LdtInformation : Ptr32 Void
+0x154 VadFreeHint : Ptr32 Void
+0x158 VdmObjects : Ptr32 Void
+0x15c DeviceMap : Ptr32 Void
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : Uint8B
+0x170 Session : Ptr32 Void
+0x174 ImageFileName : [16] UChar // 进程名称
+0x184 JobLinks : _LIST_ENTRY
+0x18c LockedPagesList : Ptr32 Void
+0x190 ThreadListHead : _LIST_ENTRY
+0x198 SecurityPort : Ptr32 Void
+0x19c PaeTop : Ptr32 Void
+0x1a0 ActiveThreads : Uint4B
+0x1a4 GrantedAccess : Uint4B
+0x1a8 DefaultHardErrorProcessing : Uint4B
+0x1ac LastThreadExitStatus : Int4B
+0x1b0 Peb : Ptr32 _PEB // 3环的PEB地址
+0x1b4 PrefetchTrace : _EX_FAST_REF
... ...

EPROCESS断链源码:

#include <ntddk.h>

NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);
NTSTATUS DriverUnload(PDRIVER_OBJECT driver);

NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
UNREFERENCED_PARAMETER(reg_path);

PEPROCESS pEprocess, pCurProcess;
PCHAR ImageFileName;

DbgBreakPoint();

__asm
{
mov eax, fs: [0x124] ; // 获取指向 _KTHREAD 的指针
mov eax, [eax + 0x44]; // 获取指向 _KPROCESS 的指针, 即EPROCESS 的首地址
mov pEprocess, eax;
}
pCurProcess = pEprocess;

do
{
ImageFileName = (PCHAR)pCurProcess + 0x174; // 进程名
if (strcmp(ImageFileName, "notepad.exe") == 0)
{
PLIST_ENTRY curNode;

curNode = (PLIST_ENTRY)((ULONG)pCurProcess + 0x88); // +88 指向链表结构体

curNode->Flink->Blink = curNode->Blink;
curNode->Blink->Flink = curNode->Flink;

DbgPrint("断链成功!\n");
break;
}
pCurProcess = (PEPROCESS)(*(PULONG)((ULONG)pCurProcess + 0x88) - 0x88); // 更新进程

} while (pEprocess != pCurProcess);

driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

NTSTATUS DriverUnload(PDRIVER_OBJECT driver)
{
UNREFERENCED_PARAMETER(driver);
DbgPrint("驱动卸载成功\n");
return STATUS_SUCCESS;
}

关键代码解释:

(1)更新pCurProcess的说明

        

// + 0x88:从 _LIST_ENTRY结构体中的Flink 指向的是下一个进程的双向循环链表结构体首地址
// - 0x88:链表指向的是下一个进程结构体+88的位置,-0x88让指针指向EPROCESS的首地址
pCurProcess = (PEPROCESS)(*(PULONG)((ULONG)pCurProcess + 0x88) - 0x88);

(2)获取EPRCESS的两种办法

第1种:通过内核函数 PsGetCurrentProcess。原理已在前面部分解释。

kd> u psgetcurrentprocess
nt!PsGetCurrentProcess:
804ef608 64a124010000 mov eax,dword ptr fs:[00000124h] // 获取指向 _KTHREAD 的指针
804ef60e 8b4044 mov eax,dword ptr [eax+44h] // 获取指向 _KPROCESS 的指针, 即EPROCESS 的首地址

第2种:

获取进程模块的步骤为:获取 KPCR -> KTHREAD-> ETHREAD -> EPROCESS

对应的汇编是:

    

__asm
{
mov eax, fs: [0x124] ; // 获取指向 _KTHREAD 的指针, 即 ETHREAD 的首地址。原理与上面一样
mov eax, [eax + 0x220]; // ETHREAD + 0x220 -> ThreadsProcess : Ptr32 _EPROCESS
mov pEprocess, eax;
}

_ETHREAD 结构体:

kd> dt _EThread
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD // _KTHREAD 结构体
+0x1c0 CreateTime : _LARGE_INTEGER
+0x1c0 NestedFaultCount : Pos 0, 2 Bits
+0x1c0 ApcNeeded : Pos 2, 1 Bit
+0x1c8 ExitTime : _LARGE_INTEGER
+0x1c8 LpcReplyChain : _LIST_ENTRY
+0x1c8 KeyedWaitChain : _LIST_ENTRY
+0x1d0 ExitStatus : Int4B
+0x1d0 OfsChain : Ptr32 Void
+0x1d4 PostBlockList : _LIST_ENTRY
+0x1dc TerminationPort : Ptr32 _TERMINATION_PORT
+0x1dc ReaperLink : Ptr32 _ETHREAD
+0x1dc KeyedWaitValue : Ptr32 Void
+0x1e0 ActiveTimerListLock : Uint4B
+0x1e4 ActiveTimerListHead : _LIST_ENTRY
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : Ptr32 Void
+0x208 LpcWaitingOnPort : Ptr32 Void
+0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
+0x210 IrpList : _LIST_ENTRY
+0x218 TopLevelIrp : Uint4B
+0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT
+0x220 ThreadsProcess : Ptr32 _EPROCESS // PPEB
+0x224 StartAddress : Ptr32 Void
... ...

3、总结

(1)隐藏模块或进程就是一个断链的过程,将自身节点从双向循环链表中删除就可以达到隐藏的效果。

(2)破坏双向循环链表的连续性就可以达到隐藏全部的效果。

(3)为什么删除进程的节点后,程序任然可以运行?这是因为CPU调度的基本单位是线程,和进程无关。

(4)找到EPROCESS也有多种方法。

看雪ID:ATrueMan

https://bbs.kanxue.com/user-home-968999.htm

*本文为看雪论坛优秀文章,由 ATrueMan 原创,转载请注明来自看雪社区

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复

球分享

球点赞

球在看


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458521559&idx=2&sn=83e08849a9c752983a3ff2839f5103f9&chksm=b18d3f5d86fab64b91af043d8beadb0941e21cc0b7d92f8a6be08295804f5572af0eff575048&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh