當前位置:
首頁 > 新聞 > 通過.NET實現Gargoyle

通過.NET實現Gargoyle

Gargoyle是一種幫助惡意軟體逃脫內存掃描的技術,由Josh Lospinoso於2017年對外公布,不過只是作為概念驗證發布的,它的強項在於確保注入的代碼在大多數情況下是不可執行(隱藏)的,從而使內存掃描技術難以識別出惡意代碼。客觀的說,這是一項很棒的技術,因為研究人員曾經使用 Cobalt Strike (一款非常優秀的後滲透平台)進行過測試,除此之外還用WinDBG( 查看和調試windows內核的一些東西難免需要用到WinDbg)和Volatility插件(Volatility是一款開源內存取證框架,能夠對導出的內存鏡像進行分析,通過獲取內核數據結構,使用插件獲取內存的詳細情況以及系統的運行狀態。)來進行過檢測。

由於Powershell支持較高版本的.net,所以研究人員發現通過.NET實現Gargoyle也是可能的。

動態.NET代碼執行

目前許多攻擊性技術都儘可能避免讓惡意代碼在目標設備上的硬碟上工作,這樣做的目的是為了避免傳統的殺毒掃描並避免在硬碟留下可以進行取證的痕迹,這就導致了內存取證技術的發展,以幫助高效打擊惡意攻擊。對於本機代碼,傳統上使用反射DLL注入(reflective DLL injection)技術,Reflective DLL Injection提出來就是解決這個問題的,完全不需要在硬碟上寫入inject.dll,而是直接在內存當中操作;process hollowing(現代惡意軟體常用的一種進程創建技術)以及許多其他類似技術來執行。但是,.NET可以通過使用動態程序集載入和反射,以非常簡單的逃避機制來實現以上這些技術的目標。

例如,在PowerShell中研究人員可以看到如何在內存中動態載入.NET程序集,並使用反射技術來實例化類並進行調用。

對於本文的詳細背景信息,如果你感興趣,可以閱讀《如何檢測惡意使用的.NET》,其中有更多詳細信息。此外,本文的一些信息也在Joe Desimone發布的一篇精彩文章中有所介紹,他在其中還演示了如何使用他發布的Get-ClrReflection.ps1 PowerShell腳本檢測內存中載入的.NET程序集。

.NET定時器和回調

最開始的Gargoyle技術使用的是本機Windows定時器對象,以便在定義的時間間隔內執行回調。然後,當定時器到期時,使用由內核傳送到目標進程的APC執行這些操作。但在.NET的實現過程中,它採用了更準時和高級的定時器實現方法。

正如研究人員從Timer()類的構造函數中看到的那樣,研究人員可以指定.NET回調方法和在定時器上提供的參數。不過其中的一個限制是,回調必須與TimerCallback委託匹配,具體的過程研究人員可以在下面看到。

定時器是系統常用的集之一,程序員可以根據自己的需求定製一個定時器類型,也可以使用.net內建的定時器類型。在.net中一共為程序員提供了3種定時器:

·System.Windows.Forms.Timer類型

·System.Threading.Timer類型

·System.Timers.Timer類型

概括來說,這3種類型都實現了定時的功能。程序員通常需要做的是為定時器設置一個間斷時間,設置定時器到時的處理方法,然後可以等待定時器不斷地定時和出發時間處理。

由此,我猜測此處使用的應該是System.Threading.Timer類,TimerCallback委託就是其中的建構函數,使用TimerCallback委託來指定由調用的方法Timer, 在創建計時器的線程中,此方法不會執行,它在系統提供一個單獨的線程池線程中執行。由於這種委託條件的限制,研究人員僅限於使用此簽名調用方法。在理想的情況下,研究人員可能希望純粹指定對Assembly.Load()的回調,將程序集的位元組數組作為狀態參數,從而確保惡意代碼執行。遺憾的是,委託不匹配,並且在載入程序集時自動運行代碼並不像在DllMain()函數中放置代碼那樣簡單,就像本機DLL的情況一樣。因此,研究人員需要製作一個經過簡化的封裝器(Wrapper)來處理內存中惡意程序集的載入和執行,然後對計時器進行清理和重新調度。

載入的實現

現在我們有了一個.NET timer原語,它允許我們定義回調,不過要實現載入,

還需要進行以下3步的操作:

1.研究人員需要自定義一些載入代碼,且這些代碼需要永久載入。因此,研究人員的辦法是儘可能使他們簡單化且無攻擊性,以便將其隱藏起來;

2.研究人員無法將程序集單個的進行卸載(具體原因,請點此查看),因此為了清理植入的設備中的具備全部惡意功能的程序集,研究人員需要將其載入到新的應用程序域(Appdomain)中,然後卸載,AppDomain類似於一個輕量級進程,它是 .net/mono 代碼運行時的一個邏輯容器;

3.研究人員需要找到一些方法來載入.NET載入程序集,然後調用一個方法來創建.NET定時器,以便將來載入研究人員用來測試的惡意程序集。

在本文中,「AssemblyLoader」就是研究人員自定義的最簡單的無害載入程序集,「DemoAssembly」將是一個PoC程序集,表示在實際使用中具有完整功能的惡意植入。

以下是一段C#代碼,涵蓋了上述第1點和第2點所需的大部分內容:

public class AssemblyLoaderProxy : MarshalByRefObject, IAssemblyLoader

{

public void Load(byte[] bytes)

{

var assembly = AppDomain.CurrentDomain.Load(bytes);

var type = assembly.GetType("DemoAssembly.DemoClass");

var method = type.GetMethod("HelloWorld");

var instance = Activator.CreateInstance(type, null);

Console.WriteLine("--- Executed from : ", AppDomain.CurrentDomain.FriendlyName, method.Invoke(instance, null));

}

}

