遠端執行緒注入是最常用的一種注入技術,在應用層注入是通過CreateRemoteThread
這個函數實現的,該函數通過建立執行緒並呼叫 LoadLibrary
動態載入指定的DLL來實現注入,而在核心層同樣存在一個類似的核心函數RtlCreateUserThread
,但需要注意的是此函數未被公開,RtlCreateUserThread
其實是對NtCreateThreadEx
的包裝,但最終會呼叫ZwCreateThread
來實現注入,RtlCreateUserThread
是CreateRemoteThread
的底層實現。
基於LoadLibrary實現的注入原理可以具體分為如下幾步;
AllocMemory
,在對端應用層開闢空間,函數封裝來源於《核心遠端堆分配與銷燬》
章節;MDLWriteMemory
,將DLL路徑字串寫出到對端記憶體,函數封裝來源於《核心MDL讀寫程序記憶體》
章節;GetUserModuleAddress
,獲取到kernel32.dll
模組基址,函數封裝來源於《核心遠端執行緒實現DLL注入》
章節;GetModuleExportAddress
,獲取到LoadLibraryW
函數的記憶體地址,函數封裝來源於《核心遠端執行緒實現DLL注入》
章節;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 = 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 = 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
程序內,輸出效果圖如下所示;
回到應用層程序,則可看到如下圖所示的注入成功提示資訊;