簡介

Rhadamanthys,一種自 2022 年以來持續且複雜的資訊竊取軟體,對惡意軟體分析師構成了艱鉅的挑戰。它的原生 loader 因其 多層次的混淆技術 而特別引人注目,這些技術專門設計用於阻礙靜態分析和自動化逆向工程工具 [1] 。本報告將深入技術檢視 Rhadamanthys loader 所採用的混淆機制,並詳述成功用於重構其原始控制流的靜態方法論。重點嚴格放在這些反分析措施的技術實現與規避。

頂級惡意軟體分析:Rhadamanthys Loader 逆向工程如何還原被遮蔽的函式邏輯? | 資訊安全新聞

Rhadamanthys Loader 中的分層混淆技術

該 loader 採用多管齊下的方法來模糊其邏輯,使二進位檔看起來像是一堆看似不相關的程式碼區塊。這種技術組合比任何單一方法都更有效,能有效打破大多數反組譯器和反編譯器所做的假設。

1. 控制流扁平化 (Control Flow Flattening, CFF)

混淆的基礎是經典的 控制流扁平化 (CFF) 實作。在一般的程式中,基本區塊透過條件或非條件跳轉直接連結。然而,CFF 移除了這些直接連結,代之以一個中心調度器 (dispatcher) 來管理執行順序。

調度器透過一個狀態變數運作,在所分析的樣本中,該狀態變數儲存在 ebx 暫存器中 [1] 。每個基本區塊在執行完其預期操作後,會計算 下一個 區塊的狀態值,然後無條件跳轉回調度器。接著,調度器對 ebx 值執行二元搜尋或類似大型 switch 的操作,以確定要執行的正確下一個區塊。這個過程由調度器的以下組合語言片段所示:

.text:0040A5F0 loc_40A5F0:               ; CODE XREF: sub_40A560+68↑j
.text:0040A5F0                 cmp     ebx, 0BB2D365h  ; Compare state variable EBX with a high-entropy block ID
.text:0040A5F6                 mov     eax, edx       ; Prepare EAX for jump target calculation
.text:0040A5F8                 lea     edi, off_48B8A4
.text:0040A5FE                 cmovl   eax, edi       ; Conditional move based on comparison result
.text:0040A601                 mov     eax, [eax]     ; Load offset from memory (part of jump target obfuscation)
.text:0040A603                 add     eax, esi       ; Add constant key register ESI
.text:0040A605                 jmp     eax            ; Indirect jump to the next block

使用高熵數值,例如 0x0BB2D365 ,作為狀態值,進一步複雜化了手動分析和模式識別 [1] 。結果是一個具有數千條指令和數百個區塊的函式,其中邏輯流完全被遮蔽。

2. Jump Target Obfuscation

CFF 透過 Jump Target Obfuscation 得到進一步強化。跳轉回調度器的目標位址不是一個簡單的 jmp [register] ,而是動態計算出來的。此計算涉及記憶體載入和與一個 key 暫存器相加,在本例中具體是 esi [1]

用於間接跳轉的指令序列為:

.text:0040A5D0                 lea     eax, off_48B850  ; Load the address of the offset value
.text:0040A5DC                 cmovz   eax, edi         ; Conditional move for flow control
.text:0040A5DF                 mov     eax, [eax]       ; Load offset from memory (Memory Value)
.text:0040A5E1                 add     eax, esi         ; Add key register (ESI)
.text:0040A5E3                 jmp     eax              ; Jump to calculated address (Target Address)

這種動態計算是擊敗自動化控制流圖 (CFG) 生成的主要機制。由於跳轉目的地無法靜態解析,反組譯器看到的是一堆孤立的區塊,沒有父項或子項,使 CFG 分析變得無用 [1]

3. Constant Obfuscation

為了防止分析師輕易識別 key 值,所有程式 constants 也被混淆。這項技術涉及將 constant 隱藏在記憶體載入和算術運算之後。被混淆的 constant 通常用於與函式的回傳值 ( eax ) 進行比較 [1]

