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

2023-12-02 07:01

本文主要是介绍6.7 Windows驱动开发:内核枚举LoadImage映像回调,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

LoadImage映像回调是Windows操作系统提供的一种机制,它允许开发者在加载映像文件(如DLL、EXE等)时拦截并修改映像的加载过程。LoadImage映像回调是通过操作系统提供的ImageLoad事件机制来实现的。

当操作系统加载映像文件时,它会调用LoadImage函数。在LoadImage函数内部,操作系统会触发ImageLoad事件,然后在ImageLoad事件中调用注册的LoadImage映像回调函数。开发者可以在LoadImage映像回调函数中执行自定义的逻辑,例如修改映像文件的内容,或者阻止映像文件的加载。

LoadImage映像回调可以通过Win32 API函数SetImageLoadCallback或者操作系统提供的驱动程序回调函数PsSetLoadImageNotifyRoutine来进行注册。同时,LoadImage映像回调函数需要遵守一定的约束条件,例如必须是非分页代码,不能调用一些内核API函数等。

我们来看一款闭源ARK工具是如何实现的:

如上所述,如果我们需要拿到回调数组那么首先要得到该数组,数组的符号名是PspLoadImageNotifyRoutine我们可以在PsSetLoadImageNotifyRoutineEx中找到。

第一步使用WinDBG输入uf PsSetLoadImageNotifyRoutineEx首先定位到,能够找到PsSetLoadImageNotifyRoutineEx这里的两个位置都可以被引用,当然了这个函数可以直接通过PsSetLoadImageNotifyRoutineEx函数动态拿到此处不需要我们动态定位。

我们通过获取到PsSetLoadImageNotifyRoutineEx函数的内存首地址,然后向下匹配特征码搜索找到488d0d88e8dbff并取出PspLoadImageNotifyRoutine内存地址,该内存地址就是LoadImage映像模块的基址。

如果使用代码去定位这段空间,则你可以这样写,这样即可得到具体特征地址。

