基于PMI实现对读写行为检测

VSole2022-10-09 17:42:14

这几天分析某驱动样本发现几种检测读写的方法,就有意思的一种和大家分享。

PMI

使用CPU提供的性能监视(PM)功能,对内存读写时的关键挂靠函数(KeStackAttachProcess, KeAttachProcess)实现了一种类似hook的技术。

通过监视调用情况,来判断进程内存是否被读取。关于处理器的性能监视功能,不同的处理器架构都有差异,可以参考白皮书,或者这个翻译的帖子:https://www.codenong.com/cs106475009/

大致原理:

设置ICACHE_MISS类型的监视事件开启性能监视功能。当L1指令高速缓存未命中次数达到溢出值时,触发PM中断,执行样本注册的PMI回调函数。

在回调函数中判断中断的位置是否处于监视函数代码的序言部分(这样才能获取到正确的调用参数),如果处于那么通过调用参数判断挂靠的目标进程是否为被保护的进程,进而记录下调用信息。不处于则使用clflush或clflushopt指令使监视函数代码从缓存中失效,以提高下次被中断到的概率。

下面是详细的流程:

判断处理器对性能监视功能的支持性

通过cpuid的0号功能来判断处理器是不是intel(看代码样本尚未支持amd)。

Cupid的a号功能判断处理器是否支持版本2以上的性能监视功能。

单个核心是否支持4个以上的计数器。

是否支持clflush指令,并且处理器是否支持ICACHE_MISS类型的监视事件。

获取监视函数(KeStackAttachProcess, KeAttachProcess)

解析nt模块的导出表得到两个监视函数地址,并解析异常表得到两个监视函数的序言部分的大小(SizeOfProlog),应该是为了兼容不同系统的做法。

配置并开启性能监视

1、注册PMI回调函数

获取nt模块导出的HalDispatchTable地址,这是hal提供的一张函数地址表。

调用其中的hal!HalpSetSystemInformation并指定HalProfileSourceInterruptHandler来注册自己的PMI回调函数。

其中的foo就是注册的回调函数地址。

样本使用Windows提供的接口来注册回调函数,当然也可以通过配置IA32_X2APIC_LVT_PMI寄存器来指定PMI发生时的中断向量号。

Windows系统中配置的向量号是0xfe。

中断历程实际是_KINTERRUPT.ServiceRoutine字段指向的函数:hal!HalpPerfInterrupt

而这个函数很简单就是逐个调用hal!HalpPerfInterruptHandler中注册的PMI回调函数。

所以hal!HalpSetSystemInformation的作用很简单就是指定的foo参数设置到HalpPerfInterruptHandler中。

2、开启每个处理器核心中的计数器

开启参数中CounterMode_0指明计数器的类型(固定用途、通用),当然样本只用到了通用的性能监视计数器。

CtrxIndex_4是每个处理器核心中开启的计数器的索引。

EVESEL_8是选择的性能监视事件,也就是在一开始提到的ICACHE_MISS类型。

CtrCount_C是计数值,这个参数直接决定了PMI发生的频率,计数值越低,频率越高,当然中断到监视函数的概率也越高。

开启前保证计数器处于停止状态。

重置指定计数器的计数值。

选择监视事件。

开启性能监视。

回调函数处理

样本注册的回调函数为PMICallBackMode0_140058d40,唯一的参数是_KTRAP_FRAME,记录了发生中断的寄存器环境。

通过_KTRAP_FRAME,可以拿到发生中断的地址,用来判断是否命中监视函数的序言部分。

如果命中,进而判断是否挂靠指定进程。

如果未命中,将定时使用clflush或clflushopt指令让监视函数代码从缓存中失效。

最后,不管是否命中,都需要清除溢出标志,重置计数器以允许下一次PMI处理程序调用。

总结

我将样本实现PMI的关键代码拷贝出来编译后在虚拟机中运行,并测试XT扫描进程钩子时读写内存的情况,确实可以命中!

(测试时保证处理器支持PM并且虚拟机处理器开启虚拟化CPU性能计数器)。

