遠程線程注入
0x00 前言
遠程線程注入是一種經典的DLL注入技術。其實就是指一個新進程中另一個進程中創建線程的技術。
0x01 介紹
1.遠程線程注入原理
畫了一個圖大致理解了下遠程線程注入dll的原理。

如果是實現注入dll的話,流程大致就是:
通過OpenProcess獲取目標進程句柄。
通過VirtualAllocEx在目標進程空間中申請內存,通過WriteProcessMemory放入需要載入的dll的路徑。
通過GetModuleHandleA獲取諸如kernel32.dll這類系統dll的模塊句柄,進而獲取LoadLibraryA這類載入動態鏈接庫的函數地址(固定)
通過CreateRemoteThread的參數傳入目標進程對象句柄、寫入到目標進程空間的dll路徑、LoadLibraryA函數地址,實現中目標中創建多線程加載dll。
2.函數介紹
OpenProcess 函數
打開現有的本地進程對象。
函數聲明
HANDLE WINAPI OpenProcess( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId);參數
- dwDesiredAccess [in]
訪問進程對象。此訪問權限針對進程的安全描述符進行檢查。此參數可以是一個或多個進程訪問權限。如果調用該函數的進程啟用了SeDebugPrivilege權限,則無論安全描述符的內容如何,都會授予所請求的訪問權限。- bInheritHandle [in]
若此值為TRUE,則此進程創建的進程將繼承該句柄。否則,進程不會繼承此句柄。- dwProcessId [in]
要打開的本地進程的標識符。
如果指定的進程是系統進程(0x00000000),則該函數失敗,最后一個錯誤代碼為ERROR_INVALID_PARAMETER。如果指定的進程是空閑進程或CSRSS進程之一,則此功能將失敗,並且最后一個錯誤代碼為ERROR_ACCESS_DENIED,因為它們的訪問限制會阻止用戶級代碼打開它們。
如果您使用GetCurrentProcessId作為此函數的參數,請考慮使用GetCurrentProcess而不是OpenProcess,以提高性能。返回值
- 如果函數成功,則返回值是指定進程的打開句柄。
- 如果函數失敗,返回值為NULL。 要獲取擴展錯誤信息,請調用GetLastError。
VirtualAllocEx 函數
在指定進程的虛擬地址空間內保留,提交或更改內存區域的狀態。 該函數初始化其分配給零的內存。
函數聲明
LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect);參數
- hProcess [in]
過程的句柄。該函數在該進程的虛擬地址空間內分配內存。
句柄必須具有PROCESS_VM_OPERATION權限。有關更多信息,請參閱流程安全和訪問權限。- lpAddress [in]
指定要分配的頁面的所需起始地址的指針。
如果您正在保留內存,則該函數會將該地址舍入到分配粒度的最接近的倍數。
如果您提交已經保留的內存,該功能會將該地址舍入到最接近的頁面邊界。要SESSION 0 隔離頁面的大小和主機上的分配粒度,請使用GetSystemInfo函數。
如果lpAddress為NULL,則該函數確定在哪里分配該區域。- dwSize [in]
要分配的內存大小,以字節為單位。
如果lpAddress為NULL,則函數將dwSize循環到下一個頁面邊界。
如果lpAddress不為NULL,則該函數將從lpAddress到lpAddress + dwSize的范圍內分配包含一個或多個字節的所有頁面。這意味着,例如,跨越頁面邊界的2字節范圍會導致功能分配兩個頁面。- flAllocationType [in]
內存分配類型。此參數必須包含以下值之一:
VALUE MEANING MEM_COMMIT 為指定的預留內存頁分配內存費用(從磁盤上的內存和分頁文件的總體大小)。 該函數還保證當調用者稍后初次訪問存儲器時,內容將為零。 除非/直到虛擬地址被實際訪問,實際的物理頁面才被分配 MEM_RESERVE 保留進程的虛擬地址空間的范圍,而不會在內存或磁盤上的分頁文件中分配任何實際物理存儲 MEM_RESET 表示由lpAddress和dwSize指定的內存范圍內的數據不再受關注。 頁面不應從頁面文件中讀取或寫入頁面文件。 然而,內存塊將在以后再次被使用,所以不應該被分解。 該值不能與任何其他值一起使用 MEM_RESET_UNDO 只能在早期成功應用了MEM_RESET的地址范圍上調用MEM_RESET_UNDO。 它指示由lpAddress和dwSize指定的指定內存范圍內的數據對呼叫者感興趣,並嘗試反轉MEM_RESET的影響。 如果功能成功,則表示指定地址范圍內的所有數據都是完整的。 如果功能失敗,地址范圍中的至少一些數據已被替換為零
- flProtect [in]
要分配的頁面區域的內存保護。 如果頁面被提交,您可以指定任何一個內存保護常量。
如果lpAddress指定了一個地址,flProtect不能是以下值之一:
PAGE_NOACCESS
PAGE_GUARD
PAGE_NOCACHE
PAGE_WRITECOMBINE返回值
- 如果函數成功,則返回值是分配的頁面區域的基址。
- 如果函數失敗,返回值為NULL。 要獲取擴展錯誤信息,請調用GetLastError。
WriteProcessMemory 函數
在指定的進程中將數據寫入內存區域。 要寫入的整個區域必須可訪問或操作失敗。
函數聲明
BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten);參數
- hProcess [in]
要修改的進程內存的句柄。 句柄必須具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION訪問進程。- lpBaseAddress [in]
指向寫入數據的指定進程中的基地址的指針。 在數據傳輸發生之前,系統會驗證指定大小的基地址和內存中的所有數據是否可以進行寫入訪問,如果不可訪問,則該函數將失敗。- lpBuffer [in]
指向緩沖區的指針,其中包含要寫入指定進程的地址空間的數據。- nSize [in]
要寫入指定進程的字節數。- lpNumberOfBytesWritten [out]
指向變量的指針,該變量接收傳輸到指定進程的字節數。 此參數是可選的。 如果lpNumberOfBytesWritten為NULL,則忽略該參數。返回值
- 如果函數成功,則返回值不為零。
- 如果函數失敗,返回值為0(零)。 要獲取擴展錯誤信息,請調用GetLastError。
CreateRemoteThread 函數
創建在另一個進程的虛擬地址空間中運行的線程。
使用CreateRemoteThreadEx函數創建在另一個進程的虛擬地址空間中運行的線程,並可選地指定擴展屬性。函數聲明
HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_ LPDWORD lpThreadId);參數
- hProcess [in]
要創建線程的進程的句柄。 句柄必須具有PROCESS_CREATE_THREAD,PROCESS_QUERY_INFORMATION,PROCESS_VM_OPERATION,PROCESS_VM_WRITE和PROCESS_VM_READ訪問權限,如果某些平台上沒有這些權限,可能會失敗。 有關更多信息,請參閱流程安全和訪問權限。- lpThreadAttributes [in]
指向SECURITY_ATTRIBUTES結構的指針,該結構指定新線程的安全描述符,並確定子進程是否可以繼承返回的句柄。 如果lpThreadAttributes為NULL,則線程將獲得默認安全描述符,並且該句柄不能被繼承。 線程的默認安全描述符中的訪問控制列表(ACL)來自創建者的主令牌。dwStackSize [in]
堆棧的初始大小,以字節為單位。 系統將此值循環到最近的頁面。 如果此參數為0(零),則新線程使用可執行文件的默認大小。 有關更多信息,請參閱線程堆棧大小。- lpStartAddress [in]
指向由線程執行的類型為LPTHREAD_START_ROUTINE的應用程序定義函數的指針,並表示遠程進程中線程的起始地址。 該功能必須存在於遠程進程中。 有關更多信息,請參閱ThreadProc。- lpParameter [in]
指向要傳遞給線程函數的變量的指針。- dwCreationFlags [in]
控制線程創建的標志。若是 0,則表示線程在創建后立即運行。- lpThreadId [out]
指向接收線程標識符的變量的指針。
如果此參數為NULL,則不返回線程標識符。返回值
- 如果函數成功,則返回值是新線程的句柄。
- 如果函數失敗,返回值為NULL。 要獲取擴展錯誤信息,請調用GetLastError。
0x02 編碼實現
測試demo,指定進程pid和dll位置。
TestCreatePId.cpp
// TestCreatePId.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <iostream>
void InjectDLL(DWORD PID, char* Path)
{
DWORD dwSize;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
dwSize = strlen(Path) + 1;
LPVOID lpParamAddress = VirtualAllocEx(hProcess, 0, dwSize, PARITY_SPACE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, lpParamAddress, (PVOID)Path, dwSize, NULL);
HMODULE hModule = GetModuleHandleA("kernel32.dll");
LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, lpParamAddress, 0, NULL);
WaitForSingleObject(hThread, 1000);
CloseHandle(hThread);
}
int main(int argc,char *argv[])
{
char* a = argv[2];
//std::cout << atoi(argv[1]) << a <<std::endl;
InjectDLL(int(argv[1]),a);
//InjectDLL(atoi(argv[1]), "c:\\test\\TestDll.dll");
return 0;
}
0x03 實測
經測試對應位數的dll可以加載到對應位數的進程中。
不過遇到個問題,這里也記錄下,當想在同一進程載入兩個dll,如果先載入的dll有dllmain中,里面的函數沒有走完邏輯,那么第二個dll會在線程掛起結束之后載入到進程。比如我載入到第一個dll里面DLL_PROCESS_ATTACH有個MessageBox,我沒給他結束窗口,第二個dll沒有加載到進程中,而結束了MessageBox窗口才注入到了進程。

0x04 總結
部分進程需要以管理員權限才可注入到進程,具體原因是OpenProcess打開高權限進程會因權限不足無法打開,其實就是權限繼承到原因,這里如果以powershell默認啟動就會獲得SE_DEBUG權限,而進程令牌權限提升准備再挑一篇文章單獨總結學習。
另一個問題就是,不能成功注入到一些系統服務的進程,因為系統存在 SESSION 0 隔離。如果想向系統服務進程中注入,需要突破SESSION 0 隔離,這個也准備另啟一篇文章做記錄。
