Talos Vulnerability Report

TALOS-2018-0669

Atlantis Word Processor Office Open XML uninitialized TTableRow code execution vulnerability

October 1, 2018
CVE Number

CVE-2018-4001

Summary

An exploitable uninitialized pointer vulnerability exists in the Office Open XML parser of Atlantis Word Processor, version 3.2.5.0. A specially crafted document can cause an uninitialized pointer representing a TTableRow to be assigned to a variable on the stack. This variable is later dereferenced and then written to allow for controlled heap corruption, 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.2.5.0

start    end        module name
00400000 007f0000   awp      C (no symbols)           
    Loaded symbol image file: awp.exe
    Image path: C:\Program Files (x86)\Atlantis\awp.exe
    Image name: awp.exe
    File version:     3.2.5.0
    Product version:  3.2.5.0

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-457: Use of Uninitialized Variable

Details

Atlantis' Word Processor is a traditional word processor that both portable and flexible, and contains a variety of features. This word processor is ideally suited for 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 and even has a similar interface. Atlantis also has the capability to encrypt document files and fully customize the interface. This application is written in Delphi and contains the majority of its capabilities within a single relocatable binary.

When opening a document file, the application will construct an instance of the TDoc object. Eventually, this instance will be passed as an argument to the function containing the following code. This code is a case statement and is responsible for switching to the correct document parser based on the document file type enumeration that is stored within the TDoc instance. Eventually,k at [1], the application will call the function responsible for parsing Office Open XML documents.

awp+0x1ade4d:
005ade4d 8b45e8          mov     eax,dword ptr [ebp-18h]    // TDoc instance
005ade50 8b80dc000000    mov     eax,dword ptr [eax+0DCh]   // TDoc file type enumeration
005ade56 83f805          cmp     eax,5
005ade59 776a            ja      awp+0x1adec5 (005adec5)
005ade5b ff248562de5a00  jmp     dword ptr awp+0x1ade62 (005ade62)[eax*4]
...
005ade98 55              push    ebp
005ade99 e8ea27ffff      call    awp+0x1a0688 (005a0688)    // [1] call .docx file format parser
005ade9e 59              pop     ecx
005ade9f 8885d7f8ffff    mov     byte ptr [ebp-729h],al
005adea5 eb2b            jmp     awp+0x1aded2 (005aded2)

This function will initialize a number of variables that are used by the entire Office Open XML parser. This function is important because it is considered by the author to be the upper-most function and all child functions called by this are executing within closures which allows numerous callees to modify the variables within this function. Some of these variables include the current styles that have been parsed, a variable containing the root TXML element, and even information about the table row (and table) that is currently being parsed. The current table row that this vulnerability revolves around is used specifically for keeping track of the current table row and is hence represented by a TTableRow object. This variable is located in this function's frame at %ebp-dc. After initializing some large arrays that are assumed by the author to contain different styles used by elements within the document, at [2] the application will search through the XML document for the "w:body" element. Once this is located, it will be passed as an argument to the function call at [3] in order to parse all of its child elements.

awp+0x1a0688:
005a0688 55              push    ebp
005a0689 8bec            mov     ebp,esp
005a068b 81c400ffffff    add     esp,0FFFFFF00h
005a0691 53              push    ebx
005a0692 56              push    esi
005a0693 57              push    edi
...
005a09f8 8b45b0          mov     eax,dword ptr [ebp-50h]                // TXML instance containing the entirety of the document.
005a09fb 8945f0          mov     dword ptr [ebp-10h],eax                // Instance is duplicated to this variable
005a09fe 55              push    ebp
005a09ff ba8c0c5a00      mov     edx,offset awp+0x1a0c8c (005a0c8c)     // Reference to "w:body" string
005a0a04 8b45b0          mov     eax,dword ptr [ebp-50h]
005a0a07 e80875f2ff      call    awp+0xc7f14 (004c7f14)                 // [2] Locate "w:body" element
...
005a0a0c 8a1570e86600    mov     dl,byte ptr [awp+0x26e870 (0066e870)]
005a0a12 e8e9fbffff      call    awp+0x1a0600 (005a0600)                // [3] Iterate through children of element
005a0a17 59              pop     ecx

As mentioned previously, the following function is used to iterate through all the children within the "w:body" tag. At the beginning of the function, this element is stored in the %edi register. A TXML element is also similar to a TList. At [4], the application will read the number of child elements from the TXML element and use it in the loop that follows. For each child element, the application will call the recursive function at [5].

