[翻譯]紅隊戰術: 結合直接系統調用和sRDI來繞過AV / EDR

English:https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/python

 

0x00 簡介

 

在本文咱們將介紹如何使用直接系統調用(Direct System Calls)以及配合sRDI注入來繞過R3層的行爲監控。git

 

隨着安全技術的防護能力逐漸加強,另外一方面,攻擊技術也在不斷髮展,做爲一個Red Team須要研究更先進的技術來繞過當下比較流行的防護和檢測機制。程序員

 

近期一篇惡意代碼的研究報告聲稱,使用"直接系統調用"技術來繞過安全軟件用戶層Hook的惡意樣本正在與日俱增。github

 

研究報告:https://www.cyberbit.com/blog/endpoint-security/malware-mitigation-when-direct-system-calls-are-used/shell

 

做爲一名ReadTeamer,要與時俱進!! 如今輪到咱們也來更新一波shellcode攻擊代碼了。編程

 

咱們將接下來將使用這種技術證實,在不觸碰磁盤的狀況下繞過AV/EDR監控的用戶層Hook,使用Cobalt Strike來dump LSASS.exe進程內存。sass

 

PoC代碼能夠在這裏下載:https://github.com/outflanknl/Dumpert安全

 

0x01 什麼是直接系統調用?

 

爲了弄清楚直接系統調用的真正含義,首先咱們須要先深刻Windows操做系統底層架構。架構

 

若是你使用過MS-DOS年代Windows系統,也許你會記得,一個簡單的程序崩潰能夠引發整個操做系統癱瘓。dom

 

這是由於操做系統在實模式(Real Mode)運行,處理器在實模式下運行時,不會有內存隔離的概念(沒有嚴格限制或者聲明,哪些內存區域是能夠訪問,哪些不能訪問)。

 

也就意味者,若是你寫的程序出現了bug致使內存破壞(Memory Currption)會致使整個操做系統中止運行。

 

 

 

一直到後來出現了能夠支持保護模式的新處理器和操做系統,這一現象才被改變。

 

爲了防止一個進程崩潰致使操做系統也跟着崩潰,在保護模式下引入了許多安全措施,經過虛擬內存(Virtual Memory)和權限級別(Privilege Levels),和一個叫Rings的概念,來隔離運行的不一樣進程之間,以及進程和操做系統之間的內存訪問。

 

Rings一共有4層,Ring0 ~ Ring3分別對應4個特權級別。

 

Windows操做系統中實際只使用了兩個特權級別:

 

一個是Ring3層,平時咱們所見到的應用程序運行在這一層,因此叫它用戶層,也叫User-Mode。因此下次聽到別人講(Ring三、用戶層、User-Mode)時,實際上是在講同一個概念。

一個是Ring0層,像操做系統內核(Kernel)這樣重要的系統組件,以及設備驅動都是運行在Ring0,內核層,也叫Kernel-Mode。

 

 

 

 

經過這些保護層來隔離普通的用戶程序,不能直接訪問內存區域,以及運行在內核模式下的系統資源。

 

當一個用戶層程序須要執行一個特權系統操做,或者訪問內核資源時。處理器首先須要切換到Ring0模式下才能執行後面的操做。

 

切換Ring0的代碼,也就是直接系統調用所在的地方。

 

咱們經過監控Notepad.exe進程保存一個.txt文件,來演示一個應用層程序如何切換到內核模式執行的:

 

 

 

                                                           WriteFile call stack in Process Monitor .
 
 

上面截圖展現了Notepad.exe進程保存一個文件時的執行流程(call stack),從下往上看執行流程。

 

咱們能夠看到 notepad調用了kernel32模塊中的WriteFile 函數,而後該函數內部又調用了ntdll中的NtWriteFile來到了Ring3與Ring0的臨界點。

 

由於程序保存文件到磁盤上,因此操做系統須要訪問相關的文件系統和設備驅動。應用層程序本身是不容許直接訪問這些須要特權資源的。

 

