【转】Windows平台内核级文件访问

1. 背景
    
windows 平台下,应用程序通常使用 API 函数来进行文件访问,创建,打开,读写文件。从 kernel32 CreateFile/ReadFile/WriteFile 函数,到本地系统服务,再到 FileSystem 及其 FilterDriver ,经历了很多层次。在每个层次上,都存在着安全防护软件,病毒或者后门作监视或者过滤的机会。作为安全产品开发者,我们需要比别人走得更远,因此我们需要一个底层的 “windows 平台内核级文件访问 的方法来确保我们能够看到正确的干净的文件系统。

2.
用途
    
直接的内核级别文件访问,在信息安全领域内有广泛的用途。用于入侵者的方面,可以让他绕过杀毒软件, IDS 等安全保护系统的监视。用于检测者的方面,可以看到一个干净的系统,以此来查杀隐藏的后门或者 rootkit 。用于监控者的方面,则可以了解最新的绕过监控的技术,可以根据来设计更新的监控方案。

3.
直接访问 FSD 的内核级别文件访问
    FSD(FileSystemDriver)
层是文件 API 函数经过本地系统服务层 (native API) 最后到达的驱动层次。如果我们可以模仿操作系统,在我们自己的驱动程序里直接向 FSD 发送 IRP ,就可以绕过那些 native API win32 API 了,也就可以绕过设置在这些层次上面的 API 钩子等监控措施。

3.1
文件的 Create Open
    
文件的 Create Open 可以通过发送 IRP_MJ_CREATE FSD ,或者调用 IoCreateFile 函数来完成。 Create Open 的区别实际上在于 IoCreateFile/IRP_MJ_CREATE 的一个参数 Disposition 的取值。使用 IoCreateFile 函数的样例代码:

HANDLE openfile(WCHAR *  name,ACCESS_MASK access,ULONG share) 
{
    
//return 0 for error.
    HANDLE hfile;
    IO_STATUS_BLOCK iosb;
    
int stat;
    OBJECT_ATTRIBUTES oba;
    UNICODE_STRING nameus;
    
///
    if(KeGetCurrentIrql()>PASSIVE_LEVEL){return 0;}
    RtlInitUnicodeString(
&nameus,name);
    InitializeObjectAttributes(
&oba,&nameus,OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,0,0);
    stat
=IoCreateFile(&hfile,access,&oba,&iosb,0,FILE_ATTRIBUTE_NORMAL,share,FILE_OPEN,0,0,0,0,0,0);
    
if(!NT_SUCCESS(stat)){return 0;}
    
return hfile;
}


HANDLE createnewfile(WCHAR
*  name,ACCESS_MASK access,ULONG share) 
{
    
//return 0 for error.
    HANDLE hfile;
    IO_STATUS_BLOCK iosb;
    
int stat;
    OBJECT_ATTRIBUTES oba;
    UNICODE_STRING nameus;
    
///
    if(KeGetCurrentIrql()>PASSIVE_LEVEL){return 0;}
    RtlInitUnicodeString(
&nameus,name);
    InitializeObjectAttributes(
&oba,&nameus,OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,0,0);
    stat
=IoCreateFile(&hfile,access,&oba,&iosb,0,//AllocationSize this set to 0 that when file opened it was zeroed.
        FILE_ATTRIBUTE_NORMAL,share,FILE_OVERWRITE_IF,0,0,0,0,0,0);
    
if(!NT_SUCCESS(stat)){return 0;}
    
return hfile;
}

     通过发送 IRP_MJ_CREATE FSD 的方法与此类似,可以参考 IFSDDK document IRP_MJ_CREATE 说明。不同于上面方法的是需要自己创建一个 FILE_OBJECT ,好于上面方法的是这种方法不需要一个 HANDLE HANDLE 是线程依赖的 ,FileObject 则是线程无关。

3.2
文件的 Read Write
    
我们通过给 FSD 发送 IRP_MJ_READ 来读取文件,给 FSD 发送 IRP_MJ_WRITE 来改写文件。
    