awp+0x1a0600:
005a0600 55              push    ebp
005a0601 8bec            mov     ebp,esp
005a0603 81c4e0feffff    add     esp,0FFFFFEE0h
005a0609 53              push    ebx
005a060a 56              push    esi
005a060b 57              push    edi
005a060c 8895e3feffff    mov     byte ptr [ebp-11Dh],dl
005a0612 8bf8            mov     edi,eax                // "w:body" element
...
005a0647 8b7704          mov     esi,dword ptr [edi+4]  // [4] Grab the number of child elements
005a064a 4e              dec     esi
005a064b 85f6            test    esi,esi
005a064d 7c32            jl      awp+0x1a0681 (005a0681)
...
awp+0x1a0652:
005a0652 55              push    ebp
005a0653 8bd3            mov     edx,ebx
005a0655 8bc7            mov     eax,edi
005a0657 e89876e6ff      call    awp+0x7cf4 (00407cf4)      // Grab child from current element
005a065c e867d9ffff      call    awp+0x19dfc8 (0059dfc8)    // [5]
005a0661 59              pop     ecx
005a0662 80bde3feffff00  cmp     byte ptr [ebp-11Dh],0
005a0669 7412            je      awp+0x1a067d (005a067d)
...
005a067d 43              inc     ebx                        // Iterate to next child element
005a067e 4e              dec     esi
005a067f 75d1            jne     awp+0x1a0652 (005a0652)

This next function is a recursive function that is entirely responsible for parsing the XML that composes an Office Open XML document. As a result, it has a large number of cases used to dispatch to the correct parser for each specific element. This function is responsible for containing the scope of the TTableRow that is abused by this vulnerability. At [6], the function will first convert the XML tag name into a token/enumeration. This will then be used in a case statement in order to handle processing of the element. At [7], the TTableRow element will have one of its properties checked which will be used to determine whether this element needs to be saved. Finally before the function leaves, the current TTableRow element will be freed at [8].

awp+0x19dfc8:
0059dfc8 55              push    ebp
0059dfc9 8bec            mov     ebp,esp
0059dfcb 81c440ffffff    add     esp,0FFFFFF40h
0059dfd1 53              push    ebx
0059dfd2 56              push    esi
0059dfd3 57              push    edi
...
0059dfff 8b45fc          mov     eax,dword ptr [ebp-4]      // Current XML node
0059e002 8b4020          mov     eax,dword ptr [eax+20h]    // XML Tag name
0059e005 e82ea2ffff      call    awp+0x198238 (00598238)    // [6] Convert tag name to enumeration
0059e00a 83e07f          and     eax,7Fh
0059e00d 83f847          cmp     eax,47h
0059e010 7f3d            jg      awp+0x19e04f (0059e04f)    // Cases > 0x47
0059e012 0f845d0a0000    je      awp+0x19ea75 (0059ea75)
...
0059e04f 83c0b5          add     eax,0FFFFFFB5h             // Subtract 0x4b from enumeration
0059e052 83f812          cmp     eax,12h                    // Cases 0x4b through 0x5d
0059e055 0f874a220000    ja      awp+0x1a02a5 (005a02a5)
...
/* Cases for each specific XML tag in the document */
...
awp+0x1a0199:
005a0199 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
005a019c 8b4008          mov     eax,dword ptr [eax+8]      // Frame containing TTableRow instance
005a019f 8b8024ffffff    mov     eax,dword ptr [eax-0DCh]   // TTableRow
005a01a5 83b8c800000000  cmp     dword ptr [eax+0C8h],0     // [7] Check property/style of TTableRow
005a01ac 0f8ec6000000    jle     awp+0x1a0278 (005a0278)
...
005a0278 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
005a027b 8b4008          mov     eax,dword ptr [eax+8]      // Frame containing TTableRow instance
005a027e 8b8024ffffff    mov     eax,dword ptr [eax-0DCh]   // TTableRow
005a0284 e85f26e6ff      call    awp+0x28e8 (004028e8)      // [8] TObject::Free

When processing a row within a table, the "tr" tag is handled by the following code which is 0x5d. The code block at [9] is executed when this element is encountered. This results in the construction of a new TTableRow object. Immediately following this at [10], the resulting object is assigned into the current TTableRow that is defined within the upper-most frame. At this point, the application will copy any properties from the previously parsed TTableRow into the newly constructed one [11].

