iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Security

《Nyahello!從零開始的 30 天 malware 學習日誌》系列 第 12

【Day 12】認識 Debugger!開始學習 malware 進階動態分析

  • 分享至 

  • xImage
  •  

前言

回顧一下我們學過的內容,從一開始拿到一個惡意程式,先到檢測平台觀察、使用基本靜態工具檢查資訊、使用基礎動態工具尋找網路連線、再到使用 IDA 深入分析,我們已經成功學會如何在這支程式中尋找蛛絲馬跡,並嘗試去拼湊他所執行及可能發生的惡意行為,於是在了解這些資訊以後,我們接下來要使用進階的動態分析工具,去實際觀察這個惡意程式執行起來每一步的動作,並透過修改去觀察不同的結果。

Debugger

所謂的 Debugger,顧名思義,就是拿來 debug 的工具。簡單來說,他是一種用來測試或檢查另一個程式執行過程的軟體或硬體,可以協助軟體開發的過程,因為程式在剛寫好時通常都包含許多錯誤,在開發程式時,你會提供輸入給程式並觀察輸出結果,但並不會看到程式是如何產生這些輸出的。Debugger 可以讓你深入了解程式在執行期間實際做了哪些事情,因為他的設計目的,就是讓開發人員能夠觀察、測量以及控制程式執行時的內部狀態與執行流程。

Debugger 可以提供許多反組譯器難以甚至無法取得的資訊,反組譯器只能提供程式在執行第一條指令之前的靜態快照,也就是程式的原始樣貌,但是 Debugger 卻可以提供程式執行時的動態視角。例如,Debugger 可以顯示記憶體位址中的內容如何隨著程式執行而持續改變、測量與控制程式執行的能力,對於惡意程式分析而言具有極其重要的價值。

Debugger 可以讓我們查看每一個記憶體位置、每一個 CPU 暫存器,以及每一個函式參數的實際數值,此外,除錯器也允許我們在程式執行的任何時刻修改任何執行狀態。例如,只要你知道某個變數足夠的資訊,包括它在記憶體中的位置,你就可以在程式執行期間隨時改變該變數的值。

之後會介紹兩款 Debugger:

  • x64dbg
  • WinDbg

Source-Level vs. Assembly-Level

Source-Level Debugger

大多數軟體開發人員都熟悉 Source-Level Debugger,這種 Debugger 能讓程式設計師在撰寫程式時進行除錯,通常內建於整合開發環境(IDE)中,它可以設定中斷點(Breakpoint),當程式執行到特定的原始碼行時就會暫停,以便檢查程式內部變數的狀態,並且可以一次一行地逐步執行程式。

例設有一個 C 程式:

int a = 5;
int b = 3;
int c = a + b;

可以直接在

int c = a + b;

這一行設 Breakpoint

程式執行到這一行時就會停下來,可以直接看到:

a = 5
b = 3
c = ?

Assembly-Level Debugger

Assembly-Level Debugger,有時也稱為低階除錯器(Low-Level Debugger),操作的對象是組合語言程式碼,而不是原始碼。與 Source-Level Debugger 相同,我們也可以利用組合語言層級除錯器一次執行一條指令、設定中斷點使程式在特定的組合語言指令處停止,以及檢查記憶體中的內容。惡意程式分析人員大量使用 Assembly-Level Debugger,因為它們不需要取得程式的原始碼即可進行分析。

例如只有:

malware.exe

沒有:

malware.c

這樣就只能看到:

mov eax, 5
add eax, 3
call 401200

但是一樣可以:

  • 一條一條執行指令
  • 查看暫存器(eax、ebx…)
  • 查看記憶體
  • 設定 Breakpoint

只是看到的是組合語言,不是 C 程式碼。

Kernel-Mode vs. User-Mode Debugging

User-Mode

在 User-Mode 下,Debugger 與被除錯的程式都執行在同一台電腦上。進行 User-Mode 除錯時,除錯的是單一的可執行程式,而作業系統會將它與其他執行中的程式彼此隔離。

例如:

notepad.exe
malware.exe
chrome.exe

這些都是 User-Mode 的程式

可以直接在同一台電腦上:

x64dbg
    │
    ▼
malware.exe

兩者都在同一台 Windows 裡執行。

這也是平常使用:

  • x64dbg
  • WinDbg(User Mode)

的方式

Kernel-Mode

與除錯 User-Mode 的程式相比,除錯 Kernel-Mode 的程式更加困難,因為 Kernel-Mode 通常需要使用兩台不同的系統。Kernel-Mode Debugging 需要在兩台系統上進行,因為整個系統只有一個核心(Kernel),如果核心在中 Breakpoint 處暫停,系統上的所有應用程式都將無法繼續執行。因此,其中一台系統負責執行被除錯的程式,而另一台系統則執行 Debugger,此外,作業系統必須事先設定成允許 Kernel-Mode Debugging,並且需要將兩台電腦彼此連接。

