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

1. 三环断链(EPB断链)

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

1.1. 步骤说明

获取dll模块的步骤:

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

详细步骤:

  1. TEB 的获取方法为:

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

  3. 通过 PEB 结构体发现, ldr 位于 PEB 偏移 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

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 步骤说明

获取进程模块的步骤为:

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

详细步骤说明: [todo]