awp+0x1a001f:
005a001f 8b4508          mov     eax,dword ptr [ebp+8]
005a0022 83b8e4feffff01  cmp     dword ptr [eax-11Ch],1
005a0029 0f856d020000    jne     awp+0x1a029c (005a029c)
005a002f b201            mov     dl,1
005a0031 a19cee5500      mov     eax,dword ptr [awp+0x15ee9c (0055ee9c)]    // TTableRow
005a0036 e88528e6ff      call    awp+0x28c0 (004028c0)                      // [9] Construct a TTableRow instance
005a003b 8b5508          mov     edx,dword ptr [ebp+8]                      // Caller frame
005a003e 8b5208          mov     edx,dword ptr [edx+8]                      // Upper-most frame
005a0041 898224ffffff    mov     dword ptr [edx-0DCh],eax                   // [10] New TTableRow instance is assigned to current TTableRow
...
005a0047 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
005a004a 8b4008          mov     eax,dword ptr [eax+8]                      // Upper-most frame
005a004d 8b8020ffffff    mov     eax,dword ptr [eax-0E0h]                   // Previous table row
005a0053 8b5508          mov     edx,dword ptr [ebp+8]                      // Caller frame
005a0056 8b5208          mov     edx,dword ptr [edx+8]                      // Upper-most frame
005a0059 8b9224ffffff    mov     edx,dword ptr [edx-0DCh]                   // TTableRow that triggers vulnerability
005a005f 8d7004          lea     esi,[eax+4]
005a0062 8d7a04          lea     edi,[edx+4]
005a0065 b914090000      mov     ecx,914h
005a006a f3a5            rep movs dword ptr es:[edi],dword ptr [esi]        // [11] Copy the properties from the previously parsed TTableRow
...
005a006c 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
005a006f 8b4008          mov     eax,dword ptr [eax+8]                      // Upper-most frame
005a0072 8b4084          mov     eax,dword ptr [eax-7Ch]                    // XML Style TList
005a0075 8b4004          mov     eax,dword ptr [eax+4]                      // XML Style TList length
005a0078 8945c0          mov     dword ptr [ebp-40h],eax                    // Backup the current style list length

Alternatively, when parsing a "tbl" tag (case 0x5a) the following case will be executed. After extracting some properties/styles from the document similarly to the other table-related XML tags, the application will construct a TList at [12] to contain them. Next the parser will read the current TTableRow that is stored in the variable belonging to the upper-most function at -0xdc(%ebp), and then write it into a local variable at [13]. Afterwards, the application will construct a new TTableRow instance, and then store it at [14]. If this function is executed before the current TTableRow is initialized (explicitly done by the previously described code), this will result in the instruction at [13] initializing the local variable containing the current TTableRow with an uninitialized value.

awp+0x19f99d:
0059f99d 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
0059f9a0 8b4008          mov     eax,dword ptr [eax+8]                      // Upper-most frame
0059f9a3 8b4008          mov     eax,dword ptr [eax+8]                      // Document scope frame
0059f9a6 8b40e8          mov     eax,dword ptr [eax-18h]                    // TDoc
0059f9a9 80b8b703000000  cmp     byte ptr [eax+3B7h],0
0059f9b0 740c            je      awp+0x19f9be (0059f9be)
...
awp+0x19f9be:
0059f9be 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
0059f9c1 ff80e4feffff    inc     dword ptr [eax-11Ch]
0059f9c7 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
0059f9ca 8b4008          mov     eax,dword ptr [eax+8]                      // Upper-most frame
0059f9cd 8b4084          mov     eax,dword ptr [eax-7Ch]                    // XML Style TList
0059f9d0 8945f0          mov     dword ptr [ebp-10h],eax                    // Backup XML Style TList
0059f9d3 a140684000      mov     eax,dword ptr [awp+0x6840 (00406840)]      // TList
0059f9d8 e86783e6ff      call    awp+0x7d44 (00407d44)                      // [12] TList constructor
...
awp+0x19fa96:
0059fa96 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
0059fa99 8b4008          mov     eax,dword ptr [eax+8]                      // Upper-most frame
0059fa9c 8b8024ffffff    mov     eax,dword ptr [eax-0DCh]                   // Current TTableRow
0059faa2 8945dc          mov     dword ptr [ebp-24h],eax                    // [13] Write to local variable containing Current TTableRow
...
0059faa5 8b4508          mov     eax,dword ptr [ebp+8]                      // Caller frame
0059faa8 8b4008          mov     eax,dword ptr [eax+8]                      // Upper-most frame
0059faab 8b8020ffffff    mov     eax,dword ptr [eax-0E0h]                   // Source TTableRow
0059fab1 8945e0          mov     dword ptr [ebp-20h],eax                    // Local variable containing Source TTableRow
0059fab4 b201            mov     dl,1
0059fab6 a19cee5500      mov     eax,dword ptr [awp+0x15ee9c (0055ee9c)]    // TTableRow
0059fabb e8002ee6ff      call    awp+0x28c0 (004028c0)                      // TTableRow constructor
...
0059fac0 8b5508          mov     edx,dword ptr [ebp+8]                      // Caller frame
0059fac3 8b5208          mov     edx,dword ptr [edx+8]                      // Upper-most frame
0059fac6 898220ffffff    mov     dword ptr [edx-0E0h],eax                   // [14]

Later in the same handler for the "tbl" tag (case 0x5a), the application will execute the following code. The code will first shift the old source table row back to its original state. This is done by freeing the current one with TObject::Free at [15]. Afterward at [16], the application will take the currently saved TTableRow out of the local variable belonging to the frame. Lastly, the application will take the local variable containing the current TTableRow and write it back into the upper-most function's original variable, -0xdc(%ebp), at [17]. Due to the case for handling the "tbl" tag (case 0x5a) being able to be called before this local variable is initialized, this will result in an uninitialized pointer being written into the current TTableRow belonging to the upper-most frame.

awp+0x19fd0f:
0059fd0f 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
0059fd12 8b4008          mov     eax,dword ptr [eax+8]      // Upper-most frame
0059fd15 8b8020ffffff    mov     eax,dword ptr [eax-0E0h]   // Source TTableRow
0059fd1b e8c82be6ff      call    awp+0x28e8 (004028e8)      // [15] TObject::Free
...
0059fd20 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
0059fd23 8b4008          mov     eax,dword ptr [eax+8]      // Upper-most frame
0059fd26 8b55e0          mov     edx,dword ptr [ebp-20h]    // Currently saved TTableRow
0059fd29 899020ffffff    mov     dword ptr [eax-0E0h],edx   // [16] Move back into source
...
0059fd2f 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
0059fd32 8b4008          mov     eax,dword ptr [eax+8]      // Upper-most frame
0059fd35 8b55dc          mov     edx,dword ptr [ebp-24h]    // Local variable containing Current TTableRow
0059fd38 899024ffffff    mov     dword ptr [eax-0DCh],edx   // [17] Write back to upper-most frame's current TTableRow

At this point, the application just needs to be convinced to use the current TTableRow stored in the upper-most frame. The provided proof-of-concept does this by embedding another "tbl" tag, followed by defining a "tr" tag for a table row. When parsing a "tr" element (case 0x5d), the following code will be executed. This code will parse various things within the current row element and initialize its properties with some of the attributes defined by the various elements within. Eventually the function call at [18] will be called with the current frame as its argument which will continue by parsing the children belonging to the row.

awp+0x1a001f:
005a001f 8b4508          mov     eax,dword ptr [ebp+8]
005a0022 83b8e4feffff01  cmp     dword ptr [eax-11Ch],1
005a0029 0f856d020000    jne     awp+0x1a029c (005a029c)
...
005a0147 8b9e4c240000    mov     ebx,dword ptr [esi+244Ch]      // Check TTableRow properties for list of sizes
005a014d 4b              dec     ebx
005a014e 83fb00          cmp     ebx,0
005a0151 7c1c            jl      awp+0x1a016f (005a016f)
...
awp+0x1a016f:
005a016f 8b864c240000    mov     eax,dword ptr [esi+244Ch]
005a0175 8b5508          mov     edx,dword ptr [ebp+8]
005a0178 8b5208          mov     edx,dword ptr [edx+8]
005a017b 898228ffffff    mov     dword ptr [edx-0D8h],eax
...
005a0181 55              push    ebp
005a0182 e895cdffff      call    awp+0x19cf1c (0059cf1c)        // [18] Dispatch to function that uses uninitialized TTableRow from upper-most frame