这种中断实现的监视调用的方法应用场景有限,即使使用clflsuh指令提高了对监视函数的命中概率,实际测试效果也一般,考虑运行效率的情况下不能将计数值设置的足够小,就不能保证每次调用监视函数时都能命中。

但在游戏安全方面,用来监视高频率的读写行为确实很有效果,并且其不依赖系统函数的隐蔽性和中断的随机性意味着更难被作弊者发现。

回调函数
本作品采用《CC 协议》,转载必须注明作者和本文链接
x32TLS回调函数实验
2023-05-31 09:34:55
TLS回调函数介绍TLS回调函数是在程序运行时由操作系统自动调用的一组函数,用于在进程加载和卸载时执行一些初始化和清理操作。在TLS回调函数中,可以访问当前线程的TLS数据,并对其进行修改或检查。值得一提的是TLS回调可以用来反调试,原理实为在实际的入口点代码执行之前执行检测调试器代码。为了栈平衡,我们要把传进这个回调函数的参数所占用的
动态函数PHP中支持一个功能叫 variable function ,变量函数的意思。//最终是system;当一个变量后边带括号,那他就被视作一个函数。编译器会解析出变量的值,然后会去找当前是否存在名为“system()”的函数并执行它。这里就不给实例了,很多免杀案例中都用到了这个特性。也是被疯狂查杀的特征。回调函数回调函数,简单来说就是一个函数不是由我直接调用,而是通过另一个函数去调用它。
结果分析Hook前Hook后,我们的弹窗本该是hello的但是hook后,程序流程被我们修改了。760D34B2 55 push ebp760D34B3 8BEC mov ebp,esp通过这两条指令,函数就可以在堆栈中为局部变量分配存储空间,并在函数执行过程中保存和恢复现场。这样做的好处是可以避免局部变量和其他函数之间的冲突,同时也可以提高函数的可读性和可维护性。
Dobby一共两个功能,其一是inlinehook,其二是指令插桩,两者原理差不多,主要介绍指令插桩。所谓指令插桩,就是在任意一条指令,进行插桩,执行到这条指令的时候,会去执行我们定义的回调函数
在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数
全局钩子注入在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数
相关例子如下,编译的时候需要在Debug模式:#define _CRTDBG_MAP_ALLOC. main.cpp : {163} normal block at 0x000002882AE17740, 12 bytes long.Data: < M > 07 00 00 00 4D 00 00 00 09 03 00 00. main.cpp : {162} normal block at 0x000002882AE148C0, 4 bytes long.内存的申请在C++编程语言中,内存申请对应的关键字是new或malloc,其实new最后调用的也是malloc函数,对应源代码文件是debug_heap.cpp。在包含相关头文件之后,malloc函数的调用栈为:malloc -> _malloc_dbg -> heap_alloc_dbg -> heap_alloc_dbg_internal。_CrtMemBlockHeader* _block_header_prev; // 双向链表,访问该双向链表的全局变量为__acrt_first_block. size_t _data_size; // malloc分配的大
当L1指令高速缓存未命中次数达到溢出值时,触发PM中断,执行样本注册的PMI回调函数。是否支持clflush指令,并且处理器是否支持ICACHE_MISS类型的监视事件。
Win32k组件最初的设计和编写是完全建立的用户层上的,但是微软在 Windows NT 4.0 的改变中将 Win32k.sys 作为改变的一部分而引入,用以提升图形绘制性能并减少 Windows 应用程序的内存需求。窗口管理器(User)和图形设备接口(GDI)在极大程度上被移出客户端/服务端运行时子系统(CSRSS)并被落实在它自身的一个内核模块中。
电子邮件安全和威胁检测服务提供商Vade公司近日发布了一份报告,详细阐述了最近发现的一起网络钓鱼攻击,这起攻击成功欺骗了Microsoft 365身份验证系统。由于Microsoft 365在商业界得到广泛采用,被泄露的账户很有可能属于企业用户。此外据报告显示,Vade的研究人员还发现了一种涉及使用被欺骗的Adobe版本的网络钓鱼攻击。小心那些声称你的Microsoft 365账户存在风险或要求紧急验证个人信息的电子邮件。
VSole
网络安全专家