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

news2024/11/19 19:32:17

远程线程注入是最常用的一种注入技术,在应用层注入是通过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进程内,输出效果图如下所示;

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/641211.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【模型评估】AP 和他们的兄弟们:mAP、AP50、APs、APm、APl

AP是在目标检测任务中&#xff0c;尝尝被用于评估模型预测能力的指标。那AP是什么&#xff1f;为什么能够充当不同模型综合对比评测的公认指标呢&#xff1f; 在学习下文之前&#xff0c;混淆矩阵和ROC可以先了解下&#xff1a; 【模型评估】混淆矩阵&#xff08;confusion_m…

世界中西医结合医学研究院一行莅临万民健康交流指导

为进一步发展中医药产业&#xff0c;深入挖掘中医药文化&#xff0c;坚持中西医并重&#xff0c;传承精华&#xff0c;守正创新&#xff0c;助力乡村振兴、促进乡村医疗产业发展。6 月 10 日 &#xff0c; 世界中西医结合医学研究院医学工程院院士罗先义 、谈家桢生命基金会主任…

测试左移及其相关实践

本文首发于个人网站「BY林子」&#xff0c;转载请参考版权声明。 之前在《敏捷测试的核心》、《构建测试的体系化思维&#xff08;进阶篇&#xff09;》和《一页纸测试策略》等文章中提到过测试左移&#xff0c;但是没有专门针对这个主题做过系统的介绍&#xff0c;但又总是被社…

M4内核的FPU/DSP使用总结

FPU简介 近年&#xff0c;在Cortex-M3之后ARM公司又推出Cortex-M4内核&#xff0c;ARM Cortex-M4处理器是由ARM专门开发的最新嵌入式处理器&#xff0c;在M3的基础上强化了运算能力&#xff0c;新加了浮点、DSP、并行计算等。Cortex-M4处理器的最大亮点之一&#xff0c;也是本文…

dom-to-image分享多张异步图片遇到的坑

dom-to-image库 存在的问题 github-issue地址 问题&#xff1a;当超过一张图片时&#xff0c;ios/safari首次会出现某张图片空白&#xff0c;再次生成canvas才正常。 之前有一张图片时通过执行2次domtoimage.toJpeg&#xff08;魔法&#xff09;&#xff0c;当超过1张图片时&a…

JavaWeb笔记(一)

Java网络编程 在JavaSE阶段&#xff0c;我们学习了I/O流&#xff0c;既然I/O流如此强大&#xff0c;那么能否跨越不同的主机进行I/O操作呢&#xff1f;这就要提到Java的网络编程了。 **注意&#xff1a;**本章会涉及到计算机网络相关内容&#xff08;只会讲解大致内容&#x…

PyTorch 深度学习 || 专题八:PyTorch 全连接网络分类

PyTorch 全连接网络分类 文章目录 PyTorch 全连接网络分类1. 非线性二分类2. 泰坦尼克号数据分类2.1 数据的准备工作2.2 全连接网络的搭建2.3 结果的可视化 1. 非线性二分类 import sklearn.datasets #数据集 import numpy as np import matplotlib.pyplot as plt from sklear…

Java企业级信息系统开发学习笔记(4.2)Spring Boot项目单元测试、热部署与原理分析

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/pG623】 文章目录 一、Spring Boot单元测试概述1.1 对项目HelloWorld01进行单元测试1. 添加测试依赖启动器和单元测试2. 创建测试类与测试方法 1.2 对项目HelloWorld02进行单元测试1. 添加单元测试…

C++冷知识:构造函数初始化时,为什么使用 : 而不是使用作用域内初始化对象?

:是什么&#xff1f; 这样的行为被称之为初始化列表。具体展示如下&#xff1a; 直接初始化对象。 以一个线程池类为例&#xff1a; class ThreadPool { public:// 构造函数&#xff0c;创建指定数量的线程ThreadPool(size_t num_threads) : stop(false){....}// 析构函数&…

【计算摄影学】总目录

