Talos Vulnerability Report


Atlantis Word Processor Word document paragraph property (0xD608) sprmTDefTable uninitialized length code execution vulnerability

October 1, 2018
CVE Number



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 Atlantis Word Processor

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:
    Product version:
    File flags:       0 (Mask 0)
    File OS:          4 Unknown Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0409.04e4

Product URLs


CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H


CWE-122: Heap-based Buffer Overflow


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.

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)
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
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)
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)
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].

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]
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)
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)
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)
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.

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
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
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.

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.

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
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.

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].

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)
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.

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)
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)
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
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.

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)
00592562 807e0c00        cmp     byte ptr [esi+0Ch],0       // Check if TMemory is empty
00592566 7427            je      awp+0x19258f (0059258f)
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)
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)
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)
0058f85f 3d05d60000      cmp     eax,0D605h
0058f864 7f2a            jg      awp+0x18f890 (0058f890)
0058f890 2d08d60000      sub     eax,0D608h
0058f895 0f8453050000    je      awp+0x18fdee (0058fdee)
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.

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.

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
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.
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.

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
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
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
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
0059653e 8945c8          mov     dword ptr [ebp-38h],eax ss:002b:00187d0c=00000000

0:000> r @eax

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
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
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
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.


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.