内核编程之SSDTHook(3)Hook NtCreateSection监控所有可执行模块加载
2016-03-18 16:51
411 查看
本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处:/article/9672565.html
在上两篇博文中,我介绍了SSDTHook的原理,并给出了一个实例--通过Hook NtOpenProcess来实现进程保护:/article/9672564.html
这次我们玩个更好玩的,拦截所有可执行模块的载人!杀软都有这种功能,当一个程序运行时,他能得到具体路径,检查是否安全后,还可以通知用户询问是否允许运行。
这么有趣的功能,要如何实现呢,其实出了hook也有办法,但是我们现在研究的是hook,就hook ssdt吧,那么问题来了,要hook那个系统服务函数呢?在用户模式创建进程,大家一下就会想到CreateProcess,然后想到CreateProcessAsUser等,正好SSDT里有一个NtCreateProcess,很多人想当然就想到要hook这个(其实我当时也是),其实我们有一个捷径,我们可以hook这个函数:NtCreateSection(NT 5.x,2000-XP)或NtCreateUserProcess(NT
6.x,NT 10.x,Vista-W10),当一个可执行模块载人时,系统会先调用NtCreateFile来打开文件,创建文件对象,然后调用NtCreateSection,再调用NtCreateProcess,然后再完成一些其他工作,比如创建线程,通知Win32子系统,因此我们可以Hook这个系统服务函数来拦截可执行模块的载人,但在Vista以后,系统不会再调用NtCreateSection,而是整个过程统统交给了NtCreateUserProcess来处理,但是这不以为着在Vista+对NtCreateSection做hook一点价值都没有,其实,DLL的载人内存也需要经过这一个过程,因此我们可以通过对这个函数做hook实现对DLL载人的监控和拦截,再退一步说,如果我们想在XP上监控和拦截进程的创建,就要hook
NtCreateSection。
我们首先来看看NtCreateSection的原型:
Windows加载所有的可执行模块在参数 SectionPageProtection 和 AllocationAttributes 与普通的映射文件有所区别,具有以下特点:
SSDTHook和原理和实现细节都在上两篇博文(/article/9672563.html 和/article/9672564.html)中详细说,我们就详细说说怎么得到要创建的进程的路径吧。
首先我们需要得到文件对象的指针,由于我们在NtCreateSection里可以得到文件句柄,所以可以用ObReferenceObjectByHandle得到文件对象的指针,然后要得到文件路径有两种方法,第一种方法比较复杂:FILE_OBJECT中有一个成员FileName,是不包含卷名称的文件路径,然后,通过FILE_OBJECT里有一个成员DeviceObject,然后通过ObQueryNameString、RtlUnicodeStringToAnsiString、RtlVolumeDeviceToDosName得到卷名称,第二种方法就很简单了,直接调用IoQueryFileDosDeviceName就行了。本例中我们用第二种方法,因此不解释了,各位直接看代码吧。
另外还需要说明的一点是,我们在内核中得到的路径是以“\??\”开头的,这是为何呢?还记得之前驱动开发的博文中创建设备和符号连接吗,磁盘设备上有卷设备,而X盘中的X:就是卷设备的符号连接,符号连接在内核下是以“\??\”开头的,用户模式下是以“\\.\”开头的。
另外,可能还会有人问怎么在得到路径后通知应用程序,以便应用程序检查是否安全以及通知用户呢?可以在应用程序中用一个线程一直读我们创建的设备,驱动派遣函数中阻塞I/O请求(可以用Event,KeWaitForSingleObject等,在后续的驱动开发博文中讲),然后在hook里面激活事件,然后应用程序读取得到路径,线程通信,再次读取,驱动程序再次阻塞I/O请求……和轮询相比,消耗的系统资源很少,而且非常简单。
好了,该说的都已经说了,不该说的也说了,最后上代码,注释也比较丰富,还不明白就看代码吧:
在上两篇博文中,我介绍了SSDTHook的原理,并给出了一个实例--通过Hook NtOpenProcess来实现进程保护:/article/9672564.html
这次我们玩个更好玩的,拦截所有可执行模块的载人!杀软都有这种功能,当一个程序运行时,他能得到具体路径,检查是否安全后,还可以通知用户询问是否允许运行。
这么有趣的功能,要如何实现呢,其实出了hook也有办法,但是我们现在研究的是hook,就hook ssdt吧,那么问题来了,要hook那个系统服务函数呢?在用户模式创建进程,大家一下就会想到CreateProcess,然后想到CreateProcessAsUser等,正好SSDT里有一个NtCreateProcess,很多人想当然就想到要hook这个(其实我当时也是),其实我们有一个捷径,我们可以hook这个函数:NtCreateSection(NT 5.x,2000-XP)或NtCreateUserProcess(NT
6.x,NT 10.x,Vista-W10),当一个可执行模块载人时,系统会先调用NtCreateFile来打开文件,创建文件对象,然后调用NtCreateSection,再调用NtCreateProcess,然后再完成一些其他工作,比如创建线程,通知Win32子系统,因此我们可以Hook这个系统服务函数来拦截可执行模块的载人,但在Vista以后,系统不会再调用NtCreateSection,而是整个过程统统交给了NtCreateUserProcess来处理,但是这不以为着在Vista+对NtCreateSection做hook一点价值都没有,其实,DLL的载人内存也需要经过这一个过程,因此我们可以通过对这个函数做hook实现对DLL载人的监控和拦截,再退一步说,如果我们想在XP上监控和拦截进程的创建,就要hook
NtCreateSection。
我们首先来看看NtCreateSection的原型:
NTSTATUS ZwCreateSection( _Out_ PHANDLE SectionHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PLARGE_INTEGER MaximumSize, _In_ ULONG SectionPageProtection, _In_ ULONG AllocationAttributes, _In_opt_ HANDLE FileHandle );
Windows加载所有的可执行模块在参数 SectionPageProtection 和 AllocationAttributes 与普通的映射文件有所区别,具有以下特点:
(AllocationAttributes == SEC_IMAGE) && (SectionPageProtection & PAGE_EXECUTE)
SSDTHook和原理和实现细节都在上两篇博文(/article/9672563.html 和/article/9672564.html)中详细说,我们就详细说说怎么得到要创建的进程的路径吧。
首先我们需要得到文件对象的指针,由于我们在NtCreateSection里可以得到文件句柄,所以可以用ObReferenceObjectByHandle得到文件对象的指针,然后要得到文件路径有两种方法,第一种方法比较复杂:FILE_OBJECT中有一个成员FileName,是不包含卷名称的文件路径,然后,通过FILE_OBJECT里有一个成员DeviceObject,然后通过ObQueryNameString、RtlUnicodeStringToAnsiString、RtlVolumeDeviceToDosName得到卷名称,第二种方法就很简单了,直接调用IoQueryFileDosDeviceName就行了。本例中我们用第二种方法,因此不解释了,各位直接看代码吧。
另外还需要说明的一点是,我们在内核中得到的路径是以“\??\”开头的,这是为何呢?还记得之前驱动开发的博文中创建设备和符号连接吗,磁盘设备上有卷设备,而X盘中的X:就是卷设备的符号连接,符号连接在内核下是以“\??\”开头的,用户模式下是以“\\.\”开头的。
另外,可能还会有人问怎么在得到路径后通知应用程序,以便应用程序检查是否安全以及通知用户呢?可以在应用程序中用一个线程一直读我们创建的设备,驱动派遣函数中阻塞I/O请求(可以用Event,KeWaitForSingleObject等,在后续的驱动开发博文中讲),然后在hook里面激活事件,然后应用程序读取得到路径,线程通信,再次读取,驱动程序再次阻塞I/O请求……和轮询相比,消耗的系统资源很少,而且非常简单。
好了,该说的都已经说了,不该说的也说了,最后上代码,注释也比较丰富,还不明白就看代码吧:
#include <Ntifs.h> #include <ntddk.h> extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject); extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp); extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp); #define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SEC_IMAGE 0x1000000 typedef struct _DEVICE_EXTENSION { UNICODE_STRING SymLinkName; //我们定义的设备扩展里只有一个符号链接名成员 } DEVICE_EXTENSION, *PDEVICE_EXTENSION; //我们将 NtCreateSection hook 到自己的函数 NTSTATUS NTAPI MyNtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle); //KeServiceDescriptorTable 中我们感兴趣的结构 typedef struct _KESERVICE_DESCRIPTOR_TABLE { PULONG ServiceTableBase; PULONG ServiceCounterTableBase; ULONG NumberOfServices; PUCHAR ParamTableBase; }KESERVICE_DESCRIPTOR_TABLE, *PKESERVICE_DESCRIPTOR_TABLE; //ntoskrnl.exe (ntoskrnl.lib) 导出的 KeServiceDescriptorTable extern "C" extern PKESERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable; //关闭页面保护 void PageProtectClose() { __asm{ cli; mov eax, cr0; and eax, not 10000h; mov cr0, eax; } } //启用页面保护 void PageProtectOpen() { __asm{ mov eax, cr0; or eax, 10000h; mov cr0, eax; sti; } } //根据 ZwXXXX的地址 获取服务函数在 SSDT 中所对应的服务的索引号 #define SYSTEMCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1)) ULONG oldNtCreateSection;//之前的NtCreateSection ULONG ProtectProcessID = 0; #pragma code_seg("INIT") extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) { DbgPrint("DriverEntry\r\n"); pDriverObject->DriverUnload = DriverUnload;//注册驱动卸载函数 //注册派遣函数 pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctlDispatchRoutine; NTSTATUS status; PDEVICE_OBJECT pDevObj; PDEVICE_EXTENSION pDevExt; //创建设备名称的字符串 UNICODE_STRING devName; RtlInitUnicodeString(&devName, L"\\Device\\MySSDTHookDevice"); //创建设备 status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj); if (!NT_SUCCESS(status)) return status; pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲设备 pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展 //创建符号链接 UNICODE_STRING symLinkName; RtlInitUnicodeString(&symLinkName, L"\\??\\MySSDTHookDevice_link"); pDevExt->SymLinkName = symLinkName; status = IoCreateSymbolicLink(&symLinkName, &devName); if (!NT_SUCCESS(status)) { IoDeleteDevice(pDevObj); return status; } //Hook SSDT PageProtectClose(); //得到原来的地址,记录在 oldNtCreateSection oldNtCreateSection = KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateSection)]; //修改SSDT中 NtCreateSection 的地址,使其指向 MyNtCreateSection KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateSection)] = (ULONG)&MyNtCreateSection; DbgPrint("Old Addr:0x%X\r\n", oldNtCreateSection); PageProtectOpen(); return STATUS_SUCCESS; } DRIVER_UNLOAD DriverUnload; extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { PageProtectClose(); //修改SSDT中 NtCreateSection 的地址,使其指向 oldNtCreateSection //也就是在驱动卸载时恢复原来的地址 KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateSection)] = oldNtCreateSection; PageProtectOpen(); PDEVICE_OBJECT pDevObj; pDevObj = pDriverObject->DeviceObject; PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展 //删除符号链接 UNICODE_STRING pLinkName = pDevExt->SymLinkName; IoDeleteSymbolicLink(&pLinkName); //删除设备 IoDeleteDevice(pDevObj); } extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; //得到I/O堆栈的当前这一层,也就是IO_STACK_LOCATION结构的指针 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength;//得到输入缓冲区的大小 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;//得到控制码 PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到缓冲区指针 switch (code) { case IOCTL1: DbgPrint("Get ioctl code 1\r\n"); break; default: status = STATUS_INVALID_VARIANT; //如果是没有处理的IRP,则返回STATUS_INVALID_VARIANT,这意味着用户模式的I/O函数失败,但并不会设置GetLastError } // 完成IRP pIrp->IoStatus.Status = status;//设置IRP完成状态,会设置用户模式下的GetLastError pIrp->IoStatus.Information = 0;//设置操作的字节 IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP,不增加优先级 return status; } NTSTATUS NTAPI MyNtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle){ if ((AllocationAttributes == SEC_IMAGE) && (SectionPageProtection & PAGE_EXECUTE)){ if (FileHandle){ PFILE_OBJECT FileObject; NTSTATUS status; if ((status = ObReferenceObjectByHandle(FileHandle, 0, NULL, KernelMode, (PVOID*)&FileObject, NULL)) == STATUS_SUCCESS){ POBJECT_NAME_INFORMATION FilePath; if ((status = IoQueryFileDosDeviceName(FileObject, &FilePath)) == STATUS_SUCCESS){ DbgPrint("FilePath: %ws\r\n", FilePath->Name.Buffer); ExFreePool(FilePath);// IoQueryFileDosDeviceName 获取的 OBJECT_NAME_INFORMATION 需要手动释放 } else DbgPrint("E: IoQueryFileDosDeviceName failed with code 0x%X\r\n", status); ObDereferenceObject(FileObject);//使获取到的 FileObject 引用计数减1 } else DbgPrint("E: ObReferenceObjectByHandle failed with code 0x%X\r\n", status); } else DbgPrint("E: FileHandle is NULL.\r\n"); } //定义一个函数指针 _NtCreateSection, 根据 oldNtCreateSection 记录的真实函数的地址进行 Call //也就是说其他进程直接交还给系统的 NtCreateSection 处理 typedef NTSTATUS(NTAPI * _NtCreateSection)(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle); _NtCreateSection _oldNtCreateSection = (_NtCreateSection)oldNtCreateSection; return _oldNtCreateSection(SectionHandle, DesiredAccess, ObjectAttributes, MaximumSize, SectionPageProtection, AllocationAttributes, FileHandle); }
相关文章推荐
- golang 快捷付值需要注意的地方
- 关于导入Java Web项目的一些注意操作
- Spring+quartz 实现动态管理任务
- 关于JAVA的GC算法
- 【matlab】:matlab如何实现切分图片并保存?
- Mongo 备份 还原
- 2016年c#实验3.2:实验字符串反转
- C# 字符串取文件名
- C#调用手动编写的COM组件(非ATL向导生成,参考上一篇手把手写COM组件)
- Django~urls.py--->views.py
- C++虚继承(三) --- C++ 对象的内存布局(下)(陈皓)
- QML中的AnchorChanges锚布局改变元素
- Java集合框架:Collections工具类
- 实现二叉查找树 -- C语言
- Java集合框架:Collections工具类
- 用三种方式取出给定字符串中的目标字串
- Java多线程系列--“基础篇”07之 线程休眠
- C++虚继承(二) --- C++ 对象的内存布局(上)(陈皓)
- Java 异常处理
- eclipse黑色高亮主题下载和配置