Headline
CVE-2018-3984: TALOS-2018-0652 || Cisco Talos Intelligence Group
An exploitable uninitialized length vulnerability exists within the Word document-parser of the Atlantis Word Processor 3.0.2.3 and 3.0.2.5. A specially crafted document can cause Atlantis to skip initializing a value representing the number of columns of a table. Later, the application will use this as a length within a loop that will write to a pointer on the heap. Due to this value being controlled, a buffer overflow will occur, which can lead to code execution under the context of the application. An attacker must convince a victim to open a document in order to trigger this vulnerability.
Summary
An exploitable uninitialized length vulnerability exists within the Word document-parser of the Atlantis Word Processor. A specially crafted document can cause Atlantis to skip initializing a value representing the number of columns of a table. Later, the application will use this as a length within a loop that will write to a pointer on the heap. Due to this value being controlled, a buffer overflow will occur, which can lead to code execution under the context of the application. An attacker must convince a victim to open a document in order to trigger this vulnerability.
Tested Versions
Atlantis Word Processor 3.0.2.3 Atlantis Word Processor 3.0.2.5
full module list
start end module name
00400000 007f0000 awp C (no symbols)
Image path: C:\Program Files (x86)\Atlantis\awp.exe
Image name: awp.exe
Browse all global symbols functions data
Timestamp: Fri Jun 19 15:22:17 1992 (2A425E19)
CheckSum: 00000000
ImageSize: 003F0000
File version: 3.2.5.0
Product version: 3.2.5.0
File flags: 0 (Mask 0)
File OS: 4 Unknown Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04e4
Product URLs
https://www.atlantiswordprocessor.com/en/
CVSSv3 Score
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE
CWE-122: Heap-based Buffer Overflow
Details
Atlantis’= Word Processor is a traditional word processor that contains many features and aims to flexible for the user. This word processor is ideally suited for both writers and students and provides a number of useful features that can help simplify and even improve one’s writing. Atlantis Word Processor is fully compatible with other word processors such as Microsoft Office Word 2007. Atlantis also has the capability to encrypt document files and to fully customize the interface. This application is written in Delphi and contains the majority of its capabilities within a single relocatable binary.
When Atlantis tries to parse a Microsoft Word Binary Document, the application will first fingerprint it to determine the correct file format. Once discovering that the file is a compound document file, it will locate the “WordDocument” stream, check the stream’s signature, and then read the Fib out of its header. After storing a couple of fields out of the Fib, the application will then use a field from the Fib to determine which stream contains table information which can be “1Table” or “0Table”. Once identifying the correct table stream, the application will then read an offset to the CLX array and its size out of the Fib and use this to locate the Clx array. When parsing this array, the application will check if the elements in the array are pointing to compressed pieces/text. If an individual piece is compressed, the application will re-calculate the character position and write it back into the array. If the CLX array size (as stored in the Fib) is smaller than a multiple of the size of each individual element, this new character position will be written outside the bounds of the array, leading to a heap-based buffer overflow.
When first loading a document, the application will call the following function. This function takes a TDoc and the file format type as an index and is responsible for fingerprinting the file and then parsing it. Firstly at [1], the application will call the function 0x5ab474 which will read a filename from the function’s frame and then write a file handle into the TDoc variable at %ebp-18. After the handle is allocated, the application will read the file into a buffer and then call the function at 0x5ad9aa to verify that the file matches the type that was specified. Once this has been verified to be a Word Document (doc), the application will then call the function at [2] in order to parse the file.
awp+0x1ad81d:
005ad81d 55 push ebp
005ad81e e851dcffff call awp+0x1ab474 (005ab474) // [1] Open up the file, and return the handle.
005ad823 59 pop ecx
005ad824 84c0 test al,al
005ad826 750d jne awp+0x1ad835 (005ad835)
...
awp+0x1ad8f2:
005ad8f2 55 push ebp
005ad8f3 680fd95a00 push offset awp+0x1ad90f (005ad90f)
005ad8f8 64ff30 push dword ptr fs:[eax]
005ad8fb 648920 mov dword ptr fs:[eax],esp
005ad8fe 55 push ebp
005ad8ff e8d82cfdff call awp+0x1805dc (005805dc) // Reads the file into a local buffer
005ad904 59 pop ecx
...
awp+0x1ad9a4:
005ad9a4 55 push ebp
005ad9a5 8b45f0 mov eax,dword ptr [ebp-10h] // Pointer to File Format Type index
005ad9a8 8bc3 mov eax,ebx
005ad9aa e86d3afdff call awp+0x18141c (0058141c) // Verify the file matches the format specified by %eax
005ad9af 59 pop ecx
005ad9b0 84c0 test al,al
005ad9b2 0f8592000000 jne awp+0x1ada4a (005ada4a)
...
awp+0x1ade4d:
005ade4d 8b45e8 mov eax,dword ptr [ebp-18h] // TDoc
005ade50 8b80dc000000 mov eax,dword ptr [eax+0DCh]
005ade56 83f805 cmp eax,5
005ade59 776a ja awp+0x1adec5 (005adec5)
005ade5b ff248562de5a00 jmp dword ptr awp+0x1ade62 (005ade62)[eax*4] // Jump to the correct file format parser
005ade62 7ade jp awp+0x1ade42 (005ade42)
...
awp+0x1ade89:
005ade89 55 push ebp
005ade8a e8259dfeff call awp+0x197bb4 (00597bb4) // [2] Parse the .doc file
005ade8f 59 pop ecx
005ade90 8885d7f8ffff mov byte ptr [ebp-729h],al
005ade96 eb3a jmp awp+0x1aded2 (005aded2)
To parse a .doc file, the application will execute the following function. This will first perform a number of things to figure out how to handle the .doc file. First, the application will check the beginning of the “WordDocument” stream for a 16-bit signature 0xa5ec. Once that is determined, Atlantis can then read from the Fib in the “WordDocument” stream’s header to locate which stream contains table data [3]. If this bit is set [1], then the table can be located in the “1Table” stream. If it is cleared [0], then the table will be located in the “0Table” stream. The correct stream is then opened by the call at [4] or [5]. Finally, the last stream that this function will open is the “Data” stream. This stream is opened at [6].
awp+0x197d0f:
00597d0f 0fb707 movzx eax,word ptr [edi]
00597d12 3deca50000 cmp eax,0A5ECh // Check first word at begining of WordDocument stream for signature
00597d17 0f9445f7 sete byte ptr [ebp-9]
...
awp+0x197d4d:
00597d4d f6470b02 test byte ptr [edi+0Bh],2 // [3] Check FibBase.b.fWhichTblStm to determine whether to use the "0Table" or "1Table" stream
00597d51 7531 jne awp+0x197d84 (00597d84)
...
awp+0x197d53:
00597d53 8d45fc lea eax,[ebp-4]
00597d56 e819c7e6ff call awp+0x4474 (00404474)
00597d5b 50 push eax
00597d5c 6a00 push 0
00597d5e 6a10 push 10h
00597d60 6a00 push 0
00597d62 a1080e6700 mov eax,dword ptr [awp+0x270e08 (00670e08)] // Reference to string "0Table"
00597d67 8b00 mov eax,dword ptr [eax]
00597d69 50 push eax
00597d6a 8b4508 mov eax,dword ptr [ebp+8]
00597d6d 8b806cffffff mov eax,dword ptr [eax-94h]
00597d73 50 push eax
00597d74 8b00 mov eax,dword ptr [eax]
00597d76 ff5010 call dword ptr [eax+10h] // [4] Uses IStorage->OpenStream to open the "0Table" stream
00597d79 85c0 test eax,eax
00597d7b 7d36 jge awp+0x197db3 (00597db3)
...
awp+0x197d84:
00597d84 8d45fc lea eax,[ebp-4]
00597d87 e8e8c6e6ff call awp+0x4474 (00404474)
00597d8c 50 push eax
00597d8d 6a00 push 0
00597d8f 6a10 push 10h
00597d91 6a00 push 0
00597d93 a1a00b6700 mov eax,dword ptr [awp+0x270ba0 (00670ba0)] // Reference to string "1Table"
00597d98 8b00 mov eax,dword ptr [eax]
00597d9a 50 push eax
00597d9b 8b4508 mov eax,dword ptr [ebp+8]
00597d9e 8b806cffffff mov eax,dword ptr [eax-94h]
00597da4 50 push eax
00597da5 8b00 mov eax,dword ptr [eax]
00597da7 ff5010 call dword ptr [eax+10h] // [5] Uses IStorage->OpenStream to open the "1Table" stream
00597daa 85c0 test eax,eax
00597dac 7d05 jge awp+0x197db3 (00597db3)
...
awp+0x197db3:
00597db3 8d45f8 lea eax,[ebp-8]
00597db6 e8b9c6e6ff call awp+0x4474 (00404474)
00597dbb 50 push eax
00597dbc 6a00 push 0
00597dbe 6a10 push 10h
00597dc0 6a00 push 0
00597dc2 a174106700 mov eax,dword ptr [awp+0x271074 (00671074)] // Reference to string "Data"
00597dc7 8b00 mov eax,dword ptr [eax]
00597dc9 50 push eax
00597dca 8b4508 mov eax,dword ptr [ebp+8]
00597dcd 8b806cffffff mov eax,dword ptr [eax-94h]
00597dd3 50 push eax
00597dd4 8b00 mov eax,dword ptr [eax]
00597dd6 ff5010 call dword ptr [eax+10h] // [6] Uses IStorage->OpenStream to open the "Data" stream
00597dd9 eb22 jmp awp+0x197dfd (00597dfd)
After opening up the required streams, Atlantis will begin to parse required data out of them. At [6], the application will begin to parse various records such as the Dop table (Document Properties), PlcfBkl table (Bookmarks), etc. After parsing this, the application will take the sum of the various ccp fields that are listed. This fields are needed by parsers of the Word Document to determine the location of the different sections of a document. Eventually, the application will execute the instruction at [7] and continue parsing more of the file format.
awp+0x197dfd:
00597dfd 55 push ebp
00597dfe e83549ffff call awp+0x18c738 (0058c738) // [6] Parse the Dop fields out of the table stream
00597e03 59 pop ecx
...
// Various functions that read an FcLcb from the Fib structure of the WordDocument and reads them into a TMemory object
...
awp+0x197fbc:
00597fbc 55 push ebp
00597fbd 8b574c mov edx,dword ptr [edi+4Ch] // ccpText
00597fc0 035750 add edx,dword ptr [edi+50h] // ccpFtn
00597fc3 035754 add edx,dword ptr [edi+54h] // ccpHdd
00597fc6 035760 add edx,dword ptr [edi+60h] // ccpEdn
00597fc9 8b4734 mov eax,dword ptr [edi+34h]
00597fcc 034738 add eax,dword ptr [edi+38h]
00597fcf 03473c add eax,dword ptr [edi+3Ch]
00597fd2 034748 add eax,dword ptr [edi+48h]
00597fd5 e84e47ffff call awp+0x18c728 (0058c728) // Returns %edx depending on 0xa5ec signature or %eax otherwise
00597fda 59 pop ecx
...
awp+0x198001:
00598001 55 push ebp
00598002 e80df1ffff call awp+0x197114 (00597114) // [7] Continue parsing the document
Inside the function 0x597114, the application will initialize a number of variables on the stack. At [7] and [8], the application will initialize arrays that are intended to contain various Sprm properties associated with the word document with the value 0x80000000. These arrays are important in that they will be initialized later when the application attempts to parse the different pieces and to determine which properties to apply to which parts of each piece.
awp+0x19728c:
0059728c 8d8dd8d8ffff lea ecx,[ebp-2728h] // Sprm array containing properties from file
00597292 8d956cd8ffff lea edx,[ebp-2794h]
00597298 e89b1b0a00 call awp+0x238e38 (00638e38) // [7] Initialize it
0059729d 33c0 xor eax,eax
0059729f 8985b0d8ffff mov dword ptr [ebp-2750h],eax
...
0059729d 33c0 xor eax,eax
0059729f 8985b0d8ffff mov dword ptr [ebp-2750h],eax
005972a5 8d85c8d8ffff lea eax,[ebp-2738h] // Secondary Sprm array
005972ab bac8080000 mov edx,8C8h
005972b0 e8dbc20900 call awp+0x233590 (00633590) // [8]
Later at [9], the application enter a loop which will iterate through all of the different Plc field types defined within the document. Inside this loop, the application will then parse the Clx field that is defined within the Fib. This is done in order to identify the different sections that are defined by the document and to determine how to apply properties to each character or paragraph.
awp+0x197374:
00597374 8a1e mov bl,byte ptr [esi] // [9] Loop that iterates over all the PLC field types
00597376 8bc3 mov eax,ebx
00597378 84c0 test al,al
0059737a 741c je awp+0x197398 (00597398)
0059737c 04fb add al,0FBh
0059737e 2c02 sub al,2
00597380 7216 jb awp+0x197398 (00597398)
00597382 8b4508 mov eax,dword ptr [ebp+8]
00597385 8b4008 mov eax,dword ptr [eax+8]
00597388 8b40e8 mov eax,dword ptr [eax-18h] // TDoc
0059738b 80b8b303000000 cmp byte ptr [eax+3B3h],0
00597392 0f858a060000 jne awp+0x197a22 (00597a22)
00597398 33c0 xor eax,eax
0059739a 8ac3 mov al,bl
0059739c 83f805 cmp eax,5
0059739f 0f87b1010000 ja awp+0x197556 (00597556)
005973a5 ff2485ac735900 jmp dword ptr awp+0x1973ac (005973ac)[eax*4] // Branch to the case for each Plc field type
...
awp+0x197a22:
00597a22 46 inc esi // Iterate to the next Plc field
00597a23 ff8dac90ffff dec dword ptr [ebp-6F54h]
00597a29 0f8545f9ffff jne awp+0x197374 (00597374)
After identifying the Plc field, the application will begin to read the contents of the Clx field. First at [10], the application will read the size (lcb) of the FcLcb type followed by reading the character position (fc) at [11]. At [12], the application will allocate space for it and then read the contents of the CLX array from the “WordDocument” stream.
awp+0x197623:
00597623 8b4508 mov eax,dword ptr [ebp+8] // Caller frame
00597626 50 push eax
00597627 8b4508 mov eax,dword ptr [ebp+8] // Caller frame
0059762a 8b4008 mov eax,dword ptr [eax+8]
0059762d 8b900efbffff mov edx,dword ptr [eax-4F2h] // Fib.fibRgFcLcbBlob.97.Clx.lcb
00597633 8b4508 mov eax,dword ptr [ebp+8] // Caller frame
00597636 8b4008 mov eax,dword ptr [eax+8]
00597639 8b80ccfaffff mov eax,dword ptr [eax-534h]
0059763f e8e450ffff call awp+0x18c728 (0058c728) // [10] Return %edx if Word Document Signature
00597644 59 pop ecx
...
00597645 8985cc90ffff mov dword ptr [ebp-6F34h],eax
0059764b 8b4508 mov eax,dword ptr [ebp+8]
0059764e 50 push eax
0059764f 8b4508 mov eax,dword ptr [ebp+8]
00597652 50 push eax
00597653 8b4508 mov eax,dword ptr [ebp+8] // Caller frame
00597656 8b4008 mov eax,dword ptr [eax+8]
00597659 8b900afbffff mov edx,dword ptr [eax-4F6h] // Fib.fibRgFcLcbBlob.97.Clx.fc
0059765f 8b4508 mov eax,dword ptr [ebp+8] // Caller frame
00597662 8b4008 mov eax,dword ptr [eax+8]
00597665 8b80c8faffff mov eax,dword ptr [eax-538h]
0059766b e8b850ffff call awp+0x18c728 (0058c728) // [11] Return %edx if Word Document Signature
00597670 59 pop ecx
...
00597671 e8a24fffff call awp+0x18c618 (0058c618) // Seek to Clx.fc
00597676 59 pop ecx
...
00597677 8b85cc90ffff mov eax,dword ptr [ebp-6F34h]
0059767d e8beace6ff call awp+0x2340 (00402340) // [12] Allocate memory for Clx.lcb
00597682 8985c890ffff mov dword ptr [ebp-6F38h],eax
...
00597688 8b4508 mov eax,dword ptr [ebp+8]
0059768b 50 push eax
0059768c 8b95cc90ffff mov edx,dword ptr [ebp-6F34h]
00597692 8b85c890ffff mov eax,dword ptr [ebp-6F38h]
00597698 e8af4fffff call awp+0x18c64c (0058c64c) // Read contents of Clx field
0059769d 59 pop ecx
...
0059769e 33c9 xor ecx,ecx
005976a0 eb11 jmp awp+0x1976b3 (005976b3) // Check if RgPrc field needs to be parsed
After reading the CLX array from the stream, the application will then execute the following loop in order to handle the piece descriptor table. At [13], the application will check to see if the current piece is compressed in order to re-calculate the size of the specified piece. Eventually this information along with the beginning character position of the current piece is then passed to the function call at [14].
awp+0x197732:
00597732 8bbdb890ffff mov edi,dword ptr [ebp-6F48h] // aCP
00597738 c1e703 shl edi,3 // sizeof(aPcd)
0059773b 03bdc090ffff add edi,dword ptr [ebp-6F40h] // Clx.Pcdt.PlcPcd.aPcd
00597741 8b4702 mov eax,dword ptr [edi+2]
00597744 a900000040 test eax,40000000h // [13] Check if Pcd specified that piece is compressed
00597749 7413 je awp+0x19775e (0059775e)
0059774b 25ffffffbf and eax,0BFFFFFFFh
00597750 d1e8 shr eax,1
00597752 894702 mov dword ptr [edi+2],eax // Divide data by 2 and write it back to array if it is compressed
00597755 c685bf90ffff01 mov byte ptr [ebp-6F41h],1 // Set Fc compression flag
0059775c eb19 jmp awp+0x197777 (00597777)
...
awp+0x197882:
00597882 55 push ebp
00597883 53 push ebx
00597884 8a85bf90ffff mov al,byte ptr [ebp-6F41h] // Fc compression flag
0059788a 50 push eax
0059788b 80bdbf90ffff00 cmp byte ptr [ebp-6F41h],0
00597892 0f94c1 sete cl
00597895 83e17f and ecx,7Fh
00597898 41 inc ecx // Set multiplier based on the compression flag
...
00597899 8b85b090ffff mov eax,dword ptr [ebp-6F50h] // aCP
0059789f 2b85b490ffff sub eax,dword ptr [ebp-6F4Ch] // aCP + 1
005978a5 0fafc8 imul ecx,eax // Multipliy based on state of compression flag
...
005978a8 8b5702 mov edx,dword ptr [edi+2]
005978ab 8b85b490ffff mov eax,dword ptr [ebp-6F4Ch] // aCP start
005978b1 e89ee4ffff call awp+0x195d54 (00595d54) // [14]
005978b6 59 pop ecx
...
005978b7 ff85b890ffff inc dword ptr [ebp-6F48h] // aCP index
005978bd ff8d9c90ffff dec dword ptr [ebp-6F64h] // Counter
005978c3 0f8569feffff jne awp+0x197732 (00597732)
Inside the function 0x595d54, the application will first calculate the ccp position using the values from the Fib. After determining the correct position based on the values from the ccp, the application will grab the fc from the piece descriptor table and pass it to the function call at [15]. At the beginning of the function call, the application will pass through the aPcd.fc position to the function call at [16]. This function call is responsible for for parsing the different paragraph properties that are pointed to by the current piece descriptor within the bounds of the current Plc that was parsed earlier.
awp+0x195e37:
00595e37 8b4d10 mov ecx,dword ptr [ebp+10h]
00595e3a 03993c91ffff add ebx,dword ptr [ecx-6EC4h] // ccpText position
00595e40 3bd3 cmp edx,ebx
00595e42 7e47 jle awp+0x195e8b (00595e8b)
...
awp+0x195e8b:
00595e8b 837dec00 cmp dword ptr [ebp-14h],0 // aCP size
00595e8f 7e56 jle awp+0x195ee7 (00595ee7)
00595e91 807d0c02 cmp byte ptr [ebp+0Ch],2 // ccp index
00595e95 7550 jne awp+0x195ee7 (00595ee7)
...
awp+0x195ee7:
00595ee7 837dec00 cmp dword ptr [ebp-14h],0 // aCP size
00595eeb 0f8eab0f0000 jle awp+0x196e9c (00596e9c)
00595ef1 8b4510 mov eax,dword ptr [ebp+10h] // frame
00595ef4 50 push eax
00595ef5 8b45e8 mov eax,dword ptr [ebp-18h] // aPcd.fc position
00595ef8 e8ebc6ffff call awp+0x1925e8 (005925e8) // [15] \
00595efd 59 pop ecx
\
awp+0x1925e8:
005925e8 55 push ebp
005925e9 8bec mov ebp,esp
005925eb 53 push ebx
005925ec 8b5508 mov edx,dword ptr [ebp+8] // caller frame
005925ef 52 push edx
005925f0 6a01 push 1
005925f2 8b5508 mov edx,dword ptr [ebp+8] // caller frame
005925f5 8b5208 mov edx,dword ptr [edx+8]
005925f8 8d8aa4fdffff lea ecx,[edx-25Ch] // pointer to TMemory object for PlcfBtePapx array from Fib
005925fe 8b5508 mov edx,dword ptr [ebp+8]
00592601 81c2c8d8ffff add edx,0FFFFD8C8h // pointer to array to contain destination paragraph properties
00592607 e824ffffff call awp+0x192530 (00592530) // [16] Begin to parse property information at aPcd.fc
0059260c 59 pop ecx
0059260d 8bd8 mov ebx,eax
0059260f 84db test bl,bl
00592611 7444 je awp+0x192657 (00592657)
As mentioned prior, there are a number of property arrays that are initialized to 0x80000000 inside of the function at 0x597114. The function call at 0x592530 is responsible for actually initializing them with the correct values. First the function will store the pointer to the target property array which gets written to at [17] along with the Fc field from the piece descriptor table. After checking a number of things, the Fc field will be sought out and then parsed in order to get access to the pn offset, which represents the sector that the paragraph properties are located at. This is then handed off to the call at [18]. At this point, the application will begin to parse the paragraph properties and store them in the property array. Eventually, when the application gets to [19], a case statement will determine which property was parsed and then store any of its operands into the Sprm array. This vulnerability involves the sprmTDefTable property not actually being parsed and thus leaving the index in the Sprm array uninitialized as 0x80000000. If the sprmTDefTable existed, the code at [20] would correctly the number of columns into the Sprm array.
awp+0x192530:
00592530 55 push ebp
00592531 8bec mov ebp,esp
00592533 83c4f8 add esp,0FFFFFFF8h
00592536 53 push ebx
00592537 56 push esi
00592538 57 push edi
00592539 8bf1 mov esi,ecx
0059253b 8955f8 mov dword ptr [ebp-8],edx // [17] pointer to property array
0059253e 8945fc mov dword ptr [ebp-4],eax // aPcd.fc position
00592541 33db xor ebx,ebx
00592543 8b45f8 mov eax,dword ptr [ebp-8]
00592546 8b00 mov eax,dword ptr [eax]
00592548 3d00000080 cmp eax,80000000h // Check if property is uninitialized
0059254d 7413 je awp+0x192562 (00592562)
...
awp+0x192562:
00592562 807e0c00 cmp byte ptr [esi+0Ch],0 // Check if TMemory is empty
00592566 7427 je awp+0x19258f (0059258f)
...
awp+0x19258f:
0059258f 8b450c mov eax,dword ptr [ebp+0Ch] // Caller frame
00592592 50 push eax
00592593 8bd6 mov edx,esi
00592595 8b45fc mov eax,dword ptr [ebp-4] // aPcd.fc position
00592598 e87ffcffff call awp+0x19221c (0059221c)
0059259d 59 pop ecx
0059259e 8bf8 mov edi,eax
...
005925a0 81ff00000080 cmp edi,80000000h // Check if property from current function is uninitialized
005925a6 740d je awp+0x1925b5 (005925b5)
005925a8 55 push ebp
005925a9 8bc7 mov eax,edi // pn position from Papx
005925ab e80cfeffff call awp+0x1923bc (005923bc) // [18] \\ Read properties from pn sector
005925b0 59 pop ecx
005925b1 84c0 test al,al
005925b3 7525 jne awp+0x1925da (005925da)
\\
awp+0x18f66c:
0058f66c 55 push ebp
0058f66d 8bec mov ebp,esp
0058f66f 83c4f8 add esp,0FFFFFFF8h
0058f672 53 push ebx
0058f673 56 push esi
0058f674 57 push edi
0058f675 8b5d08 mov ebx,dword ptr [ebp+8]
0058f678 8b5bfc mov ebx,dword ptr [ebx-4] // result
0058f67b 8b4508 mov eax,dword ptr [ebp+8]
0058f67e 8b40f8 mov eax,dword ptr [eax-8] // pointer to Sprm property
0058f681 0fb700 movzx eax,word ptr [eax]
0058f684 3d11840000 cmp eax,8411h
0058f689 0f8f6e010000 jg awp+0x18f7fd (0058f7fd)
...
awp+0x18f7fd:
0058f7fd 3d12d60000 cmp eax,0D612h
0058f802 0f8f9f000000 jg awp+0x18f8a7 (0058f8a7)
0058f808 0f84ee060000 je awp+0x18fefc (0058fefc)
0058f80e 3d13a40000 cmp eax,0A413h
0058f813 7f4a jg awp+0x18f85f (0058f85f)
...
awp+0x18f85f:
0058f85f 3d05d60000 cmp eax,0D605h
0058f864 7f2a jg awp+0x18f890 (0058f890)
...
awp+0x18f890:
0058f890 2d08d60000 sub eax,0D608h
0058f895 0f8453050000 je awp+0x18fdee (0058fdee)
...
awp+0x18fdee:
0058fdee 8b4508 mov eax,dword ptr [ebp+8] // [19] sprmTDefTable property
0058fdf1 8b40f8 mov eax,dword ptr [eax-8]
0058fdf4 83c004 add eax,4
0058fdf7 8bd0 mov edx,eax
0058fdf9 8b4d08 mov ecx,dword ptr [ebp+8]
0058fdfc 8b49f8 mov ecx,dword ptr [ecx-8]
0058fdff 83c102 add ecx,2
0058fe02 0fb709 movzx ecx,word ptr [ecx]
0058fe05 03d1 add edx,ecx
0058fe07 8955fc mov dword ptr [ebp-4],edx
...
0058fe0a 8b5508 mov edx,dword ptr [ebp+8]
0058fe0d 0fb600 movzx eax,byte ptr [eax]
0058fe10 89436c mov dword ptr [ebx+6Ch],eax // [20] write the number of columns into sprm property array
Returning back to the function 0x595d54, the application will continue to parse the different characters inside the current piece. Eventually the code at [21] will be executed. This code is responsible for processing any tables that were discovered within the piece. First the application will verify that the property is initialized followed by constructing a TTableRow object. After some pointer arithmetic, the application will read the uninitialized length from the property array at [22]. This is then immediately stored at offset 0xc4 of the TTableRow data at [23]. With the provided proof-of-concept, this value will be the incorrect length of 0x80000000.
awp+0x196365:
00596365 8b4510 mov eax,dword ptr [ebp+10h] // [21]
00596368 83b88892ffff01 cmp dword ptr [eax-6D78h],1 // Check if property is initialized
0059636f 0f8514040000 jne awp+0x196789 (00596789)
00596375 b201 mov dl,1
00596377 a19cee5500 mov eax,dword ptr [awp+0x15ee9c (0055ee9c)]
0059637c e83fc5e6ff call awp+0x28c0 (004028c0) // Construct a TTableRow...
00596381 8945dc mov dword ptr [ebp-24h],eax // ...and store it
00596384 8b45dc mov eax,dword ptr [ebp-24h]
00596387 83c004 add eax,4
0059638a 8945cc mov dword ptr [ebp-34h],eax // Store a pointer into TTableRow's data
0059638d 8b4510 mov eax,dword ptr [ebp+10h]
00596390 8b80a492ffff mov eax,dword ptr [eax-6D5Ch] // [22] Read the number of columns from the property array
00596396 8b55cc mov edx,dword ptr [ebp-34h]
00596399 8982c4000000 mov dword ptr [edx+0C4h],eax // [23] Store it to offset 0xc4 of the TTableRow's data
After storing the number of columns into offset +0xc4 of the TTableRow data, the application will execute the following code. This code will read the number of columns at [24] and then assign it to a variable at [25]. Again, due to the number of columns being uninitialized this results in a loop length of 0x80000000. After assigning the number of columns, the loop for the table’s columns will be entered at [26]. This loop will take the current index, and multiply it by 18. This resulting value will then be used to grab a pointer out of the TTableRow. Due to the loop having a number of columns that is larger than the specified in the table, the iteration at [28] will result in an index being calculated that is out of bounds of the TTableRow. This results in the pointer at [27] being controllable by an attacker. There are a then number of ways that this pointer is used one of which it is written to which can cause controlled heap corruption.
awp+0x19652b:
0059652b 8b45cc mov eax,dword ptr [ebp-34h] // TTableRow's data
0059652e 8b80c4000000 mov eax,dword ptr [eax+0C4h] // [24] Read uninitialized length
00596534 48 dec eax
00596535 85c0 test eax,eax
00596537 0f8c86010000 jl awp+0x1966c3 (005966c3)
0059653d 40 inc eax
0059653e 8945c8 mov dword ptr [ebp-38h],eax // [25] Store uninitialized length to loop counter
...
awp+0x196565:
00596565 8b45d0 mov eax,dword ptr [ebp-30h] // [26] Loop using length
00596568 03c0 add eax,eax
0059656a 8d04c0 lea eax,[eax+eax*8] // Multiply current index by 18
0059656d 8b55cc mov edx,dword ptr [ebp-34h]
00596570 8d84c2c8000000 lea eax,[edx+eax*8+0C8h] // Grab pointer out of TTableRow's data
00596577 8945c4 mov dword ptr [ebp-3Ch],eax // [27] Store uninitialized pointer
...
// Number of places this uninitialized pointer is used.
...
awp+0x1966ad:
005966ad ff45d0 inc dword ptr [ebp-30h] // Increase index
005966b0 8345bc10 add dword ptr [ebp-44h],10h // Iterate to next property
005966b4 83c370 add ebx,70h
005966b7 83c604 add esi,4
005966ba ff4dc8 dec dword ptr [ebp-38h] // [28] Decrease current loop counter
005966bd 0f85a2feffff jne awp+0x196565 (00596565)
The provided proof of concept will crash while using the pointer within the loop at the following code. At [29], the application will read the out-of-bounds pointer and then add 0x4c to it. Immediately afterward, a function will be called with this pointer. Eventually, at [30], the application will write NULL to the arbitrary pointer resulting in heap corruption which can result in code execution under the context of the application.
awp+0x196624:
00596624 8b45c4 mov eax,dword ptr [ebp-3Ch] // [29] Uninitialized pointer to Sprm table value
00596627 8d504c lea edx,[eax+4Ch] // Add 0x4c bytes to pointer
0059662a 8d43e8 lea eax,[ebx-18h]
0059662d e8e6f6ffff call awp+0x195d18 (00595d18) // Call function which uses pointer
\
awp+0x195d3c:
00595d3c 894a04 mov dword ptr [edx+4],ecx
00595d3f 8b4808 mov ecx,dword ptr [eax+8]
00595d42 81f900000080 cmp ecx,80000000h
00595d48 7506 jne awp+0x195d50 (00595d50)
00595d4a 33c0 xor eax,eax
00595d4c 894208 mov dword ptr [edx+8],eax // [30] Write to uninitialized pointer
00595d4f c3 ret
Crash Information
Set a breakpoint where Sprm array is allocated at.
0:006> bp 597114
Open up a document and execute to break at that location.
0:006> g
Breakpoint 0 hit
eax=073c9714 ebx=00000000 ecx=0018f004 edx=0000361e esi=0018edf0 edi=0018f100
eip=00597114 esp=0018eccc ebp=0018f004 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
awp+0x197114:
00597114 55 push ebp
Set a breakpoint where the loop counter for the TTableRow is assigned.
0:000> bp 59653e
Execute once, so we’re at the first TTableRow, which is on the second-to-last page. Notice that the number of columns is three, which is correct.
0:000> g
Breakpoint 1 hit
eax=00000003 ebx=00000eff ecx=ff000000 edx=0c4340a4 esi=00000008 edi=0c0fe428
eip=0059653e esp=0018777c ebp=00187d44 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
awp+0x19653e:
0059653e 8945c8 mov dword ptr [ebp-38h],eax ss:002b:00187d0c=00000028
Execute again so we’re on the second TTableRow on the last page. Notice that the length is uninitialized and is thus incorrect.
0:000> g
Breakpoint 1 hit
eax=80000000 ebx=0018822c ecx=80000000 edx=0c437f8c esi=0000000a edi=0c0fe428
eip=0059653e esp=0018777c ebp=00187d44 iopl=0 ov up ei ng nz ac pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000a96
awp+0x19653e:
0059653e 8945c8 mov dword ptr [ebp-38h],eax ss:002b:00187d0c=00000000
0:000> r @eax
eax=80000000
Set a breakpoint where the calculated pointer is incremented and stored to local variable.
0:000> bp 596577
Execute and notice that each iteration results in 0x90 being added to the pointer.
0:000> g
Breakpoint 2 hit
eax=0c4381f4 ebx=0018829c ecx=80000000 edx=0c437eec esi=00187f84 edi=0c0fe428
eip=00596577 esp=0018777c ebp=00187d44 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
awp+0x196577:
00596577 8945c4 mov dword ptr [ebp-3Ch],eax ss:002b:00187d08=0c438164
0:000> g
Breakpoint 2 hit
eax=0c438284 ebx=0018830c ecx=80000000 edx=0c437eec esi=00187f88 edi=0c0fe428
eip=00596577 esp=0018777c ebp=00187d44 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
awp+0x196577:
00596577 8945c4 mov dword ptr [ebp-3Ch],eax ss:002b:00187d08=0c4381f4
Disable pointer that is used to monitor loop.
0:000> bd 2
Continue execution until crash.
0:000> g
(828.460): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0018b25c ebx=0018b2bc ecx=00000000 edx=0c43c008 esi=0018813c edi=0c0fe428
eip=00595d24 esp=00187778 ebp=00187d44 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
awp+0x195d24:
00595d24 890a mov dword ptr [edx],ecx ds:002b:0c43c008=????????
Exploit Proof of Concept
To use the proof of concept, simply open up the document in the target application. The application should crash at the instruction specified while trying to write to the pointer.
Timeline
2018-09-10 - Vendor Disclosure
2018-09-11 - Vendor patched via beta version
2018-09-26 - Vendor released
2018-10-01 - Public Disclosure
Discovered by a member of Cisco Talos.