Last week Microsoft released patch for reported by me vulnerability in FastFat driver marking it as:
MS14-063 – Vulnerability in FAT32 Disk Partition Driver Could Allow Elevation of Privilege (2998579)
[CVE-2014-4115].
Let me present some of the most interesting parts of advisory and add couple details/comments.
[VIDEO]When the bug kicks in…
Microsoft informs that the following OS’s are vulnerable:
• Microsoft Windows Server 2003 SP2
• Vista SP2
• Server 2008 SP2
BUT… as you can imagine “forgotten” in this year Windows XP and its FastFAT driver also contains this vulnerability but bug in it will stay there forever…Let we see Windows XP SP3 reaction on evil usb stick with malformed FAT32 partition.
[+]General description
Vulnerable code existing in FastFAT.sys driver can be exploited via intentionally malformed FAT32 Boot sector delivered with usb stick. All values of field “Number of FATs” greater than 2 will lead to not enough memory allocation for further actions made by FastFAT.sys driver and in consequences to pool overflow.
[+]How that thing happens ? – code analysis
First of all view of my malformed FAT32 Boot Sector:
Member "Value (dec)" "Value (hex)" Size "00000000 struct BOOTSECTOR_FAT32" {...} 00000200 " 00000000 int8 jmp[00000003]" 00000003 " 00000003 char OemName[00000008]" MSDOS5.0 00000008 " 0000000B struct BPB_FAT32" {...} 00000035 " 0000000B uint16 BytesPerSector" 512 0200 00000002 " 0000000D int8 SectorsPerCluster" 1 01 00000001 " 0000000E uint16 ReservedSectors" 36 0024 00000002 " 00000010 int8 NumberOfFATs" 119 77 00000001 " 00000011 uint16 RootEntries" 0 0000 00000002 " 00000013 uint16 TotalSectors" 0 0000 00000002 " 00000015 int8 Media" -8 F8 00000001 " 00000016 uint16 SectorsPerFAT" 0 0000 00000002 " 00000018 uint16 SectorsPerTrack" 63 003F 00000002 " 0000001A uint16 HeadsPerCylinder" 255 00FF 00000002 " 0000001C uint32 HiddenSectors" 63 0000003F 00000004 " 00000020 uint32 TotalSectorsBig" 80262 00013986 00000004 "++ FAT32 Section " 00000024 uint32 SectorsPerFAT" 618 0000026A 00000004 " 00000028 uint16 Flags" 0 0000 00000002 " 0000002A uint16 Version" 0 0000 00000002 " 0000002C uint32 RootCluster" 2 00000002 00000004 " 00000030 uint16 InfoSector" 1 0001 00000002 " 00000032 uint16 BootBackupStart" 6 0006 00000002 " 00000034 int8 Reserved[0000000C]" 0000000C " 00000040 int8 DriveNumber" -128 80 00000001 " 00000041 int8 Unused" 1 01 00000001 " 00000042 int8 ExtBootSignature" 41 29 00000001 " 00000043 uint32 SerialNumber" 3896654535 E8423AC7 00000004 " 00000047 char VolumeLabel[0000000B]" "NO NAME " 0000000B " 00000052 char FileSystem[00000008]" "FAT32 " 00000008 " 0000005A blob BootCode[000001A6]" 000001A6
I will reference to some fields and their values in further analysis.
Activation of special pool will help us to locate moment when a pool overflow starts to appear
verifier /volatile /flags 0x1 /adddriver fastfat.sys
After insertion of usb stick…
[+]Crash dump info
kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION (d6) N bytes of memory was allocated and more than N bytes are being referenced. This cannot be protected by try-except. When possible, the guilty driver's name (Unicode string) is printed on the bugcheck screen and saved in KiBugCheckDriver. Arguments: Arg1: 8a011008, memory referenced Arg2: 00000001, value 0 = read operation, 1 = write operation Arg3: b7ae63da, if non-zero, the address which referenced memory. Arg4: 00000000, (reserved) Debugging Details: ------------------ WRITE_ADDRESS: 8a011008 Special pool FAULTING_IP: Fastfat!FatCommonWrite+444 b7ae63da 8978fc mov dword ptr [eax-4],edi MM_INTERNAL_CODE: 0 IMAGE_NAME: Fastfat.SYS DEBUG_FLR_IMAGE_TIMESTAMP: 48025b94 MODULE_NAME: Fastfat FAULTING_MODULE: b7ada000 Fastfat DEFAULT_BUCKET_ID: DRIVER_FAULT BUGCHECK_STR: 0xD6 PROCESS_NAME: System TRAP_FRAME: bacdf8c0 -- (.trap 0xffffffffbacdf8c0) ErrCode = 00000002 eax=8a01100c ebx=8a7ece90 ecx=00186c00 edx=00000800 esi=89a14b18 edi=00004800 eip=b7ae63da esp=bacdf934 ebp=bacdfab4 iopl=0 nv up ei ng nz ac pe cy cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010297 Fastfat!FatCommonWrite+0x444: b7ae63da 8978fc mov dword ptr [eax-4],edi ds:0023:8a011008=???????? Resetting default scope LAST_CONTROL_TRANSFER: from 8051cc4f to 804f8cb5 STACK_TEXT: bacdf848 8051cc4f 00000050 8a011008 00000001 nt!KeBugCheckEx+0x1b bacdf8a8 8054051c 00000001 8a011008 00000000 nt!MmAccessFault+0x8e7 bacdf8a8 b7ae63da 00000001 8a011008 00000000 nt!KiTrap0E+0xcc bacdfab4 b7adab9a 89a14b18 8a7ece90 89c59020 Fastfat!FatCommonWrite+0x444 bacdfaf8 804ee119 89c59020 8a7ece90 806d12a4 Fastfat!FatFsdWrite+0xad bacdfb08 8064d628 89ada170 00004000 89c59020 nt!IopfCallDriver+0x31 bacdfb2c 804ef411 bacdfb68 bacdfd40 00000000 nt!IovCallDriver+0xa0 bacdfb40 8050c497 89ada107 bacdfb68 bacdfbfc nt!IoSynchronousPageWrite+0xaf bacdfc24 8050ce3d e10ec820 e10ec828 e10ec828 nt!MiFlushSectionInternal+0x3bf bacdfc60 804e38a2 89c33d70 e10ec820 00000004 nt!MmFlushSection+0x1b5 bacdfce8 804e3bc4 00001000 00000000 00000001 nt!CcFlushCache+0x386 bacdfd2c 804e61ee 89da0290 8055b0c0 89da1da8 nt!CcWriteBehind+0xdc bacdfd74 80534c02 89da0290 00000000 89da1da8 nt!CcWorkerThread+0x126 bacdfdac 805c6160 89da0290 00000000 00000000 nt!ExpWorkerThread+0x100 bacdfddc 80541dd2 80534b02 00000000 00000000 nt!PspSystemThreadStartup+0x34 00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16 STACK_COMMAND: kb FOLLOWUP_IP: Fastfat!FatCommonWrite+444 b7ae63da 8978fc mov dword ptr [eax-4],edi SYMBOL_STACK_INDEX: 3 SYMBOL_NAME: Fastfat!FatCommonWrite+444 FOLLOWUP_NAME: MachineOwner FAILURE_BUCKET_ID: 0xD6_VRF_Fastfat!FatCommonWrite+444 BUCKET_ID: 0xD6_VRF_Fastfat!FatCommonWrite+444 Followup: MachineOwner
[+]Detailed analysis
With active special pool for fastfat.sys You should land in below area when the bug kicks in:
[x86]
PAGE:0001C37A movzx edx, word ptr [eax+90h]
PAGE:0001C381 movzx ecx, cx
PAGE:0001C384 imul edx, ecx
PAGE:0001C387 mov [ebp+BytesPerFat], edx
PAGE:0001C38D
PAGE:0001C38D loc_1C38D:
PAGE:0001C38D mov cl, [eax+96h]
PAGE:0001C393 cmp cl, 2 ; cl = Number of FATs
PAGE:0001C396 jbe short RegularFATsAmount
PAGE:0001C398 push ‘itaF’ ; Tag
PAGE:0001C39D movzx eax, cl
PAGE:0001C3A0 push eax ; Number of FATs used as pool size
PAGE:0001C3A1 push 11h ; PoolType
PAGE:0001C3A3 call ds:__imp__ExAllocatePoolWithTag
PAGE:0001C3A9 mov edi, eax
PAGE:0001C3AB mov eax, [ebp+VCB]
PAGE:0001C3AE jmp short loc_1C3B6
PAGE:0001C3B0 ; —————————————————————————
PAGE:0001C3B0
PAGE:0001C3B0 RegularFATsAmount:
PAGE:0001C3B0 lea edi, [ebp+var_174]
PAGE:0001C3B6
PAGE:0001C3B6 loc_1C3B6:
PAGE:0001C3B6 mov [ebp+unknow], edi
PAGE:0001C3BC and [ebp+counter], 0
PAGE:0001C3C3 cmp byte ptr [eax+96h], 0
PAGE:0001C3CA jbe short loc_1C410
PAGE:0001C3CC mov ecx, [ebp+offsetToFirstFAT]
PAGE:0001C3CF mov edx, ecx
PAGE:0001C3D1 sub edx, [ebp+StartingVbo]
PAGE:0001C3D4 lea eax, [edi+0Ch]
PAGE:0001C3D7
PAGE:0001C3D7 fillArrayLoop:
PAGE:0001C3D7 mov edi, [ebp+offsetToFirstFAT]
PAGE:0001C3DA mov [eax-4], edi
PAGE:0001C3DD mov [eax-0Ch], ecx
PAGE:0001C3E0 and dword ptr [eax-8], 0
PAGE:0001C3E4 mov [eax], edx
PAGE:0001C3E6 mov edi, [ebp+unknow3]
PAGE:0001C3EC mov [eax+4], edi
PAGE:0001C3EF inc [ebp+counter]
PAGE:0001C3F5 add ecx, [ebp+BytesPerFat]
PAGE:0001C3FB add eax, 18h
PAGE:0001C3FE mov edi, [ebp+VCB]
PAGE:0001C401 movzx edi, byte ptr [edi+96h] ; to EDI goes Number of FATs
PAGE:0001C408 cmp [ebp+counter], edi
PAGE:0001C40E jb short fillArrayLoop
[/x86]
as You can see above, when number of FATs is bigger than 2 it’s amount is used to allocate pool of that size.
[x86]
PAGE:0001C39D movzx eax, cl
PAGE:0001C3A0 push eax ; Number of FATs used as pool size
PAGE:0001C3A1 push 11h ; PoolType
PAGE:0001C3A3 call ds:__imp__ExAllocatePoolWithTag
[/x86]
Next loop appears and it iterates amount of times indicated by number of FATs. In this loop ( fillArrayLoop ), pool allocated by code describe above is filled by array of structures
where size of structure equals 24 bytes (structure consists of 6 DWORD’s). Is easy to deduce that iteration will lead to pool overflow because during pool allocation number of FATs was taken in count without size of structure element. It’s even easier to understand on pseudo code:
NumberOfFATs = *(_BYTE *)(VCB + 150); if ( NumberOfFATs <= 2u ) { ptrPool = &v100; } else { ptrPool = ExAllocatePoolWithTag((POOL_TYPE)17, NumberOfFATs, 'itaF'); [BUG]//NumberOfFATs !!! instead of NumberOfFATs * sizeof(SomeStructure)=24 v14 = VCB; } counter = 0; if ( *(_BYTE *)(v14 + 0x96) ) { v18 = offsetToFirstFAT; unknow1 = offsetToFirstFAT - unknow2; v20 = (int)((char *)ptrPool + 12); do { *(_DWORD *)(v20 - 4) = offsetToFirstFAT; *(_DWORD *)(v20 - 12) = v18; *(_DWORD *)(v20 - 8) = 0; *(_DWORD *)v20 = unknow1; *(_DWORD *)(v20 + 4) = unknow3; ++counter; v18 += BytesPerFat; v20 += 24; } while ( counter < NumberOfFATs ); }
[+]Exploitability
Taking glance at how this array looks in memory after couple iterations:
FOR 3 loops cycle :
#1 00 48 00 00 00 00 00 00 00 48 00 00 00 08 00 00 00 02 00 00 ef ef ef ef <- not initialized #2 00 1c 05 00 00 00 00 00 00 48 00 00 00 08 00 00 00 02 00 00 ef ef ef ef <- not initialized #3 00 f0 09 00 00 00 00 00 00 48 00 00 00 08 00 00 00 02 00 00 ef ef ef ef <- not initialized
Which values at particular offset we are able to control ?
Offset +0: (v20 – 12)
For first iteration it’s value equals offsetToFirstFAT and further it’s result of adding: (v20 – 12) = (v20 – 12) + BytesPerFat.
Where BytesPerFAT equals : Bytes per sector = 512 * Sectors per fat = 618 == 0x4D400 (316416)
Offset +4: (v20 – 8)
Constant 0
Offset + 8: (v20 – 4)
Value of this field always equals offsetToFirstFAT.
Where offsetToFirstFAT : Bytes per sector * Reserved sectors == (0x4800)
Taking in count information’s about how this bug is triggered and what portion of date we are able to control during page overflow….how do you think, it’s exploitable or not ? I will leave the answer for the reader. But…
the most lucrative scenario for attacker would be to create malformed FAT32 partition which contains .exe file and gives possibility (not causes crash after usb stick insertion) to victim to run it.
Exe for e.g would be responsible of kernel pool spraying. After that it would write any random date to malformed partition triggering in the same time bug in FatCommonWrite. Ohh yeah….my observations shows that when you create nearly empty malformed partition containing couple files, bug only triggers when you open one of these file and try to save(write) modifications.
For sure we should not disregard this vulnerability because history and e.g recently Chris Evans write-up about off-by-one shows that any kind of scenario can become real in some circumstances.
Microsoft why so long ????!!!!
There is a lot of complaints on Microsoft because this particular vulnerability is patched in newer system version like Windows 7/8/8.1 so it implicates that it was good know vuln for MS for a long time and they did not patch it…I gonna leave it without comment, but also process of patching affected systems mentioned at the beginning of this post took a while because this vulnerability was submitted by me to MS at the beginning of March this year (2014) and currently we have October 2014….well 😉