例如:

  • Windows 核心(ntoskrnl.exe)
  • Driver(.sys)
  • Kernel Rootkit
  • 檔案系統驅動程式
  • 防毒驅動程式

它們都在 Kernel-Mode 執行。

如果讓 Kernel 停在 Breakpoint:

Kernel
↓

停止

那整個 Windows 都會停住。

因為:

  • 滑鼠
  • 鍵盤
  • 視窗
  • 檔案系統

全部都需要 Kernel,所以不能像 User-Mode 一樣,在同一台電腦上 Debug。

所以需要使用兩台電腦,架構如下:

      Debug PC
+----------------+
| WinDbg         |
+----------------+
        │
   Serial / USB / Network
        │
        ▼
+----------------+
| Target PC      |
| Windows Kernel |
+----------------+
  • Target PC(目標機):執行 Windows 核心與被分析的 Driver。
  • Debug PC(除錯機):執行 WinDbg,控制目標機。

當 Target 停下來時:

Target
↓

Breakpoint
↓

整台 Windows 暫停

Debug PC 仍然正常運作,因此可以查看:

  • 暫存器
  • 記憶體
  • Driver 狀態
  • Call Stack

使用 Debugger

使用 Debugger 來分析程式有兩種方式:

  1. 第一種方式是在 Debugger 中直接啟動程式,當程式被載入到記憶體後,它會在 Entry Point 之前立即停止執行,此時,就有對整個程式完整的控制權。

  2. 另一種方式是將 Debugger Attach 到一個已經正在執行中的程式,當 Debugger 成功附加後,該程式的所有 Threads 都會被暫停,接著就可以開始進行除錯。如果要分析一個已經執行一段時間的程式,或是想分析一個受到惡意程式影響的 Process,這種方式會是很好的選擇。

Single-Stepping

單步執行是使用 Debugger 最基本的功能,也就是一次只執行一條指令。單步執行可以讓我們觀察程式內部所發生的一切事情,理論上可以利用單步執行來執行整個程式,但對於複雜的程式而言,這樣會浪費一堆時間。單步執行適合用來理解某一小段程式碼的細節,我們必須有選擇性地決定要分析哪些程式碼。分析時應該專注於整體流程,否則很容易迷失在過多的細節之中。

例如,下圖的反組譯程式碼展示了如何利用 Debugger 來協助理解某一段程式碼

https://ithelp.ithome.com.tw/upload/images/20260907/20183878wNY085wSc5.png

這個程式會在一個迴圈中持續存取並修改某個資料位址,最後顯示的資料值看起來既不像 ASCII 文字,也不像任何可以辨識的資料,但是我們可以利用 Debugger 逐步執行這個迴圈,以了解這段程式碼究竟在做什麼。如果我們使用 WinDbg 或 x64dbg 對這個迴圈執行單步執行,就可以看到資料在每一次迴圈中都被修改。

https://ithelp.ithome.com.tw/upload/images/20260907/20183878ZwO4FkonPi.png

例如,在上圖中,可以看到這個函式所修改的 13 個位元組,每經過一次迴圈就會改變一次,當 Debugger Attach 到程式後,就可以清楚看出,這個函式利用單一位元組的 XOR 運算來解碼出字串 LoadLibraryA。如果只依靠靜態分析,要辨識出這個字串將會困難得多。

Stepping-Over vs. Stepping-Into

Stepping-Over

如果使用 Stepping-Over,在跑的時候就會跳過去而不會跑進去看。

例如:

call A

如果按:

Step Over

Debugger 會:

call A

↓↓↓↓↓↓

A()
全部執行完

↓↓↓↓↓↓

停在

call 後面

例如:

call A

mov eax,1

按一次 Step Over:

直接停在:

mov eax,1

完全不看 A() 裡面的程式。

Stepping-Into

如果使用 Stepping-Into,在跑的時候就會進去看。

例如:

main()
{
    A();
    B();
}

其中:

A()
{
    printf("Hello");
}

CPU 執行到:

call A

如果按 Step Into

call A
    │
    ▼
A()

push ebp
mov ebp,esp
...

會直接跑進 A() 裡。

也就是:

要看這個函式裡面到底做了什麼

結語

今天對於 Debugger 有了基本的認知,之後在進行 Debug 的時候就可以知道要做甚麼並且對於操作步驟有更充分的理解!

參考資料

  • Practical Malware Analysis (Chapter 8)

本日貓味

https://ithelp.ithome.com.tw/upload/images/20260907/201838785AicXkg6lE.jpg


上一篇
【Day 11】實戰 IDA!使用函式分析與圖形閱讀 malware 資訊
下一篇
【Day 13】開啟 x64dbg!認識 Debugger 的基本操作介面
系列文
《Nyahello!從零開始的 30 天 malware 學習日誌》17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言