欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

Dll 模块隐藏技术

Dll 模块隐藏技术

Dll 模块隐藏技术
BY: tj08jx(fengxiaoxi)
;***********************************************************************
其实帖子都是这样看的少,细读的没几个,
如果你细细读完这篇文章,你会学到一下内容:
1.PEB,TEB,LDR_DATA_TABLE_ENTRY等数据结构
2.自己覆盖掉自己执行过的一段代码
3.调试这个Dll你会发现DLL_PROCESS_ATTACH中的代码在OD首次停下即加载停止时已经执行完了!
   多么美妙啊!哈哈,OD不会发现你执行了什么
4.本例最后附件有个 ASM 的控制台程序,用 SDK 编写,你可以学到用汇编写一个基本的控制台程序的格式
5.最后那个控制台源码还包含完整的CreateRemoteThread注入进程的写法
;**************************************************************************

本文主要讲的是怎样隐藏一个dll模块,这里说的隐藏是指,dll被加载后怎样使它 用一般的工具无法检测出来。
为什么要这么做呢?
1.远程线程中的应用
     (1)大家都知道,远程线程注入主要有两种一种是直接copy母体中预注入的代码到目标进程地址空间(WriteProcessMemory),
 然后启动注入的代码(CreateRemoteThread),这种远程线程一旦成功实现,那么它只出现在目标进程的内存中,
 并没有对应的磁盘文件,堪称进程隐藏中的高招,可是缺点就是,你必须要在注入代码中对所有直接寻址的指令进行修正,
 这可是个力气活,用汇编写起来很烦。
     (2)另一种更为常用的方法是注入一个 dll 文件到目标进程,这种方法的实现可以是 以一个 消息Hook 为由进行注入,
 或者仍然使用 CreateRemoteThread,这种方法的优点是 Dll 文件自带 重定位 表,也就是说你不必再为修正直接寻址
 指令而烦恼了!,dll 自己会重定位!~~~嗯,真是不错的方法 --- 可是我们说它不如上面说的方法牛。为什么?
 因为它的致命伤就是 可以用进程管理工具 看见被加载的 dll 文件名、文件路径。这真是太不爽了,因为只要用户看看模块列表
 很容易发现可疑模块!,再依据名字,找到路径,定位文件 --- dll文件就这样暴露了.这样也就不是很完美的隐藏进程。
 [现在不用怕啦~~ 本文将介绍的方法就是为了上述 不足而存在地~~~,让一般的工具看不到已加载的某个dll]
2.自身文件的需要
   这个说起来比较简单,比如我的一个程序运行了,我不想让用户知道我的EXE使用了某个dll,那么同样的也需要这种隐身技术.
3. 技术实现
  (1).
   说完了这么多,该说说,到底应该怎么实现了.
   熟悉SEH的肯定对 PEB 这个结构并不陌生-- PEB (Process Environment Block)进程环境信息块,这里储存着进程的重要信息
   主要原理就是这个结构,和它的成员相关结构 
   首先我们回顾一下如何找到这个结构,常见的代码是这个:
    mov   eax,fs:[30h]    ;就这一句足矣,执行后 eax --> PEB (eax指向PEB结构,即eax中是PEB结构在进程空间中的地址)
  
   熟悉TEB和SEH中反调试知识的童鞋一定对上面这个很熟悉了~~不多说了--(不懂得童鞋去学习一下SEH的相关知识你就会认清 fs 了)
   下面看一下 PEB 结构的定义 :