1.数码相机 《数码相机中的图像传感器和信号处理》和《光与赢的魔幻乐园有趣的透镜》 1.1 数码相机概览 1.2 数码相机中光学系统 2.图像传感器 《数码相机中的图像传感器和信号处理》和《智能cmos图像传感器与应用》 2.1 图像传感器基础知识 2.2 CCD图像传感器 2.3 CMOS图像…

RankNet方法在移动终端的应用

RankNet方法在移动终端的应用 RankNet代码示例pythonJava 移动终端的应用 RankNet RankNet 是一种排序学习方法&#xff0c;由 Microsoft Research 提出&#xff0c;用于解决排序问题。它基于神经网络&#xff0c;并使用一对比较的方式来训练和优化模型。 在 RankNet 中&…

你的企业还没搭建这个帮助中心网页,那你太落后了!

作为现代企业&#xff0c;拥有一个完善的帮助中心网页已经成为了不可或缺的一部分。帮助中心网页不仅可以提供给用户有关产品或服务的详细信息&#xff0c;还可以解答用户的疑问和提供技术支持&#xff0c;使用户在使用产品或服务时遇到问题可以很快地得到解决。因此&#xff0…

内网隧道代理技术(四)之NETSH端口转发

NETSH端口转发 NETSH介绍 netsh是windows系统自带命令行程序&#xff0c;攻击者无需上传第三方工具即可利用netsh程序可进行端口转发操作&#xff0c;可将内网中其他服务器的端口转发至本地访问运行这个工具需要管理员的权限 本地端口转发 实验场景 现在我们有这么一个环境…

AntDB存储技术——水平动态扩展技术

数据库集群安装完成后&#xff0c;其数据存储容量是预先规划并确定的。随着时间的推移以及业务量的增加&#xff0c;数据库集群中的可用存储空间不断减少&#xff0c;面临数据存储容量扩充的需求。 通过增加数据节点&#xff0c;扩充集群数据容量&#xff0c;必然需要对已有数…

云服务器是什么? 云服务器有哪些选择?

欢迎前往我的个人博客云服务器查看更多关于云服务器和建站等相关文章。 随着互联网技术的发展和云计算技术的应用&#xff0c;越来越多的企业倾向于使用云服务器来满足其不断增长的计算需求。云服务器是一种基于云计算技术的虚拟服务器&#xff0c;它能够为企业提供高性能、可…

创业很长时间以后

创业过很长时间以后…综合能力是有滴 创业和打工后的思维习惯 为了效率&#xff0c;一般情况是这样滴 趣讲大白话&#xff1a;区别还是有滴 【趣讲信息科技195期】 **************************** 创业还是很难滴 每年成立很多新公司 有很多公司关门 公司平均生存时间&#xff1…

AntDB 企业增强特性介绍——AntDB在线数据扩容关键技术

数据库集群安装完成后&#xff0c;其数据存储容量是预先规划并确定的。随着时间的推移以及业务量的增加&#xff0c;数据库集群中的可用存储空间不断减少&#xff0c;面临数据存储容量扩充的需求。 传统的在线扩容的流程大致如下。 &#xff08;1&#xff09;在集群中加入新的 …

Golang | Web开发之Gin路由访问日志自定义输出实践

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; 专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享 “ 花开堪折直须折&#xf…

2022届本科毕业生10大高薪专业:大数据专业进入top3

对于普通人来讲&#xff0c;报考一个高薪的职业还是重中之重。那么什么专业高薪呢&#xff0c;很多人觉得是程序员&#xff0c;但这是职业而不是大学专业&#xff0c;专业千千万&#xff0c;选什么好呢&#xff0c;接下来看一看。 最近国家统计局发布了2022年城镇单位就业人员…

MMU翻译的时候以哪种level去执行是什么意思

【问题】 以哪个el去执行是什么意思&#xff1f;执行这条指令就会切到切换指令里指定的el吗&#xff1f; 【回答】 在一个core中&#xff0c;至少有一下Translation regime&#xff0c;AT S12E2R, <Xt> 就是使用EL2 Translation regime完成地址翻译。 Secure EL1&…