Windows内核编程 文件监控(ssdt hook)

        本文主要针为进行内核编程的一些初学者提供一些错误,如有错误,希望大家能够指出。

        这里先简单介绍一下概念。说到SSDT HOOK,可以从MEP技术说起,MEP(即执行路径修改)主旨就是拦截系统函数或相关处理例程,让它们转向我们自己的函数进行处理,这样就能实现过滤参数或者修改目标函数处理结果的目的。而SSDT(即 系统服务描述符表),主要是将位于Ring3的应用API函数和Ring0的内核函数联系起来,我们所要做的就是对文件监控所用的函数进行执行地址的修改,将它们转到我们自己编写的函数上来,实现挂钩。

       所有编程的开始当然是对环境的搭建,这里推荐《寒江独钓》等书籍,只要按照上面的步骤去做的话,基本上环境的搭建是没有问题的,注意的问题是当你在安装服务的时候,输入程序路径要将文件名(比如first.sys)一同写进去,此外,Makefile文件可以在下载的winddk当中找一份样例即可。

      关于编程语言,一般都是使用c或者c++,当然如果对于汇编语言比较熟悉也可以使用汇编。要注意的是c和c++的编译方式是不同的,意思是你不能用编译c++的方式去编译c的代码,在你所写的代码当中,如果出现了c语言和c++语言相混叠的情况,需要在代码中使用extern进行标注。

        啰嗦的了半天,可以进行正式的代码部分了,很多人在开始写内核编程的代码时候其实是毫无头绪的(包括本人),这里推荐去看看关于Windows驱动开发部分内容的书籍。我们可以这么想,一般编程,都需要一个主函数,所以呢驱动开发同样需要一个主函数,称为驱动入口函数(DriverEntry)

#pragma code_seg("INIT")  
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) //使用的是c++,出现的c语法部分添加 extern"C"
{
   //函数内容
}
要注意的是,这个入口函数需要我们为它添加一块内存空间。既然有驱动入口,我们就要考虑驱动的问题了,我们的程序驱动的对象是什么呢,驱动的当然是设备了,所以这里我们开始创建设备,
    //创建设备名称的字符串  
    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;  
    }
这里我们稍微说一说设备扩展,为什么会有设备扩展呢,因为默认的设备结构实际上是固定的,我们所要使用的设备绝非只有这一种结构,设备扩展给了我们这样一个机会能够像设备的结构当中添加我们所需要的内容。

写到这时候,我们发现驱动和设备都做好了,那么触发条件又在哪里呢?这时候,我们就用到了驱动派遣函数。派遣函数是windows驱动程序当中非常重要的概念,主要负责处理I/O请求。我们可以这么理解,我们所处的环境是用户模式,如果此时我们使用我们的鼠标双击打开一个文件时候,我们的操作系统是怎么知道并且判断我们的操作的呢?答案很简单,我们的操作会被转化为一个IRP的数据结构,不同的IRP数据会根据类型传递到不同的派遣函数内。而我们需要在入口函数中注册IPR函数,使得我们的设备获得触发条件。

//注册派遣函数  
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine;  
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine;  
关于派遣函数的具体内容,笔者建议还是去多搜索一些资料,这是比较重要的一块内容。

程序的架构差不多搭建好了,我们要正式开始挂钩函数了,要注意的是挂钩函数需要禁用windows的内存保护,一般而言有三种方法。第一种是更改注册表但是需要重启。第二种是利用内存描述符表,描述一块可写内存。第三种是将控制寄存器CR0的wp位设为0,这里我们采用方法三

/关闭内存保护  
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;  
    }  
}
在hook我们所要的函数之前 我们首先要知道函数的索引号,有兴趣的可以使用windebug工具找到所用函数的索引号(windows并不是一个开源的操作系统)。

通用的方式是直接获取函数入口处的第二个字节,这个就是相应的索引号。下面以NtCreateFile为例

    //根据 ZwXXXX的地址 获取服务函数在 SSDT 中所对应的服务的索引号  
    #define SYSTEMCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))
    PageProtectClose();  
    //得到原来的地址,记录在 oldNtCreateFile  
    oldNtCreateFile = KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateFile)];
    //修改SSDT中 NtCreateFile 的地址,使其指向 MyNtCreateFile  
    KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateFile)] = (ULONG)&MyNtCreateFile;
    PageProtectOpen();  
在挂钩完成之后我们就可以自行编写我们的Nt函数了。

NTSTATUS NTAPI MyNtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,
ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength){  
    //添加我们需要实现内容的代码部分 
    typedef NTSTATUS(NTAPI * _NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,
ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength);  
    _NtCreateFile _oldNtCreateFile = (_NtCreateFile)oldNtCreateFile;  
  
    return _oldNtCreateFile(FileHandle,DesiredAccess,ObjectAttributes,IoStatusBlock,AllocationSize,FileAttributes,
ShareAccess,CreateDisposition,CreateOptions,EaBuffer,EaLength);  
}
关于自己所写的Nt函数,我们首先要理解函数的各个参数的含义,才能方便我们实现文件监控的内容

