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

news2025/1/20 1:41:01

在笔者之前的文章《内核特征码搜索函数封装》中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核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,r8d
        fffff801`80748a8b 488d0cd9        lea     rcx,[rcx+rbx*8]
        fffff801`80748a8f 488bd7          mov     rdx,rdi
        fffff801`80748a92 e80584a3ff      call    nt!ExCompareExchangeCallBack (fffff801`80180e9c)
        fffff801`80748a97 84c0            test    al,al
        fffff801`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,r8d
        fffff801`80748a8b 488d0cd9        lea     rcx,[rcx+rbx*8]
        fffff801`80748a8f 488bd7          mov     rdx,rdi
        fffff801`80748a92 e80584a3ff      call    nt!ExCompareExchangeCallBack (fffff801`80180e9c)
        fffff801`80748a97 84c0            test    al,al
        fffff801`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这是后期打开的。

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

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

相关文章

HTML块元素和行内元素

HTML块元素和行内元素 1.分类2.块元素3.行内元素 1.分类 在HTML中&#xff0c;根据元素的表现形式&#xff0c;一般可以分为两类&#xff1a; 块元素&#xff08;block&#xff09;行内元素&#xff08;inline&#xff09; 2.块元素 在HTML中&#xff0c;块元素在浏览器显示…

制作一个RISC-V的操作系统二-RISC-V ISA介绍

文章目录 ISA的基本介绍啥是ISA为什么要设计ISACISCvsRISCISA的宽度知名ISA介绍 RISC-V历史和特点RISC-V发展RISC-V ISA 命名规范模块化的ISA通用寄存器Hart特权级别Control and Status Register&#xff08;CSR&#xff09;内存管理与保护异常和中断 ISA的基本介绍 啥是ISA …

单显卡插槽安装英伟达Tesla P4 AI加速卡

Tesla P4是专业AI显卡&#xff0c;只有70瓦功耗&#xff0c;可以作为AI入门使用。 安装时碰到的几个问题&#xff1a; 首先因为单显卡插槽&#xff0c;就需要先安装好机器&#xff0c;然后ssh登录进行相关配置。安装的时候来回插拔了好多次&#xff01; 其次就是安装驱动时&a…

【java毕业设计源码】基于SSM框架的在线智能题库管理系统设计与实现

该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等学习内容。 目录 一、项目介绍&#xff1a; 二、文档学习资料&#xff1a; 三、模块截图&#xff1a; 四、开发技术与运行环境&#xff1a; 五、代码展示&#xff1a; 六、数据库表截图&#xff1a…

麒麟系统添加环境变量

环境变量添加方法 方法一&#xff1a;用户主目录下的.profile或.bashrc文件&#xff08;推荐&#xff09; 登录到你的用户&#xff08;非root&#xff09;&#xff0c;在终端输入&#xff1a; sudo vim ~/.profile 或者 sudo vim ~/.bashrc 翻到该文件最后&#xff0c…

位图布隆过滤器(附面试题)

文章目录 目录 文章目录 前言 一 . 位图 1.1 面试题 1.2 位图概念 1.3 位图的实现 1.4 位图的应用 二 . 布隆过滤器 2.1 布隆过滤器提出 2.2布隆过滤器概念 2.3 布隆过滤器的查找 2.4 实现 2.5 布隆过滤器删除 2.6 布隆过滤器优点 2.7 布隆过滤器缺陷 2.8 布隆过滤器使用场景 三…

解决ZED SDK安装后不可用,出现“核心已转储”的闪退问题

在陈述问题简单回顾下ZED SDK安装的步骤 ZED的运行需要显卡支持&#xff0c;cuda加速&#xff0c;因此需要提前安装好显卡驱动以及对应的cuda和cudnn&#xff0c;基础工作在此不再赘述&#xff0c;以下步骤默认已经完成上述准备工作。 建议新建一个虚拟环境以限定ZED使用的py…

SpringIOC第二课,@Bean用法,DI详解,常见面试题Autowired VS Resource

一、回顾 但是我们之前MVC时候&#xff0c;在页面上&#xff0c;为什只用Controller,不用其他的呢&#xff1f; 用其他的好使吗&#xff1f;(我们可以在这里看到&#xff0c;出现404的字样&#xff09; Service ResponseBody public class TestController {RequestMapping(&quo…

前端对浏览器的理解

