Windows程式設計系列:遠執行緒注入

zhaotianff發表於2024-03-13

遠執行緒注入

遠執行緒(RemoteThread)注入是指一個程序在另一個程序中建立執行緒的技術,這是一種很經典的DLL注入技術。

雖然比較古老,但是很實用。透過遠執行緒注入,再配合api函式的hook技術,可以實現很多有意思的功能。

實現遠執行緒注入的關鍵函式

OpenProcess

開啟現有的本地程序,函式宣告如下:

1 WINAPI
2 OpenProcess(
3     _In_ DWORD dwDesiredAccess,
4     _In_ BOOL bInheritHandle,
5     _In_ DWORD dwProcessId
6     );

引數:

dwDesiredAccess:

訪問程序物件。此訪問許可權為針對程序的安全描述符進行檢查,此引數可以是一個或多個程序訪問許可權。如果呼叫該函式的程序啟用了SeDebugPrivilege許可權,則無論安全描述符的內容是什麼,它都會授予所請求的訪問許可權。

bInheritHandle:

若此值為TRUE,則此程序建立的程序將繼承該控制代碼。否則,程序不會繼承此控制代碼。

dwProcessId:

要開啟的本地程序的PID

返回值:

成功,返回程序的控制代碼

失敗,返回NULL,要獲取錯誤資訊,呼叫GetLastError

VirtualAllocEx

在指定程序的虛擬地址空間內保留、提交或更改記憶體的狀態。宣告如下:

1 WINAPI
2 VirtualAllocEx(
3     _In_ HANDLE hProcess,
4     _In_opt_ LPVOID lpAddress,
5     _In_ SIZE_T dwSize,
6     _In_ DWORD flAllocationType,
7     _In_ DWORD flProtect
8     );

hProcess

程序的控制代碼。VirtualAllocEx函式在該程序的虛擬地址空間內分配記憶體,控制代碼必有具有PROCESS_VM_OPERATION許可權。

lpAddress

指定要分配頁面所需起始地址的指標。如果lpAddress為NULL,則該函式自動分配記憶體。

dwSize

要分配的記憶體大小,以位元組為單位。

flAllocationType

記憶體分配型別。此引數必須為以下值之一

