驱动开发:内核LoadLibrary实现DLL注入

驱动,开发,内核,loadlibrary,实现,dll,注入 · 浏览次数 : 482

小编点评

**内容生成过程** 1. **获取模块基地址** - 使用 `GetProcessAddress` 获取进程 ID 的 `kernel32.dll` 模块地址。 2. **应用层开堆** - 使用 `AllocMemory` 获取内存地址。 3. **写入数据到内存** - 使用 `ExAllocatePool` 创建内存并写入数据。 4. **执行开线程函数** - 使用 `MyCreateRemoteThread` 创建一个远程线程,执行线程注入。 5. **输出结果** - 编写结果并输出到C盘中。 **代码** ```c #include #include #include #include // ...模块基地址获取函数... // ...应用层开堆函数... // ...写入数据到内存函数... // ...执行开线程函数函数... // ...输出结果函数... // ...创建远程线程函数... // ...输出结果函数... ``` **注入成功提示信息** ``` Hello LyShark.com C:\lyshark_hook.dll 注入成功 ```

正文

远程线程注入是最常用的一种注入技术,在应用层注入是通过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: me@lyshark.com

#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: me@lyshark.com

#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: me@lyshark.com

#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进程内,输出效果图如下所示;

回到应用层进程,则可看到如下图所示的注入成功提示信息;

与驱动开发:内核LoadLibrary实现DLL注入相似的内容:

驱动开发:内核LoadLibrary实现DLL注入

远程线程注入是最常用的一种注入技术,在应用层注入是通过`CreateRemoteThread`这个函数实现的,通过该函数通过创建线程并调用 `LoadLibrary` 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数`RtlCreateUserThread`,但需要注意的是此函数未被公开,`RtlCreateUserThread`其实是对`NtCreateThreadEx`的包

驱动开发:内核ShellCode线程注入

还记得`《驱动开发:内核LoadLibrary实现DLL注入》`中所使用的注入技术吗,我们通过`RtlCreateUserThread`函数调用实现了注入DLL到应用层并执行,本章将继续探索一个简单的问题,如何注入`ShellCode`代码实现反弹Shell,这里需要注意一般情况下`RtlCreateUserThread`需要传入两个最重要的参数,一个是`StartAddress`开始执行的内存块

驱动开发:内核枚举ShadowSSDT基址

在笔者上一篇文章`《驱动开发:Win10枚举完整SSDT地址表》`实现了针对`SSDT`表的枚举功能,本章继续实现对`SSSDT`表的枚举,ShadowSSDT中文名`影子系统服务描述表`,SSSDT其主要的作用是管理系统中的图形化界面,其`Win32`子系统的内核实现是`Win32k.sys`驱动,属于GUI线程的一部分,其自身没有导出表,枚举`SSSDT`表其与`SSDT`原理基本一致。

驱动开发:内核枚举LoadImage映像回调

在笔者之前的文章`《驱动开发:内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。

驱动开发:内核枚举Registry注册表回调

在笔者上一篇文章`《驱动开发:内核枚举LoadImage映像回调》`中`LyShark`教大家实现了枚举系统回调中的`LoadImage`通知消息,本章将实现对`Registry`注册表通知消息的枚举,与`LoadImage`消息不同`Registry`消息不需要解密只要找到`CallbackListHead`消息回调链表头并解析为`_CM_NOTIFY_ENTRY`结构即可实现枚举。

驱动开发:内核枚举进程与线程ObCall回调

在笔者上一篇文章`《驱动开发:内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。

驱动开发:内核测试模式过DSE签名

微软在`x64`系统中推出了`DSE`保护机制,DSE全称`(Driver Signature Enforcement)`,该保护机制的核心就是任何驱动程序或者是第三方驱动如果想要在正常模式下被加载则必须要经过微软的认证,当驱动程序被加载到内存时会验证签名的正确性,如果签名不正常则系统会拒绝运行驱动,这种机制也被称为驱动强制签名,该机制的作用是保护系统免受恶意软件的破坏,是提高系统安全性的一种手段

驱动开发:内核监控进程与线程回调

在前面的文章中`LyShark`一直在重复的实现对系统底层模块的枚举,今天我们将展开一个新的话题,内核监控,我们以`监控进程线程`创建为例,在`Win10`系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现,此类函数的原理是创建一个回调事件,当有进程或线程被创建或者注销时,系统会通过回调机制将该进程相关信息优先返回给我们自己的函数待处理结束后再转向系统层。

驱动开发:内核注册并监控对象回调

在笔者上一篇文章`《驱动开发:内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止

驱动开发:内核无痕隐藏自身分析

在笔者前面有一篇文章`《驱动开发:断链隐藏驱动程序自身》`通过摘除驱动的链表实现了断链隐藏自身的目的,但此方法恢复时会触发PG会蓝屏,偶然间在网上找到了一个作者介绍的一种方法,觉得有必要详细分析一下他是如何实现的驱动隐藏的,总体来说作者的思路是最终寻找到`MiProcessLoaderEntry`的入口地址,该函数的作用是将驱动信息加入链表和移除链表,运用这个函数即可动态处理驱动的添加和移除问题。