《高級惡意軟件開發之RDI的進化.pdf》由會員分享,可在線閱讀,更多相關《高級惡意軟件開發之RDI的進化.pdf(45頁珍藏版)》請在三個皮匠報告上搜索。
1、演講人:張鵬瑤時間:2024.08.25張鵬瑤(evilash),Offensive Security Researcher自我介紹SELF INTRODUCTION紅隊全鏈路技術棧,有豐富的紅隊攻防單兵實戰經驗擅長方向:內網、AD域滲透,免殺武器化,Windows紅隊研發Counter-Strike 1.6(不是反恐精英游戲)作者曾任職啟明星辰ADLab高級安全研究員、黑客在思考知識星球圈主。RDI 原理介紹R e f l e c t i v e D l l I n j e c t i o nRDI 的進化E v o l u t i o n i n R D IOPSEC 優化O P S E
2、C i n R D IPost-ex Job 自清理S e l f C l e a n u p i n P o s t-e x J o b背景ABSTRACTRDI(ReflectiveDLLInjection)由 stephenfewer 發布于11年前RDI是一個隱匿的內存不落地執行技術如今,RDI 仍然是 C2/RAT 在初始化加載以及 post-ex 期間最常用的關鍵技術之一隨著 RDI 在 C2 中應用的變化,以及檢測技術的發展,他的規避性也是十分值得研究的關于 RDIREFLECTIVE DLL INJECTION關于 RDIREFLECTIVE DLL INJECTIONOPSE
3、C 優化OPSEC IN RDI當ReflectiveLoader函數執行完畢后,內存中會出現2塊內存區域,其中一塊是初始RDI代碼區域,另一塊為RDI執行后創建的展開DLL文件的RWX區域,都會被EDR/AV關注到。OPSEC 優化OPSEC IN RDI當新內存展開后,包含ReflectiveLoader函數的內存區域已經完成使命,可以清理掉,有兩種方式:第一種:在執行后的DLLMain中獲取老內存地址,進行FreeCobaltStrike:BRC4:OPSEC 優化OPSEC IN RDI第二種:在RDI中進行自身的釋放KaynStrike:以ROP的思想,通過捕獲寄存器狀態,更改寄存器
4、的值設置特定的上下文,使用NtContinue執行,構造了可以自己清理自身所在內存區域的操作OPSEC 優化OPSEC IN RDI原始RDI將DOS Header也拷貝到了新內存,這樣在內存中會存在非常明顯的PE頭但是裝載PE只需要在RDI階段使用到 SizeOfImage、SizeOfHeaders、entryPoint、ImageBase.規避方式:a.可以裝載完成后,清理掉PE頭部b.對DLL預處理,將需要的信息定義一個自定義結構OPSEC 優化OPSEC IN RDI原始RDI以 PAGE_EXECUTE_READWRITE 來申請新內存先申請RW權限的內存,但是PE的不同Secti
5、on需要不同類型的權限,所以需要最后對應更改,這樣分開后,每一個內存區域都像是一些垃圾內存,沒有連續的RWX區域。OPSEC 優化OPSEC IN RDIRDI中,在修復IAT的時候會有潛在的堆?;厮輽z測,主要是LoadLibrary()此外,從調用堆棧的角度,DLL執行起來的內存是私有“浮動內存”,使用yara掃描此內存區域可以輕松檢測解決思路:a.添加對LoadLibrary調用的堆棧欺騙,比如ProxyDllLoad(注意檢測規則)b.使用module stomping,但是要劫持Sleep流程,要在Sleep期間恢復原樣c.修改beacon,使用wininet、winhttp等相關的A
6、PI動態導入,不出現在IAT中d.如果只是檢測winhttp這類比較敏感的dll,可以加載一些會導入winhttp的但又不在檢測規則內的DLLOPSEC 優化OPSEC IN RDI原始RDI中,ReflectiveLoader函數隨著PE的展開也被帶到了新內存。這個導出函數已經完成使命了,但是他還存在于DLL的.text段內,被復制到了新的內存中,這也是一個潛在的IOC。那么有沒有什么方法,可以不讓RDI這塊代碼區域被復制過去?OPSEC 優化OPSEC IN RDI可以只在Loader Memory進行PE load,而不申請新的private內存對DLL文件進行預處理,比如,創建一個su
7、spended進程,將內存中的PE保存下來,提前做好內存對齊,再進行后續構建shellcode。RDI執行起來后,免去了FOA和RVA的轉換以及額外申請內存的操作ReflectiveDLLInjection IAT HOOK反射DLL修補prepended RDIRDI的進化EVOLUTION IN RDIRDI的進化EVOLUTION IN RDI反射DLL修補修改現有的PE頭,Patch匯編指令,去調用當前DLL導出的ReflectiveLoader函數地址CS內默認Beacon加載方式以及post-ex Job模塊執行均采用這種方式。RDI的進化EVOLUTION IN RDIprepe
8、nded RDI(前置式RDI)把ReflectiveLoader函數相同作用的功能放到DLL的前面,RDI向后去取DLL的基地址 展開后也不需要考慮ReflectiveLoader函數也被拷貝到新內存的問題 不用制作包含ReflectiveLoader函數的DLL最早出現于Double Pulsar的利用:由于RDI和DLL是分開的,不在DLL中,所以RDI在copy時就不會將RDI copy到新內存RDI的進化EVOLUTION IN RDIsRDI 執行前需要預處理,拼接RDI函數和DLL文件 處理流程比較OPSEC 比較早的前置式RDI的實現sRDI from monoxgas:Boo
9、tstrap shellcode+RDI shellcode+DLL Bytes+User dataRDI的進化EVOLUTION IN RDICobaltStrike UDRL arsenal-KitCS的UDRL使用了#pragma 指令,code_seg可用于指定哪個部分用于存儲特定函數,然后可以使用字母值對這些部分進行排序,例如.text$a、.text$b使LdrEnd()在.text段的最尾部定義ReflectiveLoader函數在.text段的最開始RDI的進化EVOLUTION IN RDIobfuscation in RDI加密后,還可以進一步降低最終shellcode的熵
10、(entropy),進行一些降熵的操作,最終可以得到靜態規避效果極佳的shellcodeRDI的進化EVOLUTION IN RDIBRC4:obfuscated shellcode:RDI的進化EVOLUTION IN RDIHOOK 可以在IAT處理的過程中做IAT Hook 干預Beacon內API調用特征 睡眠加密/混淆Phantom ExecutionSELF CLEANUP IN POST-EX JOB由于遠程進程注入的研究/武器化成本過高,常規的遠程進程注入基本無法繞過多數防護軟件所以目前執行功能模塊時一般會讓RDI過程在自身進程進行執行,從而一定程度規避被檢測查殺任務執行方式規
11、避性穩定性fork&runSelf InjectWorker ProcessSelf Inject主流C2執行功能方式Phantom ExecutionSELF CLEANUP IN POST-EX JOB功能模塊執行完畢,退出線程后,會殘留包含RDI函數的shellcode以及DLL在Beacon進程內展開的內存塊Phantom ExecutionSELF CLEANUP IN POST-EX JOBLoader MemoryNew MemoryPhantom ExecutionSELF CLEANUP IN POST-EX JOB隨著不斷執行截圖操作,殘留內存會不斷增加,比如執行5次:所以
12、,我們最好在每次插件執行完畢后,也清理掉內存中被展開的DLLPhantom ExecutionSELF CLEANUP IN POST-EX JOB所以如何清理RDI過程后的殘留內存?第一,當然是執行完后清理,那么如何知道插件執行完畢?第二,如何拿到RDI內分配的地址RDI本身是可以拿到分配的內存地址,所以,能不能讓RDI來釋放兩塊內存?指針執行可能一些管道傳輸操作沒有結束,清理時機不對的話,輕則拿不到結果,重則導致內存沖突問題使用Beacon或者DLLMain獲取,實現比較繁瑣,并且也需要干預DLLMain代碼,這樣使插件變得不通用Phantom ExecutionSELF CLEANUP
13、IN POST-EX JOB可以創建一個線程并等待,從而安全的執行后續的清理操作,我們在DLL中執行完畢后手動加入ExitThread(),在RDI以創建線程的方式去執行,是可以成功清理掉新分配的內存的Phantom ExecutionSELF CLEANUP IN POST-EX JOB別忘了我們還需要在要執行的DLL中加入ExitThread(),還是不夠完美,DLL內默認執行并不會出現退出線程的操作。通過修改寄存器狀態,構造RSP直接為退出線程API,實現線程的安全退出。Phantom ExecutionSELF CLEANUP IN POST-EX JOB清理完新的內存之后,我們可以再
14、用構造ROP的方式(和KaynStrike一樣)來清理掉RDI自身Phantom ExecutionSELF CLEANUP IN POST-EX JOBx86下構造堆棧方式需要修改:執行DLLMain自清理Phantom ExecutionSELF CLEANUP IN POST-EX JOB組合起來,就得到了一個僅依賴RDI過程,不需要修改Beacon、以及DLL本身的后滲透任務執行、自清理方式Phantom ExecutionSELF CLEANUP IN POST-EX JOB那這次,真的結束了嗎?好像還沒有。Phantom ExecutionSELF CLEANUP IN POST-
15、EX JOB還有一個問題,萬一某些DLL插件的執行時間過長?RDI中一直在等待執行結束才可以清理自身,那么原始的RDI和原始DLL所在的內存就一直在停留,增加了被掃描的風險??刹豢梢栽俳鉀Q一個問題?當RDI的使命完成了,先把RDI的內存清理掉,然后接著執行新內存的DLLMainPhantom ExecutionSELF CLEANUP IN POST-EX JOB但是,這里本身是靠RDI來清理后續的新內存,假如清理掉RDI還怎么清理后續新內存?并且清理掉RDI誰來執行DLLMain?所以需要再開發一段shellcode,放到尾部去避免在RDI本身去執行這些引發的沖突問題。使用#pragma指令
16、來使CleanJob存放到我們將其放到排序$y,也就是倒數第二個函數Phantom ExecutionSELF CLEANUP IN POST-EX JOBCleanJob需要計算從前面的ReflectiveLoader到CleanJob的大小,然后先執行DLLMain再清理CleanJob到PE尾部的區域,最后清理自己,比較麻煩。Phantom ExecutionSELF CLEANUP IN POST-EX JOB進一步優化,在RDI申請新內存的時候,把CleanJob先拷貝到新內存,然后再拷貝DLL過去展開,然后調用CleanJob負責清理和執行。首先定義一個函數,用來獲取CleanJo
17、b函數的大小以及地址:Phantom ExecutionSELF CLEANUP IN POST-EX JOB然后在RDI申請空間的時候,申請SizeOfImage的大小+CleanJob函數的大小,先將CleanJob函數放進去:Phantom ExecutionSELF CLEANUP IN POST-EX JOBCleanJob的功能就是接收DLLMain的地址以及RDI的起始地址,然后先清理原始RDI地址,接著執行DLLMain,等待結束后,清理當前新內存包含自身的內存空間Phantom ExecutionSELF CLEANUP IN POST-EX JOB最終展開后,在新的內存空間,應該是這樣的:Phantom ExecutionSELF CLEANUP IN POST-EX JOBPhantom ExecutionSELF CLEANUP IN POST-EX JOBPhantom ExecutionSELF CLEANUP IN POST-EX JOB最終,得到了一個從RDI本身來執行并清理兩塊內存,而不用更改DLL本身的通用DLL加載以及清理方法歡迎關注微信公眾號: