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

相关文章

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

基于Java开发一个极简版敏感词检测工具

《基于Java开发一个极简版敏感词检测工具》这篇文章主要为大家详细介绍了如何基于Java开发一个极简版敏感词检测工具,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录你是否还在为敏感词检测头疼一、极简版Java敏感词检测工具的3大核心优势1.1 优势1:DFA算法驱动,效率提升10

Kotlin 枚举类使用举例

《Kotlin枚举类使用举例》枚举类(EnumClasses)是Kotlin中用于定义固定集合值的特殊类,它表示一组命名的常量,每个枚举常量都是该类的单例实例,接下来通过本文给大家介绍Kotl... 目录一、编程枚举类核心概念二、基础语法与特性1. 基本定义2. 带参数的枚举3. 实现接口4. 内置属性三、

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

C#之枚举类型与随机数详解

《C#之枚举类型与随机数详解》文章讲解了枚举类型的定义与使用方法,包括在main外部声明枚举,用于表示游戏状态和周几状态,枚举值默认从0开始递增,也可手动设置初始值以生成随机数... 目录枚举类型1.定义枚举类型(main外)2.使用生成随机数总结枚举类型1.定义枚举类型(main外)enum 类型名字

Java 与 LibreOffice 集成开发指南(环境搭建及代码示例)

《Java与LibreOffice集成开发指南(环境搭建及代码示例)》本文介绍Java与LibreOffice的集成方法,涵盖环境配置、API调用、文档转换、UNO桥接及REST接口等技术,提供... 目录1. 引言2. 环境搭建2.1 安装 LibreOffice2.2 配置 Java 开发环境2.3 配

C语言自定义类型之联合和枚举解读

《C语言自定义类型之联合和枚举解读》联合体共享内存,大小由最大成员决定,遵循对齐规则;枚举类型列举可能值,提升可读性和类型安全性,两者在C语言中用于优化内存和程序效率... 目录一、联合体1.1 联合体类型的声明1.2 联合体的特点1.2.1 特点11.2.2 特点21.2.3 特点31.3 联合体的大小1

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3