;=================================================================
    PEB STRUCT                ; sizeof = 1E8h
  InheritedAddressSpace      BYTE    ?  ; 0000h
  ReadImageFileExecOptions    BYTE    ?  ; 0001h
  BeingDebugged        BYTE    ?  ; 0002h
  SpareBool        BYTE    ?  ; 0003h
  Mutant          PVOID    ?  ; 0004h
  ImageBaseAddress      PVOID    ?  ; 0008h
  Ldr          PVOID    ?  ; 000Ch PTR PEB_LDR_DATA
  ProcessParameters      PVOID    ?  ; 0010h PTR RTL_USER_PROCESS_PARAMETERS
  SubSystemData        PVOID    ?  ; 0014h
  
  ~~~~~~~~~~~~~~~~~      ~~~~    ~~  ;PEB 结构以下部分省略
   PEB  ENDS
;==================================================================
由于PEB结构太庞大了,因此本文指截取了开头的一部分,因为我们主要使用的是它的 Ldr 成员,看见了吗? 对!,就是它在结构偏移 0Ch 处
后面已经指出了Ldr成员是一个指向 PEB_LDR_DATA 结构的指针,下面我们就得看看这个结构了:
;==================================================================
PEB_LDR_DATA STRUCT              ; sizeof = 24h
  _Length          DWORD    ?  ; original name Length
  Initialized        BYTE    ?  ; 04h
            db   3   dup(?)  ; padding
  SsHandle        PVOID    ?  ; 08h
  InLoadOrderModuleList      LIST_ENTRY  <>  ; 0Ch
  InMemoryOrderModuleList      LIST_ENTRY  <>  ; 14h
  InInitializationOrderModuleList    LIST_ENTRY  <>  ; 1Ch
PEB_LDR_DATA ENDS
;==================================================================
啊哈~~~这里我们看到了想要的东西, Module 这个单词被我们发现了,ModuleList 就是模块 列表嘛~~~
InLoadOrderModuleList 就是按照模块加载顺序描述模块信息的,InMemoryOrderModuleList是按照内存中存储顺序描述,
InInitializationOrderModuleList是按照初始化dll模块的顺序描述的(你可以利用它们之一获得kernel32.dll的基址
这是许多无导入表程序的必做之事)
为了弄清Module信息究竟是怎么储存的,我们又必须知道LIST_ENTRY结构的定义
一个LIST_ENTRY结构描述了一个双链表
;======================================
LIST_ENTRY  STRUCT
  Flink  pLIST_ENTRY    
  Blink  pLIST_ENTRY
LIST_ENTRY  ENDS
pLIST_ENTRY typedef PTR LIST_ENTRY  ;pLIST_ENTRY 表示指向LIST_ENTRY结构的指针
;=====================================

根据图片我们可以看出LIST_ENTRY的用法,它嵌入在一个结构类型内,Flink指向下一个这种结构类型内的LIST_ENTRY
这样由表头,就可以找到所有的data struct结构了!
啊哈~~  MSDN 又说 InMemoryOrderModuleList 指向一个 LDR_DATA_TABLE_ENTRY 结构,也就是说,我们的图片的
 data struct 1,2,3  就是指 LDR_DATA_TABLE_ENTRY 结构,再看看 它的 定义:(虽然结构有点绕,别晕啊~~快胜利了)
;===========================================================
LDR_DATA_TABLE_ENTRY STRUCT
   InLoadOrderLinks      LIST_ENTRY   ;0h
  InMemoryOrderLinks;      LIST_ENTRY   ;8h
    InInitializationOrderLinks;    LIST_ENTRY  ;10h
   DllBase;        dword    ;18h    ;DllBase模块基址
   EntryPoint;        dword    ;1Ch    ;模块入口点
   SizeOfImage;        dword    ;20h    ;模块的内存映像大小
  FullDllName;        UNICODE_STRING  ;24h    
   BaseDllName;        UNICODE_STRING  ;2Ch    
   Flags;          dword    ;34h
  
  ~~~~~~~~~~~~~~~~~~~~~~~~~~    ~~~~~~~~~  ;LDR_DATA_TABLE_ENTRY结构以下部分省略
