Windows NTFS 系统分析和漏洞研究

语言: CN / TW / HK

简介

NTFS(New Technology File System)是微软1993年推出的用于Windows系统的文件系统,用于代替原来的FAT文件系统,从而提高性能。

本文将介绍 NTFS 漏洞挖掘方面的一些基础知识,并分析一个真实的漏洞供大家参考。

结构介绍

VCB 卷控制块,用来控制挂载的卷,如C盘、D盘等。

FCB 文件控制块,用来控制普通的文件或目录。

SCB 流控制块,用来控制文件中的流 (stream)。

CCB 上下文控制块,每次打开文件时会创建CCB,关闭文件后CCB会被销毁。

文件对象的以下成员会保存相应的结构。

FileObject->FsContext = SCB

FileObject-> FsContext2 = CCB

由于结构太长所以就不在文中展示结构详细内容,大家可以查看此链接: ntfsstru.h

ntfs.sys 通过 NtfsDecodeFileObject 函数,来获取相应的VCB FCB等结构。

TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );

获取方式就是通过查询 FileObject->FsContext 获取Scb,再通过 Scb 获取Fcb,Vcb。

TypeOfOpen 用于判断打开文件的类型,如果和函数可处理的类型不符,就会返回错误参数。

typedef enum _TYPE_OF_OPEN {

    UnopenedFileObject = 1,
    UserFileOpen,
    UserDirectoryOpen,
    UserVolumeOpen,
    StreamFileOpen,
    UserViewIndexOpen

} TYPE_OF_OPEN;

攻击面分析

NTFS 各种操作可以直接通过IOCTL调用,通过IOCTL调用的请求都会先进入 NtfsUserFsRequest 进行分发。

NtfsUserFsRequest
        /* File */
        NtfsQueryStorageReserve();
        NtfsGetStatisticsEx();
        NtfsQueryUsnJournal();
        NtfsReadUsnJournal();
        NtfsReadFileUsnData();
        NtfsWriteUsnCloseRecord();
        NtfsSetSparse();
        NtfsQueryFileRegions();
        NtfsQueryAllocatedRanges();
        NtfsCreateOrGetObjectId();
        NtfsGetObjectId();
        NtfsDeleteObjectId();
        NtfsGetRepairState();
        NtfsWaitForRepair();
        NtfsQueryVolumeNumaInfo();
        NtfsCheckForSection();
        NtfsReadFromPlex();
        NtfsSetZeroOnDeallocate();

        /* Dir */
        NtfsQueryStorageReserve();
        NtfsGetStatisticsEx();
        NtfsQueryUsnJournal();
        NtfsReadUsnJournal();
        NtfsReadFileUsnData();
        NtfsWriteUsnCloseRecord();
        NtfsCreateOrGetObjectId();
        NtfsDeleteObjectId();
        NtfsGetRepairState();
        NtfsWaitForRepair();
        NtfsQueryVolumeNumaInfo();

        /* Volume */
        NtfsGetRetrievalPointerBase();
        NtfsQueryStorageReserve();
        NtfsGetStatisticsEx();
        NtfsSetRepairState();
        NtfsGetRepairState();
        NtfsQueryVolumeNumaInfo();
        NtfsGetVolumeData();
        NtfsIsVolumeDirty();
        NtfsMarkVolumeDirty();
        NtfsIsVolumeMounted();
        NtfsGetBootAreaInfo();
        NtfsReadFromPlex();
        NtfsSetExtendedDasdIo();
        NtfsGetMftRecord();
        NtfsDefineStorageReserve();
        NtfsDeleteStorageReserve();
        NtfsRepairStorageReserve();
        NtfsSetPersistentVolumeState();
        NtfsQueryPersistentVolumeState();
        NtfsPrefetchFile();

通过调用 NtQueryInformationFile 和 NtSetInformationFile,选择不同的_FILE_INFORMATION_CLASS 会触发不同的函数。

NtQueryInformationFile(hFile,
        &IOB,
        OutBuffer,
        QuerySize,
        FileDirectoryInformation
);

可调用的函数:
NtfsQueryNameInfo()
NtfsQueryStandardInfo()
NtfsQueryBasicInfo()
NtfsQueryEaInfo()
NtfsQueryAlternateNameInfo()
NtfsQueryStreamsInfo()
NtfsQueryCompressedFileSize()
NtfsQueryNetworkOpenInfo()
NtfsQueryLinksInfo()
NtfsQueryIdInfo()
NtfsQueryDesiredStorageClassInfo()
NtfsQueryStatInfo()
NtfsQueryStatLxInfo()
NtfsQueryCaseSensitiveInfo()
NtfsQueryStorageReserveIdInfo()
NtfsGetSfioReservation()

