從某惡意軟體Dropper樣本,探尋分析.NET惡意軟體的方法
概述
作為安全專家,我們更喜歡對新型惡意軟體進行分析。每當有安全團隊釋出新型攻擊分析報告時,我們都會第一時間閱讀。從這些報告中,我們發現了一個規律,即使是能力最強的攻擊者,也會傾向於使用各種OS工具來執行攻擊,而不會傾向選擇使用某個API。
針對攻擊者,他們不一定需要掌握太多的開發技術,也不一定需要親自編寫惡意軟體才能實現惡意的目的。一個比較恰當的例子就是Fauxpersky惡意軟體,該惡意軟體是用AutoHotKey編寫而成的,這是一個用於自動執行任務的合法工具,攻擊者利用了該工具的特點,將其改裝成了一個能夠有效竊取使用者憑據的惡意軟體。
可能與大多數人的認知相反,安全研究人員並沒有將大量的事件投入到複雜的APT(高階持續威脅)的研究之中。實際上,安全研究人員更多會研究比較常見、沒有那麼高技術含量、卻能產生較大影響的威脅。
接下來,讓我們言歸正傳,首先簡要分析一下.NET。.NET是微軟在21世紀初推出的一個程式設計框架,其目標是希望讓程式設計變得更容易。使用.NET時,即使開發者在編寫一個簡單的程式,也可以使用C語言分配和釋放記憶體,或者使用C++編寫非常複雜的程式碼。在.NET語言中,最受歡迎的是C#語言,這是一個面向物件的現代通用語言,並且已經被廣泛使用。從底層來看,C#對Windows、Linux、macOS的支援都非常好,開發過程便捷,語法較為流暢,再借助Visual Studio的自動完成功能和Windows API呼叫功能,該語言的編寫能變得更加輕鬆。此外,在編譯專案時,C#將會被編譯為EXE或DLL。
針對.NET的逆向工程
在聽了上面的這些介紹後,你可能會提出疑問,“如果使用.NET程式設計比使用C或C++更容易,那麼如果我想要在編譯後得到二進位制的PE檔案,會有什麼問題嗎?”問題在於,得到的檔案不是真正的原始可執行檔案,如果我們檢視該檔案,將無法找到X86彙編程式碼,原因在於.NET的工作方式不同。
圖片來源: ofollow,noindex">http://www.developingthefuture.net/compilation-process-and-jit-compiler/
在編譯.NET專案時,它實際上被編譯成MSIL語言( Microsoft Intermediate Language )。而我們在使用即時編譯器(JIT)時,實際上就是對程式碼進行了編譯。如果大家有興趣瞭解更多關於.NET編譯的資訊,建議參考 Microsoft的相關文件 。在這裡,我們可以將MSIL類比成組合語言,只不過它是在更高的層次上。
那麼,為什麼使用.NET進行編譯會讓分析工作變得如此繁瑣呢?我們來看看使用C/C++編寫的可執行檔案與使用.NET編寫的可執行檔案二者組合語言的差異。當我們對一個“普通”(比如用C語言編寫的)可執行檔案進行逆向時,反彙編程式將會展示出x86/64彙編程式碼。但是,對於使用.NET編譯的可執行檔案,“彙編程式碼”也能展現,但它卻是截然不同的。將程式碼編譯成MSIL,意味著程式碼中會包含很多方便反編譯的元資料。事實上,如果想要成功對.NET的程式碼進行逆向,我們需要的只是一個.NET反編譯器和一點點耐心。
最近,我在用於測試惡意軟體樣本的主機上發現了一些奇怪的自動執行檔案。我很好奇,這些自動執行檔案是如何產生的。在追溯這臺主機上的所有檔案活動時,我注意到Patient Zero是在我發現Autoruns前幾分鐘在這臺機器上執行的惡意軟體樣本。因此,我查看了該樣本的原始可執行檔案,並發現這個檔案是從.NET專案編譯而成的。這就意味著我們需要使用一組完全不同的工具來對它進行分析。我們不能再使用IDA Pro這樣的反彙編程式,而是需要一個.NET反彙編程式。我最喜歡的工具是 dnSpy ,這是一個很棒的偵錯程式,並且具有良好的使用者介面,這個工具是基於另一個優秀的專案 ILSpy 開發而成的。
通過使用dnSpy反編譯器,我們能看到明確的程式碼,這一程式碼非常接近於惡意軟體的原始碼。儘管一些變數、物件和類可能與實際名稱不同,但顯示的程式碼依然非常清晰。
反混淆處理
但是,當我們檢視反編譯程式碼以及類和函式的名稱時,我們發現了一些奇怪的地方,看上去該惡意軟體進行了混淆:
混淆後的名稱空間:
混淆並加殼的程式碼,看上去沒有任何實際意義:
由於.NET程式可以很容易的反編譯,因此許多開發人員(和惡意軟體作者)會使用各種混淆方法,使逆向工程變得更加複雜。幸運的是,有一些工具能幫助我們進行反混淆處理。
為了簡化反混淆的過程,我們使用了一個名為“ Detect It Easy ”的工具。我們將檔案拖動到相應的視窗,就能夠看到關於該檔案的一些重要資訊,其中包括其使用的混淆器的資訊。經過分析,我們所研究的惡意軟體使用了 SmartAssembly混淆工具 。
在我們知道所使用的混淆工具之後,我們就開始尋找針對它的反混淆方法。在這裡,我建議使用 de4dot ,這是一個開源的.NET反混淆器和去殼器。
在執行de4dot之後,我們看到惡意檔案已經被去殼,並進行了反混淆:
現在,我們終於得到了這個神祕的惡意軟體樣本。
使用dnSpy開啟樣本後,我們發現這回名稱空間變得不一樣了,現在是有意義的:
動態分析
首先,這些並不是原始的名稱空間名稱。為了保證易讀性,反混淆器自動生成了這些名稱空間和其他物件、符號的通用名稱,我們無從得知作者指定的原始名稱。
這個Dropper最初是使用Visual Basic編寫的,但dnSpy允許我們將MSIL程式碼轉回VB或C#,我為了讓語法更容易理解,所以選擇了C#。在後續深入分析的過程中,我們看到的所有程式碼截圖都是由MSIL轉換而來的C#,儘管該MSIL最初是由VB編寫的程式碼編譯而成。
當檢視“-”名稱空間時,我們可以看到一個名為“Class 14”的類(由反混淆器命名),以及其下面的許多類方法:
通過檢視Class14中的程式碼,我們可以清楚地看到一些程式碼嘗試使用Interaction.Environ呼叫列舉環境變數:
我們可以清楚的看到,環境變數實際上是兩個經Base64編碼的字串連線。當我試圖進行Base64解碼時,我們得到一些無法理解的模糊內容:
在檢視程式碼的其他部分時,我們發現它呼叫了一些加密庫和函式:
該樣本中包含著許多面條程式碼(Spaghetti Code,指控制結構複雜、混亂、難以理解的程式碼),這些程式碼大量引用其他類,使得研究人員難以對其進行靜態分析。在這時,很容易發現Base64解碼後的內容也是加密的,因此我們有兩種方案可以選擇:
1、靜態分析:快速編寫一些程式碼,並對程式碼中使用的所有金鑰進行解密。
2、動態分析:使用偵錯程式逐步執行程式,並觀察該程式如何對值進行解密和反模糊處理。
我果斷選擇了第二種方案,主要是因為其效率更高,因為程式碼已經非常清晰,而且我們逐步執行的並不是討厭的彙編程式碼。
dnSpy也可以作為偵錯程式使用,只要點選相應的行並按下F9,就能輕鬆在程式碼中放置斷點。
我在第1081行放置了一個斷點,這是smethod_56()函式的開始位置。如圖中所示,該函式接受一個引數string_0(同樣,字串變數的名稱也是由反編譯器生成)。
string_0是一個Base64編碼的值。
當我們按F10跳過當前子函式(Step Over)時,可以看到各種物件中都填充著資料。
這樣一來,理解smethod_56()的功能就變得很簡單。該函式從3個不同的變數中建立3位元組的陣列:
S2 (1B2c3D4e5F6g7H8) S (cffffffffffffffffff) String_0 (Sk1N1W/kLlYPS5rz2GRFew==)
然後,會例項化rfc2898DeriveBytes類,並將這三個引數傳遞給它:
Password:上面截圖中的“nia”,作為密碼; Bytes2:由s變數產生的位元組陣列,作為加鹽(Salt); 2:派生金鑰的迭代次數。
然後,該函式建立另一個位元組陣列bytes3,其中包含剛剛生成的金鑰。
該函式將繼續例項化RijndaelManaged類,並呼叫CreateDecryptor函式,使用bytes3和bytes作為其引數,這兩個引數分別是金鑰和IV。
當我們繼續單步執行(Step Over)時,可以發現有幾個經過混淆和加密的字串是通過呼叫smethod_0()來實現解密的,其值需要反混淆和解密:
對每個值進行反模糊處理和解密後,我們可以在下面的變數窗格中看到它的解密值:
在這裡,text2包含可執行檔案(當前正在執行)自身的完整路徑。
我們進一步深入分析程式碼,發現其他的一些字串也被反混淆和解密,這些字串能夠表明該惡意軟體的更多行為特徵。
正如我們在這段程式碼中所看到的,幾個混淆/加密的字串由一個不同的(但非常相似的)函式負責反混淆和解密,也就是smethod_76()。請注意顯示為中文的不同加密金鑰:
如果我們再次檢視變數窗格,可以發現有更多路徑被解密。我們可以清楚地看到:sourceFileName代表msbuild.exe的原始路徑,而destFileName包含使用者臨時目錄中名為svhost.exe的檔案的路徑:
如果回到程式碼,可以看到實際上有一個檔案複製的操作:
這意味著,臨時目錄中的svhost.exe實際上就是MSBuild.exe。在分析過程中,我們實際上可以訪問該路徑,並對這個檔案進行分析:
Svchost(實際上是msbuild)將用於建立一個非常小的二進位制檔案,它會修改登錄檔值,以建立永續性。
惡意軟體特性:通過注入終止程序
這個Dropper的一個特性是能夠終止研究工具。我在執行Procmon的過程中發現了這一點。當偵錯程式在class_14中執行第2094行時,我的所有研究工具都停止了工作。這樣一來,我開始好奇:這裡究竟發生了什麼?惡意軟體是如何使這些工具關閉的?
通常,當惡意軟體想要關閉特定目標程式時,它會根據一個應用程式列表(列表中可能會有Wireshark、sysinternals等工具),每隔n秒輪詢一次程序列表,如果存在正在執行的特定程序,就會呼叫TerminateProcess()實現對程序的關閉。
而在這裡,這個惡意軟體Dropper則截然不同。我沒有在程式碼中發現任何對TerminateProcess()的直接或間接呼叫,這裡所說的間接呼叫是通過GetProcAddress()實現。
在除錯惡意軟體的構成中,我注意到ProcessHacker退出,並且在惡意可執行檔案終止之前無法再次開啟。當然,這似乎是惡意軟體的某種機制,用於防止安全研究人員對其進行動態分析。
其常用的方法是使用Windows API的CreateToolHelp32Snapshot()函式。這個函式將獲取程序的“快照”,以及堆、模組和其他資訊。
對於我們的樣本,它會獲取所有正在執行程序的列表,並在每個程序上呼叫CreateToolHelp32Snapshot(),然後將結果儲存在陣列中。這個陣列的每個成員都儲存著正在執行的程序的相關資訊。
一旦檢測到安全研究的相關程序,Dropper就會解密下面的程式碼,將其注入到相應程序並執行,通過呼叫NtTerminateProcess()來阻止安全研究工作。
總結
在研究過程中,我們以這一.NET惡意軟體Dropper為例,詳細講解了逆向工程的方法及所需要的工具,並發現了該惡意軟體的一個重要特性。
針對.NET惡意軟體,只需要突破反編譯的瓶頸,隨後的分析過程也就變得順理成章了。
此後,我們還將對這一Dropper投放的惡意軟體進行詳細分析,並嘗試通過靜態分析得到該惡意軟體的全部功能。