昨天認識了 x64dbg 的基本操作介面,接下來要介紹的這個章節是大量的觀念,包含如何看懂 Memory Map、記憶體位址如何重新定位、以及如何 Debug thread,以便日後在閱讀記憶體資訊時能夠擁有完整的認知,往後的幾天會繼續介紹執行、下斷點、再到如何 patch 掉程式等等。
這個視窗會顯示所有由被 debug 的程式分配的記憶體區塊,選擇 View > Memory 以後就可以看到,如下圖

Memory Map 可以讓我們看到程式載入 RAM 後的記憶體配置,且 Memory Map 裡會標出目前分析的 exe,以及它的各個 section,程式載入的 DLL 和程式碼與資料區段也會出現在 Memory Map。在 Memory Map 裡面對任何一排點兩下就會顯示這個段落的 memory dump。或者也可以在 Memory Dump 中對資料按右鍵,選擇 View in Disassembler,就會把該處的資料送到 Disassembler Window 中查看。
舉例來說假設 debug malware 時看到:
eax = 0x02A51000
call eax
可能會想:
0x02A51000到底是什麼?
這時候就可以開 Memory Map
可能看到:
00400000 malware.exe
76000000 kernel32.dll
77000000 ntdll.dll
02A50000 Private memory RWX
那就可以去想:
call eax
↓
0x02A51000
↓
落在一塊 Private RWX Memory
↓
可能是 runtime 解密/解壓/產生的程式碼
接著:
Memory Map → 找到區域 → Dump → Disassembler → 分析程式碼
這是 Memory Map 在 malware analysis 中很常見的用法
Memory Map 可以幫助了解一個 PE 檔案在程式執行期間是如何進行 Rebasing(重新定位)的。而所謂的 Rebasing,是指 Windows 中的一個模組沒有被載入到它原本偏好的基底位址(preferred base address)時所發生的處理過程。
Windows 中所有的 PE 檔案都有一個偏好的基底位址(preferred base address),這個位址稱為 Image Base,並定義在 PE Header之中。
Image Base 不一定就是惡意程式實際被載入記憶體時所使用的位址,不過通常都是。大多數執行檔都被設計成載入到 0x00400000,這只是因為許多 Windows 平台的編譯器都將這個位址作為預設值。開發人員也可以選擇讓執行檔以其他位址作為基底位址。支援 ASLR(Address Space Layout Randomization,位址空間配置隨機化)安全機制的執行檔,通常會被重新定位,不過 DLL 發生 relocation 的情況更加常見。
之所以需要 relocation,是因為單一應用程式可能會匯入許多 DLL,而每個 DLL 都有自己偏好的記憶體基底位址,也就是它們希望自己被載入的位置。如果載入兩個 DLL,而這兩個 DLL 的 preferred load address 都是:
0x10000000
那麼它們不可能同時被載入這個位置,因此,Windows 會將其中一個 DLL 載入 0x10000000,然後將另一個 DLL 重新定位到其他記憶體位置。
大多數 Windows 作業系統內建的 DLL 都具有不同的 preferred base address,因此通常不會互相發生位址衝突(collision),不過第三方應用程式所使用的 DLL,就常常會有相同的 preferred base address。
relocation 的過程,不只是單純把程式碼載入到另一個記憶體位置而已,許多指令在參照記憶體時使用的是相對位址(relative address),但另外一些指令使用的是絕對位址(absolute address),如下圖

這些指令中的大部分,不論被載入到記憶體中的哪個位置,都能正常運作,因為它們使用的是相對位址。然而,在標記 ❶ 的資料存取指令將無法正常運作,因為它使用絕對位址來存取某個記憶體位置。如果檔案被載入到記憶體中的位置,不是它原本偏好的基底位置(preferred base location),那麼這個絕對位址就會變成錯誤的。因此,當檔案被載入到不同位址時,這條指令必須被修改,大多數 DLL 在檔案中都會附帶一份需要進行這類修正(fix-up)的位址清單,這些資訊位於 PE 的 .reloc section 中。
DLL 會在 .exe 之後被載入,而且載入順序可能有所不同。這表示,如果 DLL 發生 rebasing,通常無法事先預測它最後會位於記憶體中的哪個位置。DLL 的 relocation section 可以被移除;如果一個沒有 relocation section 的 DLL 無法被載入它原本的 preferred base address,那麼這個 DLL 就無法被載入。
對 DLL 進行 relocation 對效能並不好,而且會增加載入所需的時間。編譯 DLL 時,編譯器會為 DLL 選擇一個預設的 base address,而通常所有 DLL 的預設 base address 都是一樣的。這個事實大幅增加了發生 relocation 的可能性,因為所有 DLL 都被設計成希望載入到相同的位置。有經驗的程式設計師會注意到這個問題,因此會替自己的 DLL 選擇適當的 base address,以盡可能減少 relocation 的發生。
下圖使用 x64dbg 的 Memory Map 功能,展示了 EXE-1 執行時 DLL relocation 的情況

如圖所示,我們有:
DLL-A 的 preferred load address 是:
0x10000000
而且它已經被載入記憶體中。
EXE-1 的 preferred load address 是:
0x00400000
當 DLL-B 被載入時,它的 preferred load address 同樣也是:
0x10000000
因此,它被重新定位到:
0x00340000
DLL-B 中所有使用絕對記憶體位址(absolute address)的參照,都會被修改,使它們能夠在這個新的位址上正常運作。
如果一邊使用 IDA Pro 查看 DLL-B,一邊使用 debugger 對應用程式進行除錯,看到的位址將不會相同,因為 IDA Pro 並不知道程式執行期間發生的 rebasing。因此,每當要在 debugger 中查看一個從 IDA Pro 得到的記憶體位址時,可能都必須經常進行位址調整。為了避免這個問題,可以使用 manual load process(手動載入流程)。
惡意程式經常會使用多個 threads,可以選擇 View > Threads 來開啟 Threads Window,查看目前程式中的所有 threads。這個視窗會顯示各個 threads 所在的記憶體位置,以及它們目前的狀態,例如:
如果要開始 Debug 某一個特定的 thread,需要先暫停所有 threads、設定 breakpoint,然後再讓程式繼續執行,點擊主工具列上的 Pause 按鈕,可以暫停目前所有正在執行的 threads。下圖顯示了一個 Threads Window 的範例,其中五個 threads 都已經被暫停。

也可以終止個別的 thread。只要在某個 thread 上按右鍵,就會顯示上圖的選項,接著選擇:
Kill Thread
即可終止該 thread。
同一個 process 中的每一個 thread 都擁有自己的 stack,而重要的資料經常會被儲存在 stack 上,可以利用 Memory Map 查看這些 stack 在記憶體中的位置。
例如,在下圖中,你可以看到 main thread 的 stack 標示為:

stack of main thread
身為一個沒有修過作業系統的外系學生,讀到這個章節覺得特別的深澀,尤其在純觀念的介紹下比較難理解,希望日後做到課程 lab 的時候會比較能夠吸收,感覺也是該去惡補一下作業系統了QQ