DWORD Status = NtSetInformationFile(
        hFile,
        &IOB,
        SetBuffer,
        sizeof(SetBuffer),
        FileRenameInformation
    );

可调用的函数:
        NtfsSetRenameInfo()
        NtfsSetDispositionInfo()                
        NtfsSetPositionInfo()               
        NtfsSetEndOfFileInfo()          
        NtfsSetValidDataLengthInfo()            
        NtfsSetStorageReserveIdInfo()       
        NtfsSetDesiredStorageClassInfo()    
        NtfsSetAllocationInfo()         
        NtfsSetCaseSensitiveInfo()          
        NtfsSetSfioReservation()            
        NtfsSetShortNameInfo()              
        NtfsSetLinkInfo()

完整调用流程

以下是打开普通文件并调用IOCTL的代码示例:

hFile = (HANDLE)CreateFile(
        L"test.txt",
        GENERIC_ALL,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_ALWAYS,
        FILE_FLAG_OVERLAPPED, 
        NULL
    );

    BYTE * bufInput = (BYTE * )malloc(0x1000);
    BYTE * bufOutput = (BYTE * )malloc(0x1000);;
    DWORD status;
    DWORD nbBytes = 0;

    memset(bufInput , 0x41, 0x1000);
    memset(bufOutput , 0x41, 0x1000);

    *((DWORD*)brutebufInput) = 0x1;
    //调用 NtfsDefineStorageReserve (0x90410)
    status = DeviceIoControl(hFile , 0x90410, bufInput, 0x1000, bufOutput , 0x1000, &nbBytes, NULL);
    if (FALSE == status)
    {
        printf("[*] Error Code:%d\n", GetLastError());
    }
    return 0;

前面几节我们讲述了 NTFS 漏洞挖掘的基础知识,最后再总结一下大致的流程。

首先代码通过CreateFile 打开一个文件/卷/目录,然后将得到的句柄传入到 NtSetInformationFile 、NtQueryInformationFile 或者 DeviceIoControl ,然后代码将会触发 Ntfs.sys 中的内核函数,比如示例中的 NtfsDefineStorageReserve ,接着将会调用 NtfsDecodeFileObject 去查询FileObject中的Scb,通过Scb 获取 Vcb 和 Fcb等结构,最后就会执行业务代码,比如重名命,创建重解析点等业务操作。

CVE-2021-43231 漏洞分析

Patch的地方一共两处,先来分析第一处。

左边为 Patch 后的代码,通过diff发现,Patch后增加了一段check代码,即绿框标注的部分。该代码对v14 的长度进行了检查,其中ParentScb->NormalizedName.Length 为目录名字的总长度,ShortName 是我们Inbuffer传入的 unicode_string 长度,如果v14 + 2后大于0xFFFE就表示出现了整数溢出,所以会直接返回,阻止代码继续向下执行。

红框的代码是存在漏洞的代码,如果没有check,我们可以通过修改注册表或通过策略组,开启长路径模式,然后将目录的长度加到接近0xffff,例如 :\?\C:\aaaaa\aaaaa\aaa…\…\test.txt , 这样 v51就会溢出。然后申请ExAllocatePoolWithTag 时将会申请很小的池块,下面将ParentScb->NormalizedName.Length 作为length ,进行memmove,即可造成池溢出。

第二处Patch :

这个Patch和第一处一样,都是设置超长路径然后达到整数溢出。

下面来说一下如何设置超长路径:

引用自:https://blog.csdn.net/ZxqSoftWare/article/details/108519131