應用程序直接訪問設備驅動會引發一些意外的後果(固然操做系統不會出事,最多就是應用程序的執行流程出錯致使崩潰)。因此,在進入內核層以前,調用的最後一個用戶層API就是負責切換到內核模式的。

 

CPU中經過執行syscall指令,來進入內核模式,至少x64架構是這樣的。咱們能夠經過下面 WinDBG截圖中看到,反彙編的NtWriteFile指令:

 

 

 

                                                   Disassembled NtWriteFile API call in WinDBG.
 

把被調用函數相關的參數PUSH到棧上之後,ntdll中的NtWriteFile函數的職責就是,設置EAX爲對應的"系統調用號",最後執行syscall指令,CPU就來到了內核模式(Ring0)下執行。

 

進入內核模式後,內核經過diapatch table(SSDT),來找到和系統調用號對應的Kernel API,而後將用戶層棧上的參數,拷貝到內核層的棧中,最後調用內核版本的ZwWriteFile函數。

 

當內核函數執行完成時,使用幾乎相同的方法回到用戶層,並返回內核API函數的返回值(指向接收數據的指針或文件句柄)。

 

像NtWriteFile這樣在進入內核層以前的函數,通常也是大部分安全產品,好比:AV、EDR和Sanbox軟件常常設置Hook的地方,它們經過Inline Hook來劫持執行流程到本身引擎中,以便完成對一些敏感API的監控,

 

若是發現任何可疑的參數,則直接返回失敗,或彈出窗口警告。

 

正如上圖NtWriteFile函數的反彙編指令,你可能注意到了,它只有短短8行彙編指令,在這些指令中最重要的就是:系統調用號、syscall指令、進入NtWriteFile函數前,PUSH到棧上的正確的參數,以及使用正確的調用約定。

 

 

有了上面這些知識的鋪墊,咱們爲什麼不本身來實現這幾行彙編代碼,模擬直接系統調用(Direct System Calls)就能夠不用再調用NTDLL中的任何函數了,同時咱們也Bypass了User-Mode(Ring3)下面任何設置在NTDLL函數中的Hook。

 

這正是本文的目的,在開始動手以前,咱們先來簡單瞭解一下Windows編程接口。

0x02 Windows編程接口

 
​下圖中展現的是Windows系統架構概述
 

 

 

用戶層的應用程序要想和底層系統交互,一般使用應用程序編程接口(Application Programming Interface )也就是所謂的API。若是你是編寫C/C++應用的Windows程序開發程序員,一般使用 Win32 API。

 

Win32API是微軟封裝的一套API接口,由幾個DLL(所謂的Win32子系統DLL)組成。在Win32 API下面使用的是Naitve API(ntdll.dll),這個纔是真正用戶層和系統底層交互的接口,通常稱爲用戶層和內核層之間的橋樑。

 

可是ntdll中函數大部分都沒有被微軟記錄到官方的開發文檔中,爲了兼容性問題,大多數狀況在寫程序時,應該避免直接使用ntdll中的API。

 

 

 

微軟在Native API上面又封裝一層的神奇之處正是由於Native API是用戶層與內核層之間的橋樑,這樣就能夠在不影響Win32編程接口的狀況下對系統結構進行修改。

 

如今咱們對系統調用和Windows編程API有了一些瞭解,讓咱們看看如何經過編程來繞過Win32接口層,直接調用系統API並繞過潛在的Ring3層Hook。

0x03 直接使用系統調用

 
咱們前面已經展現瞭如何使用反彙編來找到Native API對應的系統調用號。使用調試器可能有點小麻煩,此次咱們選擇使用IDA來打開一個ntdll.dll文件的副本,來找到須要的NativeAPI。

 

 

 
                                               Disassembled NtWriteFile API call in IDA.
 

有一個小問題尚未提到,系統調用號會受OS版本影響而變化,有時甚至是Service Pack、內置版本號等。不過不用擔憂,Google Project Zero項目的 @j00ru 成員統計了全部Windows系統版本中的Native API的系統調用號。

 

