【滴水基礎(chǔ)】4.Win32Api調(diào)用(下2)
第三十:模塊隱藏
---使用API,遍歷進(jìn)程有那些模塊
#模塊隱藏之?dāng)噫?/p>
#線程環(huán)境塊:TEB(Thread Environment Block)
---用戶層的結(jié)構(gòu)體,它記錄了相關(guān)線程的信息。每一個(gè)線程都有自己的TEB
---FS:[0]就是當(dāng)前線程的TEB
#進(jìn)程環(huán)境塊:PEB(Process Environment Block)
---用戶層的結(jié)構(gòu)體,它記錄了相關(guān)進(jìn)程的信息。每一個(gè)線程都有自己的PEB
---哪個(gè)線程在執(zhí)行的時(shí)候,fs就存儲(chǔ)那個(gè)線程的TEB
---TEB偏移0x30即FS:[0x30],就是當(dāng)前線程的TEB
---使用Windbg(我的Win11直接在搜索框里面搜索)

---隨便打開一個(gè)exe模塊,使用"dt _TEB"命令來查看TEB結(jié)構(gòu)體信息

---可以發(fā)現(xiàn)TEB信息如下,這里存在三個(gè)主要的指針,指向三個(gè)重要的結(jié)構(gòu)體
---這里打開的是X64的WinDbg
---查看_PEB結(jié)構(gòu)體(可以看出,這里x64位系統(tǒng)PEB和TEB的偏移因該是0x60)
#x86和x64的區(qū)別(32位系統(tǒng)也稱x86,64位系統(tǒng)也稱x64)
----1、內(nèi)存尋址能力區(qū)別:32位系統(tǒng)尋址能力是4G容量,而64位系統(tǒng)可以支持128GB大內(nèi)存,甚至更大。
---2、運(yùn)算速度區(qū)別:x64的CPU數(shù)據(jù)寬度為64位,64位指令集可以運(yùn)行64位數(shù)據(jù)指令,
---處理器一次可提取64位數(shù)據(jù)(只要兩個(gè)指令,一次提取8個(gè)字節(jié)的數(shù)據(jù))
----比32位(四個(gè)指令,一次提取4個(gè)字節(jié)的數(shù)據(jù)),性能會(huì)相應(yīng)提升一倍。
---3.通常情況下,x86(32位)的軟件可在64位和32位上的系統(tǒng)運(yùn)行,但x64(64位)的軟件在32位系統(tǒng)上有可能出現(xiàn)不兼容的情形。
#查看_PEB_LDR_DATA結(jié)構(gòu)體信息
---理解其三個(gè)成員的順序,其指向_LDR_DATA_TABLE_ENTRY元素中開始的三個(gè)成員
--- _LDR_DATA_TABLE_ENTRY 中存儲(chǔ)著就是關(guān)于有關(guān)模塊信息的元素(比如模塊名等)
---什么是雙向鏈表

---這里我搞錯(cuò)了一點(diǎn),X86和X64不僅僅是操作系統(tǒng),還有exe文件
---比如我用VC6編譯的Test.exe文件就是X86系統(tǒng)的(可以在資源管理器查看)
---Test.exe就是打印0-9
---還有就是我系統(tǒng)自帶的WinDbg(X64)

---可以通過命令設(shè)置WinDbg轉(zhuǎn)為x86模式,需要切換到該 32 位進(jìn)程轉(zhuǎn)儲(chǔ)的 32 位視圖

---x64切換x86模式代碼如下(emmm找了好久這個(gè)代碼)
---再來查看_TEB,發(fā)現(xiàn)_PEB和TEB的偏移是0x30
---查看_NT_TIB
---查看_CLIENT_ID
---查看_PEB_LDR_DATA
---其中_LIST_ENTRY存儲(chǔ)的是指向_LDR_DATA_TABLE_ENTRY的雙向鏈表指針
---32位的結(jié)構(gòu)體存儲(chǔ)信息

---用DTDebug打開之前的Test.exe(這個(gè)沒有dll,打開就是main函數(shù)的領(lǐng)空)

---查看FS的值:2C8000(注意:每次打開的都不一樣)

---查看對(duì)應(yīng)內(nèi)存的值:dd 2C8000

---查看_CLIENT_ID結(jié)構(gòu)體信息(偏移0x20)

---則獲取到了:進(jìn)程ID、線程ID

---找到TEB的偏移0x30,右鍵:follow in Dump進(jìn)入指針指向的地址
---因?yàn)門EB的偏移0x30的內(nèi)存里面,存儲(chǔ)者指向PEB的指針

---follow in Dump之后,發(fā)現(xiàn)來到了PEB的內(nèi)存地址

---查看PEB中LDR的偏移

---找到0xC的偏移

---再次follow in Dump到_PEB_LDR_DATA的內(nèi)存地址
---然后根據(jù)0xC的偏移,來到InLoadOrderModuleList的位置

---里面存儲(chǔ)的指針也就是_LDR_DATA_TABLE_ENTRY的起始位置
---注意:前面加_的應(yīng)該是存儲(chǔ)的結(jié)構(gòu)體指針

---可以發(fā)現(xiàn)InLoadOrderModuleList的單位?_LIST_ENTRY里面
---起始就是雙向鏈表的兩個(gè)指針,可見_PEB_LDR_DATA這也是存在的3個(gè)?_LIST_ENTRY
---?_LIST_ENTRY這個(gè)雙鏈表指向進(jìn)程裝載的模塊,結(jié)構(gòu)中的每個(gè)指針,指向了一個(gè)LDR_DATA_TABLE_ENTRY 的結(jié)構(gòu):

---查看_LDR_DATA_TABLE_ENTRY結(jié)構(gòu)體的成員
---選擇第一個(gè)follow in dump,進(jìn)入_LDR_DATA_TABLE_ENTRY的起始位置
---PEB_LDR_DATA 中的三個(gè)LIST_ENTRY全部是雙向鏈表結(jié)構(gòu)
---它的兩個(gè)成員Flink,Blink都指向LDR_DATA_TABLE_ENTRY
---首先看Test.exe加載了幾個(gè)模塊

---這里也是_LIST_ENTRY的Flink

---BaseDllName的偏移是0x024

---發(fā)現(xiàn)_LDR_DATA_TABLE_ENTRY的InLoadOrderModuleList的Blink正好是_LDR_DATA_DATA的InLoadOrderModuleList的Flink

---查看 BaseDllName 的單位_UNICODE_STRING(重要)

---偏移4個(gè)單位才是模塊名稱,也是對(duì)的上的

#總結(jié)
---_TEB偏移0x30,里面存儲(chǔ)著指向_PEB的指針
---_PEB偏移0xC,里面存儲(chǔ)著指向_PEB_LDR_DATA結(jié)構(gòu)體的指針
---_PEB_LDR_DATA偏移0xC,里面存儲(chǔ)著_LIST_ENTRY結(jié)構(gòu)體的2個(gè)指針
---通過_LIST_ENTRY的Flink成員獲取_LDR_DATA_TABLE_ENTRY結(jié)構(gòu)體地址
---(注意:這里的Flink指向的是_LDR_DATA_TABLE_ENTRY結(jié)構(gòu)中的InLoadOrderLinks成員)
---通過_LDR_DATA_TABLE_ENTRY的偏移獲取BaseDllName或FullDllName成員信
---這里感覺很難,可以再梳理一下

---TEB > PEB > _PEB_LDR_DATA > _LDR_DATA_TABLE_ENTRY 的關(guān)系如下

---Ldr中有的三個(gè)List,根據(jù)MSDN規(guī)定LIST_ENTRY的結(jié)構(gòu),其中有兩個(gè)成員:Flink和Blink,他們分別指向著一個(gè)LDR_DATA_TABLE_ENTRY
---不同模塊之間,通過Flink和Blink連接

------而且不管怎樣,第一個(gè)加載的一定是Test.exe模塊(參考進(jìn)程創(chuàng)建的流程)

