惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
I
InfoQ
C
Check Point Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
Last Week in AI
Last Week in AI
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
B
Blog
T
The Blog of Author Tim Ferriss
H
Help Net Security
云风的 BLOG
云风的 BLOG

博客园 - jasonCao

遗传算法在自动组卷中的应用 Delphi调用C#dll的问题 Windows驱动开发,几个BLOG值得经常看看 IIS的ISAPI接口简介 文件过滤系统驱动开发Filemon学习笔记 Windows文件系统过滤驱动开发教程(11) Windows文件系统过滤驱动开发教程(10) Windows文件系统过滤驱动开发教程(9) Windows文件系统过滤驱动开发教程(8) Windows文件系统过滤驱动开发教程(7) Windows文件系统过滤驱动开发教程(6) Windows文件系统过滤驱动开发教程(5) Windows文件系统过滤驱动开发教程(4) Windows文件系统过滤驱动开发教程(3) Windows文件系统过滤驱动开发教程(2) Windows文件系统过滤驱动开发教程(0,1) Windows 文件过滤驱动经验总结(转) GMGuard网页防篡改保护系统 从底层了解ASP.NET体系结构 编写高性能web应用程式的10个技巧_asp.net技巧
Windows平台内核级文件访问
jasonCao · 2008-06-28 · via 博客园 - jasonCao

Windows平台内核级文件访问
作者:baiyuanfan (baiyuanfan@163.com)
日期:2.1,2006

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)。但是这样虽然绕过了那些过滤驱动,却同时也绕过了正常的FSD如Ntfs/Fastfat,因为正常的FSD也是作为一个过滤驱动存在的。磁盘文件对象的对应的最底层的FDO是Ftdisk.sys,它已经因为过于底层而不能处理我们投递的IRP请求。
    其实正常的FSD信息存储在一个Vpb结构中,我们可以使用IoGetBaseFileSystemDeviceObject这个未公开的内核函数来得到它。它就是我们发送IRP的目标了。

4.2对付替换DispatchRoutine的FSD 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监视或篡改了。