【Day 04】- 今天来把 Module 藏起来(基於 PEB 断链,隐藏 DLL 的方法)

Agenda

  • 资安宣言
  • 测试环境与工具
  • 前情提要
  • 学习目标
  • 技术原理与程序码
  • References
  • 下期预告

资安宣言


撰写本系列文章目的在於提升资讯安全之实务能力,
并透过实作体悟到资讯安全领域的重要性,
本系列所有文章之内容皆有一定技术水平,
不得从事非法行为、恶意攻击等非法活动,
「一切不合法规之行为皆受法律所约束」,
为了避免造成公司、厂商或玩家之间困扰,
所有实作不会拿已上市产品、Online Game 等等来作范例学习,
且部分具有深度、价值之内容,将会提升一定阅读门槛(不对该技术做分析、解说),
请勿透过本系列文章所学,从事任何非法活动,请不要以身试法!!!


测试环境与工具

前情提要

就在昨天(铁人赛 Day 03)的时候有说到 Blackbone 的其中一个 DLL Injection 方法,
还没看的人可以先去快速看一下:【Day 03】- 打针!打针!从 R0 注入的那件事!

在昨天的文章中,最後部分有提到两个隐藏 Module 的方法,
而就在今天!就是这篇文章!将要来说说第一个隐藏技术:
PEB(Process Environment Block)断链

另外:
明天,也就是铁人赛 Day 05 将会介绍 PEB 断链的缺点与找出隐藏 Module 的方法,
後天,也就是铁人赛 Day 06 将会介绍其它更厉害、更难以侦测的隐藏 Module 方法!

最後:
这个 Module 系列就会在铁人赛 Day 06 结束,
当然,隐藏 Module 的方法百百种,这几天只简单的讲几个例子~
铁人赛 Day 07 将会进入 Process 伪装、隐藏,甚至是 Rookit 常用的技术。

学习目标

  • 1.了解 PEB(Process Environment Block)
  • 2.完成 PEB 断链,达到隐藏 DLL 效果

技术原理与程序码

(在阅读这篇文章之前,应该要确保目前脑袋是清晰的)

首先要先说一下,
小弟我目前还属於菜鸟阶段,正不断努力学习中,
若有发现错误或不妥之处还请不吝赐教。
欢迎大家多多留言,互相交流交流。

那麽进入今天主题~~

第一个问题:
什麽是 PEB(Process Environment Block)???
/images/emoticon/emoticon12.gif

  • PEB(Process Environment Block)
    • Process_Environment_Block
    • 出现在 EProcess 中
    • 出现在 TEB(Thread Environment Block) 中
    • 储存着 Process「执行中」的相关资讯
    • 包含 Module 资讯
    • 包含 Debug 资讯
    • 包含 Title 资讯
    • 包含 Path 资讯
    • Image Information
    • Heap Information
    • ... ... ...
    • ... ... ...

那这个 PEB 长什麽样子?可以怎麽看?

  1. 使用 Administrator 权限打开 WinDbg
  2. 选 Kernel Debug...
    • (需要打开测试模式,依照提示的指示进行操作)
  3. 选 Local 按确定
    • (需要打开测试模式,依照提示的指示进行操作)
  4. 输入 !process 0 0 notepad.exe 查看 notepad.exe
    lkd> !process 0 0 notepad.exe
    PROCESS ffff8f899b178080
        SessionId: 1  Cid: 0b2c    Peb: ecacd59000  ParentCid: 10b8
        DirBase: 984ad000  ObjectTable: ffffa685c250d6c0  HandleCount: 262.
        Image: notepad.exe
    
    • 其中 PROCESS 後面的 Address 是 EProcess Address
    • 继续看就可以看到 Peb: ecacd59000
    • notepad.exe 的 Peb Address 就是 0xecacd59000
  5. 输入 !peb ecacd59000 查看 notepad.exe 的 PEB
    • 有一大堆... 大家就自己看了...
  6. 输入 dt _peb 查看 PEB 结构
    • 这边要提醒一下,MSDN 上面写的都不太完整 XD(所以要自己操作)
    • 结果大家可以自己看,太多了:
    lkd> dt _peb
    ntdll!_PEB
       +0x000 InheritedAddressSpace : UChar
       +0x001 ReadImageFileExecOptions : UChar
    
        --- --- --- --- ---
        --- --- --- --- ---
        --- --- --- --- ---
    
       +0x010 ImageBaseAddress : Ptr64 Void
       +0x018 Ldr              : Ptr64 _PEB_LDR_DATA
       +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
    
        --- --- --- --- ---
        --- --- --- --- ---
        --- --- --- --- ---
    
        --- --- --- --- ---
        --- --- --- --- ---
        --- --- --- --- ---
    
       +0x3a0 WaitOnAddressHashTable : [128] Ptr64 Void
       +0x7a0 TelemetryCoverageHeader : Ptr64 Void
       +0x7a8 CloudFileFlags   : Uint4B
    

