您的位置:首页 > 编程语言

内核编程之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的原型:

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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: