驅動開發:內核LoadLibrary實現DLL註入

来源:https://www.cnblogs.com/LyShark/archive/2023/06/13/17170818.html
-Advertisement-
Play Games

遠程線程註入是最常用的一種註入技術,在應用層註入是通過`CreateRemoteThread`這個函數實現的,通過該函數通過創建線程並調用 `LoadLibrary` 動態載入指定的DLL來實現註入,而在內核層同樣存在一個類似的內核函數`RtlCreateUserThread`,但需要註意的是此函數... ...


遠程線程註入是最常用的一種註入技術,在應用層註入是通過CreateRemoteThread這個函數實現的,該函數通過創建線程並調用 LoadLibrary 動態載入指定的DLL來實現註入,而在內核層同樣存在一個類似的內核函數RtlCreateUserThread,但需要註意的是此函數未被公開,RtlCreateUserThread其實是對NtCreateThreadEx的包裝,但最終會調用ZwCreateThread來實現註入,RtlCreateUserThreadCreateRemoteThread的底層實現。

基於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 許可協議。轉載請註明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本期直播《“元”來如此,“服務”直達——揭秘鴻蒙新流量陣地》聚焦**元服務**的**商業流量價值**,介紹元服務提供的服務直達和卡片動態變化等**輕量化服務**。網約停車旗艦平臺小強停車做客直播間,分享小強停車在HarmonyOS生態中,如何通過元服務為廣大用戶帶來更加便捷易用的線上預約停車體驗。快 ...
  • ## 顏色 1. **RGB** (紅,綠,藍)三種顏色的集合,最低值是0(十六進位00)到最高值255(十六進位FF) 2. **HSL** H色相(0-360),S飽和度(百分比),L亮度(百分比) 3. (不)透明度 **rgba、hsla** (新版瀏覽器可不寫a,直接寫4個值) ## li ...
  • ### 前言 前不久,在我的一個項目中,需要展示一個橫向滾動的標簽頁,它支持滑鼠橫向拖動和點擊切換。在實現的過程中,我發現這個小功能需要同時用到前端的三輛馬車,但是實現難度不高,而且最終效果還不錯,是個難得的初學者項目,於是萌生了寫這篇文章的想法,希望對初學者有所幫助。同時為了避免初學者學習框架,我 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 使用背景: 1.因為svg圖標在任何設備下都可以高清顯示,不會模糊。而icon會在顯卡比較低的電腦上有顯示模糊的情況 2.svg圖標在頁面render時 速度會比icon稍微快一點 3.實現小程式換膚功能 ;方案見:www.yuque.c ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • 前幾天打算給博客添加一個圖片預覽的效果,可在網上找了半天也沒找到合適的庫,於是自己乾脆自己手寫了個。 ...
  • # CSS特性 ## 1、繼承性 ##### 特性: 1、子元素有預設繼承父元素樣式的特點(**子承父業**) 2、可以繼承的常見屬性(文字控制屬性都可以繼承) 1.color 2.font-style、font-weight、font-size、font-family 3.text-indent, ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...