public static int StartTimer(string gargoyleDllContentsInBase64)

{

Console.WriteLine("Start timer function called");

byte[] dllByteArray = Convert.FromBase64String(gargoyleDllContentsInBase64);

Timer t = new Timer(new TimerCallback(TimerProcAssemblyLoad), dllByteArray, 0, 0);

return 0;

}

private static void TimerProcAssemblyLoad(object state)

{

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

Console.WriteLine("Hello from timer!");

String appDomainName = "TemporaryApplicationDomain";

AppDomain applicationDomain = System.AppDomain.CreateDomain(appDomainName);

var assmblyLoaderType = typeof(AssemblyLoaderProxy);

var assemblyLoader = (IAssemblyLoader)applicationDomain.CreateInstanceFromAndUnwrap(assmblyLoaderType.Assembly.Location, assmblyLoaderType.FullName);

assemblyLoader.Load((byte[])state);

Console.WriteLine("Dynamic assembly has been loaded in new AppDomain " + appDomainName);

AppDomain.Unload(applicationDomain);

Console.WriteLine("New AppDomain has been unloaded");

Timer t = new Timer(new TimerCallback(TimerProcAssemblyLoad), state, 1000, 0);

}

這是一個相當少量的代碼,除了一些名稱和調試語句之外,可能還會進行惡意檢查,以防止它具有惡意屬性。如果使用更大的無害集並且將此代碼作為其中的一小部分,則尤其要進行惡意檢查。

這是通過定義一個回調函數來實現的,該函數將使用以下流程載入惡意程序集(本文中使用的是DemoAssembly):

1.從一個新AppDomain中的byte []數組載入DemoAssembly;

2.實例化DemoClass對象;

3.執行HelloWorld()方法;

4.卸載AppDomain來清理內存中的DemoAssembly;

5.重新設置定時器,以無限地重複該過程;

執行Assembly Loader(程序集載入)

為了實現以上提到的第3步,研究人員可以利用本機代碼中的COM來觸發載入程序集並調用StartTimer()方法,該方法將設置.NET定時器,然後定期在定時器上載入研究人員植入的惡意DemoAssembly。以下代碼段顯示了此過程的關鍵代碼部分:

// execute managed assembly

DWORD pReturnValue;

hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(

L"AssemblyLoader.dll",

L"AssemblyLoader",

L"StartTimer",

lpcwstr_base64_contents,

&pReturnValue);

這為研究人員提供了2個觸發載入器的選項,研究人員可以運行執行此代碼的本機應用程序,也可以將其作為本機DLL注入合法應用程序,然後立即卸載。最終的結果是,研究人員得到了一個.NET載入程序程序集,它看起來無法載入是無害的,但使用.NET可以在未來以固定的時間間隔在內存中載入功能齊全的. net植入程序。

如果研究人員將所有這些功能部分組合在一起,就會得到如下所示的結果。

然後,如果研究人員使用ProcessHacker之類的工具檢查載入的.NET程序集,將只能看到載入程序集,而不會看到臨時AppDomain或研究人員植入的惡意DemoAssembly的證據。

另外,如果研究人員使用像Get-ClrReflection.ps1這樣的工具,那麼研究人員將看不到惡意痕迹,因為研究人員的設置的載入程序集是通過硬碟載入的,而內存中的DemoAssembly絕對不可能在殺毒軟體掃描時載入。

如何防止通過.NET實現的Gargoyle

通過.NET方法實現的Gargoyle和Gargoyle的原始方法一樣,都是基於對某個時間點的內存掃描技術和一般內存取證的繞過。這意味著可以通過實時跟蹤解決方案檢查通過.NET方法實現的Gargoyle活動。在《如何檢測惡意使用的.NET》中,研究人員介紹了如何跟蹤.NET程序集的載入活動的具體方法。例如,如果我們使用研究人員發布的PoC ETW跟蹤工具只跟蹤高風險模塊載荷,就可以清楚地看到惡意DemoAssembly的持續重新載入過程。

不過,實時跟蹤解決來預防此惡意活動,仍然存在一些問題,比如攻擊者可以在時間點上做文章。所以一個比較好的預防方法就是.NET確實留下了一些可以使用WinDBG的SOS調試擴展來獲取的活動痕迹。使用DumpDomain命令,可以顯示了大量「TemporaryApplicationDomain」應用程序域和對正在載入的動態模塊的引用的殘留證據。

然而,還有更直接的方法,就是是否有可能在系統範圍內分析.NET定時器以識別潛在的可疑回調。 Microsoft實際上就為.NET提供了一個名為ClrMD的內存診斷庫,我之前使用過這個庫,這是我的第一個調用埠。

其實,Criteo Labs實際上已經發布了一篇關於如何使用ClrMD(出於非安全目的)的文章,包括發布代碼的細節。不過,研究人員通過進行代碼調整,自定義了一個工具,可以對定時器回調進行觀察,並識別潛在的可疑實例。

如果研究人員在運行Visual Studio的示例系統上運行此工具就可以找到Visual Studio相關進程中存在的各種定時器回調示例。

但是,這些回調示例都非常相似,並且在系統或Microsoft名稱空間中都存在這些回調,這意味著利用這些名稱空間中的回調來發現惡意用例是可能的。不過如上所述,在實際檢測中,會遇到許多約束條件,這會導致研究人員根據實際情況,自定義代碼來檢查各種定時器回調。

為了應付安全人員的這個檢查,攻擊者可能會對這種回調進行監測,為此,研究人員要做的就是讓回調看起來儘可能合法合理,本文的所有測試代碼均可在這裡獲得。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

在Microsoft Edge中實現DOM樹

TAG:嘶吼RoarTalk |