---不難發(fā)現(xiàn),PEB_LDR_DATA給出的是三個(gè)List(InLoadOrderModuleList,InMemoryOrderModuleLis以及InInitializationOrderModuleList)
---模塊加載首個(gè)基址,也可以看成是整個(gè)List雙向鏈表的表頭,然后通過這個(gè)雙向循環(huán)鏈表的不斷的遍歷,來依此獲取不同List加載的順序。
---同時(shí)系統(tǒng)為每一個(gè)DLL維護(hù)的一個(gè)LDR_DATA_TABLE_ENTRY,
---該結(jié)構(gòu)中,每一個(gè)DLL在不同List中,不但包含著著前繼加載模塊或者后繼加載模塊,還有著非常詳細(xì)的各個(gè)加載模塊的信息,包括加載基址和DLL名稱等等。
?---這樣,我們根據(jù)PEB_LDR_DATA 后找到LDR_DATA_TABLE_ENTRY
---通過不斷地遍歷,讀取其中的各項(xiàng)結(jié)構(gòu),至此,我們可以得出每一個(gè)List的在測(cè)試系統(tǒng)下,模塊的加載的依此順序:

----總結(jié):關(guān)系如下

#模塊隱藏的思路:
---對(duì)于指向Test.exe的雙鏈表進(jìn)行斷鏈操作,跳過第一個(gè)Test.exe模塊(讓PEB_LDR_DATA的Flink指向模塊A,讓模塊A的Blink指向PEB_LDR_DATA)
---由于存在3個(gè)List,所以上面的操作要進(jìn)行三次
---這里的斷鏈思想是這樣的:通過改變雙向鏈表的指針
---進(jìn)而將代表Kernel32.dll的_LDR_DATA_TABLE_ENTRY結(jié)構(gòu)體進(jìn)行跳過

---運(yùn)行該exe,用DTDebug的attach打開,點(diǎn)E查看模塊加載

---按回車嘗試隱藏模塊

---發(fā)現(xiàn)模塊已經(jīng)隱藏

#但是通過WinDbg查看內(nèi)核的情況還是可以看到該模塊
---通過查看內(nèi)存情況(我的Windows11無法打開kernel debug,因該是不支持本地內(nèi)核調(diào)試)
---發(fā)現(xiàn)kernel32.dll沒有被隱藏
---而且這里不能斷鏈,如果斷鏈,內(nèi)存就卸載了該模塊

#在PE結(jié)構(gòu)中查看模塊在內(nèi)存中存儲(chǔ)的規(guī)律(PE指紋)
---查看Kernel32.dll的基址0x75860000

---db 0x75860000,開頭5A 4D對(duì)應(yīng)的MZ
---然后往后找64個(gè)Byte,發(fā)現(xiàn):00 00 00 E8
---然后將0x75860000偏移E8,對(duì)應(yīng)的PE
---這里的MZ、PE,是任何模塊都存在的指紋
---因此:最好的隱藏:無模塊注入,也就是代碼注入

第三十一:代碼注入
#回顧遠(yuǎn)程線程
---原來的遠(yuǎn)程線程是執(zhí)行目標(biāo)進(jìn)程的已有函數(shù)
---代碼注入:1.將自定義函數(shù)復(fù)制到目標(biāo)進(jìn)程 2.指定遠(yuǎn)程線程執(zhí)行自定義函數(shù)

#代碼注入的問題:
---1.復(fù)制的函數(shù)本質(zhì)是什么?
---2.復(fù)制的函數(shù)可以正常執(zhí)行的前提條件是什么?
#函數(shù)的本質(zhì)
---函數(shù)在本質(zhì)就是機(jī)器碼,而VC6將機(jī)器碼翻譯成匯編代碼來幫助人們查看
#不能使用全局變量進(jìn)行拷貝
---g=1:對(duì)應(yīng)的匯編:
---00427e34是全局變量的地址,將1存儲(chǔ)到這個(gè)地址中
---機(jī)器碼:05 34 7E 42 00代表全局變量的地址:00427e3205
---01是存儲(chǔ)的值,C7代表的是mov
---但是如果直接復(fù)制機(jī)器碼會(huì)失敗,因?yàn)榱硗庖粋€(gè)進(jìn)程沒有00427e3205的全局變量的地址
#不能使用常量字符串
---查看匯編和機(jī)器碼
---字符串的的本質(zhì):在堆中申請(qǐng)內(nèi)存,將字符串以數(shù)組的形式存儲(chǔ)到堆內(nèi)存,然后將堆內(nèi)存數(shù)組的首地址放到緩沖區(qū)中[ebp-4]
---機(jī)器碼(6C 2F 42 00)代表堆內(nèi)存地址,但是另一個(gè)進(jìn)程不存在該堆內(nèi)存的地址
#不能使用系統(tǒng)調(diào)用
---對(duì)應(yīng)的匯編和機(jī)器碼
---程序?qū)essageBox的函數(shù)(根據(jù)exe的導(dǎo)入表)地址放入堆內(nèi)存,
---MessageBox()的調(diào)用流程

#不能嵌套調(diào)用其它函數(shù)
---查看匯編:直接call的是Test的函數(shù)地址
---但是在另外一個(gè)進(jìn)程中,這個(gè)函數(shù)地址是不一致的
#總結(jié)
---發(fā)現(xiàn)限制太多,基本做不了什么事情

#代碼注入思路
---在同一個(gè)OS中,系統(tǒng)函數(shù)的地址都是一樣的,如LoadLibrary()
---在當(dāng)前進(jìn)程,創(chuàng)建相關(guān)系統(tǒng)函數(shù),然后將系統(tǒng)函數(shù)的地址,傳到目標(biāo)進(jìn)程中

---對(duì)之前的Test.exe進(jìn)行代碼注入,任務(wù)管理器查看Pid:2060
---注意:我的VC是32位,所以只能注入32位的exe
---注意:一般的程序頂不住,我最后寫了一個(gè)死循環(huán)才注入成功
----注入代碼:在D盤創(chuàng)建一個(gè)Test.txt文件
---之前試了很多次,都沒有成功,但是我的調(diào)試語句卻輸出創(chuàng)建文件成功(還以為是我的代碼問題,copy了網(wǎng)上成功的代碼還是不行)
---猜測(cè)是被注入的程序在被注入的時(shí)候,直接崩了,所以沒有頂?shù)轿募?chuàng)建成功,因此寫了一個(gè)死循環(huán),發(fā)現(xiàn)文件創(chuàng)建成功
---發(fā)現(xiàn)文件被創(chuàng)建

#代碼注入的總結(jié)

#為什么要進(jìn)行修正函數(shù)的
---對(duì)于調(diào)用函數(shù)來說,會(huì)call一個(gè)函數(shù)的地址:00401005
---但是從00401005跟進(jìn)去發(fā)現(xiàn),并不是Fun函數(shù)的真正地址
---而是JMP到真正的Fun函數(shù)的地址
---E9代表的是JMP命令,16代表的:當(dāng)前的跳轉(zhuǎn)地址的下一行地址,和Fun()地址相差16
---當(dāng)前跳轉(zhuǎn)地址下一行=00401005+5個(gè)機(jī)器碼=0040100A
---Fun()地址:0x0040100A+0x16=401020
#總結(jié)
---真正的地址 = 匯編Call的地址 + 5 + JMP下一行到真正函數(shù)地址的偏移
#再來看代碼里面的修正函數(shù)起始地址的代碼
---原來的地址裝在DWORD里面,是小端存儲(chǔ),所以 *(00401005)=E9 16 00 00 00
---但是如果將DWORD類型的dwFunAddr轉(zhuǎn)換為BYTE,那么*(BYTE*)(00401005)=E9(因?yàn)槲覀冎恍枰x取1Byte,所以轉(zhuǎn)為Byte類型)
---獲取JMP下一行到真正函數(shù)地址的偏移:讀取dwFunAddr的后面4個(gè)Byte,所以需要轉(zhuǎn)化為DWORD類型
---dwFunAddr+1則是跳過E9進(jìn)行讀取
#總結(jié)代碼注入
---優(yōu)點(diǎn):難以檢測(cè)
---缺點(diǎn):繁瑣復(fù)雜