Story about MS14-063

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:

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

as You can see above, when number of FATs is bigger than 2 it’s amount is used to allocate pool of that size.

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

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 😉

This entry was posted in Bugs, Security and tagged , , , , . Bookmark the permalink.

Comments are closed.