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

2023-11-20 17:40

本文主要是介绍驱动开发:内核LoadLibrary实现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,          // 执行函数地址 LoadLibraryWIN 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注入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/396322

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time