call    sub_40F830
mov     ecx, dword_48AE7C   ; Load memory address for the first part of the constant
mov     edx, 0C4D77A40h     ; Immediate value for the second part of the constant
add     ecx, edx            ; Calculate the constant value (Constant = [Memory] + Immediate)
cmp     eax, ecx            ; Compare function result EAX against the calculated constant

這裡的記憶體載入操作至關重要。如果 constant 只是兩個 immediate value 相加的結果,反編譯器內建的優化會自動折疊該計算。透過引入記憶體載入,即使記憶體是唯讀的,大多數反編譯器也無法靜態解析該 constant [1]

解混淆方法論:靜態 Jump Resolution

由於混淆函式龐大的程式碼和複雜性,使用 Triton 和 angr 等符號執行工具進行解混淆的初步嘗試受到了 path explosion 的阻礙 [1] 。這使得必須轉向更具針對性的靜態分析方法,該方法依賴於對混淆器設計的一個關鍵觀察。

Constant Key Register 洞察

關鍵洞察是,用於 jump target calculation 的暫存器 esi 在單個混淆函式的整個執行過程中是 constant [1] 。這將動態跳轉計算簡化為靜態計算。目標位址始終計算為:

$$ \text{Target Address} = \text{Memory Value} + \text{ESI} $$

其中 $\text{Memory Value}$ 是從資料區段載入的 offset (例如, off_48B850 ),$\text{ESI}$ 是該函式的 constant key 暫存器值。

控制流重構程序

靜態解混淆程序專注於解析這些間接跳轉並重構原始 CFG。

  1. 函式邊界識別: 混淆器在每個函式的基礎上應用其邏輯。混淆函式邊界的一個可靠指標是其末端存在單個 ret 指令 [1]
  2. ESI 值確定: 必須確定 esi constant 值。這可以透過追蹤函式進入點的暫存器值或觀察其初始化來完成。
  3. 跳轉模式匹配: 使用解混淆腳本掃描函式的程式碼,尋找特定的 jump target obfuscation 模式 (例如, mov eax, [eax] , add eax, esi , jmp eax )。
  4. 目標計算和修補: 對於每個匹配的跳轉模式,腳本執行靜態計算:它從指令中指定的記憶體位址讀取 offset,並加上已知的 esi 值以找到真正的目的地。然後用直接跳轉 ( jmp Target Address ) 替換間接跳轉,有效地「解扁平化」控制流。

這種靜態解析技術非常有效,因為它利用了混淆器的一個設計限制:需要一個 constant key 才能可靠地計算跳轉目標。

控制流的架構視覺化

從混淆控制流到重構、解扁平化控制流的轉換可以使用狀態機圖來視覺化。

混淆控制流 (CFF)

CFF 結構為所有基本區塊引入了一個由調度器調節的單一進入點和退出點。圖中突顯了間接跳轉的複雜性。

graph TD     A[Function Entry] --> B{Dispatcher: cmp ebx, ...};     B --> C{Block 1: Work};     B --> D{Block 2: Work};     B --> E{Block N: Work};     C --> F[Set EBX for next block];     D --> G[Set EBX for next block];     E --> H[Set EBX for next block];     F --> I["Indirect Jump: jmp [mem] + ESI"];     G --> I;     H --> I;     I --> B;     B --> J[Function Exit: ret];

重構控制流 (解扁平化)

經過靜態 jump resolution 和修補後,邏輯流得到恢復,從而可以進行傳統的 CFG 分析。調度器和間接跳轉被消除。

graph TD     A[Function Entry] --> C{Block 1: Work};     C --> D{Block 2: Work};     D --> E{Block N: Work};     E --> J[Function Exit: ret];

惡意軟體規避的相關環境

複雜混淆的使用是惡意軟體開發中持續的趨勢。在 Rhadamanthys 中看到的技術是對自動化分析工具日益複雜的回應。其潛在目標——規避偵測和分析——在不同的惡意軟體家族和平台中保持一致。

