Win64 驱动内核编程-13.回调监控模块加载
回调监控模块加载
模块加载包括用户层模块(.DLL)和内核模块(.SYS)的加载。传统方法要监控这两者加在必须 HOOK 好几个函数,比如 NtCreateSection 和 NtLoadDriver
等,而且这些方法还不能监控未知的驱动加载方法。其实为了监控模块加载而HOOK API 是非常傻的,因为微软已经提供了一对标准的 API 实现此功能。它们
分别是 PsSetLoadImageNotifyRoutine 和 PsRemoveLoadImageNotifyRoutine,可以设置/取消一个“映像加载通告例程”,当有驱动或者 DLL 被加载时,回调函
数就会被调用。有人可能认为这个标准方法的监控非常表层,其实恰恰相反,这个方法非常底层,大部分隐秘的加载驱动的方法都可以绕过 NtLoadDriver,但
是无法绕过“映像加载通告例程”。所以用此方法监控驱动加载是最合适的了。
之前说过,这个通告例程不仅仅管加载驱动,连进程加载 DLL 也管,那我们怎么判断到底是加载驱动还是加载 DLL 呢?如果说根据后缀名判断则很明显是一个挫方法。我的方
法是, 根据回调函数 e LoadImageNotifyRoutine 的第二个参数判断,如果 D PID 是0 0 ,则表示加载驱动,如果 D PID 位非零,则 表示加载 DLL 。原因很简单,我之前说过这个函数很底层,到了一定的深度之后就无法判断到底是谁主动引发的行为了,一切都是系统的行为。当然,你也可以认为这是通过回调来监控驱动加载的缺点。判断了是驱动后,就通过 ImageInfo->ImageBase 来获取驱动的映像基址。过 如果不想让这个驱动加载,就通过 e ImageBase 得 来获得 y DriverEntry 的地址(ImageBase 就是 DOS 头,根据 DOS 头找到 NT 头,然后在 NT 头的 OptionalHeader里就能找到入口点了。入口点的数据就是 DriverEntry 的地址) , 并 写入 “拒绝访问 ” 的机器码 即可。
//添加: PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine); //删除: PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine);其中 NotifyRoutine 是一个函数指针,此回调函数的原型是: VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE) ( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo ); 下面是实现模块监控,并且拒绝Powertool的驱动加载的例子代码: #include <ntddk.h> #include <ntimage.h>#define dprintf DbgPrintBOOLEAN VxkCopyMemory( PVOID pDestination, PVOID pSourceAddress, SIZE_T SizeOfCopy ) {PMDL pMdl = NULL;PVOID pSafeAddress = NULL;pMdl = IoAllocateMdl( pSourceAddress, (ULONG)SizeOfCopy, FALSE, FALSE, NULL );if( !pMdl ) return FALSE;__try{MmProbeAndLockPages( pMdl, KernelMode, IoReadAccess );}__except(EXCEPTION_EXECUTE_HANDLER){IoFreeMdl( pMdl );return FALSE;}pSafeAddress = MmGetSystemAddressForMdlSafe( pMdl, NormalPagePriority );if( !pSafeAddress ) return FALSE;RtlCopyMemory( pDestination, pSafeAddress, SizeOfCopy );MmUnlockPages( pMdl );IoFreeMdl( pMdl );return TRUE; }VOID UnicodeToChar(PUNICODE_STRING dst, char *src) {ANSI_STRING string;RtlUnicodeStringToAnsiString(&string,dst, TRUE);strcpy(src,string.Buffer);RtlFreeAnsiString(&string); }void DenyLoadDriver(PVOID DriverEntry) { UCHAR fuck[]="\xB8\x22\x00\x00\xC0\xC3"; VxkCopyMemory(DriverEntry,fuck,sizeof(fuck)); }PVOID GetDriverEntryByImageBase(PVOID ImageBase) { PIMAGE_DOS_HEADER pDOSHeader; PIMAGE_NT_HEADERS64 pNTHeader; PVOID pEntryPoint; pDOSHeader = (PIMAGE_DOS_HEADER)ImageBase; pNTHeader = (PIMAGE_NT_HEADERS64)((ULONG64)ImageBase + pDOSHeader->e_lfanew); pEntryPoint = (PVOID)((ULONG64)ImageBase + pNTHeader->OptionalHeader.AddressOfEntryPoint); return pEntryPoint; }VOID LoadImageNotifyRoutine (__in_opt PUNICODE_STRING FullImageName,__in HANDLE ProcessId,__in PIMAGE_INFO ImageInfo ) { PVOID pDrvEntry; char szFullImageName[260]={0}; if(FullImageName!=NULL && MmIsAddressValid(FullImageName)) { if(ProcessId==0) { DbgPrint("[MyDriver]%wZ\n", FullImageName); pDrvEntry=GetDriverEntryByImageBase(ImageInfo->ImageBase); DbgPrint("[MyDriver]DriverEntry: %p\n",pDrvEntry); UnicodeToChar(FullImageName,szFullImageName); if(strstr(_strlwr(szFullImageName),"kevp64.sys")) { DbgPrint("[MyDriver]Deny load [WIN64AST.SYS]"); //禁止加载win64ast.sys DenyLoadDriver(pDrvEntry); } } } }加载: PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine); 注销: PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)LoadImageNotifyRoutine);执行结果,通过Pchunter看监控当前驱动信息,PowerTool驱动被拒绝加载之后不但自己没有提示,而且还在桌面上留下了自己的驱动文件,这相当于是你双击了一个exe,结果在exe入口函数的地方内存编程不可操作了,这种很难检测出问题来:
禁止加载驱动的方式也可以用来禁止加载dll。
总结
以上是生活随笔为你收集整理的Win64 驱动内核编程-13.回调监控模块加载的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: Win64 驱动内核编程-12.回调监控
- 下一篇: Win64 驱动内核编程-14.回调监控