前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >驱动开发:内核枚举LoadImage映像回调

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

原创
作者头像
王瑞MVP
发布于 2022-11-18 02:11:38
发布于 2022-11-18 02:11:38
74600
代码可运行
举报
运行总次数:0
代码可运行

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

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

image.png
image.png

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

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

image.png
image.png

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

image.png
image.png

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

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#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.com \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;
}

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

image.png
image.png

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

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

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

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#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.com \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;
}

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

image.png
image.png

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

image.png
image.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
Mysql系列第十六讲 变量详解
上面使用中介绍的,全局变量需要添加global关键字,会话变量需要添加session关键字,如果不写,默认为session级别。
易兮科技
2020/10/09
6470
玩转Mysql系列 - 第16篇:变量详解
Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能。
路人甲Java
2019/10/09
6400
MySQL高级篇-容易忽略的核心内容变量
 在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
用户4919348
2022/04/13
8250
MySQL高级篇-容易忽略的核心内容变量
【说站】mysql中系统变量的两种类型
以上就是mysql中系统变量的两种类型,希望对大家有所帮助。更多mysql学习指路:MySQL
很酷的站长
2022/11/23
6390
MySQL(变量)
全局变量用global来修饰,而会话变量用session,通常session可以省略。
全栈开发日记
2022/05/12
2K0
MySQL数据库,详解变量使用(一)
上⾯使⽤中介绍的,全局变量需要添加global关键字,会话变量需要添加session
用户1289394
2021/11/16
7920
第38次文章:数据库结尾
本周介绍一下数据库中的最后一种语言,TCL事务控制语言。以及存储过程和函数,还有最后的流程过程结构。顺利结束数据库的基础内容。同时祝各位同学国庆快乐呀!
鹏-程-万-里
2019/09/29
9390
第38次文章:数据库结尾
MySQL数据库,详解变量使用(二)
set @@session.tx_isolation='read-uncommitted';set @@tx_isolation='read-committed';
用户1289394
2021/11/16
8530
【重学 MySQL】七十八、深度解析! 变量的奥秘与操作技巧
在MySQL数据库中,变量是存储和操作数据的重要工具。它们可以用于存储查询或计算的中间结果,或者输出最终的结果数据。变量的使用可以简化复杂的SQL查询和操作,提高数据库的性能和灵活性。
用户11332765
2024/10/28
1310
【重学 MySQL】七十八、深度解析! 变量的奥秘与操作技巧
第16章_变量、流程控制与游标
在 MySQL 数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
程序员Leo
2023/08/07
4120
第16章_变量、流程控制与游标
10-变量
系统变量 # 变量 /* 系统变量 全局变量 会话变量 自定义变量 用户变量 局部变量 */ # 一。系统变量 # 变量由系统提供,不由用户定义,属于服务器层面 /* 使用语法:(不写global,session默认使用会话变量) 1. 查看所有系统变量 SHOW GLOBAL VARIABLES; # 查看全局变量 SHOW 【SESSION】 VARIABLES; # 查看会话变量 2. 查看满足条件的部分系统变量 SHOW GLOBAL VARIABLES L
Ywrby
2022/10/27
1560
MySQL数据库原理学习(二十六)
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。
用户1289394
2023/01/05
1460
MySQL中变量的定义和变量的赋值使用
说明:现在市面上定义变量的教程和书籍基本都放在存储过程上说明,但是存储过程上变量只能作用于begin…end块中,而普通的变量定义和使用都说的比较少,针对此类问题只能在官方文档中才能找到讲解。
全栈程序员站长
2022/06/30
9.5K0
⑩⑤【DB】详解MySQL存储过程:变量、游标、存储函数、循环,判断语句、参数传递..
注意:用户定义的变量无需对其进行声明或初始化,不声明或初始化获取到的值为NULL。
.29.
2023/11/20
2.3K0
⑩⑤【DB】详解MySQL存储过程:变量、游标、存储函数、循环,判断语句、参数传递..
MySQL TCL 事务控制
MySQL 中并非所有的数据库存储引擎都支持事务操作,比如 MyISAM 就不支持。所以,使用事务处理的时候一定要确定所操作的表示是否支持事务处理,可以通过查看建表语句来查看有没有指定事务类型的存储引擎。当然,事务处理是为了保障表数据原子性、一致性、隔离性、持久性。这些都需要消耗系统资源,请谨慎选择。
恋喵大鲤鱼
2023/10/12
2230
MySQL 进阶之存储过程/存储函数/触发器
上面给大家演示了存储过程中的基本语法,现在只是在存储过程中定义了一条简单的select 语句 ,并没有任何逻辑。
叫我阿杰好了
2022/11/07
2.3K0
MySQL 进阶之存储过程/存储函数/触发器
【MySQL-17】存储过程-[变量篇]详解-(系统变量&用户定义变量&局部变量)
YY的秘密代码小屋
2024/07/26
3140
【MySQL-17】存储过程-[变量篇]详解-(系统变量&用户定义变量&局部变量)
MySQL(七)
针对”所有”客户端”所有”时刻。 基本语法: set global {变量名} = {新变量值}; 或者 set @@global.{变量名} = {新变量值};
1ess
2021/11/01
5340
MySQL基础-变量/流程控制/游标
在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据
用户9645905
2023/04/01
2.3K0
MySQL基础-变量/流程控制/游标
MySQL 系统变量(system variables)
    MySQL系统变量(system variables)实际上是一些系统参数,用于初始化或设定数据库对系统资源的占用,文件存放位置等等。这些个系统变量可以分为全局以及会话级别层面来修改,有些也可以进行动态修改。本文主要介绍了系统变量的一些概念以及如何设置查看这些系统变量。
Leshami
2018/08/13
1.9K0
推荐阅读
相关推荐
Mysql系列第十六讲 变量详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档