打开策略管理器:按下win徽标键+R,输入gpedit.msc并回车,或者直接通过开始菜单打开gpedit;
定位到Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesystem;
在当前位置寻找策略Enable NTFS long paths,在较老的系统版本中,该项策略会在Filesystem下的NTFS策略组中;
双击Enable NTFS long paths策略,将状态改为Enabled并保存即可。
STACK_TEXT:  
ffffd802`168d0598 fffff802`0792eb12     : ffffd802`168d0700 fffff802`07799200 00000000`00000100 00000000`00000000 : nt!DbgBreakPointWithStatus
ffffd802`168d05a0 fffff802`0792e0f6     : 00000000`00000003 ffffd802`168d0700 fffff802`078280c0 00000000`00000139 : nt!KiBugCheckDebugBreak+0x12
ffffd802`168d0600 fffff802`078132c7     : 00000000`00000000 ffffb208`6533d010 ffff9c05`74301fa8 ffffb208`6533d010 : nt!KeBugCheck2+0x946
ffffd802`168d0d10 fffff802`07825169     : 00000000`00000139 00000000`0000001d ffffd802`168d1070 ffffd802`168d0fc8 : nt!KeBugCheckEx+0x107
ffffd802`168d0d50 fffff802`07825590     : 00000000`000000ec 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiBugCheckDispatch+0x69
ffffd802`168d0e90 fffff802`07823923     : 00000000`00000000 00000000`00000001 00000000`00000001 00000000`00000000 : nt!KiFastFailDispatch+0xd0
ffffd802`168d1070 fffff802`07859917     : ffff9c05`41e00100 fffff802`07675aaa ffffffff`ffffffff 00000000`00000004 : nt!KiRaiseSecurityCheckFailure+0x323
ffffd802`168d1200 fffff802`07675aaa     : ffffffff`ffffffff 00000000`00000004 00000000`000000ff ffff9c05`4f201f40 : nt!RtlRbInsertNodeEx+0x1b17f7
ffffd802`168d1210 fffff802`076a5772     : ffff9c05`41e00000 ffff9c05`41e00100 00000000`4f2000ff 00000000`00000000 : nt!RtlpHpSegPageRangeShrink+0x1ba
ffffd802`168d1280 fffff802`07dce149     : ffffb208`00000000 00000000`00000000 ffffb208`62b809d0 01000000`00100000 : nt!ExFreeHeapPool+0x6b2
ffffd802`168d1360 fffff802`07a949e4     : ffffd802`168d1500 ffffb208`6533d010 ffffb208`6a8ccd60 ffffb208`62ef4d90 : nt!ExFreePool+0x9
ffffd802`168d1390 fffff802`07a8ed00     : ffffd802`168d1599 00000000`00000000 ffffb208`616bfda0 ffffb208`6533d010 : nt!IopDeleteFile+0x184
ffffd802`168d1410 fffff802`07624357     : 00000000`00000000 00000000`00000000 ffffd802`168d1599 ffffb208`6a8ccd90 : nt!ObpRemoveObjectRoutine+0x80
ffffd802`168d1470 fffff802`07a0f74e     : ffffb208`616bfda0 00000000`00000000 00000000`00000000 ffffb208`616bfda0 : nt!ObfDereferenceObjectWithTag+0xc7
ffffd802`168d14b0 fffff802`07a78285     : 00000002`00000043 fffff803`b5c4347c 00000001`0000001c ffffd802`168d15d0 : nt!ObCloseHandleTableEntry+0x29e
ffffd802`168d15f0 fffff802`07a7a0dd     : ffffb208`675a7080 ffffb208`675a7080 ffffffff`ffffff01 ffffb208`680524d8 : nt!ExSweepHandleTable+0xd5
ffffd802`168d16a0 fffff802`07a77fd0     : ffffffff`ffffffff ffffb208`68052080 ffffd802`168d16f0 fffff802`07a3bdec : nt!ObKillProcess+0x35
ffffd802`168d16d0 fffff802`07acc476     : ffffb208`68052080 ffff9c05`e4e10560 ffffd802`168d1920 00000000`00000000 : nt!PspRundownSingleProcess+0x204
ffffd802`168d1760 fffff802`07b10d88     : 00000000`00000000 00000000`00000001 00000000`000000c0 0000002b`0975e000 : nt!PspExitThread+0x5f6
ffffd802`168d1860 fffff802`0762b0d7     : ffffb208`61680101 00000000`00000000 ffffb208`69aeb650 00000000`00000000 : nt!KiSchedulerApcTerminate+0x38
ffffd802`168d18a0 fffff802`07817760     : 0000015b`286b2db0 ffffd802`168d1950 0000002b`099ff838 ffffb208`00000000 : nt!KiDeliverApc+0x487
ffffd802`168d1950 fffff802`07824c5f     : ffffd802`168d1b00 00000000`00000000 00000000`00000000 ffffb208`69aeb650 : nt!KiInitiateUserApc+0x70
ffffd802`168d1a90 00007ff9`be0f07c4     : 00007ff9`be0a2dc7 00000000`00010002 00000000`00000001 0000015b`00000001 : nt!KiSystemServiceExit+0x9f
0000002b`099ff7e8 00007ff9`be0a2dc7     : 00000000`00010002 00000000`00000001 0000015b`00000001 0000015b`286bff40 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
0000002b`099ff7f0 00007ff9`bce57034     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
0000002b`099ffaf0 00007ff9`be0a2651     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
0000002b`099ffb20 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

NTFS 文件系统经过多年的发展,漏洞越来越难发现,文中的漏洞也是需要通过开启长路径模式才能触发,在默认情况下是无法触发的。这篇文章简单的介绍了一下NTFS的一些结构和攻击面,并分析了一个实际的漏洞,希望能带给大家一些参考。