遠程線程註入是最常用的一種註入技術,在應用層註入是通過`CreateRemoteThread`這個函數實現的,通過該函數通過創建線程並調用 `LoadLibrary` 動態載入指定的DLL來實現註入,而在內核層同樣存在一個類似的內核函數`RtlCreateUserThread`,但需要註意的是此函數... ...
遠程線程註入是最常用的一種註入技術,在應用層註入是通過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 = 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
進程內,輸出效果圖如下所示;
回到應用層進程,則可看到如下圖所示的註入成功提示信息;
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/17170818.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!