如果我们是通过一个 HANDLE 来执行 ( 如使用 IoCreateFile 打开的文件 ) ,就要先用 ObReferenceObjectByHandle 函数来获得这个 Handle 对应的 FileObject 。我们只能给 FileObject 发送 IRP


stat
= ObReferenceObjectByHandle(handle,GENERIC_READ, * IoFileObjectType,KernelMode,(PVOID * ) & fileob, 0 );
     之后我们使用 IoAllocateIrp 分配一个 IRP 。根据 FileObject->DeviceObject->Flags 的值,我们判断目标文件系统使用什么样的 IO 方式。

     if (fileob -> DeviceObject -> Flags  &  DO_BUFFERED_IO)
    
{
        irp
->AssociatedIrp.SystemBuffer=buffer;//buffered io
    }

    
else   if (fileob -> DeviceObject -> Flags  &  DO_DIRECT_IO)
    
{
        mdl
=IoAllocateMdl(buffer,count,0,0,0);
        MmBuildMdlForNonPagedPool(mdl);
        irp
->MdlAddress=mdl;//direct io
    }

    
else
    
{
        irp
->UserBuffer=buffer;//neither i/o, use kernel buffer
    }

    对每种不同的 IO 方式使用不同的地址传递方式。随后我们填充 IRP 内的各个参数域,就可以发送 IRP 了。以 Read 为例:

    irpsp -> FileObject = fileob;
    irpsp
-> MajorFunction = IRP_MJ_READ;
    irpsp
-> MinorFunction = IRP_MN_NORMAL; // 0
    irpsp -> Parameters.Read.ByteOffset = offsetused;
    irpsp
-> Parameters.Read.Key = 0 ;
    irpsp
-> Parameters.Read.Length = count;
     接着要考虑如果 IRP 不能及时完成,会异步的返回的情况,我们安装一个 CompletionRoutine ,在 CompletionRoutine 里面设置一个事件为已激活,通知我们的主线程读取或者写入操作已经完成。

IoSetCompletionRoutine(irp,IoCompletion, & event , 1 , 1 , 1 );

NTSTATUS
  IoCompletion(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp,
    IN PVOID  Context
    )
{
    KeSetEvent((PRKEVENT)Context, IO_DISK_INCREMENT, 
0);
    
return STATUS_MORE_PROCESSING_REQUIRED;
}

     现在可以发送 IRP 了。如果不采取特殊的措施的话, IRP 发送目标是 FileObject 对应的 DeviceObject 。发送后,等待 IRP 的完成并且释放资源,返回。

    stat = IoCallDriver(fileob -> DeviceObject,irp);
    
if (stat == STATUS_PENDING) {
        KeWaitForSingleObject(
&event, Executive,KernelMode,0,0);
        stat
=irp->IoStatus.Status;
    }

    
if ( ! NT_SUCCESS(stat))
    
{
        IoFreeIrp(irp);
        
if(mdl){IoFreeMdl(mdl);}//if DO_DIRECT_IO
        return -1;
    }

    stat
= irp -> IoStatus.Information; // bytes read
    IoFreeIrp(irp);
    
if (mdl) {IoFreeMdl(mdl);} // if DO_DIRECT_IO
     return  stat;
3.3 文件的 Delete
    Delete
实际上是通过向 FSD 发送 IRP_MJ_SET_INFORMATION IRP ,并把 IrpSp->Parameters.SetFile.FileInformationClass 设置为 FileDispositionInformation ,用一个 FILE_DISPOSITION_INFORMATION 结构填充 buffer 来执行的。

    fdi.DeleteFile = TRUE;

    irpsp
-> MajorFunction = IRP_MJ_SET_INFORMATION;
    irpsp
-> Parameters.SetFile.Length  =   sizeof (FILE_DISPOSITION_INFORMATION);
    irpsp
-> Parameters.SetFile.FileInformationClass  =  FileDispositionInformation; 
    irpsp
