驱动开发:内核LoadLibrary实现DLL注入
远程线程注入是最常用的一种注入技术,在应用层注入是通过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: 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注入的更多相关文章
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- 【Spring注解驱动开发】自定义组件如何注入Spring底层的组件?看了这篇我才真正理解了原理!!
写在前面 最近,很多小伙伴出去面试都被问到了Spring问题,关于Spring,细节点很多,面试官也非常喜欢问一些很细节的技术点.所以,在 Spring 专题中,我们尽量把Spring的每个技术细节说 ...
- 行为驱动开发iOS <收藏>
前段时间在design+code购买了一个学习iOS设计和编码在线课程,使用Sketch设计App,然后使用Swift语言实现Designer News客户端.作者Meng To已经开源到Github ...
- 驱动开发:内核枚举ShadowSSDT基址
在笔者上一篇文章<驱动开发:Win10枚举完整SSDT地址表>实现了针对SSDT表的枚举功能,本章继续实现对SSSDT表的枚举,ShadowSSDT中文名影子系统服务描述表,SSSDT其主 ...
- 《Linux设备驱动开发具体解释(第3版)》(即《Linux设备驱动开发具体解释:基于最新的Linux 4.0内核》)网购链接
<Linux设备驱动开发具体解释:基于最新的Linux 4.0内核> china-pub spm=a1z10.3-b.w4011-10017777404.30.kvceXB&i ...
- 驱动开发:内核中实现Dump进程转储
多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导 ...
- 驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章<驱动开发:内核特征码搜索函数封装>中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核LoadImage映像回调,在Win64环境下我们可以设置一个 ...
- 驱动开发:内核监视LoadImage映像回调
在笔者上一篇文章<驱动开发:内核注册并监控对象回调>介绍了如何运用ObRegisterCallbacks注册进程与线程回调,并通过该回调实现了拦截指定进行运行的效果,本章LyShark将带 ...
- 驱动开发:内核运用LoadImage屏蔽驱动
在笔者上一篇文章<驱动开发:内核监视LoadImage映像回调>中LyShark简单介绍了如何通过PsSetLoadImageNotifyRoutine函数注册回调来监视驱动模块的加载,注 ...
- Windows内核安全与驱动开发
这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...
随机推荐
- ABC291题解(D-G)
ABC291 D - Flip Cards Solution: 考虑DP,定义状态\(F_{i,0}\)为第\(i\)张卡片正面朝上的方案数,\(F_{i,1}\)为第\(i\)张卡片背面朝上的方案数 ...
- NEFU-NSILAB2021选拔赛WriteUp
Web signin 打开看到源码: <?php highlight_file(__FILE__); $file = $_GET['file']; if ($file) { include $f ...
- day03-搭建微服务基础环境02
搭建微服务基础环境02 3.创建使用会员微服务模块-service consumer 3.1需求分析 浏览器向service consumer请求某个数据,service consumer会去向ser ...
- CentOS 的 YUM安装时卡死解决方案
YUM是基于RPM的软件包管理器 YUM is an RPM-based package manager 补充说明 Supplementary note yum命令 是在Fedora和RedHat以及 ...
- 二进制安装Kubernetes(k8s) v1.23.4
1.环境 网段 物理主机:192.168.1.0/24 service:10.96.0.0/12 pod:172.16.0.0/12 如果有条件建议k8s集群与etcd集群分开安装 1.1.k8s基础 ...
- kubernetes核心实战(三)--- ReplicationController
5.ReplicationController ReplicationController 确保在任何时候都有特定数量的 Pod 副本处于运行状态.换句话说,ReplicationController ...
- 有关Spring的ioc理解之代理模式
AOP代理模式可以实现事务控制和业务逻辑代码横切. 使用代理模式,动态代理实现横切. 什么是代理? 接口就是指定要做的事情,要实现的逻辑. 代理类似于房源租房 public interface ZuF ...
- TS 导入导出那些事
前言 最近用 TypeScript 写 npm 包,各种模块.命名空间.全局定义等等扰得我睡不着觉. 我便苦心研究,总结了几个比较冷门的,国内貌似基本上找不到资料的导入导出用法,顺便在其中又插入一些不 ...
- 开源.NetCore通用工具库Xmtool使用连载 - 加密解密篇
[Github源码] <上一篇>详细介绍了Xmtool工具库中的正则表达式类库,今天我们继续为大家介绍其中的加密解密类库. 在开发过程中我们经常会遇到需要对数据进行加密和解密的需求,例如密 ...
- Go语言微服务框架go-micro(入门)
Micro用于构建和管理分布式系统,是一个工具集,其中go-micro框架是对分布式系统的高度抽象,提供分布式系统开发的核心库,可插拔的架构,按需使用 简单示例 编写protobuf文件: synta ...