遠程執行緒注入是最常用的一種注入技術,在應用層注入是通過CreateRemoteThread這個函式實作的,該函式通過創建執行緒并呼叫 LoadLibrary 動態載入指定的DLL來實作注入,而在內核層同樣存在一個類似的內核函式RtlCreateUserThread,但需要注意的是此函式未被公開,RtlCreateUserThread其實是對NtCreateThreadEx的包裝,但最侄訓呼叫ZwCreateThread來實作注入,RtlCreateUserThread是CreateRemoteThread的底層實作,
基于LoadLibrary實作的注入原理可以具體分為如下幾步;
- 1.呼叫
AllocMemory,在對端應用層開辟空間,函式封裝來源于《內核遠程堆分配與銷毀》章節; - 2.呼叫
MDLWriteMemory,將DLL路徑字串寫出到對端記憶體,函式封裝來源于《內核MDL讀寫行程記憶體》章節; - 3.呼叫
GetUserModuleAddress,獲取到kernel32.dll模塊基址,函式封裝來源于《內核遠程執行緒實作DLL注入》章節; - 4.呼叫
GetModuleExportAddress,獲取到LoadLibraryW函式的記憶體地址,函式封裝來源于《內核遠程執行緒實作DLL注入》章節; - 5.最后呼叫本章封裝函式
MyCreateRemoteThread,將應用層DLL動態轉載到行程內,實作DLL注入;
總結起來就是首先在目標行程申請一塊空間,空間里面寫入要注入的DLL的路徑字串或者是一段ShellCode,找到該記憶體中LoadLibrary的基址并傳入到RtlCreateUserThread中,此時行程自動加載我們指定路徑下的DLL檔案,
注入依賴于RtlCreateUserThread這個未到處內核函式,該內核函式中最需要關心的引數是ProcessHandle用于接收行程句柄,StartAddress接收一個函式地址,StartParameter用于對函式傳遞引數,具體的函式原型如下所示;
typedef DWORD(WINAPI* pRtlCreateUserThread)(
IN HANDLE ProcessHandle, // 行程句柄
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN BOOL CreateSuspended,
IN ULONG StackZeroBits,
IN OUT PULONG StackReserved,
IN OUT PULONG StackCommit,
IN LPVOID StartAddress, // 執行函式地址 LoadLibraryW
IN LPVOID StartParameter, // 引數傳遞
OUT HANDLE ThreadHandle, // 執行緒句柄
OUT LPVOID ClientID
);
由于我們加載DLL使用的是LoadLibraryW函式,此函式在運行時只需要一個引數,我們可以將DLL的路徑傳遞進去,并呼叫LoadLibraryW以此來將特定模塊拉起,該函式的定義規范如下所示;
HMODULE LoadLibraryW(
[in] LPCWSTR lpLibFileName
);
根據上一篇文章中針對注入頭檔案lyshark.h的封裝,本章將繼續使用這個頭檔案中的函式,首先我們實作這樣一個功能,將一段準備好的UCHAR字串動態的寫出到應用層行程記憶體,并以寬位元組模式寫出在對端記憶體中,這段代碼可以寫為如下樣子;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
// 驅動卸載例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("Uninstall Driver \n");
}
// 驅動入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("Hello LyShark \n");
DWORD process_id = 7112;
DWORD create_size = 1024;
DWORD64 ref_address = 0;
// 分配記憶體堆 《內核遠程堆分配與銷毀》 核心代碼
NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);
DbgPrint("對端行程: %d \n", process_id);
DbgPrint("分配長度: %d \n", create_size);
DbgPrint("[*] 分配內核堆基址: %p \n", ref_address);
UCHAR DllPath[256] = "C:\\hook.dll";
UCHAR Item[256] = { 0 };
// 將位元組轉為雙字
for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
{
Item[x] = DllPath[y];
}
// 寫出記憶體 《內核MDL讀寫行程記憶體》 核心代碼
ReadMemoryStruct ptr;
ptr.pid = process_id;
ptr.address = ref_address;
ptr.size = strlen(DllPath) * 2;
// 需要寫入的資料
ptr.data = https://www.cnblogs.com/LyShark/archive/2023/06/13/ExAllocatePool(PagedPool, ptr.size);
// 回圈設定
for (int i = 0; i < ptr.size; i++)
{
ptr.data[i] = Item[i];
}
// 寫記憶體
MDLWriteMemory(&ptr);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
運行如上方所示的代碼,將會在目標行程7112中開辟一段記憶體空間,并寫出C:\hook.dll字串,運行效果圖如下所示;

