iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Security

Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!系列 第 12

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

  • 分享至 

  • xImage
  •  

走在時代前沿的前言

嗨大家好,我又回來了。昨天我們已經一起實作了一個好用的加密和混淆器,不知道大家對 Zig 這個語言的感受如何,歡迎留言和我分享。

今天的內容會是講一下 Windows 的進程和線程,以及各自對應的一些神奇結構體。

開始囉!

疊甲

中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」

本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!

Zig 版本

本系列文章中使用的 Zig 版本號為 0.14.1。

進程與線程

在 Windows 中,一個進程(Process)是運行在 Windows 電腦中的某個程式(Program)或是應用程式(Application)。進程可以被使用者給開啟,也可以被系統本身所啟動。在完成任務的過程中會去消耗系統資源,如記憶體、硬碟、中央處理器等等。

一個進程會由單個或是多個的線程(Thread)所組成,線程是一坨可以在進程裡獨立執行的指令的集合,並且同一個進程中的多個線程可以進行通訊和共享資料。線程是由作業系統去排程執行的,並在進程的上下文(Context)中被管理。

下圖我們使用了 System Informer 這個工具(原名 Process Hacker)幫助我們看一下進程與線程間的關係。我們可以看到在 25272 這個進程中,有很多線程正在執行任務。

System Informer

進程記憶體

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 的地址。

Get PEB in Binary Ninja

按下 Enter 後就會獲得 PEB 的地址,像這樣。

PEB Address in Binary Ninja

接著我們點一下該地址,它就會在 Memory dump 裡面跳過去,接著我們就可以看到 PEB 的記憶體。我們看一下它偏移量 2 的地方,也就是 BeingDebugged 的值,果真被設置為 1 了,代表正在被 Debug。

BeingDebugged

這個 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)模組會使用 AtlThunkSListPtrAtlThunkSListPtr32 來儲存一個指向 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 這兩個偏移量給存取,詳細來說是這樣的。

https://ithelp.ithome.com.tw/upload/images/20250926/20178131Vky3eZodHz.png

所以我們恆可以透過剛提到的兩個 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 in WinDbg

如果想看更完整的 PEB 結構內容,可以參考 System Informer 的 PEB 結構

其他文檔

這邊會放一些其他常用的文檔,因為微軟官方文檔會隱藏很多重要內容。

鐵人賽期 PoPoo,你今天轉 Po 了嗎?

完成嘍!明天不知道能不能順利更新,好緊張,明天有比賽要打。

今天跟大家初步介紹了 Windows 的進程和線程,明天會來介紹一下 Shellcode 如何執行,然後如何用 Zig 寫一個 DLL 等等的,可以期待一下!

如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!


上一篇
Day11 - 綠鬣蜥軍團實戰演練:惡意 Payload 混淆加密啟動器
系列文
Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言