WindowsAPI编程核心技术-模块加载监控
WindowsAPI编程核心技术-模块加载监控
前置芝士
PsSetLoadImageNotifyRoutine
设置模块加载回调函数,完成模块加载时通知回调函数。
1 | NTSTATUS PsSetLoadImageNotifyRoutine( |
可通过PsRemoveLoadImageNotifyRoutine
删除回调。
PLOAD_IMAGE_NOTIFY_ROUTINE
回调函数。
1 | VOID SetLoadImageNotifyRoutine( |
IMAGE_INFO
1 | typedef struct _IMAGE_INFO { |
驱动模块的卸载
回调函数收到模块加载的信息时,模块已经加载完成,不能直接控制模块的加载操,但可以通过其他方法卸载已加载的模块。
实现思路就是在驱动模块入口点DriverEntry
中直接返回NTSTATUS错误码如STATUS_ACCESS_DENIED(0xC0000022),这样已加载的驱动程序会在执行时出错,导致驱动程序启动失败。加载回调函数第三个参数ImageInfo提供了模块在内存中的加载地址,根据PE头获取NT头IMAGE_NT_HEADERS中IMAGE_OPTIONAL_HEADER的入口点偏移AddressOfEntryPoint,将DriverEntry
前几字节数据修改为以下,x86和x64通用:
1 | B8 22 00 00 C0 mov eax, 0xC0000022 |
具体实现方法:
1 | // 拒绝加载驱动 |
DLL模块的卸载
DLL的返回值不能确定DLL是否加载成功,上述方法无效。可用MmUnmmapViewOfSection
来卸载进程中已加载的模块。
当加载进程模块时系统有一个内部锁,为了避免死锁,在进程模块加载回调函数时不能映射、分配、查询、释放等操作。要想卸载DLL模块,必须等进程中所有模块加载完毕后卸载。可创建多线程延时等待,在进程模块加载完毕后用MmUnmapViewOfSection
释放。
1 | // 调用 MmUnmapViewOfSection 函数来卸载已经加载的 DLL 模块 |
源代码
Driver.h:
1 |
|
LoadImageNotify.h:
1 |
|
Driver.c:
1 |
|
LoadImageNotify.c:
1 |
|
反模块加载监控
基本原理
与反进程/线程创建监控原理极为相似,这里概念就不重复了。
回调函数存储在PspLoadImageNotifyRoutine
数组中,逆向PsSetLoadImageNotifyRoutine
函数:
1 | ;Windows 10 x64 |
通过该函数可直接获得数组地址或地址偏移。于是通过扫描内存特征码获取该函数。
Windows 7 | Windows 8.1 | Windows 10 | |
---|---|---|---|
x86 | BE | BB | BF |
x64 | 488D0D | 488D0D | 488D0D |
获取数组地址具体代码实现:
1 | // 根据特征码获取 PspLoadImageNotifyRoutine 数组地址 |
然后是一模一样的解密:
1 | // 遍历回调 |
删除模块加载回调函数,还是一模一样:
1 | // 移除回调 |
另一种想法
系统中还有PspNotifyEnableMask
全局变量,是个32位整型掩码,根据掩码低位字节设置的位可确定将调用哪些类型回调。位0、位1、位3分别决定是否触发PsSetCreateProcessNotifyRoutine回调、PsSetCreateThreadNotifyRoutine回调、PsSetLoadImageNotifyRoutine回调。若置0,对应回调失效。该全局变量不受Patch Guard保护,可被修改。但该全局变量没有导出,也不能在导出函数中出现,获取该变量地址较难。
源代码
Driver.h:
1 |
|
EnumRemove.h:
1 |
|
Driver.c:
1 |
|
EnumRemove.c:
1 |
|