-> Parameters.SetFile.DeleteHandle  =  (HANDLE)handle; 

3.4
文件的 Rename
    
类似于 Delete Rename 是向 FSD 发送 IRP_MJ_SET_INFORMATION IRP ,把 IrpSp->Parameters.SetFile.FileInformationClass 设置为 FileRenameInformation ,填充 buffer FILE_RENAME_INFORMATION 结构。

    fri.ReplaceIfExists = TRUE;
    fri.RootDirectory
= 0 ; // Set fri.FileName to full path name.
    fri.FileNameLength = wcslen(filename) * 2 ;
    wcscpy(fri.FileName,filename);
// If the RootDirectory member is NULL, and the file is being moved to a different directory, this member specifies the full pathname to be assigned to the file. 

    irpsp
-> MajorFunction = IRP_MJ_SET_INFORMATION;
    irpsp
-> Parameters.SetFile.Length  =   sizeof (FILE_FILE_RENAME_INFORMATION);
    irpsp
-> Parameters.SetFile.FileInformationClass  =  FileRenameInformation; 

    综上,于是我们可以在驱动里面通过发送IRP来直接访问文件系统了,绕过了native API win32 API层次。

4.
绕过文件系统过滤驱动和钩子

    
有了第三部分的内容,我们目前可以直接给FSD发送请求操作文件。但是这还不够,因为有很多的杀毒软件或者监视工具使用FSD Filter Driver或者FSD Hook的办法来监控文件操作。在今天这篇文章里我讲一些原理性的东西,提供绕过FSD Filter Driver / FSD Hook的思路。

4.1
对付文件系统过滤驱动

    
文件系统过滤驱动Attach在正常的文件系统之上,监视和过滤我们的文件访问。文件系统驱动栈就是由这一连串的Attach起来的过滤驱动组成。我们可以用IoGetRelatedDeviceObject这个函数来获得一个FileObject对应的最底层的那个功能驱动对象(FDO)。但是这样虽然绕过了那些过滤驱动,却同时也绕过了正常的FSDNtfs/Fastfat,因为正常的FSD也是作为一个过滤驱动存在的。磁盘文件对象的对应的最底层的FDOFtdisk.sys,它已经因为过于底层而不能处理我们投递的IRP请求。
    
其实正常的FSD信息存储在一个Vpb结构中,我们可以使用IoGetBaseFileSystemDeviceObject这个未公开的内核函数来得到它。它就是我们发送IRP的目标了。

4.2
对付替换DispatchRoutineFSD Hook

    
这是一种常用的FSD Hook方式。我们需要得到原本的DispatchRoutine,向原本的DispatchRoutine发送我们的IRP。这里提供一个思路:我们可以读取原本FSD驱动的.INIT段或者.TEXT段,查找其DriverEntry函数,在它的DriverEntry函数中肯定设置了自己的DriverObject的各个DispatchRoutine。在这个函数中我们就能找到我们想要的DispatchRoutine的地址。只需要使用特征码搜索的方法就可以搜索到这个值。

4.3
对付Inline Hook DispatchRoutine函数本身的FSD Hook

    
这种Hook方法比较狠毒,但不是非常常见于安全产品中,一般应用在木马和rootkit上,比如我自己写的rootkit。它没有更改DriverObject里面的DispatchRoutine的函数指针,而是向函数开头写入汇编指令的JMP来跳转函数。对付它的基本思路就是读取存在磁盘上的FSD的文件,加载到内存一份干净的备份,察看我们要调用的DispatchRoutine开头的几个字节和这个干净备份是否一致。如果不一致,尤其是存在JMP,RET,INT3一类的汇编指令的时候,很可能就是存在了Inline Hook。(但要充分考虑重定位的情况。)如果存在Inline Hook,我们就把干净的函数开头拷贝过来覆盖掉被感染的函数头。然后在发送IRP,就不会被Inline Hook监视或篡改了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值