此處你可以通過x64dbg附加到應用層行程內,并觀察記憶體0000000002200000會看到如下字串已被寫出,雙字型別則是每一個字符空一格,效果圖如下所示;

繼續實作所需要的子功能,實作動態獲取Kernel32.dll模塊里面LiadLibraryW這個匯出函式的記憶體地址,這段代碼相信你可以很容易的寫出來,根據上節課的知識點我們可以二次封裝一個GetProcessAddress來實作對特定模塊基址的獲取功能,如下是完整代碼案例;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
// 實作取模塊基址
PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
{
PEPROCESS EProcess = NULL;
NTSTATUS Status = STATUS_SUCCESS;
KAPC_STATE ApcState;
PVOID RefAddress = 0;
// 根據PID得到行程EProcess結構
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS)
{
return Status;
}
// 判斷目標行程是32位還是64位
BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
// 驗證地址是否可讀
if (!MmIsAddressValid(EProcess))
{
return NULL;
}
// 將當前執行緒連接到目標行程的地址空間(附加行程)
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try
{
UNICODE_STRING DllUnicodeString = { 0 };
PVOID BaseAddress = NULL;
// 得到行程內模塊基地址
RtlInitUnicodeString(&DllUnicodeString, DllName);
BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);
if (!BaseAddress)
{
return NULL;
}
DbgPrint("[*] 模塊基址: %p \n", BaseAddress);
// 得到該函式地址
RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
DbgPrint("[*] 函式地址: %p \n", RefAddress);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
// 取消附加
KeUnstackDetachProcess(&ApcState);
return RefAddress;
}
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動卸載 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
// 取模塊基址
PVOID pLoadLibraryW = GetProcessAddress(5200, L"kernel32.dll", "LoadLibraryW");
DbgPrint("[*] 所在記憶體地址 = %p \n", pLoadLibraryW);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯并運行如上驅動代碼,將自動獲取PID=5200行程中Kernel32.dll模塊內的LoadLibraryW的記憶體地址,輸出效果圖如下所示;

