CVE-2018-4001
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.
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
https://www.atlantiswordprocessor.com/en/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-457: Use of Uninitialized Variable
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]
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=????????
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.