Windows NTFS 系统分析和漏洞研究
简介
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 漏洞分析
漏洞存在于 NtfsSetShortNameInfo 函数内,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的一些结构和攻击面,并分析了一个实际的漏洞,希望能带给大家一些参考。