摘要
端點偵測與回應(Endpoint Detection and Response, EDR)系統越來越依賴複雜的遙測技術(Telemetry),例如呼叫堆疊分析,來偵測惡意的行為,特別是從 unbacked memory 執行程式碼的行為 [1]。本文探討了一種規避 EDR 呼叫堆疊特徵碼的新穎技術,具體來說是規避 Elastic Security 所實作的特徵碼,方法是利用合法、未受監控的模組中的 " call gadgets "。規避的核心在於操縱呼叫堆疊,以插入一個任意、受信任的模組位址,從而破壞 EDR 用於偵測的特徵模式。本研究提供了該機制的詳細技術分析,包括所需的 Assembly stub 和所選 gadget 的作用,突顯了資安廠商與 threat actors 之間持續的貓捉老鼠遊戲。
1. 簡介
現代 EDR 解決方案,例如 Elastic Security,利用呼叫堆疊遙測作為可疑活動的強大指標 [3] [4] [5]。一個主要的偵測向量針對源自
unbacked memory
—即與磁碟上的檔案無關的記憶體—的敏感操作,例如載入網路模組 [1]。這是 shellcode 執行的常見特徵。Elastic 針對此情境的偵測規則會尋找特定的呼叫堆疊序列,其中包括
Unbacked
entry,表示執行流程來自動態分配的記憶體 [1]。舉例來說,一個典型的規則片段會針對類似
ntdll.dll|kernelbase.dll|Unbacked
或涉及網路相關 DLL(例如
ws2_32.dll
或
wininet.dll
)的類似序列 [1] [14]。
先前的規避技術,例如 Call Stack Spoofing [8] 和 API Proxying [9],已被開發來對抗這種偵測。然而,EDR 廠商隨後也開發了規則來偵測這些規避手法 [10] [11]。這裡討論的技術建立在操縱執行流程以改變呼叫堆疊特徵碼的概念上,利用了 EDR 監控中必要的盲點,因為 EDR 為了維持效能和減少誤報,通常只專注於一組特定的程式庫 [1]。
使用此類規避技術是進階惡意軟體載入程序的關鍵特徵,它們通常採用多層次的隱匿手法。例如,CoffeeLoader 惡意軟體家族利用 Call Stack Spoofing 、 Sleep Obfuscation 和 Windows Fibers 來增強其隱匿能力 [2]。使用 call gadgets 來破壞呼叫堆疊特徵碼的技術是這個武器庫中的另一種複雜方法,展現了規避戰術的不斷演變。
2. Call Gadget 規避的技術機制
此規避技術的基本原則是在執行敏感 API 呼叫之前,立即將一個受控、來自受信任、未受監控模組的回傳位址插入到呼叫堆疊中。這破壞了 EDR 被編程用於偵測的預期惡意呼叫堆疊特徵碼 [1 [1]。
2.1. Call Gadget 的作用
該技術需要識別一個特定的組合語言指令序列,稱為
call gadget
,它位於一個未被 EDR 主動監控的合法 DLL 內。理想的 gadget 包含一條
call
指令和緊隨其後的
ret
指令。原始研究在
dsdmo.dll
中識別出一個合適的 gadget,其結構如下 [1]:
18000133d: 41 ff d2 call r10 180001346: c3 ret
當惡意程式碼跳轉到這個 gadget 時,會發生以下事件序列:
-
jmp指令將執行程序轉移到 gadget 的位址。 -
call r10指令將下一條指令(即ret)的位址推入堆疊,然後跳轉到儲存在r10暫存器中的位址。r10中的位址預先載入了目標敏感 API(例如LoadLibraryA)的位址。 -
目標 API 執行。當它回傳時,它從堆疊中彈出
ret指令的位址並將控制權回傳給 gadget。 -
gadget 中的
ret指令接著從堆疊中彈出下一個位址,即原始惡意程式碼的 Callback Function 位址,並將控制權回傳給它。
關鍵在於,EDR 記錄的呼叫堆疊現在將包含
dsdmo.dll
gadget 的位址,有效地遮蓋了呼叫來自
unbacked memory
的真實來源 [1]。
2.2. Assembly Stub 實作
為了執行此流程,需要一個客製化的 Assembly stub 來準備堆疊和暫存器,然後跳轉到 gadget。這個 stub 是作為 Callback Function(例如
WorkCallback
)的一部分執行,該函式通常是從
unbacked memory
叫用 [1]。以下組合語言程式碼演示了必要的準備程序:
- void __attribute__((naked)) WorkCallback() {
- __asm__ __volatile__ (
- ".intel_syntax noprefix;"
- "sub rsp, 0x28;" // 1. Counteract `add rsp, 28` in the gadget's epilogue (stack alignment)
- "mov r10, [rdx + 0x8];" // 2. Load the target API address (pLoadLibraryAddress) into r10
- "mov r11, [rdx + 0x10];" // 3. Load the gadget address (pGadgetAddress) into r11
- "mov rcx, [rdx];" // 4. Load the first argument (LibraryName) into rcx
- "xor rdx, rdx;" // 5. Null out rdx (second argument for LoadLibraryExA, for future-proofing)
- "xor r8, r8;" // 6. Null out r8 (third argument for LoadLibraryExA, for future-proofing)
- "jmp r11;" // 7. Jump to the gadget, which executes `call r10`
- ".att_syntax prefix;"
- );
- }
stub 執行了幾個關鍵功能:
-
堆疊對齊:
sub rsp, 0x28指令用於調整堆疊指標,通常是為了與目標 API 的呼叫慣例要求對齊,或抵消 gadget 周圍程式碼中的堆疊操縱 [1]。 -
暫存器設定:
r10暫存器載入了要呼叫的函式(例如LoadLibraryA)的位址。根據 x64 呼叫慣例,rcx暫存器載入了第一個參數(程式庫名稱)。 -
Gadget 跳轉:
最終的
jmp r11指令將控制權轉移到 call gadget 。這個跳轉是規避的關鍵,因為它確保呼叫堆疊上的下一個 Entry 是合法dsdmo.dll模組內的位址,而不是 unbacked memory 。
2.3. 呼叫堆疊視覺化
下圖說明了執行流程和產生的 Call Stack Entries,將正常的惡意流程與 call gadget 規避技術進行對比。
is broken because
the expected 'Unbacked' entry
is now masked
by 'G' (dsdmo.dll),
which is not
in the EDR's
malicious pattern list.
3. 與其他規避技術的比較
call gadget
技術與
Call Stack Spoofing
共享操縱呼叫堆疊的目標,但透過不同的機制實現。傳統的
Call Stack Spoofing
涉及手動建構虛假的堆疊框架以欺騙 EDR [8]。相較之下,
call gadget
技術利用預先存在、受信任的程式碼序列中
call
和
ret
指令的自然行為,將合法模組的位址插入到堆疊中 [1]。
此外,這項技術可以被視為對 API Proxying 或 Indirect System Calls 等方法的補充,這些方法專注於繞過 User-mode hooks [9] [2]。雖然這些方法從一開始就阻止 EDR 看到 API 呼叫,但 call gadget 技術專注於確保 如果 EDR 看到呼叫,相關聯的呼叫堆疊遙測也會看起來是良性的。這些技術的結合創造了一個更穩健的規避策略。
4. 結論
利用
call gadgets
規避 EDR 呼叫堆疊特徵碼是一種高度技術性且有效的方法,它利用了 EDR 設計中固有的取捨,特別是為了效能考量而需要限制對特定模組的監控。透過識別和利用未受監控 DLL 中的一個簡單
call r10; ret
gadget,threat actors 可以有效地掩蓋來自
unbacked memory
的敏感 API 呼叫的來源。本研究強調了 EDR 系統從靜態呼叫堆疊模式對比轉向更加動態和基於
啟發式
的分析的重要性,例如監控與執行流程相關的
熵
和記憶體保護變更,正如在其他進階規避技術中所見 [2]。這些規避方法的不斷演變,使得端點資安防護需要持續、適應性的方法。
參考文章
- Evading Elastic EDR's call stack signatures with call gadgets
- GPU 的暗黑進化:CoffeeLoader 引爆資安新危機
- Upping the ante
- Doubling down
- No more free passes
- Hiding in PlainSight part 1
- Hiding in PlainSight part 2
- Dylan Tran's 2023 post
- API proxying presented here by Chetan Nayak
- detect call stack spoofing
- detect API proxying
- Shellcode Fluctuation via CallBack
- Windows API via a CallBack Function
- rule to catch this coming from shellcode in memory