当然最后,当我们完成我们的Nt函数之后,我们需要做的是把这个驱动给卸载掉,否则我们的函数将会一直代替系统自带的函数。用到的是驱动卸载函数

DRIVER_UNLOAD DriverUnload;  
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject)  
{  
    PageProtectClose();  
    //修改SSDT中 NtCreateSection 的地址,使其指向 oldNtCreateSection  
    //也就是在驱动卸载时恢复原来的地址  
    KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateFile)] = oldNtCreateFile;
    PageProtectOpen();    
    PDEVICE_OBJECT pDevObj;  
    pDevObj = pDriverObject->DeviceObject;    
    PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展   
    //删除符号链接  
    UNICODE_STRING pLinkName = pDevExt->SymLinkName;  
    IoDeleteSymbolicLink(&pLinkName);   
    //删除设备  
    IoDeleteDevice(pDevObj);  
}    
到了这个时候,我们所做的驱动也差不多完成了,只要在cmd当中运行服务,我们的I/O操作就会引起IRP变化,从而在我们hook的函数当中发生作用了。

最后附上完整代码

#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;  
  
//我们将 NtCreateFile hook 到自己的函数  
NTSTATUS NTAPI MyNtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,
ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength);   
typedef struct _KESERVICE_DESCRIPTOR_TABLE  
{  
    PULONG ServiceTableBase;  
    PULONG ServiceCounterTableBase;  
    ULONG NumberOfServices;  
    PUCHAR ParamTableBase;  
}KESERVICE_DESCRIPTOR_TABLE, *PKESERVICE_DESCRIPTOR_TABLE;   
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 oldNtCreateFile;//之前的NtCreateFile  
#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;  
    }      
    PageProtectClose();  
    //得到原来的地址,记录在 oldNtCreateFile  
    oldNtCreateFile = KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateFile)];
    //修改SSDT中 NtCreateFile 的地址,使其指向 MyNtCreateFile  
    KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateFile)] = (ULONG)&MyNtCreateFile; 
    PageProtectOpen();   
    return STATUS_SUCCESS;  
}  
  
DRIVER_UNLOAD DriverUnload;  
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject)  
{  
    PageProtectClose();  
    //修改SSDT中 NtCreateSection 的地址,使其指向 oldNtCreateSection  
    //也就是在驱动卸载时恢复原来的地址  
    KeServiceDescriptorTable->ServiceTableBase[SYSTEMCALL_INDEX(ZwCreateFile)] = oldNtCreateFile;
    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 MyNtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,
ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength){  
	ANSI_STRING STR1;
	RtlUnicodeStringToAnsiString(&STR1,ObjectAttributes->ObjectName,TRUE);
	if(wcsstr(ObjectAttributes->ObjectName->Buffer,L"\\??\\C:\\new\\"))
	{
		if(CreateDisposition==FILE_OPEN|CreateDisposition==FILE_OVERWRITE_IF)
		{
			if(wcsstr(ObjectAttributes->ObjectName->Buffer,L"\\??\\C:\\new\\test1\\"))
			{	
				DbgPrint("%Z FileOpening Failed",&STR1);
		    		RtlFreeAnsiString(&STR1);
				return STATUS_ACCESS_DENIED;
			}
		}

	
		if(CreateDisposition==FILE_CREATE|CreateDisposition==FILE_OPEN_IF|CreateDisposition==FILE_OVERWRITE)
		{
			if(wcsstr(ObjectAttributes->ObjectName->Buffer,L"\\??\\C:\\new\\test2\\"))
			{
				DbgPrint("%Z FileCreating Failed",&STR1);
				RtlFreeAnsiString(&STR1);
				return STATUS_UNSUCCESSFUL;
			}
			else
			{
				DbgPrint("%Z CREATED",&STR1);
			}
		}
	}
	RtlFreeAnsiString(&STR1);
    typedef NTSTATUS(NTAPI * _NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,
ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength);  
    _NtCreateFile _oldNtCreateFile = (_NtCreateFile)oldNtCreateFile;  
  
    return _oldNtCreateFile(FileHandle,DesiredAccess,ObjectAttributes,IoStatusBlock,AllocationSize,FileAttributes,
ShareAccess,CreateDisposition,CreateOptions,EaBuffer,EaLength);  
}
(主要实现了对于C盘目录下new目录的文件监控,并对其子目录test1的文件实现了无法打开文件功能,对子目录test2实现了无法创建文件的功能)




已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页