With the provided proof-of-concept, the usage of the uninitialized TTableRow from the upper-most frame is triggered by a table column identified by the "tc" element (case 0x5b). Depending on the value of the uninitialized pointer, there are a number of places that can be used to corrupt memory. One such place is at [19] which will allow one to increment an arbitrary address leading to corruption. With further work, this can be made to lead to code execution within the context of the application.

awp+0x19fd95:
0059fd95 8b4508          mov     eax,dword ptr [ebp+8]
0059fd98 83b8e4feffff01  cmp     dword ptr [eax-11Ch],1
0059fd9f 0f856e020000    jne     awp+0x1a0013 (005a0013)
...
0059ff73 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
0059ff76 8b4008          mov     eax,dword ptr [eax+8]      // Upper-most frame
0059ff79 8b4008          mov     eax,dword ptr [eax+8]      // Document frame
0059ff7c 8b40e8          mov     eax,dword ptr [eax-18h]    // TDoc
0059ff7f 8b4050          mov     eax,dword ptr [eax+50h]    // TList of TPar
0059ff82 8b4004          mov     eax,dword ptr [eax+4]      // Length
0059ff85 3b45bc          cmp     eax,dword ptr [ebp-44h]
0059ff88 7e73            jle     awp+0x19fffd (0059fffd)
...
0059ffeb 8b4508          mov     eax,dword ptr [ebp+8]      // Caller frame
0059ffee 8b4008          mov     eax,dword ptr [eax+8]      // Upper-most frame
0059fff1 8b8024ffffff    mov     eax,dword ptr [eax-0DCh]   // Uninitialized TTableRow
0059fff7 ff80c8000000    inc     dword ptr [eax+0C8h]       // [19]

Crash Information

Set breakpoint at scope of current TTableRow in upper-most function

0:000> bp 5a068b

Set breakpoint at hand-off of uninitialized TTableRow to local variable

0:000> bp 59faa2

Set breakpoint at hand-off of local variable back into TTableRow

0:000> bp 59fd38
0:000> g

We're at the beginning of the upper-most function where the TTableRow is allocated for (but not initialized)

Breakpoint 0 hit
eax=00000002 ebx=00000002 ecx=0ba09228 edx=0ba09201 esi=00674b1c edi=0018f76c
eip=005a068b esp=0018f004 ebp=0018f004 iopl=0         nv up ei ng nz ac po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000293
awp+0x1a068b:
005a068b 81c400ffffff    add     esp,0FFFFFF00h

0:000> dc @ebp-dc L4
0018ef28  278806d0 0018ef40 77d4da3c 7677cd3f  ...'@...<..w?.wv

0:000> dc 278806d0 L4
278806d0  ???????? ???????? ???????? ????????  ????????????????

Value of %eax is pointing to value of uninitialized TTableRow and is saved in local variables

Breakpoint 1 hit
eax=278806d0 ebx=00000002 ecx=00000001 edx=0000149c esi=0ba014c4 edi=00000000
eip=0059faa2 esp=0018ecc4 ebp=0018ed9c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
awp+0x19faa2:
0059faa2 8945dc          mov     dword ptr [ebp-24h],eax ss:002b:0018ed78=00000000

Uninitialized TTableRow saved in local variables is restored to TTableRow in upper-most function

0:000> g
Breakpoint 2 hit
eax=0018f004 ebx=0000000a ecx=ffffffff edx=278806d0 esi=0ba368f0 edi=00000000
eip=0059fd38 esp=0018ecc4 ebp=0018ed9c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
awp+0x19fd38:
0059fd38 899024ffffff    mov     dword ptr [eax-0DCh],edx ds:002b:0018ef28=0ba9132c

0:000> g

Uninitialized pointer is used as base address to increment a property of TTableRow.

(71c.bfc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=278806d0 ebx=0ba38984 ecx=0018ebac edx=0ba3afc0 esi=0ba3882c edi=00000000
eip=0059fff7 esp=0018ead4 ebp=0018ebac iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
awp+0x19fff7:
0059fff7 ff80c8000000    inc     dword ptr [eax+0C8h] ds:002b:27880798=????????

Timeline

2018-09-10 - Vendor Disclosure
2018-09-11 - Vendor patched via beta version
2018-09-26 - Vendor released
2018-10-01 - Public Disclosure

Credit

Discovered by a member of Cisco Talos.