Day12 - 幻影般的交響樂章:Windows 進程與線程的協同世界

Day12 - 幻影般的交響樂章:Windows 進程與線程的協同世界
CX330走在時代前沿的前言
嗨大家好,我又回來了。昨天我們已經一起實作了一個好用的加密和混淆器,不知道大家對 Zig 這個語言的感受如何,歡迎留言和我分享。
今天的內容會是講一下 Windows 的進程和線程,以及各自對應的一些神奇結構體。
開始囉!
疊甲
中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」
本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!
Zig 版本
本系列文章中使用的 Zig 版本號為 0.14.1。
進程與線程
在 Windows 中,一個進程(Process)是運行在 Windows 電腦中的某個程式(Program)或是應用程式(Application)。進程可以被使用者給開啟,也可以被系統本身所啟動。在完成任務的過程中會去消耗系統資源,如記憶體、硬碟、中央處理器等等。
一個進程會由單個或是多個的線程(Thread)所組成,線程是一坨可以在進程裡獨立執行的指令的集合,並且同一個進程中的多個線程可以進行通訊和共享資料。線程是由作業系統去排程執行的,並在進程的上下文(Context)中被管理。
下圖我們使用了 System Informer 這個工具(原名 Process Hacker)幫助我們看一下進程與線程間的關係。我們可以看到在 25272 這個進程中,有很多線程正在執行任務。
進程記憶體
Windows 的進程也會使用記憶體來儲存資料和指令,當進稱被創建的時候,會分配一塊記憶體,而究竟要分配多少記憶體也可以被進程本身設定好的。前面在記憶體分配那篇已經講過了作業系統會同時使用虛擬記憶體和實體記憶體來管理記憶體,這會讓作業系統得以使用比實際可用的還要更多的記憶體,透過創建一個可以被應用程式存取的虛擬地址空間(VAS),這些虛擬地址空間會被劃分成不同的 Page 並分配給進程。
進程會有不同的記憶體類型,如下:
- Private memory
- 專為單一進程使用,其他進程不能存取
- 用於儲存屬於特定進程的資料
- Mapped memory
- 可以在兩個或多個進程之間被共享,他用於在進程間分享資料,例如共享函式庫、共享記憶體 Segments 或是共享文件等
- 它對於其他進程而言是可見的,但受到保護,不能被其他進程修改
- Image memory
- 包含執行檔的程式碼(Code)和資料(Data)和一些資源(Resources)等
- 通常與被載入進程地址空間的 DLL 文件有關
PEB (Process Environment Block)
PEB 是一個 Windows 的資料結構,裡面會包含和進程有關的資訊,像是參數、進程啟動的資訊、被分配的 Heap 的資料以及被載入的 DLL 等等。它被作業系統用於存放進程在執行時的資訊,並被 Windows 載入器用來啟動應用程式。此外,它也會存儲一些像是進程 ID(Process ID, PID)和執行檔路徑等等的資訊。每一個進程會有自己的 PEB 結構,包含其自身的資訊。
我們可以看一下 Microsoft 的官方文檔,裡面紀錄了 PEB 結構體的樣貌。
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
可以看到裡面有很多被標示為 Reserved 的成員,我們暫時先忽略不看,以下會解釋一下非 Reserved 的成員。
BeingDebugged
BeingDebugged
是一個 PEB 結構中的標誌(Flag),用於指示一個進程是否正在被除錯,當進程正在被 Debug 的話,它會被設置為 1,反之則為 0。
因為在這前面有 BYTE Reserved1[2]
這個成員,所以 BeingDebugged
會位於偏移量 2 的地方(PEB 基址 + 2),我們用 x64dbg 來看一下。
打開 x64dbg 之後,我們先隨便丟一隻程式進去。然後我們可以先用 peb()
這個函數來獲取一下現在的 PEB 的地址。
按下 Enter 後就會獲得 PEB 的地址,像這樣。
接著我們點一下該地址,它就會在 Memory dump 裡面跳過去,接著我們就可以看到 PEB 的記憶體。我們看一下它偏移量 2 的地方,也就是 BeingDebugged
的值,果真被設置為 1 了,代表正在被 Debug。
這個 BeingDebugged
會被 IsDebuggerPresent
這個 Windows API 給檢查,已確認是否有 Debugger。
Ldr
這是一個指向 PEB 中的 PEB_LDR_DATA
結構的指針,裡面包含關於這個進程載入的 DLL 的相關資訊,包括載入的 DLL 清單、每個 DLL 的基址和每個模組的大小。它會被 Windows 載入器用來追蹤進程中載入的 DLL。
看一下官方文件對於 PEB_LDR_DATA
定義的結構體。
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
LDR
可以用來找到特定的 DLL 的基址,以及哪些函數位於記憶體空間之內。惡意程式開發人員可以透過這樣的原理來建立一個自定義版本的 GetModuleHandleA/W
的函數來增加隱匿性。
ProcessParameters
這會包含在建立進程的時候傳遞給進程的命令行參數。Windows 載入器會把這些參數寫進進程的 PEB 結構裡,ProcessParameters
是一個指向 RTL_USER_PROCESS_PARAMETERS
結構體的指針。
我們依然去看一下官方文檔對於它的定義。
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
ProcessParameters
可以被用來執行進程引數偽造(Process argument spoofing),它在 MITRE ATT&CK 的 ID 為 T1564.010。
AtlThunkSListPtr & AtlThunkSListPtr32
ATL(Active Template Library)模組會使用 AtlThunkSListPtr
跟 AtlThunkSListPtr32
來儲存一個指向 Thunk 函數鏈結串列(Linked list)的指針。Thunk 函數用來呼叫實作於不同位址空間的函式,這些函式通常是從 DLL 匯出的。這個 Thunk 函式鏈結串列由 ATL 模組用來管理 thunk 的處理流程(例如分配、回收與調用)。
PostProcessInitRoutine
PostProcessInitRoutine
成員用來儲存一個指向函數的指針。當作業系統把這個進程裡所有線程的 TLS(Thread Local Storage)都初始化完成後,就會呼叫它。這個函數會用來執行進程需要的任何額外初始化任務。
SessionId
SessionId
是分配給單一 Session 的唯一識別符。用來標示哪個工作階段(如本機登入、遠端桌面等)正在執行該進程。同一個 SessionId
可以有很多個進程,他們的 SessionId
都會相同。
TEB (Thread Environment Block)
TEB 是 Windows 上的另一種資料結構,與 PEB 類似,不過他是儲存和線程相關的資訊。它在裡面會包含線程的環境、安全上下文以及其他種種相關的訊息。TEB 儲存於線程的 Stack 裡面,並被 Windows 核心用於管理線程。
我們再次看一下 Microsoft 提供的公開文檔,紀錄了 TEB 的結構的樣子:
typedef struct _TEB {
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock;
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64];
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle;
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
} TEB, *PTEB;
一樣,我們先跳過未被記錄的 Reserved 的部分,我們來看一下有被記錄的一些成員。
ProcessEnvironmentBlock (PEB)
這個東西就是一個剛剛說的 PEB 的指針,指向它所隸屬的進程的 PEB。
在 Windows 作業系統中,都永遠會有一個 Segment register 會指向 TEB 結構。不過在 32 位元跟 64 位元會有所不同,分別是:
- x86
fs
register
- x64
gs
register
然後因為我們知道,PVOID
在 32 位元系統中的大小為 4 個位元組,在 64 位元的系統中大小為 8 位元組,所以再加上一開始的 PVOID Reserved1[12]
之後,PEB 的位置就可以被 0x30 跟 0x60 這兩個偏移量給存取,詳細來說是這樣的。
所以我們恆可以透過剛提到的兩個 Segment register 存取到 PEB 結構:
- x86
fs:[0x30]
- x64
gs:[0x60]
TlsSlots
這個成員會用來儲存線程中特定的資料。Windows 的每個線程都會有自己的 TEB,每個 TEB 都有一組 TLS Slots。應用程式可以用這些 Slots 來儲存專屬於該線程的資料,像是專屬於線程的變數、句柄、狀態等等。
TlsExpansionSlots
TEB 的 TLS Expansion Slots 是一組指針,用來儲存線程的 TLS 資料,它預留給系統 DLL 使用。
進程和線程的句柄
在 Windows 作業系統上,每個進程都有一個獨特的進程識別符,或稱進程 ID(PID)。當進程創建的時候,作業系統會分配給它。PID 用於區別不同的進程,同時這個概念也會在線程中出現,線程中也會有一個唯一的識別符(Thread ID, TID),用於區分與系統中任何進程中的其他的線程。
這些 ID 可以用以下的 Windows API 來打開一個進程或是線程的句柄。
OpenProcess
- 使用 PID 打開現有的進程句柄
OpenThread
- 使用 TID 打開現有的線程句柄
在打開了進程或線程的句柄後,就可以進行後續操作,例如暫停(Suspend)進程或線程。這些句柄應該要在使用完成後用 CloseHandle
關閉它們,避免 Handle leak。
未被公開文檔紀錄的資料結構
在 Windows 中,很常會遇到在查看某些資料結構的組成時,某些成員(Member)都會在官方文檔被標注為 Reserved
。這些被保留的成員常常會以 BYTE
或是 PVOID
的型別出現。Microsoft 透過這樣的方式維護他們的機密,並防止使用者去理解並修改某些內容。
不過好在有一群逆向工程師已經有分析過很多未被記錄的資料結構,並寫成第三方的文檔,因此我們在之後可能不一定每次都會使用官方的文檔,而改用其他的網站資源。
舉例來說,剛剛的 PEB 結構裡就有很多被保留的(Reserved)成員。如果我們想要知道這些保留成員是什麼,可以透過使用 WinDbg 等各種工具去逆向它們。舉例來說,想要知道 PEB 的一些保留成員的一種方式是在 WinDbg 使用 !peb
命令。我們先來隨便丟個執行檔進去 WinDbg,並執行 !peb
指令。
如果想看更完整的 PEB 結構內容,可以參考 System Informer 的 PEB 結構。
其他文檔
這邊會放一些其他常用的文檔,因為微軟官方文檔會隱藏很多重要內容。
鐵人賽期 PoPoo,你今天轉 Po 了嗎?
完成嘍!明天不知道能不能順利更新,好緊張,明天有比賽要打。
今天跟大家初步介紹了 Windows 的進程和線程,明天會來介紹一下 Shellcode 如何執行,然後如何用 Zig 寫一個 DLL 等等的,可以期待一下!
如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!