浏览器的主要构成 用户界面 &#xff0d; 包括地址栏、后退/前进按钮、书签目录等&#xff0c;也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。 浏览器引擎 &#xff0d; 用来查询及操作渲染引擎的接口。 渲染引擎 &#xff0d; 用来显示请求的内容&#…

基于ResNet18网络完成图像分类任务

目录 1 数据处理 1.1 数据集介绍 1.2 数据读取 1.3 构造Dataset类 2 模型构建 3 模型训练 4 模型评价 5 模型预测 6 什么是预训练模型和迁移学习 7 比较“使用预训练模型”和“不使用预训练模型”的效果。 总结 在本实践中&#xff0c;我们实践一个更通用的图像分类任务…

陶博士月线反转6.4 python 化代码

陶博士月线反转6.4 python 化代码 量化系统有好几个月没有进度了&#xff0c;之前一直纠结策略问题&#xff0c;无从下手。最近和量化的同学聊了下&#xff0c;还得先把自动交易流程先跑起来后面再慢慢优化策略。 所以先拿陶博士的月线反转6.4 python 化&#xff0c;作为试水的…

最常用的Linux命令#程序员

最常用的Linux命令 #程序员 #linux 1. 文件和目录管理 icon2. 文件查看和编辑 3.流程管理 4. 系统信息 5. 用户和组管理 6. 网络配置与监控 7. 包管理

产品学习之路(一)

在做好开发的同时&#xff0c;还需要熟悉产品业务逻辑&#xff0c;不能为了功能而做功能&#xff0c;要从产品经理的角度去看待每个需求和客户痛点所在&#xff0c;这样针对产品设计出来的东西自己也有发言权&#xff1b; 目前作为一名前端开发人员&#xff0c;也在自学产品知识…

史上最全低代码平台盘点!三分钟盘点2023年顶尖二十个低代码平台!

史上最全低代码平台盘点&#xff01;三分钟盘点2023年顶尖二十个低代码平台&#xff01; 什么是低代码平台&#xff1f;2023年顶尖二十大低代码平台&#xff0c;哪个值得一试&#xff1f;低代码平台应该如何选择&#xff1f;本篇&#xff0c;我们将为大家盘点顶尖的十大低代码平…

tcexam 本地容器化搭建

PS: 参考[使用docker搭建tcexam在线考试平台-CSDN博客]&#xff0c;只记录和总结容器创建和install步骤 1. Git下载 tcexam_docker 工程源码至宿主机的 ~/git/ 目录 mkdir ~/git && cd ~/git git clone https://gitee.com/39627020/tcexam_docker.git mv tcexam_doce…

计算机网络体系的形成

目录 1、开放系统互连参考模型OSI/RM 2、两种国际标准 3、协议与划分层次 4、网络协议的三要素 5、划分层次 &#xff08;1&#xff09;文件发送模块使两个主机交换文件 &#xff08;2&#xff09;通信服务模块 &#xff08;3&#xff09;接入网络模块 6、分层带来的好…

Redis--14--BigKey 和 热点Key

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 BigKey1.什么是bigkey2.bigkey的危害3.发现bigkeyscan 4.解决bigkey 什么是热点Key&#xff1f;该如何解决1. 产生原因和危害原因危害 2.发现热点key预估发现客户端…

leetcode:232. 用栈实现队列

一、题目 原题链接&#xff1a;232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; 函数原型&#xff1a; typedef struct //我的队列结构定义 { } MyQueue; MyQueue* myQueueCreate() //我的队列创建及其初始化 void myQueuePush(MyQueue* obj, int x) //我的队…

【Python】tensorflow学习的个人纪录(1)

def learn(self, s, r, s_):s, s_ s[np.newaxis, :], s_[np.newaxis, :]v_ self.sess.run(self.v, {self.s: s_})td_error, _ self.sess.run([self.td_error, self.train_op],{self.s: s, self.v_: v_, self.r: r})return td_error代码步入&#xff1a; v_ self.ses…

华为攻击防范简介

定义 攻击防范是一种重要的网络安全特性。它通过分析上送CPU处理的报文的内容和行为&#xff0c;判断报文是否具有攻击特性&#xff0c;并配置对具有攻击特性的报文执行一定的防范措施。 攻击防范主要分为畸形报文攻击防范、分片报文攻击防范和泛洪攻击防范。 目的 目前&…