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,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.mfbz.cn/a/210081.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

光伏测算工具能测量哪些数据?

光伏测算工具在光伏电站的设计和规划过程中起着至关重要的作用。它们可以测量并分析一系列关键数据&#xff0c;以确保光伏电站的顺利建设和高效运营。本文将详细介绍光伏测算工具能测量的主要数据。 一、太阳能资源评估 光伏测算工具可以对场地的太阳能资源进行评估。这包括测…

C++模版

文章目录 C模版1、泛型编程2、函数模版2.1、函数模版概念2.2、函数模版格式2.3、函数模版原理2.4、函数模版的实例化2.5、模板参数的匹配原则 3、类模版3.1、类模版概念3.2、类模版格式3.3、类模板的实例化 C模版 1、泛型编程 泛型编程&#xff08;Generic Programming&#x…

html个人简历网页版源码

文章目录 1.个人简历1.1 简历风格1 - 纯净版1.2 简历风格2 - 蓝色版1.2 简历风格3 - 粉色心动版 源码目录结构源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134752070 html个人简历网页版源码&#xff0c;好看…

SpringBoot整合JavaMail

SpringBoot整合JavaMail发一个简单邮件 文章目录 SpringBoot整合JavaMail发一个简单邮件导入坐标配置写客户端 SpringBoot整合JavaMail发多部件邮件 导入坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starte…

SEO插件,免费的SEO插件大全

SEO插件的作用 让我们来谈谈SEO插件的作用。SEO插件是一种能够在网站建设和管理过程中&#xff0c;提供全方位、智能化SEO服务的工具。它们通常嵌入在网站后台&#xff0c;为站长提供了诸如关键词优化、页面结构调整、外链管理等一系列强大功能&#xff0c;帮助站长更好地适应…

【数电笔记】最小项(逻辑函数的表示方法及其转换)

目录 说明&#xff1a; 逻辑函数的建立 1. 分析逻辑问题&#xff0c;建立逻辑函数的真值表 2. 根据真值表写出逻辑式 3. 画逻辑图 逻辑函数的表示 1. 逻辑表达式的常见表示形式与转换 2. 逻辑函数的标准表达式 &#xff08;1&#xff09;最小项的定义 &#xff08;2&am…

一篇短文让你彻底理解什么是逻辑门电路

一、门电路概述 门电路&#xff1a;实现基本运算、复合运算的单元电路&#xff0c;如与门、与非门、或门… 注意&#xff1a;门电路中以高/低电平表示逻辑状态的1/0 正逻辑与负逻辑&#xff1a; 正逻辑&#xff1a;高电平表示1、低电平表示0 负逻辑&#xff1a;高电平表示0、低…

2022-06-17 github 访问慢的解决办法 - 手动添加hosts

访问https://hosts.gitcdn.top/hosts.txt 将网页的全部内容粘贴到你电脑的hosts文件中 在系统中找到 hosts 文件 Window&#xff1a;C:\Windows\System32\drivers\etc\hosts 或 Linux&#xff1a;/etc/hosts

Hdoop学习笔记(HDP)-Part.12 安装HDFS

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

Embedding And Word2vec

Embedding与向量数据库&#xff1a; Embedding 简单地说就是 N 维数字向量&#xff0c;可以代表任何东西&#xff0c;包括文本、音乐、视频等等。要创建一个Embedding有很多方法&#xff0c;可以使用Word2vec&#xff0c;也可以使用OpenAI 的 Ada。创建好的Embedding&#xff…

IDEA下载和安装

IDEA的下载和安装 一、概述 IDEA全称IntelliJ IDEA&#xff0c;是用于Java语言开发的集成环境&#xff0c;它是业界公认的目前用于Java程序开发最好的工具。 集成环境&#xff1a;把代码编写&#xff0c;编译&#xff0c;执行&#xff0c;调试等多种功能综合到一起的开发工具…

论文阅读 - LoRA: Low-Rank Adapatation of Large Language Models

论文链接 arxiv&#xff1a; 论文目标与背景 大语言模型有很好的性能&#xff0c;在对接下游任务&#xff08;DownStream&#xff09;完成大语言模型的微调 主要方法 &#xff1a;冻结预训练模型的权重&#xff0c;插入可训练的秩分解矩阵到Transformer结构的每一层&#…

Facebook做外贸推广如何?

Facebook作为全球最大的社交媒体平台之一&#xff0c;同时也拥有着庞大的流量以及用户&#xff0c;基于这些数据更是吸引着不少的跨境电商卖家选择此平台进行推广营销&#xff0c;那么Facebook做外贸推广到底如何呢&#xff1f;下面小编对此讲讲吧&#xff01; 1、全球覆盖流量…

Spring Boot统一异常处理 Spring拦截器

小编在前文中向大家描述了Spring AOP的相关内容&#xff1a;Spring AOP-CSDN博客感兴趣的各位老铁可查看一下&#xff01;&#xff01; 那么&#xff0c;我们本文主要是代理搭建来实现一个Spring Boot统一功能处理模块了&#xff0c;当然&#xff0c;这个也是Spring AOP的实战环…

SQL Server 2016(为数据表Porducts添加数据)

1、实验环境。 某公司有一台已经安装了SQL Server 2016的服务器&#xff0c;并已经创建了数据库PM。 2、需求描述。 在数据库PM中创建表products&#xff0c;"编号"列的值自动增长并为主键。然后使用T-SQL语句为表格插入如下数据。 3、实验步骤。 1、使用SSMS管理工…

第九节HarmonyOS 常用基础组件1-Text

一、组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声名式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。 组件根据功能可以分为以下五大类&#xff1a;基础组件…

如何优雅的进行业务分层

1.什么是应用分层 说起应用分层&#xff0c;大部分人都会认为这个不是很简单嘛 就controller&#xff0c;service, mapper三层。 看起来简单&#xff0c;很多人其实并没有把他们职责划分开&#xff0c;在很多代码中&#xff0c;controller做的逻辑比service还多,service往往当…

C/C++,图算法——求强联通的Tarjan算法之源程序

1 文本格式 #include <bits/stdc.h> using namespace std; const int maxn 1e4 5; const int maxk 5005; int n, k; int id[maxn][5]; char s[maxn][5][5], ans[maxk]; bool vis[maxn]; struct Edge { int v, nxt; } e[maxn * 100]; int head[maxn], tot 1; vo…

BUUCTF-MISC-第二题

下载并打开题目附件 图片是GIF格式动态图片 动态过程会时不时弹出flag 但是速度很快 我们需要想办法去拦截 使用Stegsolve工具进一步分析Analyse->Frame Browser 对图片进行锁帧操作 21帧51帧79帧得到flag&#xff1a;flag{he11ohongke} 本题意义&#xff1a; 对MISC图片隐写…

CSS 绝对定位问题和粘性定位介绍

目录 1&#xff0c;绝对定位问题1&#xff0c;绝对定位元素的特性2&#xff0c;初始包含块问题 2&#xff0c;粘性定位注意点&#xff1a; 1&#xff0c;绝对定位问题 1&#xff0c;绝对定位元素的特性 display 默认为 block。所以行内元素设置绝对定位后可直接设置宽高。脱离…
最新文章