LDR_DATA_TABLE_ENTRY ENDS
;===========================================================
怎么样?看成员名字就知道~~~~,模块信息就在此处!
可以肯定一个LDR_DATA_TABLE_ENTRY描述一个模块的信息,依靠LIST_ENTRY与下一个或前一个LDR_DATA_TABLE_ENTRY想连.
它的前三个成员都是 LIST_ENTRY 类型!,再看看上面图片,你应该明白了吧,PEB_LDR_DATA中的三个LIST_ENTRY的后继依次就是这三个
这里我们主要使用InLoadOrderModuleList这个成员来遍历模块,原因是InLoadOrderModuleList对应的嵌入在LDR_DATA_TABLE_ENTRY结构中
的InLoadOrderLinks位于结构首部,用它寻址会方便、清楚一些(当然你用其它两个,InMemoryOrderModuleList 和
InInitializationOrderModuleList也可以啊)
看这个汇编代码,我们要定位于首个LDR_DATA_TABLE_ENTRY:
  
    mov  eax,fs:[30h]    ;eax-->PEB
    mov  eax,[eax + 0Ch]    ;eax == PEB.Ldr --> PEB_LDR_DATA
    mov  eax,[eax + 0Ch]    ;eax == PEB_LDR_DATA.InLoadOrderModuleList.Flink --> LDR_DATA_TABLE_ENTRY
  OK,执行后eax中就是第一个LDR_DATA_TABLE_ENTRY结构的地址啦!!!
  3个mov指令,但是用到了好多结构啊~~~  
  
对于我们的遍历方式,第一个LDR_DATA_TABLE_ENTRY描述的应该是dll所属的EXE的信息,包括入口点,基址,文件名什么的 ~~~
到这里我们还差一小步 那就是 UNICODE_STRING 结构
;=============================================================
UNICODE_STRING STRUCT        ;sizeof == 08h
  Length        word  ;0h
  MaximumLength         word  ;02h
  Buffer        dword  ;04h
UNICODE_STRING STRUCT  ENDS
;=============================================================
Length  指明由Buffer字段指向的UNICODE串的长度,不包括结尾的 00 00
  比如" C:\A "这个UNICODE串那么Length就是4*2==8
Buffer  指向Unicode字串的指针!
  太好了终于大功告成,解决复杂的结构了--
  现在假设我们要获得某个模块的全路径:
    mov  eax,fs:[30h]    ;eax-->PEB
    mov  eax,[eax + 0Ch]    ;eax == PEB.Ldr --> PEB_LDR_DATA
    mov  eax,[eax + 0Ch]    ;eax == PEB_LDR_DATA.InLoadOrderModuleList.Flink --> LDR_DATA_TABLE_ENTRY
    mov  eax,[eax + 24h + 4h]  ;eax == LDR_DATA_TABLE_ENTRY.FullDllName.Buffer --> 模块路径unicode串
   执行过后,eax 中存储的就是我们遍历的首个模块的模块全路径字串的地址
   也就是模块字串名称的指针.
  要访问下一个LDR_DATA_TABLE_ENTRY,在上几句代码的基础上只需这样: 
    mov eax,[eax]   ;因为eax本身就是指向LIST_ENTRY结构地~~~,这样mov指令使得
        ;当前LIST_ENTRY的Flink传送到eax,eax自然就指向下一个LDR_DATA_TABLE_ENTRY结构了~~~
  (2).
  好了说说我们的dll隐身技术,我们就是要将这里的LDR_DATA_TABLE_ENTRY.FullDllName指向的字符串清除!
因为快照函数或其它的函数,一般都会在底层访问这个结构,在这里查询 模块 的信息,我们把它给清除了,看它还能查到了吗?
下面我们假设我们已经使用CreateRemoteThread创建一个远程线程,远程线程入口就是 LoadLibraryA 函数,传入参数就是我们的欲注入
的dll文件名,现在我们看看这个dll文件的核心代码,看它如何清除自己的FullDllName串实现隐藏:

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功