實作注入的最后一步就是呼叫自定義函式MyCreateRemoteThread該函式實作原理是呼叫RtlCreateUserThread開執行緒執行,這段代碼的最終實作如下所示;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
// 定義函式指標
typedef PVOID(NTAPI* PfnRtlCreateUserThread)
(
IN HANDLE ProcessHandle,
IN PSECURITY_DESCRIPTOR SecurityDescriptor,
IN BOOLEAN CreateSuspended,
IN ULONG StackZeroBits,
IN OUT size_t StackReserved,
IN OUT size_t StackCommit,
IN PVOID StartAddress,
IN PVOID StartParameter,
OUT PHANDLE ThreadHandle,
OUT PCLIENT_ID ClientID
);
// 實作取模塊基址
PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
{
PEPROCESS EProcess = NULL;
NTSTATUS Status = STATUS_SUCCESS;
KAPC_STATE ApcState;
PVOID RefAddress = 0;
// 根據PID得到行程EProcess結構
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS)
{
return Status;
}
// 判斷目標行程是32位還是64位
BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
// 驗證地址是否可讀
if (!MmIsAddressValid(EProcess))
{
return NULL;
}
// 將當前執行緒連接到目標行程的地址空間(附加行程)
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try
{
UNICODE_STRING DllUnicodeString = { 0 };
PVOID BaseAddress = NULL;
// 得到行程內模塊基地址
RtlInitUnicodeString(&DllUnicodeString, DllName);
BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);
if (!BaseAddress)
{
return NULL;
}
DbgPrint("[*] 模塊基址: %p \n", BaseAddress);
// 得到該函式地址
RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
DbgPrint("[*] 函式地址: %p \n", RefAddress);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
// 取消附加
KeUnstackDetachProcess(&ApcState);
return RefAddress;
}
// 遠程執行緒注入函式
BOOLEAN MyCreateRemoteThread(ULONG pid, PVOID pRing3Address, PVOID PParam)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PEPROCESS pEProcess = NULL;
KAPC_STATE ApcState = { 0 };
PfnRtlCreateUserThread RtlCreateUserThread = NULL;
HANDLE hThread = 0;
__try
{
// 獲取RtlCreateUserThread函式的記憶體地址
UNICODE_STRING ustrRtlCreateUserThread;
RtlInitUnicodeString(&ustrRtlCreateUserThread, L"RtlCreateUserThread");
RtlCreateUserThread = (PfnRtlCreateUserThread)MmGetSystemRoutineAddress(&ustrRtlCreateUserThread);
if (RtlCreateUserThread == NULL)
{
return FALSE;
}
// 根據行程PID獲取行程EProcess結構
status = PsLookupProcessByProcessId((HANDLE)pid, &pEProcess);
if (!NT_SUCCESS(status))
{
return FALSE;
}
// 附加到目標行程內
KeStackAttachProcess(pEProcess, &ApcState);
// 驗證行程是否可讀寫
if (!MmIsAddressValid(pRing3Address))
{
return FALSE;
}
// 啟動注入執行緒
status = RtlCreateUserThread(ZwCurrentProcess(),
NULL,
FALSE,
0,
0,
0,
pRing3Address,
PParam,
&hThread,
NULL);
if (!NT_SUCCESS(status))
{
return FALSE;
}
return TRUE;
}
__finally
{
// 釋放物件
if (pEProcess != NULL)
{
ObDereferenceObject(pEProcess);
pEProcess = NULL;
}
// 取消附加行程
KeUnstackDetachProcess(&ApcState);
}
return FALSE;
}
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動卸載 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
ULONG process_id = 5200;
DWORD create_size = 1024;
DWORD64 ref_address = 0;
// -------------------------------------------------------
// 取模塊基址
// -------------------------------------------------------
PVOID pLoadLibraryW = GetProcessAddress(process_id, L"kernel32.dll", "LoadLibraryW");
DbgPrint("[*] 所在記憶體地址 = %p \n", pLoadLibraryW);
// -------------------------------------------------------
// 應用層開堆
// -------------------------------------------------------
NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);
DbgPrint("對端行程: %d \n", process_id);
DbgPrint("分配長度: %d \n", create_size);
DbgPrint("分配的內核堆基址: %p \n", ref_address);
// 設定注入路徑,轉換為多位元組
UCHAR DllPath[256] = "C:\\lyshark_hook.dll";
UCHAR Item[256] = { 0 };
for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
{
Item[x] = DllPath[y];
}
// -------------------------------------------------------
// 寫出資料到記憶體
// -------------------------------------------------------
ReadMemoryStruct ptr;
ptr.pid = process_id;
ptr.address = ref_address;
ptr.size = strlen(DllPath) * 2;
// 需要寫入的資料
ptr.data = https://www.cnblogs.com/LyShark/archive/2023/06/13/ExAllocatePool(PagedPool, ptr.size);
// 回圈設定
for (int i = 0; i < ptr.size; i++)
{
ptr.data[i] = Item[i];
}
// 寫記憶體
MDLWriteMemory(&ptr);
// -------------------------------------------------------
// 執行開執行緒函式
// -------------------------------------------------------
// 執行執行緒注入
// 引數1:PID
// 引數2:LoadLibraryW記憶體地址
// 引數3:當前DLL路徑
BOOLEAN flag = MyCreateRemoteThread(process_id, pLoadLibraryW, ref_address);
if (flag == TRUE)
{
DbgPrint("[*] 已完成行程 %d 注入檔案 %s \n", process_id, DllPath);
}
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯這段驅動程式,并將其放入虛擬機中,在C盤下面放置好一個名為lyshark_hook.dll檔案,運行驅動程式將自動插入DLL到Win32Project行程內,輸出效果圖如下所示;

回到應用層行程,則可看到如下圖所示的注入成功提示資訊;

文章出處:https://www.cnblogs.com/LyShark/p/17170818.html
本博客所有文章除特別宣告外,均采用 BY-NC-SA 許可協議,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/555128.html
標籤:其他
上一篇:里氏替換原則究竟如何理解?
下一篇:返回列表