例如,對 PupkinStealer [2] 的分析展示了 .NET 生態系統中類似的努力,其中使用進階混淆來保護資訊竊取軟體的核心邏輯。雖然 Rhadamanthys 採用原生程式碼混淆,但目標是相同的。儘管 .NET 惡意軟體的混淆在實作上有所不同 (例如,操縱 Intermediate Language),但其目的與阻礙自動化分析是相同的。此外,對 CoffeeLoader [3] 的分析突顯了使用進階規避技術,例如 call stack spoofing,這與 Rhadamanthys 中的 jump target obfuscation 一樣,旨在打破動態分析和沙箱環境的假設。這些例子強調了針對性的解混淆方法論的必要性,這些方法論可以解決現代惡意軟體 loader 特有的、通常是分層的反分析功能。

Deobfuscation Script Logic

靜態 jump resolution 的實際實作通常是透過一個與反組譯器 API (例如,IDA Pro 的 IDAPython) 介面的腳本來完成的。該腳本必須能夠:1) 識別指令模式,2) 讀取指定位址的記憶體值,以及 3) 計算最終目標。以下 pseudocode 概述了解析和修補混淆跳轉的核心邏輯。

  1. # Deobfuscation Script: Core Jump Resolution Logic
  2. # This function is called for every basic block ending with the obfuscated jump.
  3. def resolve_and_patch_jump(block_address, esi_value):
  4. """
  5. Resolves the indirect jump target and patches the instruction.
  6. :param block_address: The starting address of the basic block.
  7. :param esi_value: The constant ESI key for the current function.
  8. """
  9. # 1. Find the last instruction in the block (the 'jmp eax' instruction)
  10. last_instruction = get_last_instruction(block_address)
  11. # Check if the instruction matches the pattern:
  12. # mov eax, [eax]
  13. # add eax, esi
  14. # jmp eax
  15. if not is_obfuscated_jump_pattern(last_instruction):
  16. return False
  17. # 2. Trace back to find the memory address being loaded.
  18. # This requires analyzing the 'lea eax, off_XXXXXX' instruction
  19. # and any preceding conditional moves (cmov).
  20. memory_address = trace_memory_load_address(last_instruction)
  21. if memory_address is None:
  22. print("Error: Could not trace memory load address.")
  23. return False
  24. # 3. Read the offset value from the data section.
  25. # This is the value stored at the memory_address.
  26. offset_value = read_memory_dword(memory_address)
  27. # 4. Calculate the final target address.
  28. # Target Address = Memory Value + ESI
  29. target_address = offset_value + esi_value
  30. # 5. Patch the jump instruction.
  31. # Replace the three-instruction sequence with a single direct jump.
  32. # e.g., replace 'mov/add/jmp' with 'jmp target_address'
  33. patch_code(last_instruction.address, f"jmp 0x{target_address:X}")
  34. print(f"Patched jump at 0x{last_instruction.address:X} to 0x{target_address:X}")
  35. return True
  36. # The main deobfuscation loop would iterate through all functions,
  37. # determine the ESI key, and call resolve_and_patch_jump for all blocks

這種基於腳本的方法是一種高效且可擴展的方法,可以擊敗這種特定類型的分層混淆,將難以處理的二進位檔轉換為可由自動化工具和人類分析師有效分析的檔案。

結論

Rhadamanthys loader 代表了惡意軟體規避的重大進步,採用了結合 Control Flow Flattening、動態 Jump Target Obfuscation 和 Constant Obfuscation 的分層混淆方案。其解混淆的關鍵在於認識到跳轉計算依賴於一個 constant key 暫存器 ( esi )。透過利用此限制,可以實作靜態分析方法論,以可靠地解析間接跳轉並重構原始控制流圖。這次深入的技術探討為分析師面對類似複雜的反分析技術時提供了藍圖,強調深入了解混淆器的設計是最有效的對策。