惡意軟件開發者通常會使用各種技巧使分析工作變得更加困難。這些技巧包括使逆向工程複雜化的混淆技巧、逃避沙箱的反沙箱技巧、繞過靜態檢測的打包技巧等。多年來,各種惡意軟件在野外使用的無數欺騙手段都記錄了這一點。然而,儘管有許多可用的技巧,但在典型的惡意軟件中很少實現這些技巧。
本文就以Roshtyak 後門為例介紹惡意軟件的自保護、殺軟逃逸技巧。 Raspberry Robin於2021年9月首次被發現,通過受感染的USB設備傳播。本文的主題是一個我們稱為Roshtyak 的後門,它不是典型的惡意軟件。 Roshtyak 反分析設計很多。有些是眾所周知的,有些是我們從未見過的。從技巧角度來看,Roshtyak 的自我保護非常有趣。 Roshtyak 屬於我們見過的反分析最成功的的惡意軟件之一。我們希望通過發布我們對惡意軟件及其保護技巧的研究和分析,幫助其他研究人員識別和應對類似的技巧,並強化他們的分析環境,使他們對所描述的繞過技巧加強防護。
Roshtyak 是Raspberry Robin 使用的DLL 後門,一種通過受感染的可移動驅動器傳播的蠕蟲,Raspberry Robin今年非常流行。
Red Canary 的研究人員於2022 年5 月發布了對Raspberry Robin 的首次分析。 6 月,賽門鐵克發布了一份報告,描述了一次挖礦/剪貼板劫持操作,據報導,這讓攻擊者至少賺了170萬美元。賽門鐵克沒有將惡意操作與Raspberry Robin 聯繫起來。儘管如此,根據我們的分析,其幕後組織是Raspberry Robin。該評估基於我們分析中觀察到的CC 重疊、以及很多與其他惡意軟件相似性。 Cybereason、微軟和思科在2022 年7 月/8 月發布了進一步的報告。微軟報告說,Raspberry Robin 感染導致了DEV-0243(又名Evil Corp)勒索行為。雖然我們無法確認此連接。儘管如此,我們仍然有理由相信挖礦軟件有效載荷並不是Raspberry Robin 感染被貨幣化的唯一方式。最近的其他報導也暗示了Raspberry Robin 和Evil Corp 之間可能存在的聯繫。
儘管發表瞭如此多的報導,但關於Raspberry Robin 仍有許多未知數。惡意軟件背後的最終目標是什麼?誰負責運營Raspberry Robin ?它是如何變得如此流行的?不幸的是,我們沒有所有這些問題的答案。但是,我們可以回答我們多次看到的一個重要問題:在高度混淆的DLL(或我們稱之為Roshtyak)中隱藏了哪些功能?為了回答這個問題,我們對Roshtyak 示例進行了完全逆向工程。
Roshtyak介紹Roshtyak 包含多達14 層保護層,每層都經過高度混淆並服務於特定目的。一些工件表明這些層最初是PE 文件,但被轉換為只有以前的層知道如何解密和加載的自定義加密結構。許多反調試器、反沙盒、反虛擬機和反仿真器檢查遍布各個層。如果其中一項檢查成功檢測到分析環境,那麼將採取四個操作中的一個。
惡意軟件調用自己的TerminateProcess,以避免顯示任何進一步的惡意行為,並保持後續層的加密。
Roshtyak是故意撞車的。這與終止自身俱有相同的效果,但由於Roshtyak的混淆特性,可能無法立即清楚崩潰是有意的還是因為一個漏洞。
惡意軟件故意進入無限循環。由於循環本身位於混淆代碼中並且跨越數千條指令,因此可能很難確定循環是否在為攻擊做準備。
最有趣的情況是惡意軟件通過解包和加載虛假有效負載來繞過成功檢查,這發生在第八層,它加載了幾十個反分析檢查。每個檢查的結果都用於修改全局變量的值。在第8 層的數據段加密了兩個有效載荷。真正的第9 層和偽造的有效載荷,只有在執行所有檢查後,全局變量與預期值匹配時,才會解密真正的第九層。如果檢測到分析環境,則全局變量的值將與預期值不同,從而導致Roshtyak 解包並執行虛假有效負載。
Roshtyak 的混淆導致即使是相對簡單的函數也變得很大。如果想在合理的時間範圍內對其進行逆向工程,就需要一些自定義的反混淆工具。
虛假有效載荷是一個BroAssist(又名BrowserAssistant)廣告軟件示例。我們認為,這個虛假的有效載荷旨在誤導惡意軟件分析師認為該示例沒有實際情況那麼有趣。當逆向工程師專注於快速解包示例時,整個示例可能看起來“只是”一個混淆的廣告軟件(而且是一個非常古老的廣告軟件),這可能會導致分析師對深入挖掘失去興趣。事實證明,這些虛假的有效載荷惡作劇可能非常有效。從下面的屏幕截圖中可以看出,它欺騙了至少一名研究人員,該研究人員漏洞地將Raspberry Robin 蠕蟲歸因於虛假的BrowserAssistant 有效載荷。
這表明,鑑於Roshtyak 的設計和復雜性,犯這樣的漏洞是多麼容易。
複雜的混淆技巧現在讓我們直接詳細介紹Roshtyak 採用的一些有趣的規避技巧。
段寄存器在執行的早期,Roshtyak 更喜歡使用不需要調用任何導入函數的檢查。如果其中一項檢查成功,則示例可以安靜地退出,而不會生成任何可疑的API 調用。下面是Roshtyak 檢查gs 段寄存器行為的示例。該檢查被設計為隱形的,周圍的垃圾指令使其容易被忽視。
只有帶下劃線的指令是有用的
此檢查背後的第一個想法是檢測單個執行。在上面的代碼片段之前,cx 的值被初始化為2。在彈出ecx指令之後,Roshtyak檢查cx是否仍然等於2。這將是預期的行為,因為該值應該在正常情況下通過堆棧和gs 寄存器傳播。但是,單個事件會重置gs 選擇器的值,這會導致最後彈出一個不同的值到ecx 中。
但這項檢查還有更多內容。作為上述兩個push/pop 對的副作用,gs 的值暫時更改為2。在此檢查之後,Roshtyak 進入一個循環,計算迭代次數,直到gs 的值不再是2。 gs 選擇器在線程上下文切換後也會被重置,因此循環本質上是計算迭代次數,直到發生上下文切換。 Roshtyak 多次重複此過程,求出結果的平均值,並檢查它是否屬於裸機執行環境的合理範圍。如果示例在虛擬機管理程序或模擬器中運行,則平均迭代次數可能會超出此範圍,這使Roshtyak 能夠檢測到不需要的執行環境。
Roshtyak 還檢查cs 段寄存器的值是0x1b 還是0x23。此時,0x1b 是在原生x86 Windows 上運行時的預期值,而0x23 是在WoW64 中運行時的預期值。
通過隨機ntdll gadget注入APCRoshtyak從獨立的進程中執行一些功能。例如,當它與它的CC服務器通信時,它會生成一個新的看似無害的進程,如regsvr32.exe。通過使用共享段,它將通信模塊注入到新進程的地址空間中。被注入的模塊通過APC注入執行,使用的是NtQueueApcThreadEx。
有趣的是,ApcRoutine 參數(標記要安排執行的目標例程)並不指向注入模塊的入口點。相反,它指向ntdll 中看似隨機的地址。仔細一看,我們發現這個地址不是隨機選擇的,而是Roshtyak 掃描了ntdll 的代碼段來查找pop r32;retgadget(不包括pop,因為旋轉堆棧是不可取的),並隨機選擇一個作為ApcRoutine。
隨機彈出r32; ret gadget 用作APC 注入的入口點
查看ApcRoutine 的調用約定可以理解發生了什麼。 pop 指令使堆棧指針指向NtQueueApcThreadEx 的SystemArgument1 參數,因此ret 指令有效地跳轉到SystemArgument1 指向的任何位置。這意味著通過濫用這個gadget,Roshtyak 可以將SystemArgument1 作為APC 注入的入口點。這混淆了控制流並使NtQueueApcThreadEx 調用看起來更合法。如果有人掛鉤此函數並檢查ApcRoutine 參數,它指向ntdll 代碼段的事實可能足以讓他們相信該調用不是惡意的。
檢查組合寫入內存的讀/寫性能在接下來的檢查中,Roshtyak 分配一個帶有PAGE_WRITECOMBINE 標誌的大內存緩衝區。該標誌應該修改緩存行為以優化順序寫入性能,不過這是以讀取性能和可能的內存排序為代價的。 Roshtyak 使用它來檢測它是否在物理設備上運行。它進行了一項實驗,首先寫入分配的緩衝區,然後從分配的緩衝區中讀取,同時使用一個單獨的線程作為計數器來測量讀寫性能。該實驗重複32 次,只有在大多數情況下寫性能至少是讀性能的6倍時,才會通過檢查。如果檢查失敗,Roshtyak就會故意選擇漏洞的RC4密鑰,從而導致無法正確地解密下一層。
隱藏shellcode有趣的是,注入的shellcode 也被隱藏了。當Roshtyak 準備代碼注入時,它首先創建一個大段並將其作為PAGE_READWRITE 映射到當前進程中。然後,它用隨機數據填充該段,並將shellcode 放置在隨機數據內的隨機偏移處。由於shellcode 只是一個相對較小的加載器,後面跟著看起來是隨機的打包數據,所以整個段看起來像隨機數據。
共享區段內字節的直方圖。注意,它看起來幾乎是隨機的,最可疑的符號是空字節的輕微過度表示。
然後該段從當前進程中取消映射,並映射到目標進程中,在目標進程中使用上述APC 注入技巧執行該段。添加隨機數據是為了隱藏shellcode 的存在。僅從目標進程的內存轉儲來看,該段可能看起來充滿了隨機數據並且不包含任何有效的可執行代碼。即使有人懷疑該段中間某處的實際有效代碼,也不容易找到它的確切位置。
共享段中shellcode 的開始。可能很難確定確切的起始地址,因為它通常是從奇數bt指令開始的。
Ret2Kernel32Roshtyak特別注意清理自己的攻擊痕跡。每當不再需要某個字符串或某段內存時,Roshtyak 就會清除或釋放它,以試圖清除盡可能多的證據。 Roshtyak 的圖層也是如此。每當一層完成其工作時,它就會在將執行傳遞到下一層之前進行自我釋放。但是,該層不能簡單直接地自我釋放。如果它在當前正在執行的內存區域上調用VirtualFree,整個進程將會崩潰。
因此,Roshtyak 通過在層轉換期間執行的ROP 鏈來釋放層以避免此問題。當一個層即將退出時,它會在堆棧上構造一個ROP 鏈並返回其中。下面可以看到這樣一個ROP 鏈的示例。該鏈首先返回VirtualFree 和UnmapViewOfFile 以釋放上一層的內存。然後,它返回到下一層。下一層的返回地址設置為RtlExitUserThread,以保障執行安全。
一個簡單的ROP 鏈,由VirtualFree - UnmapViewOfFile - next layer - RtlExitUserThread 組成。
MulDiv漏洞MulDiv是一個由kernel32.dll導出的函數,它接受三個32位有符號整數作為參數。它將前兩個參數相乘,將乘法結果除以第三個參數,並返回最後的結果四捨五入到最接近的整數。雖然這可能看起來是一個足夠簡單的函數,但在微軟的實現中有一個古老的符號擴展漏洞。這個漏洞現在被認為是一個功能,可能永遠不會被修復。
Roshtyak 知道該漏洞並通過調用MulDiv(1,0x80000000,0x80000000) 來測試它的存在。在真實的Windows 設備上,這會觸發漏洞並且MulDiv 漏洞地返回2,即使正確的返回值應該是1,因為(1 * -2147483648)/-2147483648=1。這允許Roshtyak 檢測不復制漏洞的模擬器.例如,這成功檢測到Wine,有趣的是,它包含一個不同的漏洞,這使得上述調用返回0。
篡改存儲在堆棧中的返回地址還有一些用來混淆函數調用的技巧。如上一節所示,Roshtyak喜歡使用ret指令調用函數。下一個技巧與此類似,因為它也操作堆棧,因此可以使用ret 指令跳轉到所需的地址。
為了實現這一點,Roshtyak 掃描當前線程的堆棧,尋找指向前一層代碼段的指針,與其他層不同,這一層沒有使用ROP 鏈技巧釋放。它用它想要調用的地址替換所有這些指針。然後它讓代碼多次返回,直到ret 指令遇到一個被劫持的指針,將執行重定向到所需的地址。