MDSec 提供了一個商業命令和控制框架,可以避免隱蔽的活動被檢測到,不過這已經不是什麼秘密了。考慮到這一點,我們一直在研發可以檢測到它們的方法。有些人會認為,建立一個難以捉摸的信標的最佳方法是,不僅要了解你的對手發現你的方式,還要嘗試找到他們將來可以檢測到你的新方法。
在這項研究中,我們將介紹一些尋找信標的有效策略,這些策略由我們為執行這些策略而開發的BeaconHunter 工具提供支持,並且我們打算在適當的時候將其開源。
信標檢測方法雖然有各種不同的方法來檢測在網絡中運行的信標,但我們將較少地關注特定的開發後功能的功能,而更多地關注識別駐留或加載到內存中的信標的一般方法。
行為信標在運行和加載時的行為可以為防御者帶來檢測機會。許多商業框架是封閉源代碼的,因此某些行為無法輕易更改,從而允許防御者創建與這些行為一致的簽名。
一個很好的例子是信標如何加載自身及其依賴項,讓我們看看與圖像加載相關的行為如何為防御者提供檢測機會。
為了讓信標提供豐富的利用後框架,它通常嚴重依賴操作系統原生的庫,允許開發人員通過避免靜態地綁定許多依賴項,使信標的大小盡可能小。
通過分析大量的信標框架,我們已經註意到,它們中的許多會在加載時加載核心信標所需的所有依賴項,而不是在使用時。在某些情況下,這將導致在終端上發生的一系列事件,防御者可以輕鬆地對其進行簽名。
例如,通過加載winhttp.dll 和wininet.dll,可以看到信標利用本地Windows HTTP 庫作為出口信標,當加載到通常不會執行HTTP 交互的進程時,這些可能會突出顯示異常。此外,一些信標還會加載使用更少的庫,如credui.dll、dbghelp.dll或samcli.dll。
使用這些DLL加載序列,就可以使用EQL規則來構建簽名,以檢測信標何時執行。
例如,使用類似於以下的EQL 規則,可以在短時間內檢測或搜索所有加載credui.dll 和winhttp.dlls 的進程:
此類檢測當然可以通過設計為模塊化或負載依賴於使用或具有延遲負載的信標來避免。
內存檢測在許多情況下,信標可能會保留在內存中,以避免磁盤檢測。信標通常是由加載器注入內存的,加載器將創建一個新的線程或劫持一個現有線程,在其中運行信標。
信標的典型加載過程可能如下所示:
一旦信標在內存中運行,通過對進程的分析,我們通常可以利用許多指標來檢測信標,讓我們看一些內存檢測的方法。
簽名檢測已知惡意軟件的內存信標的最簡單但最有效的策略之一可能是通過簽名檢測。雖然許多反病毒引擎和EDR 實施自己的內存掃描例程,但防御者可以使用Yara 規則輕鬆實現全面的內存掃描。
一個可以與yara64.exe命令行工具一起使用的簡單的Yara規則可能是這樣的,它將匹配檢測內存中列出的三個字符串中的任何一個:
為嵌入在信標中的字符串/數據或來自.text 部分的代碼創建Yara 規則在概念上可以用作檢測已知內存惡意軟件的有效技術。
Elastic 過去在如何利用它來檢測內存中的Cobalt Strike 方面做了一些出色的工作,建議閱讀這些技術如何在實踐中應用。
為了逃避這種內存掃描,信標可以使用多種技術來混淆它們在內存中的足跡,包括替換已知字符串,例如可以使用Cobalt Strikes strrep 可塑性配置文件選項或使用混淆和休眠策略,例如我們在Nighthawk 中使用的一種,用於在休眠時保護信標的所有字符串、數據和代碼。
內存掛鉤為了規避控製或更改進程的運行方式,信標或操作員可以將掛鉤應用於內存中的某些函數。這些掛鉤可以留下隱藏的痕跡,從而為防御者提供揭示隱藏信標的機會。讓我們來看看這種行為的一些具體示例。
修復ETW 和AMSI修補諸如AMSI 之類的安全控製或削弱通過Windows 事件跟踪獲得的檢測數據在攻擊性社區中已經不是什麼秘密了,事實上我們過去曾在介紹過這些策略。
這些補丁通常通過修改內存來應用,讓我們看兩個來自Sliver C2 庫的例子:
https://github.com/sliverarmory/injectEtwBypass
https://github.com/sliverarmory/injectAmsiBypass
正如我們在上面的屏幕截圖中看到的,這兩個示例都會導致信標將補丁應用到ntdll!etwEventWrite 或amsi.dll!AmsiOpenSession 函數。
考慮到這一點,低噪聲檢測的機會就出現了,只需搜索應用這些補丁的進程,以及其他常用的補丁函數,如AmsiScanBuffer或sleeppex,由Cobalt Strike的線程堆棧欺騙功能應用於這些函數。
複製寫入的修改如上所述,信標應用補丁來處理內存的情況並不少見,這可以為防御者創造檢測機會。然而,一旦執行了開發後操作,如果信標刪除了這些補丁,檢測的準確率可能會降低。例如,植入程序可以在內存中執行.NET 程序集之前應用AMSI 補丁,然後在執行後將補丁恢復為其原始操作碼。這種方法比簡單地將未修復漏洞留在內存中要明智一些。
然而,為了避免重複,Windows將把公共dll返回到運行進程共享的物理內存中。如果信標或操作員執行對這些dll應用補丁的操作,則會發生寫入時復制操作,從而使該頁面成為該進程的私有頁面。使用QueryWorkingSetEx API,我們能夠查詢有關進程特定虛擬地址的頁面的信息。在返回的PSAPI_WORKING_SET_EX_INFORMATION 結構中是一個PSAPI_WORKING_SET_EX_BLOCK 聯合,它指示查詢地址處頁面的屬性。在這個聯合中,我們能夠通過共享位的返回值來確定頁面上是否發生了寫入複製操作。此技術被諸如Moneta 之類的內存掃描儀使用,並且在檢測內存中的補丁時非常有效,即使原始補丁值已恢復。
然而,在大規模應用這種技術時存在一些誤報的風險,因為EDR 和防病毒軟件應用它們自己的內存掛鉤並不少見,這意味著它們可以使我們從查找中獲得的一些價值無效用於寫操作時的複制。然而,為了降低誤報的風險,我們可以通過解析修改頁面上的導出,並對EDR通常不掛鉤的函數(如EtwEventWrite或AmsiScanBuffer)應用更大的權重,從而對其應用更多智能。
線程異常如上所述,一旦信標在內存中運行,它通常會存在於一個或多個線程中,具體取決於信標是同步的還是異步的。與這些線程相關的異常可以提供信標活動的高信號指標,特別是當與其他指標結合或相互結合時。一些常見的可疑線程相關指標包括:
未映射的內存:源自虛擬內存且不受DLL 支持的線程是注入線程的經典標識。這些線程可以通過尋找具有MemoryType 為MEM_IMAGE 和MemoryState 為MEM_COMMIT 的內存區域的線程來輕鬆檢測到。或者,這些線程通常由EDR 檢查通過線程創建API 的內核回調來檢測。有許多工具可以查找這個標識。
延遲狀態:大部分時間信標將處於休眠狀態,然後醒來恢復其任務。為了實現這種休眠行為,通常使用諸如sleeppex這樣的windows API調用,這將使線程處於等待狀態,並將導致線程調用堆棧包含對KernelBase.dll!SleepEx 和ntdll.dll!NtDelayExecution 的調用。當與其他指標結合時,例如module stomping的跡象(稍後討論)或調用堆棧中對虛擬內存的調用,那麼這可能會提供一些信標行為。
繞過可疑線程檢測的嘗試最近變得流行起來,一些概念證明和商業實現正在已發布。
這些實現通常通過截斷線程的調用堆棧(例如通過將幀的返回地址設置為空)或通過複製現有線程的上下文來工作。考慮到這一點,我們可以尋找更多指標來添加到我們的指標中:
可疑起始地址:截斷線程調用堆棧的副作用之一是起始地址並非源自預期位置。也就是說,線程通常源自ntdll!RtlUserThreadStart 和kernel32!BaseThreadInitThunk,或者在CLR 線程的情況下源自ntdll!RtlGetAppContainerNamedObjectPath。尋找不遵循此模式的線程可用作進一步分析的可疑指標。此外,如果線程的NtQueryInformationThread(ThreadQuerySetWin32StartAddress) 的返回值與最後一幀的返回地址之間存在不匹配,則線程的起始地址也可以被認為是可疑的,這意味著有潛在的截斷。
初始幀之間的距離:如上所述,調用堆棧的初始起始地址通常源自用於執行線程初始化和創建的一組地址。注意到這些初始堆棧幀之間的距離相對一致,並且通常在第一幀和第二幀之間是靜態的(例如ntdll!RtlUserThreadStart 和kernel32!BaseThreadInitThunk)。在調用堆棧被截斷的情況下,這些幀之間的距離幾乎肯定是可變的。
複製上下文:如上所述,除了截斷線程的調用堆棧外,欺騙調用堆棧的一種方法是複制合法線程的上下文。這種技術可以有效且實施起來相對簡單。首先,在線程的線程信息塊中,有許多指向有關線程的各種信息的指針,這些跨線程的副本,例如具有相同堆棧基數(堆棧底部)和堆棧限制(堆棧上限)的多個線程,是線程上下文已被複製的良好指標。
頁面權限一般來說,信標將從虛擬內存中運行,或者如果Module Stomping,則從DLL 支持的區域內運行。
為了使信標恢復並執行其任務,信標所在的頁面需要對其應用執行權限。
例如,我們可以在下面的截圖中看到,0x22f96c5000的內存沒有一個DLL支持,它被標記為“Private:Commit”(即VirtualAlloc 導致的虛擬內存),並設置了RX 頁面權限:
這些指標是一個強烈的信號,表明有信標在該地區執行。
接下來的挑戰便是如何避免這一指標,答案很簡單,如果你的信標是從虛擬內存運行的,則它不能,在某些時候信標需要執行。折衷方案實際上是僅在信標執行任務時維護可執行權限,並在信標休眠時利用策略刪除可執行權限。因此,避免諸如SOCKS 代理之類的交易有助於最大限度地減少該指標的暴露:
一些植入程序採用了根據信標狀態調整頁面保護的策略,以及幾個開源實現,例如@Ilove2pwn_ 的Foliage、@c5pider 的Ekko、@mariuszbit 的ShellcodeFluctuation 和Josh Lospinoso 的Gargoyle。
這些策略通常會利用某種形式的事件驅動執行來休眠和喚醒信標,使用ROP小工具重新執行來調用VirtualProtect,並將信標的頁面重置為可執行權限。由MDSec的Peter Winter-Smith發現並被Ekko使用的基於計時器的技術,最初是由MDSec的Nighthawk c2逆向工程而來。簡而言之,這種技術的工作原理是使用CreateTimerQueueTimer將多個計時器排隊,然後當事件觸發器返回到之前定義的Context記錄時,使用NtContinue執行並調用VirtualProtect來重新啟用執行位。
要更詳細地理解這種技術,可以在這裡找到@Ilove2pwn_的原始文章。
Module StompingModule Stomping提供了一種將信標隱藏在內存中的替代方法,從而避免了與未映射內存中的信標相關的一些常見指標。為了實現這一點,需要加載一個不太可能被進程使用的合法DLL,信標通過模塊自我複制,然後創建一個由stomped 代碼支持的線程:
Cobalt Strike 自3.11 版本以來就提供了此功能,並且可以使用“set module_x64/module_x86”可擴展配置選項使用。
雖然這種技術可以提供許多OpSec 優勢,但它確實留下了幾個我們可以可靠檢測的指標:
1.檢測這種攻擊的最簡單的技術可能是比較內存中的模塊內容和磁盤上存在的模塊內容。代碼部分的任何變化幾乎肯定會暗示一些可疑的行為。這個進程當然是相對密集的,因為它需要從磁盤加載進程中的所有模塊,並與運行中的內存進行比較。
2.如上所述,對進程內模塊存儲器的修改將導致發生寫入操作時的複制。考慮到這一點,用於檢測內存掛鉤的相同邏輯也可以應用於檢測Module Stomping,因為在內存中覆蓋DLL 將導致生成DLL 的副本並清除共享位。
3.有幾種方法可用於執行Module Stomping,其中一些涉及利用現有的Windows API,例如LoadLibrary,而其他更複雜的實現可能使用自定義加載器將DLL 映射到內存中。一些技術具有與它們相關的已知指標,在PEB 中留下可用於尋找該技術的常駐痕跡是高度可信的。稍後將更詳細地討論這方面的示例。
檢測Cobalt Strike的方法Cobalt Strike 是最受歡迎的命令和控制框架之一,受到防御者和攻擊者的青睞。在這篇文章中,我們將討論防御者如何使用第一篇文章中介紹的技術,在不同配置和跨網絡中檢測Cobalt Strike 。所有分析均在Cobalt Strike 4.6.1 上進行。
Cobalt Strike 信標具有高度擴展性,因此某些指標可能會根據所選的擴展性配置文件選項而有所不同。
內存中的Cobalt Strike過去,在內存中尋找Cobalt Strike 簽名對於防御者來說是卓有成效的,之前Elastic 提供了全面的記錄。然而,從那時起,HelpSystems做了很多工作,Cobalt Strike 4.4 引入了休眠策略。
Cobalt Strike 為其混淆和休眠策略提供了以下可能的配置選項:
沒有休眠掩碼:信標、它的字符串和代碼將在內存中保持明文,並且可以通過內存掃描輕鬆識別。
在可擴展配置文件中啟用sleep_mask:當在malleable 配置文件中將sleep_mask 設置為true 時,beacon 將使用內置的混淆和休眠策略來屏蔽內存中使用xor 來混淆字符串和數據的信標。正如Elastic 在前面提到的帖子中所詳述的,這當然可以通過定位代碼部分來簽名。
使用用戶定義的休眠掩碼:用戶定義的休眠掩碼向用戶公開信標的混淆和休眠功能,允許他們滾動自己的實現。在這樣做的同時,它還為用戶提供了許多指向信標使用的任何堆記錄的指針,以便用戶可以對它們進行加密。利用用戶定義的休眠掩碼確實有一些折衷,特別是為了混淆.text 部分,配置必須將“userwx”選項設置為true。也就是說,如果要對其進行混淆,信標將始終存在於RWX 內存中。如果userwx 選項設置為false,則信標將從RX 內存中運行,但.text 部分不會被混淆,因此可以進行簽名。由操作員自行決定選擇他們覺得最舒服的指標。
例如,當使用將userwx 選項設置為false 的Sleep Mask Kit 時,可以使用以下Yara 規則檢測Cobalt Strike:
對注入的信標運行此Yara規則將顯示檢測到簽名:
啟用userwx 會將頁面權限設置為EXECUTE_READWRITE ,但這意味著信標現在正在混淆其.text 部分:
頁面權限Cobalt Strike 信標通常在具有RX 或RWX 頁面權限的頁面上運行,具體取決於可擴展配置文件的“userwx”配置選項的值,並且沒有Module Stomping,將由未映射的內存支持。
這是一個明確的指標,使得在內存中發現信標相對來說很簡單:
為了避免JIT程序集,可以掃描這些內存區域,搜索具有PAGE_EXECUTE_READWRITE或PAGE_EXECUTE_READ頁面權限和MEM_COMMIT標誌的頁面。當與其他指標一起使用時,這對識別信標活動可能是有價值的。當使用-p標誌時,我們將此簽入到BeaconHunter中:
線程注入內存時,Cobalt Strike 會佔用一個線程,信標是同步的。默認情況下,信標運行所在的線程是高度可疑的,並且有許多與之相關的指標。
在Process Hacker 中檢查Cobalt Strike 信標的線程可能看起來像這樣:
僅在上面的屏幕截圖中,我們就可以看到一些使線程看起來非常可疑的指標:
首先,線程通常有一個0x0 的起始地址。總體而言,這有點不規則,儘管從掃描合法進程來看,它確實有時會在某些進程(如chrome.exe)中發生。
深入了解線程的調用堆棧,我們還注意到對KernelBase!SleepEx 和ntdll.dll!NtDelayExecution 的調用。這些調用是信標處於睡眠狀態的標誌,用於在信標處於睡眠狀態時延遲線程的執行。
在調用KernelBase.dll!SleepEx 之前,我們可以在0x1b8ef69fcc7 的跟踪中看到調用,堆棧遍歷尚未解析此地址的符號,因此幾乎可以肯定它是虛擬內存。由虛擬內存支持的線程非常可疑,可能需要進一步分析。
綜上所述,防御者能夠高度自信地確定惡意活動來自線程。由於這些指標,BeaconHunter 會排除這些可疑線程:
還應該注意的是,Cobalt Strike 在21 年6 月將堆棧欺騙引入了到了工件中。但是,調用堆棧欺騙僅適用於通過工件工具包生成的exe/dll 工件,而不是通過注入線程中的shellcode 注入的信標。因此,它們不太可能有效地掩蓋內存中的信標。
分析表明,fiber的使用很少見,因此可以通過分析ntdll.dll!RtlUserFiberStart 的起始地址的調用堆棧來輕鬆找到這些fiber,當與其他指標結合使用時,可以為尋找Cobalt 工件提供一個好的開端:
Module StompingCobalt Strike 支持使用“set module_x64”和“set module_x86”可擴展選項的Mod