#include <ntddk.h>
#include <windef.h>// 指定内存区域的特征码扫描
PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize)
{PVOID pAddress = NULL;PUCHAR i = NULL;ULONG m = 0;// 扫描内存for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++){// 判断特征码for (m = 0; m < ulMemoryDataSize; m++){if (*(PUCHAR)(i + m) != pMemoryData[m]){break;}}// 判断是否找到符合特征码的地址if (m >= ulMemoryDataSize){// 找到特征码位置, 获取紧接着特征码的下一地址pAddress = (PVOID)(i + ulMemoryDataSize);break;}}return pAddress;
}// 根据特征码获取 PspLoadImageNotifyRoutine 数组地址
PVOID SearchPspLoadImageNotifyRoutine(PUCHAR pSpecialData, ULONG ulSpecialDataSize)
{UNICODE_STRING ustrFuncName;PVOID pAddress = NULL;LONG lOffset = 0;PVOID pPsSetLoadImageNotifyRoutine = NULL;PVOID pPspLoadImageNotifyRoutine = NULL;// 先获取 PsSetLoadImageNotifyRoutineEx 函数地址RtlInitUnicodeString(&ustrFuncName, L"PsSetLoadImageNotifyRoutineEx");pPsSetLoadImageNotifyRoutine = MmGetSystemRoutineAddress(&ustrFuncName);if (NULL == pPsSetLoadImageNotifyRoutine){return pPspLoadImageNotifyRoutine;}// 查找 PspLoadImageNotifyRoutine  函数地址pAddress = SearchMemory(pPsSetLoadImageNotifyRoutine, (PVOID)((PUCHAR)pPsSetLoadImageNotifyRoutine + 0xFF), pSpecialData, ulSpecialDataSize);if (NULL == pAddress){return pPspLoadImageNotifyRoutine;}// 先获取偏移, 再计算地址lOffset = *(PLONG)pAddress;pPspLoadImageNotifyRoutine = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset);return pPspLoadImageNotifyRoutine;
}VOID UnDriver(PDRIVER_OBJECT Driver)
{
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");PVOID pPspLoadImageNotifyRoutineAddress = NULL;RTL_OSVERSIONINFOW osInfo = { 0 };UCHAR pSpecialData[50] = { 0 };ULONG ulSpecialDataSize = 0;// 获取系统版本信息, 判断系统版本RtlGetVersion(&osInfo);if (10 == osInfo.dwMajorVersion){// 48 8d 0d 88 e8 db ff// 查找指令 lea rcx,[nt!PspLoadImageNotifyRoutine (fffff804`44313ce0)]/*nt!PsSetLoadImageNotifyRoutineEx+0x41:fffff801`80748a81 488d0dd8d3dbff  lea     rcx,[nt!PspLoadImageNotifyRoutine (fffff801`80505e60)]fffff801`80748a88 4533c0          xor     r8d,r8dfffff801`80748a8b 488d0cd9        lea     rcx,[rcx+rbx*8]fffff801`80748a8f 488bd7          mov     rdx,rdifffff801`80748a92 e80584a3ff      call    nt!ExCompareExchangeCallBack (fffff801`80180e9c)fffff801`80748a97 84c0            test    al,alfffff801`80748a99 0f849f000000    je      nt!PsSetLoadImageNotifyRoutineEx+0xfe (fffff801`80748b3e)  Branch*/pSpecialData[0] = 0x48;pSpecialData[1] = 0x8D;pSpecialData[2] = 0x0D;ulSpecialDataSize = 3;}// 根据特征码获取地址 获取 PspLoadImageNotifyRoutine 数组地址pPspLoadImageNotifyRoutineAddress = SearchPspLoadImageNotifyRoutine(pSpecialData, ulSpecialDataSize);DbgPrint("[LyShark] PspLoadImageNotifyRoutine = 0x%p \n", pPspLoadImageNotifyRoutineAddress);Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

将这个驱动拖入到虚拟机中并运行,输出结果如下:

有了数组地址接下来就是要对数组进行解密,如何解密?

  • 1.首先拿到数组指针pPspLoadImageNotifyRoutineAddress + sizeof(PVOID) * i此处的i也就是下标。
  • 2.得到的新地址在与pNotifyRoutineAddress & 0xfffffffffffffff8进行与运算。
  • 3.最后*(PVOID *)pNotifyRoutineAddress取出里面的参数。

增加解密代码以后,这段程序的完整代码也就可以被写出来了,如下所示。

#include <ntddk.h>
#include <windef.h>// 指定内存区域的特征码扫描
PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize)
{PVOID pAddress = NULL;PUCHAR i = NULL;ULONG m = 0;// 扫描内存for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++){// 判断特征码for (m = 0; m < ulMemoryDataSize; m++){if (*(PUCHAR)(i + m) != pMemoryData[m]){break;}}// 判断是否找到符合特征码的地址if (m >= ulMemoryDataSize){// 找到特征码位置, 获取紧接着特征码的下一地址pAddress = (PVOID)(i + ulMemoryDataSize);break;}}return pAddress;
}// 根据特征码获取 PspLoadImageNotifyRoutine 数组地址
PVOID SearchPspLoadImageNotifyRoutine(PUCHAR pSpecialData, ULONG ulSpecialDataSize)
{UNICODE_STRING ustrFuncName;PVOID pAddress = NULL;LONG lOffset = 0;PVOID pPsSetLoadImageNotifyRoutine = NULL;PVOID pPspLoadImageNotifyRoutine = NULL;// 先获取 PsSetLoadImageNotifyRoutineEx 函数地址RtlInitUnicodeString(&ustrFuncName, L"PsSetLoadImageNotifyRoutineEx");pPsSetLoadImageNotifyRoutine = MmGetSystemRoutineAddress(&ustrFuncName);if (NULL == pPsSetLoadImageNotifyRoutine){return pPspLoadImageNotifyRoutine;}// 查找 PspLoadImageNotifyRoutine  函数地址pAddress = SearchMemory(pPsSetLoadImageNotifyRoutine, (PVOID)((PUCHAR)pPsSetLoadImageNotifyRoutine + 0xFF), pSpecialData, ulSpecialDataSize);if (NULL == pAddress){return pPspLoadImageNotifyRoutine;}// 先获取偏移, 再计算地址lOffset = *(PLONG)pAddress;pPspLoadImageNotifyRoutine = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset);return pPspLoadImageNotifyRoutine;
}// 移除回调
NTSTATUS RemoveNotifyRoutine(PVOID pNotifyRoutineAddress)
{NTSTATUS status = PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)pNotifyRoutineAddress);return status;
}VOID UnDriver(PDRIVER_OBJECT Driver)
{
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");PVOID pPspLoadImageNotifyRoutineAddress = NULL;RTL_OSVERSIONINFOW osInfo = { 0 };UCHAR pSpecialData[50] = { 0 };ULONG ulSpecialDataSize = 0;// 获取系统版本信息, 判断系统版本RtlGetVersion(&osInfo);if (10 == osInfo.dwMajorVersion){// 48 8d 0d 88 e8 db ff// 查找指令 lea rcx,[nt!PspLoadImageNotifyRoutine (fffff804`44313ce0)]/*nt!PsSetLoadImageNotifyRoutineEx+0x41:fffff801`80748a81 488d0dd8d3dbff  lea     rcx,[nt!PspLoadImageNotifyRoutine (fffff801`80505e60)]fffff801`80748a88 4533c0          xor     r8d,r8dfffff801`80748a8b 488d0cd9        lea     rcx,[rcx+rbx*8]fffff801`80748a8f 488bd7          mov     rdx,rdifffff801`80748a92 e80584a3ff      call    nt!ExCompareExchangeCallBack (fffff801`80180e9c)fffff801`80748a97 84c0            test    al,alfffff801`80748a99 0f849f000000    je      nt!PsSetLoadImageNotifyRoutineEx+0xfe (fffff801`80748b3e)  Branch*/pSpecialData[0] = 0x48;pSpecialData[1] = 0x8D;pSpecialData[2] = 0x0D;ulSpecialDataSize = 3;}// 根据特征码获取地址 获取 PspLoadImageNotifyRoutine 数组地址pPspLoadImageNotifyRoutineAddress = SearchPspLoadImageNotifyRoutine(pSpecialData, ulSpecialDataSize);DbgPrint("[LyShark] PspLoadImageNotifyRoutine = 0x%p \n", pPspLoadImageNotifyRoutineAddress);// 遍历回调ULONG i = 0;PVOID pNotifyRoutineAddress = NULL;// 获取 PspLoadImageNotifyRoutine 数组地址if (NULL == pPspLoadImageNotifyRoutineAddress){return FALSE;}// 获取回调地址并解密for (i = 0; i < 64; i++){pNotifyRoutineAddress = *(PVOID *)((PUCHAR)pPspLoadImageNotifyRoutineAddress + sizeof(PVOID) * i);pNotifyRoutineAddress = (PVOID)((ULONG64)pNotifyRoutineAddress & 0xfffffffffffffff8);if (MmIsAddressValid(pNotifyRoutineAddress)){pNotifyRoutineAddress = *(PVOID *)pNotifyRoutineAddress;DbgPrint("[LyShark] 序号: %d | 回调地址: 0x%p \n", i, pNotifyRoutineAddress);}}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行这段完整的程序代码,输出如下效果:

目前系统中只有两个回调,所以枚举出来的只有两条,打开ARK验证一下会发现完全正确,忽略pyark这是后期打开的。

这篇关于6.7 Windows驱动开发:内核枚举LoadImage映像回调的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

这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

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

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

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

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

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

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

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

hdu 2489 (dfs枚举 + prim)

题意: 对于一棵顶点和边都有权值的树,使用下面的等式来计算Ratio 给定一个n 个顶点的完全图及它所有顶点和边的权值,找到一个该图含有m 个顶点的子图,并且让这个子图的Ratio 值在所有m 个顶点的树中最小。 解析: 因为数据量不大,先用dfs枚举搭配出m个子节点,算出点和,然后套个prim算出边和,每次比较大小即可。 dfs没有写好,A的老泪纵横。 错在把index在d

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。