好了,到这边基本上介绍完 PEB 是什麽了(? XD 已经快速带过

假设你/你刚才有仔细看的话,会注意到:

lkd> !peb ecacd59000
PEB at 000000ecacd59000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
--->ImageBaseAddress:         00007ff67a610000
    NtGlobalFlag:             0
    NtGlobalFlag2:            0
--->Ldr                       00007ff9baa6f3a0
    Ldr.Initialized:          Yes
--->Ldr.InInitializationOrderModuleList: 000002de3e212440 . 000002de3e27cb90
--->Ldr.InLoadOrderModuleList:           000002de3e2125b0 . 000002de3e27c320
--->Ldr.InMemoryOrderModuleList:         000002de3e2125c0 . 000002de3e27c330

    --- --- --- ---
    --- --- --- ---
    --- --- --- ---
lkd> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar

    --- --- --- --- ---
    --- --- --- --- ---
    --- --- --- --- ---

-->+0x010 ImageBaseAddress : Ptr64 Void
-->+0x018 Ldr              : Ptr64 _PEB_LDR_DATA
   +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS

    --- --- --- --- ---
    --- --- --- --- ---
    --- --- --- --- ---

    --- --- --- --- ---
    --- --- --- --- ---
    --- --- --- --- ---

   +0x3a0 WaitOnAddressHashTable : [128] Ptr64 Void
   +0x7a0 TelemetryCoverageHeader : Ptr64 Void
   +0x7a8 CloudFileFlags   : Uint4B

再次进入到 WinDbg 输入 dt _PEB_LDR_DATA
来看一下 Ldr 的数据类型:

lkd> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
   +0x000 Length           : Uint4B
   +0x004 Initialized      : UChar
   +0x008 SsHandle         : Ptr64 Void
-->+0x010 InLoadOrderModuleList : _LIST_ENTRY
-->+0x020 InMemoryOrderModuleList : _LIST_ENTRY
-->+0x030 InInitializationOrderModuleList : _LIST_ENTRY
   +0x040 EntryInProgress  : Ptr64 Void
   +0x048 ShutdownInProgress : UChar
   +0x050 ShutdownThreadId : Ptr64 Void

看一下 MSDN 可以知道:

  1. Contains information about the loaded modules for the process.
  2. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure.

哦!所以 Process 中的 Module 资讯是存在这三个表里的:

  1. InLoadOrderModuleList
    • 这张表按照「载入」顺序排列
  2. InMemoryOrderModuleList
    • 这张表按照「记忆体」顺序排列
  3. InInitializationOrderModuleList
    • 这张表按照「初始化」顺序排列

然後储存着它们讯息的 structure 是 LDR_DATA_TABLE_ENTRY

所以再次进入到 WinDbg 输入 dt _LDR_DATA_TABLE_ENTRY
来看一下 LDR_DATA_TABLE_ENTRY structure 长什麽样子:

lkd> dt _LDR_DATA_TABLE_ENTRY 
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY
   +0x010 InMemoryOrderLinks : _LIST_ENTRY
   +0x020 InInitializationOrderLinks : _LIST_ENTRY
   +0x030 DllBase          : Ptr64 Void
   +0x038 EntryPoint       : Ptr64 Void
   +0x040 SizeOfImage      : Uint4B
   +0x048 FullDllName      : _UNICODE_STRING
   +0x058 BaseDllName      : _UNICODE_STRING
   +0x068 FlagGroup        : [4] UChar
   +0x068 Flags            : Uint4B
   +0x068 PackagedBinary   : Pos 0, 1 Bit
   +0x068 MarkedForRemoval : Pos 1, 1 Bit
   +0x068 ImageDll         : Pos 2, 1 Bit
   +0x068 LoadNotificationsSent : Pos 3, 1 Bit
   +0x068 TelemetryEntryProcessed : Pos 4, 1 Bit
   +0x068 ProcessStaticImport : Pos 5, 1 Bit
   
   --- --- --- ---
   --- --- --- ---
   --- --- --- ---
   
   +0x070 HashLinks        : _LIST_ENTRY
   
   --- --- --- ---
   --- --- --- ---
   --- --- --- ---

所以所以,用一张图来表达以上资讯:
(如果感觉模糊看不清楚,请复制图片连结)
(在新分页打开就可以看高清图片!)

图片参考自:33c0c3 大大画的图

补充一下:
(因为这是补充的,所以就不详细写了)
(多看几次就会看懂我要写的内容是什麽 XD)

  1. 假设想看某个 Address 的 PEB 的话:
lkd> dt _PEB 0x000000ec`acd59000
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   
    --- --- --- ---
    --- --- --- ---
    --- --- --- ---
    
   +0x003 IsLongPathAwareProcess : 0y0
   +0x004 Padding0         : [4]  ""
   +0x008 Mutant           : 0xffffffff`ffffffff Void
   +0x010 ImageBaseAddress : 0x00007ff6`7a610000 Void
   +0x018 Ldr              : 0x00007ff9`baa6f3a0 _PEB_LDR_DATA
   
    --- --- --- ---
    --- --- --- ---
    --- --- --- ---
  1. 假设想看某个 Address 的 _PEB_LDR_DATA 的话:
    (地址来自上面的范例)
lkd> dt 0x00007ff9`baa6f3a0 _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
   +0x000 Length           : 0x58
   +0x004 Initialized      : 0x1 ''
   +0x008 SsHandle         : (null) 
   +0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x? - 0x? ]
   +0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x?0 - 0x? ]
   +0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x? - 0x? ]
   +0x040 EntryInProgress  : (null) 
   +0x048 ShutdownInProgress : 0 ''
   +0x050 ShutdownThreadId : (null) 
  1. 假设想看:
    • 某个 Address 的 _PEB_LDR_DATA
    • 里面的 InLoadOrderModuleList
    • 的 _LIST_ENTRY 的话:
    lkd> dt 0x00007ff9`baa6f3a0+0x010 _LIST_ENTRY
    ntdll!_LIST_ENTRY
     [ 0x000002de`3e2125b0 - 0x000002de`3e27c320 ]
       +0x000 Flink            : 0x? _LIST_ENTRY [ 0x? - 0x? ]
       +0x008 Blink            : 0x? _LIST_ENTRY [ 0x? - 0x? ]
    

到了这边概念大致上已经讲完,
接下来讲一下 Module 的隐藏方式:
首先,先回到 Blackbone Project 中的 Loader.c 的 BBUnlinkFromLoader Func

  1. 我们可以看到一开始先透过 PsGetProcessPeb 拿到 EProcess 中的 PEB Address
PPEB pPeb = PsGetProcessPeb( pProcess );
if (!pPeb)
{
    DPRINT( xxxxxxxxxx );
    return STATUS_NOT_FOUND;
}
  1. 开始列举 InLoadOrderModuleList
for (PLIST_ENTRY pListEntry = pPeb->Ldr->InLoadOrderModuleList.Flink;
      pListEntry != &pPeb->Ldr->InLoadOrderModuleList;
      pListEntry = pListEntry->Flink)
{

--- --- ---
--- --- ---

}
  1. Get LDR_DATA_TABLE_ENTRY Address
xxx pEntry = CONTAINING_RECORD( 
                                pListEntry, 
                                LDR_DATA_TABLE_ENTRY, 
                                InLoadOrderLinks 
                                );
#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                          (PCHAR)(address) - \
                                          (ULONG_PTR)(&((type *)0)->field)))