在線查詢系統調用號:https://j00ru.vexillium.org/syscalls/nt/64/  有了這張表,咱們能夠直接搜索咱們想要使用的Native API,就能夠看到該API在不一樣系統中的調用號。

 

咱們須要編寫彙編來調用Driect System Calls。 在Virtual Studio項目中須要啓用MASM編譯依賴的支持,咱們才能在項目中添加.asm文件。

 

 

 

                 Assembly system call functions in .asm file.
 
如今咱們只須要獲取目標系統版本信息,並使用匯編語言定義函數時,帶上具體的系統版信息。咱們能夠經過Native API  RtlGetVersion()獲取系統版本信息,並將信息保存到一個版本信息的結構中。

 

 

                                            Reference function pointers based on OS info.
 
在VS代碼中導出asm文件中定義的函數,並定義其函數原型:

 

 

                    Exported OS specific assembly functions + native API function definitions.
 
如今能夠在咱們代碼中使用這些定義的System Call函數了,而不須要通過Native API這一層:

 

 

                        Using ZwOpenProcess systemcall function as a Native API call.
 
 

0x04 使用直接系統調用,恢復Hook的API

 

要想經過這種方法來編寫一個高級木馬來徹底繞過用戶層API調用幾乎是不可能的,至少實現起來很是麻煩,由於這些參數結構的問題、等等。

 

有時候可能你只是想在惡意代碼中使用一個API函數,可是,未曾想這個API的調用堆棧某處早已被一些AV、EDR軟件設置了Hook,隨時等你上鉤。

 

讓咱們來看看如何使用直接系統調用來卸載Hook。

 

 

一般狀況下,基於用戶模式的AV、EDR軟件經過使用跳轉指令(JMP),將API入口處前5個字節修改成指向安全軟件的Hook函數。

 

卸載這種Hook的方法也早被 @SpecialHoang 和@domchell這兩位大神公佈過了: https://www.mdsec.co.uk/2019/03/silencing-cylance-a-case-study-in-modern-edrs/

 

若是你仔細研究這些卸載Hook的思路,你會注意到這些方法中用到了諸如:VirtualProtectEx、WriteProcessMemory之類的API來卸載Native API函數的Hook。

 

可是若是VirtualProtectEx這些API也被Hook和監視了呢?嘿嘿!這下咱們就能夠經過直接系統調用來卸載這些Hook,不怕半路殺出個程咬金了。

 

在咱們的PoC代碼中,基本上和普通卸載Hook的思路同樣,恢復被Hook函數的前5字節原始的彙編指令代碼。惟一的區別是咱們執行恢復時調用的是Direct System Calls函數(ZwProtectVirtualMemory 和ZwWriteVirtualMemory)。

 

 

 

                                                          Using direct system call function to unhook APIs.
 

0x05 概念證實

 ​在攻擊過程當中,一般咱們須要使用Mimikatz來獲取目標系統上的憑證、Hashes、Kerberos票據。現在,終端檢測軟件和情報分析系統在檢測和預防Mimikatz方面作的至關不錯。

 若是你正在進行評估,而且你的攻擊場景須要儘量保持隱蔽,直接在終端上使用Mimikatz並非最好的方法(即便是在內存中)。另外使用procdump等工具轉儲LSASS內存一般會被 AV、EDR的Hooks檢測到。

 所以咱們須要一個替代方案來訪問LSASS內存,@SpecialHoang 博客中公佈了一個方法,先卸載相關函數的Hook,而後再建立LSASS的內存轉儲。

  

做爲概念證實,咱們建立了一個名爲」Dumpert「的LSASS內存轉儲工具。此工具結合了直接系統調用和卸載API  Hook,可讓你建立一個LSASS的minidump。而且可能會Bypass一些AV、EDR產品的檢測。

 

 

 

獲得Minidump文件後,咱們就能夠在本身機器上使用Mimikatz來提取憑證信息。

 

 

 

                                             Mimikatz minidump import.
 