含義
MEM_COMMIT
0x00001000
從指定保留記憶體頁的磁碟) 的總記憶體大小和分頁檔案 (分配記憶體費用。 函式還保證當呼叫方稍後最初訪問記憶體時,內容將為零。 除非實際訪問虛擬地址,否則不會分配實際物理頁面。

嘗試透過指定 MEM_COMMIT 而不指定 MEM_RESERVE 和非 NULLlpAddress 來提交特定地址範圍,除非已保留整個範圍。 生成的錯誤程式碼ERROR_INVALID_ADDRESS。

嘗試提交已提交的頁面不會導致函式失敗。 這意味著,無需先確定每個頁面的當前承諾狀態即可提交頁面。

如果 lpAddress 指定 enclave 中的地址,則必須MEM_COMMITflAllocationType

MEM_RESERVE
0x00002000
保留程序的虛擬地址空間範圍,而無需在記憶體或磁碟上的分頁檔案中分配任何實際物理儲存。

使用 MEM_COMMIT 再次呼叫 VirtualAllocEx 來提交保留頁。 若要在一個步驟中保留和提交頁面,請使用 MEM_COMMIT | MEM_RESERVE呼叫 VirtualAllocEx。

其他記憶體分配函式(如 malloc 和 LocalAlloc)在釋放記憶體之前不能使用預留記憶體。

MEM_RESET
0x00080000
指示 lpAddressdwSize 指定的記憶體範圍中的資料不再感興趣。 不應從分頁檔案讀取或寫入頁面。 但是,記憶體塊稍後將再次使用,因此不應取消提交。 此值不能與任何其他值一起使用。

使用此值並不能保證使用 MEM_RESET 操作的範圍將包含零。 如果希望範圍包含零,請取消提交記憶體,然後重新提交。

使用 MEM_RESET 時, VirtualAllocEx 函式會忽略 fProtect 的值。 但是,仍必須將 fProtect 設定為有效的保護值,例如 PAGE_NOACCESS。

如果使用 MEM_RESET並且記憶體範圍對映到檔案,VirtualAllocEx 將返回錯誤。 僅當共享檢視對映到分頁檔案時,才可接受該檢視。

MEM_RESET_UNDO
0x1000000
應 僅對之前成功應用MEM_RESET的地址範圍呼叫 MEM_RESET_UNDO 。 它指示呼叫方對 lpAddressdwSize 指定的指定記憶體範圍中的資料感興趣,並嘗試反轉 MEM_RESET的影響。 如果該函式成功,則表示指定地址範圍中的所有資料都保持不變。 如果函式失敗,則至少將地址範圍中的某些資料替換為零。

此值不能與任何其他值一起使用。 如果在之前未MEM_RESET的地址範圍上呼叫MEM_RESET_UNDO,則行為未定義。 指定 MEM_RESET時, VirtualAllocEx 函式將忽略 flProtect 的值。 但是,仍必須將 flProtect 設定為有效的保護值,例如 PAGE_NOACCESS。

Windows Server 2008 R2、Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP: 在 Windows 8 和 Windows Server 2012 之前,不支援 MEM_RESET_UNDO 標誌。

此引數還可以按指示指定以下值。

MEM_LARGE_PAGES
0x20000000
使用 大頁支援分配記憶體。

如果指定此值,還必須指定 MEM_RESERVE 和 MEM_COMMIT。

MEM_PHYSICAL
0x00400000
保留可用於將 地址視窗擴充套件 (AWE) 頁對映的地址範圍。

此值必須與 MEM_RESERVE 一起使用,不能與其他值一起使用。

MEM_TOP_DOWN
0x00100000
在可能的最高地址分配記憶體。 這可能比常規分配慢,尤其是在有許多分配時。

flProtect

要分配的頁面區域的記憶體保護。如果頁面已提交,則可以指定任何一個如下記憶體保護常量。

常量/值說明
PAGE_EXECUTE
0x10
啟用對頁面已提交區域的執行訪問。 嘗試寫入已提交區域會導致訪問衝突。
CreateFileMapping 函式不支援此標誌。
PAGE_EXECUTE_READ
0x20
啟用對頁面已提交區域的執行或只讀訪問。 嘗試寫入已提交區域會導致訪問衝突。
Windows Server 2003 和 Windows XP: 在 Windows XP SP2 和 Windows Server 2003 SP1 之前, CreateFileMapping 函式不支援此屬性。
PAGE_EXECUTE_READWRITE
0x40
啟用對已提交頁面區域的執行、只讀或讀/寫訪問許可權。
Windows Server 2003 和 Windows XP: 在 Windows XP SP2 和 Windows Server 2003 SP1 之前, CreateFileMapping 函式不支援此屬性。
PAGE_EXECUTE_WRITECOPY
0x80
啟用對檔案對映物件的對映檢視的執行、只讀或寫入時複製訪問許可權。 嘗試寫入已提交的寫入時複製頁會導致為程序建立頁面的專用副本。 專用頁面標記為 PAGE_EXECUTE_READWRITE,更改將寫入新頁面。
VirtualAllocVirtualAllocEx 函式不支援此標誌。 Windows Vista、Windows Server 2003 和 Windows XP: 在具有 SP1 和 Windows Server 2008 的 Windows Vista 之前, CreateFileMapping 函式不支援此屬性。

PAGE_NOACCESS
0x01
禁用對已提交頁面區域的所有訪問。 嘗試從提交區域讀取、寫入或執行區域會導致訪問衝突。
CreateFileMapping 函式不支援此標誌。
PAGE_READONLY
0x02
啟用對已提交頁面區域的只讀訪問。 嘗試寫入已提交區域會導致訪問衝突。 如果啟用了 資料執行防護 ,則嘗試在已提交的區域中執行程式碼會導致訪問衝突。
PAGE_READWRITE
0x04
啟用對已提交頁面區域的只讀或讀/寫訪問。 如果啟用了 資料執行保護 ,則嘗試在提交的區域中執行程式碼會導致訪問衝突。
PAGE_WRITECOPY
0x08
啟用對檔案對映物件的對映檢視的只讀或寫入時複製訪問許可權。 嘗試寫入已提交的寫入時複製頁會導致為程序建立頁面的專用副本。 專用頁面標記為 PAGE_READWRITE,更改將寫入新頁面。 如果啟用了 資料執行保護 ,則嘗試在提交的區域中執行程式碼會導致訪問衝突。
VirtualAllocVirtualAllocEx 函式不支援此標誌。
PAGE_TARGETS_INVALID
0x40000000
將頁面中的所有位置設定為 CFG 的無效目標。 與任何執行頁保護(如 PAGE_EXECUTE、 PAGE_EXECUTE_READ、 PAGE_EXECUTE_READWRITE 和 PAGE_EXECUTE_WRITECOPY)一起使用。 對這些頁面中的位置的任何間接呼叫都將失敗 CFG 檢查,並且程序將終止。 分配的可執行頁面的預設行為是標記為 CFG 的有效呼叫目標。
VirtualProtectCreateFileMapping 函式不支援此標誌。
PAGE_TARGETS_NO_UPDATE
0x40000000
VirtualProtect 的保護髮生更改時,區域中的頁面將不會更新其 CFG 資訊。 例如,如果區域中的頁面是使用 PAGE_TARGETS_INVALID 分配的,則在頁面保護更改時將保留無效資訊。 僅當保護更改為可執行型別(如 PAGE_EXECUTE、PAGE_EXECUTE_READ、PAGE_EXECUTE_READWRITE 和 PAGE_EXECUTE_WRITECOPY)時,此標誌才有效。 VirtualProtect 保護更改為可執行檔案的預設行為是將所有位置標記為 CFG 的有效呼叫目標。

如果lpAddress指定了一個地址,則flProtect不能是以下值之一:

PAGE_NOACCESS
PAGE_GUARD
PAGE_NOCACHE
PAGE_WRITECOMBINE

返回值:

成功,返回分配頁面的基址

失敗,返回NULL,要獲得更多的錯誤資訊,請呼叫 GetLastError。

WriteProcessMemory

在指定的程序中將資料寫入記憶體區域,要寫入的整個區域必須可訪問,否則操作失敗。函式宣告如下:

1 BOOL
2 WINAPI
3 WriteProcessMemory(
4     _In_ HANDLE hProcess,
5     _In_ LPVOID lpBaseAddress,
6     _In_reads_bytes_(nSize) LPCVOID lpBuffer,
7     _In_ SIZE_T nSize,
8     _Out_opt_ SIZE_T* lpNumberOfBytesWritten
9     );

hProcess

要修改的程序記憶體的控制代碼,控制代碼必有具有 PROCESS_VM_WRITE和PROCESS_VM_OPERATION訪問許可權

lpBaseAddress

指向指定程序中寫入資料的基地址指標。在資料傳輸發生之前,系統會驗證指定大小的基地址和記憶體中的所有資料是否可以進行寫入訪問,如果不可以訪問,則該函式將失敗。

lpBuffer

指向緩衝區的指標,其中包含要寫入指定程序的地址空間中的資料。

nSize

要寫入指定程序的位元組數。

lpNumberOfBytesWritten

指向變數的指標,該變數接收傳輸到指定程序的位元組數。如果lpNumberOfBytes Written為NULL,則忽略該引數。

返回值:

成功,返回值不為0,

失敗,返回值為0

CreateRemoteThread

在另一個程序的虛擬地址空間中建立執行的執行緒。宣告如下:

 1 HANDLE
 2 WINAPI
 3 CreateRemoteThread(
 4     _In_ HANDLE hProcess,
 5     _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
 6     _In_ SIZE_T dwStackSize,
 7     _In_ LPTHREAD_START_ROUTINE lpStartAddress,
 8     _In_opt_ LPVOID lpParameter,
 9     _In_ DWORD dwCreationFlags,
10     _Out_opt_ LPDWORD lpThreadId
11     );

引數說明:

hProcess

要建立執行緒的程序的控制代碼。控制代碼必須具有

PROCESS_CREATE_THREAD、

PROCESS_QUERY_INFORMATION、

PROCESS_VM_OPERATION、

PROCESS_VM_WRITE

PROCESS_VM_READ

訪問許可權

lpThreadAttributes

指向SECURITY_ATTRIBUTES結構的指標,該結構指定新執行緒的安全描述符,並確定子程序是否可以繼承返回的控制代碼。如果lpThreadAttributes為NULL,則執行緒將獲得預設的安全描述符,並且不能繼承該控制代碼。

dwStackSize

堆疊的初始大小,以位元組為單位。如果此引數為o,則新執行緒使用可執行檔案的預設大小。

lpStartAddress

指向由執行緒執﹐行型別為LPTHREAD_START_ROUTINE的應用程式定義的函式指標,並表示遠端程序中執行緒的起始地址,該函式必須存在於遠端程序中。

lpParameter

指向要傳遞給執行緒函式的變數的指標。

dwCreationFlags

控制執行緒建立的標誌。若是0,則表示執行緒在建立後立即執行。

lpThreadId

指向接收執行緒識別符號的變數的指標。如果此引數為NULL,則不返回執行緒識別符號。

返回值:

成功,返回新執行緒的控制代碼,

失敗,返回NULL,如果要獲取錯誤資訊,請呼叫GetLastError

遠執行緒注入實現原理

程式在載入DLL時,通常呼叫LoadLibrary函式來實現DLL的動態載入。LoadLibrary函式的宣告如下:

1 HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);

LoadLibrary函式只有一個引數,傳遞的是要載入的DLL路徑字串。

而CreateRemoteThread需要傳遞的是目標程序空間中的多執行緒函式地址,以及多執行緒引數。

如果程式能夠獲取目標程序LoadLibrary函式的地址,而且還能夠獲取目標程序空間中某個DLL路徑字串的地址,那麼,可將LoadLibrary函式的地址作為多執行緒函式的地址,某個DLL路徑字串作為多執行緒函式的引數,並傳遞給CreateRemoteThread函式在目標程序空間中建立一個多執行緒,這樣能不能成功呢?答案是可以的。這樣就可以在目標程序空間中建立一個多執行緒,這個多執行緒就是LoadLibrary函式載入DLL。

遠執行緒注入的原理就大致清晰了。那麼要想實現遠執行緒注入DLL,還需要解決以下兩個問題:

1、是目標程序空間中LoadLibrary函式的地址是多少

2、是如何向目標程序空間中寫入DLL路徑字串函式

相關文章