// addr:   结构体中某个成员地址
// type:   结构体原本的样子
// field:  结构体的某个成员
//详细说明请谷哥...
  1. 如果找到要隐藏的 Module 的话就将其断链!
// Unlink
if ((PVOID)pEntry->DllBase == pBase)
{
    RemoveEntryList( &pEntry->InLoadOrderLinks );
    RemoveEntryList( &pEntry->InInitializationOrderLinks );
    RemoveEntryList( &pEntry->InMemoryOrderLinks );
    //RemoveEntryList( &pEntry->HashLinks );
    //最後这个 HashLinks 今天没时间讲了,有空再补充上来。

    break;
}
  1. 断链的意思就是:

    图片取自:unlink-module-hide
  2. Blackbone 实作的断链方法:
FORCEINLINE
BOOLEAN
RemoveEntryList(
    _In_ PLIST_ENTRY Entry
    )

{

    PLIST_ENTRY PrevEntry;
    PLIST_ENTRY NextEntry;

    NextEntry = Entry->Flink;
    PrevEntry = Entry->Blink;
    if ((NextEntry->Blink != Entry) || (PrevEntry->Flink != Entry)) {
        FatalListEntryError((PVOID)PrevEntry,
                            (PVOID)Entry,
                            (PVOID)NextEntry);
    }

    PrevEntry->Flink = NextEntry;
    NextEntry->Blink = PrevEntry;
    return (BOOLEAN)(PrevEntry == NextEntry);
}
  1. 「普通的」 工具找看看能不能找到 DLL

到这里,告一段落了!!!!!
不过最後我再补充一项知识,
假设想在 User mode 中做到 PEB 断链有可能吗?
答案是有的,但是必须想办法拿到 PEB 的 Address。

我们知道 EProcess 是存在於 Kernel mode,
那要怎麽在 User mode 拿到 PEB 的 Address 呢?
最一开始有说到 PEB 出现在 TEB(Thread Environment Block) 中

TEB(Thread Environment Block):
当 Windows 载入 Process 建立 Thread 时,系统会为每个 Thread 分配 TEB,
且 FS 暂存器会被设置成 FS:0 指向目前 Thread 的 TEB Address,
所以可以拿到 TEB Address。

TEB(Thread Environment Block)结构:

lkd> dt _TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : Ptr64 Void
   +0x040 ClientId         : _CLIENT_ID
   +0x050 ActiveRpcHandle  : Ptr64 Void
   +0x058 ThreadLocalStoragePointer : Ptr64 Void
-->+0x060 ProcessEnvironmentBlock : Ptr64 _PEB
   +0x068 LastErrorValue   : Uint4B
   +0x06c CountOfOwnedCriticalSections : Uint4B
   +0x070 CsrClientThread  : Ptr64 Void
   
    --- --- --- ---
    --- --- --- ---
    --- --- --- ---
    
    --- --- --- ---
    --- --- --- ---
    --- --- --- ---

看到了吧?有 Process Environment Block 耶!!!
可以再看一下,位於 0x00 的 NtTib 是 _NT_TIB 结构,

_NT_TIB 结构:

lkd> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr64 _EXCEPTION_REGISTRATION_RECORD
   +0x008 StackBase        : Ptr64 Void
   +0x010 StackLimit       : Ptr64 Void
   +0x018 SubSystemTib     : Ptr64 Void
   +0x020 FiberData        : Ptr64 Void
   +0x020 Version          : Uint4B
   +0x028 ArbitraryUserPointer : Ptr64 Void
->>+0x030 Self             : Ptr64 _NT_TIB

所以呢~所以呢~
直接:

//mov eax,fs:[0x030]    //x86 Get PEB
mov eax,gs:[0x060]      //x64 Get PEB
//mov ecx,[eax + 0x00c] //x86 PEB 的 Ldr
mov ecx,[eax + 0x018]   //x64 PEB 的 Ldr
mov xxx,ecx             //把拿到的 Address 放进 xxx 变数
pop ecx
pop eax

好了,这篇真的结束了!
明天将介绍一些方法,来对抗这种 PEB 断链隐藏 Module 的手法!

如果你/你有发现哪里写得不好或是有地方写错,
欢迎留个言,大家一起讨论讨论鸭~~
那我们下期见 o( ̄▽ ̄)ブ

References

下期预告


<<:  Day05_CAP定理

>>:  Golang 转生到web世界 - curl

寻找mail server

各位大大好, 第一次发文, 想请教有无推荐的mail server,公司用,约100使用者, 有看过...

Day7 初探CFS scheduler (上)

前言 上次讲完了过去 Linux 的排程器,今天就来讲讲 CFS (complete fair sc...

Day26 ( 高级 ) 放烟火 2 ( 爆炸效果 )

放烟火 2 ( 爆炸效果 ) 教学原文参考:放烟火 2 ( 爆炸效果 ) 这篇文章会延续「放烟火 1...

VScode 刚载完的必备扩充功能

各位在刚载好VScode都会做甚麽样的设定或安装甚麽扩充功能呢? 虽然网路上一大堆的介绍,但零零总总...

[Day4] Google Cloud

今天的内容会跟各位介绍 Google Cloud 相关的基础知识,希望不会不小心的讲成像业配文QQ,...