固然了,在目標系統中釋放一個可執行文件,並非咱們想要的,咱們須要進一步優化它。
 

0x06 sRDI - (反射式注入DLL)Shellcode Reflective DLL Injection

 

若是咱們不想磁盤落地,就得須要使用某種注入技術。咱們能夠寫一個反射式加載的DLL,可是反射式DLL注入會留下能夠被檢測到的內存數據。

 個人同事@StanHacked告訴我一種稱爲 "Shellcode Reflective DLL Injection"的DLL注入技術。

 sRDI能夠把一個普通的DLL文件轉換爲一段不依賴任何位置的Shellcode,這項技術是被Slient Break Security的Nick Landers(@monoxgas)開發,基本上屬於RDI的升級版。

 相對於標準RDI,使用SRDI的一些優勢:

 

  • 你能夠轉換任何DLL爲無位置依賴的shellcode,而且可使用標準的shellcode注入技術來使用它。
  • 你的DLL中不須要寫任何反射加載器代碼,由於反射加載器是在DLL外部的shellcode中實現的。
  • 合理使用權限,沒有大量的RWX權限數據。
  • 還能夠根據選項,抹掉PE頭特徵。 

 想了解更多sRDI細節的朋友,能夠參考這篇文章:https://silentbreaksecurity.com/srdi-shellcode-reflective-dll-injection

0x07 是時候展現真正的力量了!

 
目前咱們具有了所須要的技術,來看看咱們可否把這些技術結合起來,建立一些在Read Team行動過程當中更有用的功能!!!

 

  • 咱們使用直接系統調用和卸載Hook技術,建立一個DLL版本的"Dumpert"工具。這個DLL能夠經過命令行運行:"rundll32.exe c:\Dumpert\outflank-Dumpert.dll,Dump",而後咱們將它轉換爲sRDI shellcode。
  • 使用Virtual Studio編譯成Dumpert的DLL版本,而後經過sRDI項目中的ConvertToShellcode.py完成:「python3 ConvertToShellcode.py otflank-Dumpert.dll」
  • 經過Cobalt Strike的shinject命令講shellcode注入遠程目標。Cobalt Strike支持一種強大的腳本語言,名爲aggressor腳本,它可讓你自動完成這一步。

 

爲了讓這一步更方便,咱們提供了一個aggressor腳本:https://github.com/outflanknl/Dumpert/tree/master/Dumpert-Aggressor,啓用腳本之後,能夠在beacon菜單中使用dumpert命令來一鍵搞定!!

 

 

 

 

 

  • 這個Dumpert腳本經過shinject命令注入sRDI版本的shellcode到當前進程中(爲了不調用CreateRemoteThread API),執行後會等待一會,由於須要lsass的minidump轉儲成功,並回傳!
  • 最後,你能夠在其餘機器上來使用Minikatz命令來提取minidump中的憑證信息:」sekurlsa::minidump c:\Dumpert\dumpert.dmp「。

0x08 總結

 

 

可以繞過安全產品Hook的惡意軟件正在逐漸增多,咱們須要在咱們的項目中也嵌入這種技術。

 

本文中,咱們經過編寫彙編代碼,基於Native API的函數原型、以及不一樣版本的系統調用號,實現了直接系統函數調用。使用這些函數時,就像是在直接使用Native API中的函數同樣。

 

咱們將這種技術與API卸載Hook結合,實現了從LSASS中建立一個minidump,而且使用了sRDI結合Cobalt Strike來注入dumpert shellcode到目標系統內存中。

 

檢測惡意使用系統調用比較困難,因爲繞過了用戶模式編程接口,所以惟一能查找惡意行爲的地方只能內核中。可是因爲內核被PatchGuard保護,安全產品更要想在運行的內核中建立鉤子或修改內核文件,就更難了!!!

 

我但願這篇文章可以幫助理解黑客如今使用的這種高級攻擊技術,但願這篇文章能在Read Team行動中給你們一些有用的啓發!!

相關文章
相關標籤/搜索