首先来关注下FAT32的数据结构,下图展示的是FAT32BootSector的格式:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/06247bdc0455fa8b836e7babe28c7b91.png)
该段数据位于第一扇区。更多有关fat32的数据结构以及各字段含义请参阅官方文档:
http://www.ntfs.com/fat-partition-sector.htm
Icewall在博客中提到,修改10H偏移处的FATCount数值即可造成蓝屏。
我们用010Editor打开一个U盘,修改此处02为0x77:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/eb60df8dee5fa299bb6b7053e8a7e0af.png)
不过并没有蓝屏,并且在测试中稳定运行了很长时间。
只有在向U盘中写入数据的时候,才会触发异常:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/a6f40fdbf1cd43bfbc9cebd860c65983.png)
内核崩溃在nt!ExFreePoolWithTag函数中,很显然,这个kernelpoolblock被破坏了。
HeapOverflow调整好verifier重新来过,捕捉到异常:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/36a79707b8a9ba363a9331fbe3a29b34.png)
Windbg提示的地方位于被破坏的堆块申请的时刻.
此处的代码逻辑,如果NumberOfFATs的值大于2,那么跳入申请内存的程序分支:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/463d5deda85acf55a42090b9064dc79a.png)
在调试的时候观察NumberOfBytes参数
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/f840e9de1680a411d935bb1123e42a92.png)
这是直接把FAT32BootSector的NumberOfFATs字段数值77h作为NumberOfBytes申请内存。
Kernelpool刚申请好的样子,77h对齐,长度为80h,标记Fati。
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/bc0ca882b48bc4f1eec81bd4d69ceae6.png)
这中间经历了kernelpoolblock数据的填充,也是在这个时候,发生了溢出。
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/9ed553a3062e6b30abfaa523c0bd636f.png)
在调用nt!ExFreePoolWithTag之前,再来看一下内存:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/df5c30d88208ed62dcb1e9c0606c7e9e.png)
此时Fati标签后面的块首已经被淹没了。
Vulnerability对相邻的下方块首下硬件写入断点,可以追查到溢出的时刻:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/0b61b97ae19a9510b3bd16067eae6ec0.png)
就在申请内存之后的位置。
实际上此处的代码逻辑可以参看github上对fat32驱动实现的源码:
![MS14-063(CVE-2014-4115)FAT32驱动内核溢出分析](https://i-blog.csdnimg.cn/blog_migrate/ec3006aa8fac1aefae9ac9169615e058.png)
/*++
Copyright (c) 1989-2000 Microsoft Corporation
Module Name:
Write.c
Abstract:
This module implements the File Write routine for Write called by the
dispatch driver.
--*/
#include "FatProcs.h"
//
// The Bug check file id for this module
//
#define BugCheckFileId (FAT_BUG_CHECK_WRITE)
//
// The local debug trace level
//
#define Dbg (DEBUG_TRACE_WRITE)
//
// Macros to increment the appropriate performance counters.
//
#define CollectWriteStats(VCB,OPEN_TYPE,BYTE_COUNT) { \
PFILESYSTEM_STATISTICS Stats = &(VCB)->Statistics[KeGetCurrentProcessorNumber()].Common; \
if (((OPEN_TYPE) == UserFileOpen)) { \
Stats->UserFileWrites += 1; \
Stats->UserFileWriteBytes += (ULONG)(BYTE_COUNT); \
} else if (((OPEN_TYPE) == VirtualVolumeFile || ((OPEN_TYPE) == DirectoryFile))) { \
Stats->MetaDataWrites += 1; \
Stats->MetaDataWriteBytes += (ULONG)(BYTE_COUNT); \
} \
}
BOOLEAN FatNoAsync = FALSE;
//
// Local support routines
//
VOID
FatDeferredFlushDpc (
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
);
VOID
FatDeferredFlush (
PVOID Parameter
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FatDeferredFlush)
#pragma alloc_text(PAGE, FatCommonWrite)
#endif
NTSTATUS
FatFsdWrite (
IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine implements the FSD part of the NtWriteFile API call
Arguments:
VolumeDeviceObject - Supplies the volume device object where the
file being Write exists
Irp - Supplies the Irp being processed
Return Value:
NTSTATUS - The FSD for the IRP
--*/
{
PFCB Fcb;
NTSTATUS Status;
PIRP_CONTEXT IrpContext = NULL;
BOOLEAN ModWriter = FALSE;
BOOLEAN TopLevel;
DebugTrace(+1, Dbg, "FatFsdWrite\n", 0);
//
// Call the common Write routine, with blocking allowed if synchronous
//
FsRtlEnterFileSystem();
//
// We are first going to do a quick check for paging file IO. Since this
// is a fast path, we must replicate the check for the fsdo.
//
if (!FatDeviceIsFatFsdo( IoGetCurrentIrpStackLocation(Irp)->DeviceObject)) {
Fcb = (PFCB)(IoGetCurrentIrpStackLocation(Irp)->FileObject->FsContext);
if ((NodeType(Fcb) == FAT_NTC_FCB) &&
FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE)) {
//
// Do the usual STATUS_PENDING things.
//
IoMarkIrpPending( Irp );
//
// Perform the actual IO, it will be completed when the io finishes.
//
FatPagingFileIo( Irp, Fcb );
FsRtlExitFileSystem();
return STATUS_PENDING;
}
}
#ifdef __ND_FAT__
do {
try {
if (IrpContext == NULL) {
TopLevel = FatIsIrpTopLevel( Irp );
IrpContext = FatCreateIrpContext( Irp, CanFsdWait( Irp ) );
IrpContext->TopLevel = TopLevel;
}
//
// This is a kludge for the mod writer case. The correct state
// of recursion is set in IrpContext, however, we much with the
// actual top level Irp field to get the correct WriteThrough
// behaviour.
//
if (IoGetTopLevelIrp() == (PIRP)FSRTL_MOD_WRITE_TOP_LEVEL_IRP) {
ModWriter = TRUE;
IoSetTopLevelIrp( Irp );
}
//
// If this is an Mdl complete request, don't go through
// common write.
//
if (FlagOn( IrpContext->MinorFunction, IRP_MN_COMPLETE )) {
DebugTrace(0, Dbg, "Calling FatCompleteMdl\n", 0 );
Status = FatCompleteMdl( IrpContext, Irp );
} else {
#ifdef __ND_FAT_SECONDARY__
#if 0
{
BOOLEAN lazyWriter;
PSCB scb = (PSCB)(IoGetCurrentIrpStackLocation(Irp)->FileObject->FsContext);
DebugTrace( 0, Dbg, ("FatFsdWrite: SavedTopLevelIrp = %p, FatIsTopLevelRequest() = %d, FatIsTopLevelFat() = %d, CanWait = %d\n",
FatGetTopLevelContext()->SavedTopLevelIrp, FatIsTopLevelRequest(IrpContext), FatIsTopLevelFat(IrpContext), FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) );
lazyWriter = ( scb->LazyWriteThread[0] == PsGetCurrentThread() || scb->LazyWriteThread[1] == PsGetCurrentThread());
if (lazyWriter) {
if (IS_SECONDARY_FILEOBJECT(IoGetCurrentIrpStackLocation(Irp)->FileObject))
ASSERT( !FatIsTopLevelRequest(IrpContext) && FatIsTopLevelFat(IrpContext) );
ASSERT( FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
ASSERT( FlagOn(Irp->Flags, IRP_PAGING_IO) );
if (VolumeDeviceObject->Secondary)
ASSERT( FatGetTopLevelContext()->SavedTopLevelIrp == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP );
}
if (!FlagOn(Irp->Flags, IRP_PAGING_IO)) {
ASSERT( FatIsTopLevelRequest(IrpContext) );
} else {
ASSERT( FlagOn(Irp->Flags, IRP_NOCACHE) );
if (FatIsTopLevelRequest(IrpContext)) {
ASSERT( FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
} else {
if (FatIsTopLevelFat(IrpContext)) {
if (FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) {
if (IS_SECONDARY_FILEOBJECT(IoGetCurrentIrpStackLocation(Irp)->FileObject))
ASSERT( lazyWriter == TRUE ); // || FatGetTopLevelContext()->SavedTopLevelIrp == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP );
} else
ASSERT( FatGetTopLevelContext()->SavedTopLevelIrp == (PIRP)FSRTL_MOD_WRITE_TOP_LEVEL_IRP ||
FatGetTopLevelContext()->SavedTopLevelIrp == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP );
} else {
ASSERT( FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
if (IrpContext->TopLevelIrpContext->MajorFunction != IRP_MJ_WRITE && VolumeDeviceObject->Secondary)
DebugTrace( 0, Dbg, ("FatFsdWrite: IrpContext->TopLevelIrpContext->MajorFunction = %d\n",
IrpContext->TopLevelIrpContext->MajorFunction) );
if (IS_SECONDARY_FILEOBJECT(IoGetCurrentIrpStackLocation(Irp)->FileObject))
ASSERT( IrpContext->TopLevelIrpContext->MajorFunction == IRP_MJ_WRITE );
}
}
}
}
#endif
if (IS_SECONDARY_FILEOBJECT(IoGetCurrentIrpStackLocation(Irp)->FileObject)) {
BOOLEAN secondaryResourceAcquired = FALSE;
BOOLEAN secondaryRecoveryResourceAcquired = FALSE;
#if 0
BOOLEAN lazyWriter;
PSCB scb = (PSCB)(IoGetCurrentIrpStackLocation(Irp)->FileObject->FsContext);
#ifdef __ND_FAT_DBG__
ASSERT( FlagOn(IrpContext->NdFatFlags, ND_FAT_IRP_CONTEXT_FLAG_SECONDARY_FILE) );
#endif
lazyWriter = ( scb->LazyWriteThread[0] == PsGetCurrentThread() || scb->LazyWriteThread[1] == PsGetCurrentThread());
#endif
if (FlagOn(Irp->Flags, IRP_PAGING_IO)) {
secondaryResourceAcquired =
SecondaryAcquireResourceSharedStarveExclusiveLite( IrpContext,
&VolumeDeviceObject->Secondary->Resource,
BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
if (secondaryResourceAcquired == FALSE) {
//ASSERT( lazyWriter );
ASSERT( FlagOn(VolumeDeviceObject->Secondary->Thread.Flags, SECONDARY_THREAD_FLAG_REMOTE_DISCONNECTED) ||
FlagOn(VolumeDeviceObject->Secondary->Flags, SECONDARY_FLAG_RECONNECTING) );
FatCompleteRequest( IrpContext, Irp, Status = STATUS_FILE_LOCK_CONFLICT );
break;
}
try {
Status = FatCommonWrite( IrpContext, Irp );
} finally {
ASSERT( ExIsResourceAcquiredSharedLite(&VolumeDeviceObject->Secondary->Resource) );
SecondaryReleaseResourceLite( NULL, &VolumeDeviceObject->Secondary->Resource );
}
break;
}
Status = STATUS_SUCCESS;
while (TRUE) {
ASSERT( secondaryRecoveryResourceAcquired == FALSE );
ASSERT( secondaryResourceAcquired == FALSE );
if (FlagOn(VolumeDeviceObject->Secondary->Thread.Flags, SECONDARY_THREAD_FLAG_REMOTE_DISCONNECTED) ||
FlagOn(VolumeDeviceObject->Secondary->Flags, SECONDARY_FLAG_RECONNECTING)) {
if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) {
Status = FatFsdPostRequest( IrpContext, Irp );
break;
}
}
if (FlagOn(VolumeDeviceObject->Secondary->Thread.Flags, SECONDARY_THREAD_FLAG_REMOTE_DISCONNECTED)) {
secondaryRecoveryResourceAcquired
= SecondaryAcquireResourceExclusiveLite( IrpContext,
&VolumeDeviceObject->Secondary->RecoveryResource,
BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
if (!FlagOn(VolumeDeviceObject->Secondary->Thread.Flags, SECONDARY_THREAD_FLAG_REMOTE_DISCONNECTED) ) {
SecondaryReleaseResourceLite( IrpContext, &VolumeDeviceObject->Secondary->RecoveryResource );
secondaryRecoveryResourceAcquired = FALSE;
continue;
}
secondaryResourceAcquired
= SecondaryAcquireResourceExclusiveLite( IrpContext,
&VolumeDeviceObject->Secondary->Resource,
BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
try {
SessionRecovery( VolumeDeviceObject->Secondary, IrpContext );
} finally {
SecondaryReleaseResourceLite( IrpContext, &VolumeDeviceObject->Secondary->Resource );
secondaryResourceAcquired = FALSE;
SecondaryReleaseResourceLite( IrpContext, &VolumeDeviceObject->Secondary->RecoveryResource );
secondaryRecoveryResourceAcquired = FALSE;
}
continue;
}
secondaryResourceAcquired
= SecondaryAcquireResourceSharedLite( IrpContext,
&VolumeDeviceObject->Secondary->Resource,
BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
if (secondaryResourceAcquired == FALSE) {