Talos Vulnerability Report

TALOS-2018-0651

Atlantis Word Processor empty TTableRow TList code execution vulnerability

October 1, 2018
CVE Number

CVE-2018-3981

Summary

An exploitable uninitialized pointer vulnerability exists in the Word document parser of the the Atlantis Word Processor. A specially crafted document can cause an array fetch to return an uninitialized pointer and then performs some arithmetic before writing a value to the result. Usage of this uninitialized pointer can allow an attacker to corrupt heap memory resulting in 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-391: Unchecked Error Condition

Details

The Atlantis Word Processor is a traditional word processor that comes with a variety of features. It 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 ability 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 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)

After successfully parsing the file, resulting in a TDoc object, the application will continue by first popping the first TSection out of the TDoc [3] and then iterating through all of the TSection objects associated with the TDoc at [4]. After processing the sections, the application will then continue on to [5] where it will loop over a number of TPar object before executing.

awp+0x1ae0c8:
005ae0c8 8b45e8          mov     eax,dword ptr [ebp-18h]    // TDoc
005ae0cb 8b804c040000    mov     eax,dword ptr [eax+44Ch]
005ae0d1 e85a9de5ff      call    awp+0x7e30 (00407e30)      // [3] Grab the first TSection out of a TList
005ae0d6 8bf0            mov     esi,eax
005ae0d8 8b45e8          mov     eax,dword ptr [ebp-18h]    // TDoc
005ae0db 8b804c040000    mov     eax,dword ptr [eax+44Ch]
005ae0e1 8b5804          mov     ebx,dword ptr [eax+4]      // Adjust the TList for the removal of the first section
005ae0e4 4b              dec     ebx
005ae0e5 83fb01          cmp     ebx,1
005ae0e8 7c67            jl      awp+0x1ae151 (005ae151)
...
awp+0x1ae0ea:
005ae0ea 8b45e8          mov     eax,dword ptr [ebp-18h]    // [4] Loop over each TSection
005ae0ed 8b804c040000    mov     eax,dword ptr [eax+44Ch]
005ae0f3 8bd3            mov     edx,ebx
005ae0f5 e8fa9be5ff      call    awp+0x7cf4 (00407cf4)
005ae0fa 8bd0            mov     edx,eax
...
awp+0x1ae140:
005ae140 8b86b8050000    mov     eax,dword ptr [esi+5B8h]
005ae146 8982b8050000    mov     dword ptr [edx+5B8h],eax
005ae14c 4b              dec     ebx
005ae14d 85db            test    ebx,ebx
005ae14f 7599            jne     awp+0x1ae0ea (005ae0ea)
...
awp+0x1ae387:
005ae387 8b06            mov     eax,dword ptr [esi]        // [5] Loop over each TPar object
005ae389 85c0            test    eax,eax
005ae38b 7427            je      awp+0x1ae3b4 (005ae3b4)
...
awp+0x1ae3b4:
005ae3b4 43              inc     ebx
005ae3b5 83c604          add     esi,4
005ae3b8 80fb04          cmp     bl,4
005ae3bb 75ca            jne     awp+0x1ae387 (005ae387)

Later, the following code will be reached. At [6] the function is called with the TDoc object that resulted from parsing the document as an argument. After descending into said function, the function call at [7] will be reached. Inside this function at [8], the application will fetch a TList off of the parsed TDoc obnect and then grab a TPar using %edx as the index. This TPar will be used to terminate the loop that follows. This loop will call the function at [9] for each iteration whilst passing the number of pages and the TDoc to it.

awp+0x1ae647:
005ae647 6a00            push    0
005ae649 a02ce95a00      mov     al,byte ptr [awp+0x1ae92c (005ae92c)]
005ae64e 50              push    eax
005ae64f 8b8d74ffffff    mov     ecx,dword ptr [ebp-8Ch]    // TMemory from TPar
005ae655 8b9570ffffff    mov     edx,dword ptr [ebp-90h]    // Size from sum of TPar fields in TDoc
005ae65b 8b45e8          mov     eax,dword ptr [ebp-18h]    // TDoc
005ae65e e8c57b0200      call    awp+0x1d6228 (005d6228)    // [6] \
\
awp+0x1d6228:
005d6228 55              push    ebp
005d6229 8bec            mov     ebp,esp
005d622b 83c4f0          add     esp,0FFFFFFF0h
...
awp+0x1d626c:
005d626c 8d55f0          lea     edx,[ebp-10h]
005d626f 33c9            xor     ecx,ecx
005d6271 8bc6            mov     eax,esi                    // TDoc
005d6273 e8ecefffff      call    awp+0x1d5264 (005d5264)    // [7] \
\
awp+0x1d5264:
005d5264 53              push    ebx
005d5265 56              push    esi
005d5266 57              push    edi
005d5267 55              push    ebp
005d5268 51              push    ecx
005d5269 880c24          mov     byte ptr [esp],cl
005d526c 8bea            mov     ebp,edx
005d526e 8bd8            mov     ebx,eax
...
awp+0x1d5279:
005d5279 8b5500          mov     edx,dword ptr [ebp]        // Index
005d527c 8b4350          mov     eax,dword ptr [ebx+50h]    // TDoc
005d527f e8702ae3ff      call    awp+0x7cf4 (00407cf4)      // [8] Grab a TPar from TList off of TDoc
005d5284 8bf8            mov     edi,eax
005d5286 be01000000      mov     esi,1
005d528b eb1c            jmp     awp+0x1d52a9 (005d52a9)
...
awp+0x1d528d:
005d528d 8b83bc000000    mov     eax,dword ptr [ebx+0BCh]   // TList containg TPage elements
005d5293 8b4004          mov     eax,dword ptr [eax+4]      // Grab its length
005d5296 8bc8            mov     ecx,eax                    // Add a power of two to it
005d5298 03ce            add     ecx,esi
005d529a 8bd0            mov     edx,eax                    // Number of TPage elements
005d529c 8bc3            mov     eax,ebx                    // TDoc
005d529e e87de6ffff      call    awp+0x1d3920 (005d3920)    // [9]
005d52a3 8bc6            mov     eax,esi
005d52a5 03c0            add     eax,eax
005d52a7 8bf0            mov     esi,eax
...
awp+0x1d52a9:
005d52a9 f6470801        test    byte ptr [edi+8],1         // Check flag in TPar
005d52ad 74de            je      awp+0x1d528d (005d528d)
005d52af 8b470c          mov     eax,dword ptr [edi+0Ch]    // Grab TLine list from TPar
005d52b2 e8812be3ff      call    awp+0x7e38 (00407e38)      // Fetch the last element from it
005d52b7 83785800        cmp     dword ptr [eax+58h],0      // Check if the last element is NULL
005d52bb 74d0            je      awp+0x1d528d (005d528d)

Inside the function 0x5d3920, the application will do a number of things to use the different lists belonging to the TDoc objects to link the different TSection and TPage objects together. After executing all this at [10], the application will eventually execute the code at [11] to populate some fields inside the TSection object. After this is all initialized, the current frame is then passed to the function call at [12]. This code construct is typically used when a developer uses a closure or an anonymous function to process variables.

awp+0x1d3920:
005d3920 55              push    ebp
005d3921 8bec            mov     ebp,esp
005d3923 83c4b0          add     esp,0FFFFFFB0h
005d3926 53              push    ebx
005d3927 56              push    esi
005d3928 57              push    edi
005d3929 894de0          mov     dword ptr [ebp-20h],ecx
005d392c 8955e4          mov     dword ptr [ebp-1Ch],edx    // number of TPage elements
005d392f 8945fc          mov     dword ptr [ebp-4],eax      // TDoc
...
// [10] Link a number of different objects together
...
awp+0x1d3bcb:
005d3bcb 33db            xor     ebx,ebx
005d3bcd 8b45f4          mov     eax,dword ptr [ebp-0Ch]    // [11]
005d3bd0 8b00            mov     eax,dword ptr [eax]        // TSection field
005d3bd2 8b55f8          mov     edx,dword ptr [ebp-8]
005d3bd5 894228          mov     dword ptr [edx+28h],eax    // TPage
005d3bd8 8b45f4          mov     eax,dword ptr [ebp-0Ch]
005d3bdb 8b4004          mov     eax,dword ptr [eax+4]      // TSection field
005d3bde 8b55f8          mov     edx,dword ptr [ebp-8]
005d3be1 89422c          mov     dword ptr [edx+2Ch],eax    // TPage
005d3be4 8b45f4          mov     eax,dword ptr [ebp-0Ch]
005d3be7 8b4018          mov     eax,dword ptr [eax+18h]    // TSection field
005d3bea 8b55f8          mov     edx,dword ptr [ebp-8]
005d3bed 894248          mov     dword ptr [edx+48h],eax    // TPage
005d3bf0 8b45f4          mov     eax,dword ptr [ebp-0Ch]
005d3bf3 8b401c          mov     eax,dword ptr [eax+1Ch]    // TSection field
005d3bf6 8b55f8          mov     edx,dword ptr [ebp-8]
005d3bf9 894250          mov     dword ptr [edx+50h],eax    // TPage
005d3bfc 8b07            mov     eax,dword ptr [edi]        // TSection field
005d3bfe 80b88003000000  cmp     byte ptr [eax+380h],0      // TDoc array
005d3c05 7507            jne     awp+0x1d3c0e (005d3c0e)
005d3c07 55              push    ebp                        // current frame
005d3c08 e8b3e7ffff      call    awp+0x1d23c0 (005d23c0)    // [12]
005d3c0d 59              pop     ecx

At the beginning of the following function at [13], the application will call into the function 0x57dc14. This function will do some processing using the TDoc object and then return a TSection for the current position. Finally at [14] at [15], the application will check to see if some byte fields are set. If they are not, then execution will continue. At [16], the application will finally call the function that wraps our vulnerability. This function will also get its caller's frame passed to it.

awp+0x1d23c0:
005d23c0 55              push    ebp
005d23c1 8bec            mov     ebp,esp
005d23c3 53              push    ebx
005d23c4 8b4508          mov     eax,dword ptr [ebp+8]      // frame from caller
005d23c7 8378f000        cmp     dword ptr [eax-10h],0      // conditional
005d23cb 743c            je      awp+0x1d2409 (005d2409)
...
awp+0x1d2409:
005d2409 8b4508          mov     eax,dword ptr [ebp+8]
005d240c 8b40fc          mov     eax,dword ptr [eax-4]      // TDoc
005d240f 33d2            xor     edx,edx
005d2411 e8feb7faff      call    awp+0x17dc14 (0057dc14)    // [13]
005d2416 8bd8            mov     ebx,eax
005d2418 80bb5205000000  cmp     byte ptr [ebx+552h],0      // [14] TSection
005d241f 7420            je      awp+0x1d2441 (005d2441)
...
awp+0x1d2441:
005d2441 80bb5105000000  cmp     byte ptr [ebx+551h],0      // [15] TSection
005d2448 741e            je      awp+0x1d2468 (005d2468)
...
awp+0x1d2468:
005d2468 b001            mov     al,1
005d246a b303            mov     bl,3
005d246c 55              push    ebp                        // Current frame
005d246d e8a2fcffff      call    awp+0x1d2114 (005d2114)    // [16]
005d2472 59              pop     ecx

At [17] of the following function, the application will grab the current TPage. This TPage will be used to loop through the current TSection belonging to the TDoc. At [18], a loop will be entered which will search for a TField that fits within specific boundaries. After this is found at [19], the application will store the found TField to %esi. Later at [20], a TCompoundFragment variable will be initialized which will then be populated with the TField at [21]. Eventually the application will populate some fields in a TBox and hand off the TBox and the TCompoundDocument to the function call at [22] which contains the vulnerability.

awp+0x1d2114:
005d2114 55              push    ebp
005d2115 8bec            mov     ebp,esp
005d2117 83c4d0          add     esp,0FFFFFFD0h
005d211a 53              push    ebx
005d211b 56              push    esi
005d211c 57              push    edi
005d211d 8bd8            mov     ebx,eax
005d211f 8b4508          mov     eax,dword ptr [ebp+8]          // Caller frame
005d2122 8b4008          mov     eax,dword ptr [eax+8]
005d2125 8b40f8          mov     eax,dword ptr [eax-8]          // [17] Current TPage
005d2128 8bb884000000    mov     edi,dword ptr [eax+84h]        // Left TSection
005d212e eb14            jmp     awp+0x1d2144 (005d2144)
...
awp+0x1d2130:
005d2130 8b570c          mov     edx,dword ptr [edi+0Ch]
005d2133 4a              dec     edx
005d2134 8b4508          mov     eax,dword ptr [ebp+8]
005d2137 8b4008          mov     eax,dword ptr [eax+8]
005d213a 8b40fc          mov     eax,dword ptr [eax-4]          // TDoc
005d213d e8d2bafaff      call    awp+0x17dc14 (0057dc14)        // [18] Fetch a TSection
005d2142 8bf8            mov     edi,eax
...
awp+0x1d2144:
005d2144 33c0            xor     eax,eax
005d2146 8ac3            mov     al,bl
005d2148 83bc878805000000 cmp     dword ptr [edi+eax*4+588h],0  // Check against TSection's TField array
005d2150 7506            jne     awp+0x1d2158 (005d2158)
005d2152 837f0c00        cmp     dword ptr [edi+0Ch],0          // Check left index is larger than 0
005d2156 7fd8            jg      awp+0x1d2130 (005d2130)
...
awp+0x1d2158:
005d2158 33c0            xor     eax,eax
005d215a 8ac3            mov     al,bl
005d215c 8bb48788050000  mov     esi,dword ptr [edi+eax*4+588h] // [19] Grab the TField that was found
005d2163 85f6            test    esi,esi
005d2165 0f844c020000    je      awp+0x1d23b7 (005d23b7)
...
awp+0x1d216b:
005d216b 8d4de0          lea     ecx,[ebp-20h]                  // Result
005d216e 33c0            xor     eax,eax
005d2170 8ac3            mov     al,bl
005d2172 8bd6            mov     edx,esi                        // TField
005d2174 8b4508          mov     eax,dword ptr [ebp+8]
005d2177 8b4008          mov     eax,dword ptr [eax+8]
005d217a 8b40fc          mov     eax,dword ptr [eax-4]          // TDoc
005d217d e826870200      call    awp+0x1fa8a8 (005fa8a8)        // [20] Initialize TCompoundFragment
...
awp+0x1d2192:
005d2192 8d45e0          lea     eax,[ebp-20h]                  // TCompoundFragment
005d2195 50              push    eax
005d2196 8b4508          mov     eax,dword ptr [ebp+8]
005d2199 8b4008          mov     eax,dword ptr [eax+8]
005d219c 8b40f8          mov     eax,dword ptr [eax-8]          // TPage
005d219f 8b8880000000    mov     ecx,dword ptr [eax+80h]        // TPage.Length
005d21a5 33c0            xor     eax,eax
005d21a7 8ac3            mov     al,bl                          // TField index
005d21a9 8b948788050000  mov     edx,dword ptr [edi+eax*4+588h] // Grab the TField at index %bl
005d21b0 8b4508          mov     eax,dword ptr [ebp+8]
005d21b3 8b4008          mov     eax,dword ptr [eax+8]
005d21b6 8b40fc          mov     eax,dword ptr [eax-4]          // TDoc
005d21b9 e8aeef0200      call    awp+0x20116c (0060116c)        // [21] Determine TField and update TCompoundFragment
...
awp+0x1d2275:
005d2275 8d55e0          lea     edx,[ebp-20h]                  // TCompoundFragment
005d2278 8bc6            mov     eax,esi                        // TBox
005d227a e815d3f9ff      call    awp+0x16f594 (0056f594)        // [22]

At the beginning of the function, the application will first store its arguments followed by instantiating a TMemory object. After encountering the branches at [23] and [24], the application will use the TBox passed as one of its arguments in order to grab the TDoc object that owns it at [25]. At [26], the application will use an index from the caller's frame to fetch a TPar element from the TDoc. Finally at [27], the application will encounter a branch which should initialize a TList using the contents of another.

awp+0x16f594:
0056f594 55              push    ebp
0056f595 8bec            mov     ebp,esp
0056f597 81c4e0feffff    add     esp,0FFFFFEE0h
0056f59d 53              push    ebx
0056f59e 56              push    esi
0056f59f 57              push    edi
0056f5a0 895590          mov     dword ptr [ebp-70h],edx                    // Pointer to structure 2 functions up
0056f5a3 8945fc          mov     dword ptr [ebp-4],eax                      // TBox
...
0056f5c8 e917150000      jmp     awp+0x170ae4 (00570ae4)                    // [23] Branches forward
...
awp+0x16f5cd:
0056f5cd 8b45fc          mov     eax,dword ptr [ebp-4]                      // [25] TBox
0056f5d0 8b4004          mov     eax,dword ptr [eax+4]                      // TBox.OwnerDocument
0056f5d3 89855cffffff    mov     dword ptr [ebp-0A4h],eax                   // TDoc
...
awp+0x16f5d9:
0056f5d9 8b5590          mov     edx,dword ptr [ebp-70h]
0056f5dc 8b12            mov     edx,dword ptr [edx]                        // TPar Index
0056f5de 8b855cffffff    mov     eax,dword ptr [ebp-0A4h]                   // TDoc
0056f5e4 8b4050          mov     eax,dword ptr [eax+50h]                    // [26] TList from TDoc that contains TPar elements
0056f5e7 e80887e9ff      call    awp+0x7cf4 (00407cf4)                      // Fetches element from a TList
0056f5ec 83786000        cmp     dword ptr [eax+60h],0                      // TPar
0056f5f0 740d            je      awp+0x16f5ff (0056f5ff)
0056f5f2 8b45fc          mov     eax,dword ptr [ebp-4]
0056f5f5 f6401420        test    byte ptr [eax+14h],20h                     // TBox
0056f5f9 0f8497020000    je      awp+0x16f896 (0056f896)                    // [27] Branch to code that constructs the vulnerable TList
...
awp+0x170ae4:
00570ae4 8b4590          mov     eax,dword ptr [ebp-70h]
00570ae7 e888fbfeff      call    awp+0x160674 (00560674)
00570aec 84c0            test    al,al
00570aee 0f84d9eaffff    je      awp+0x16f5cd (0056f5cd)                    // [24] Branches backwards

First, the application will append eight bytes to the beginning of the previously allocated TMemory object followed by constructing a number of TList objects. It is prudent to note that when Delphi destroys a TList object, Delphi will append the deleted object to a global cache. This way, when it is necessary to construct a TList, Delphi can simply fetch one of the previously deleted TList objects and avoid any allocations required. When the application constructs its numerous TList objects, one of these objects is critical to triggering the vulnerability described by this document. At [25], is the TList that is constructed. This TList contains the aggregation of all of the TTableRow elements that were found in the TDoc. A little farther after at [26], the application will call into a function that's responsible for taking the TList at +6c of the TDoc and using it to extend the TList stored at %ebp-10h with its TTableRow elements. This is done inside the function call at [27]. However, due to the TList at +6c being empty, this results in the code at [27] not being executed which results in the TList at %ebp-10h also being empty. These lists should contain a number of TTableRow objects. However, due to a missing or corrupted property, (sprm) they are left empty. Later at [28], the application will fetch the first element from the empty TList. As soon as the application attempts to initialize it at [29], this will result in the application writing to the uninitialized pointer that was fetched. This should allow an attacker to corrupt heap memory belonging to the application which can result in code execution under the context of the application.

awp+0x16f896:
0056f896 55              push    ebp                                    // Current frame
0056f897 b001            mov     al,1
0056f899 e86aecffff      call    awp+0x16e508 (0056e508)                // Appends 8 bytes to a TMemory object constructed at beginning of function
0056f89e 59              pop     ecx
0056f89f a140684000      mov     eax,dword ptr [awp+0x6840 (00406840)]
0056f8a4 e89b84e9ff      call    awp+0x7d44 (00407d44)                  // Construct a TList
0056f8a9 8945c8          mov     dword ptr [ebp-38h],eax
0056f8ac a140684000      mov     eax,dword ptr [awp+0x6840 (00406840)]
0056f8b1 e88e84e9ff      call    awp+0x7d44 (00407d44)                  // Construct a TList
0056f8b6 898578ffffff    mov     dword ptr [ebp-88h],eax
0056f8bc a140684000      mov     eax,dword ptr [awp+0x6840 (00406840)]
0056f8c1 e87e84e9ff      call    awp+0x7d44 (00407d44)                  // Construct a TList
0056f8c6 8945f0          mov     dword ptr [ebp-10h],eax                // [25] Uninitialized TList
...
awp+0x16f8c9:
0056f8c9 8d45d0          lea     eax,[ebp-30h]                          // pointer to page number
0056f8cc 50              push    eax
0056f8cd 8b5590          mov     edx,dword ptr [ebp-70h]
0056f8d0 8b12            mov     edx,dword ptr [edx]
0056f8d2 8b4df0          mov     ecx,dword ptr [ebp-10h]                // List of TTableRows that should be initialized
0056f8d5 8b855cffffff    mov     eax,dword ptr [ebp-0A4h]               // TDoc
0056f8db e8f4690900      call    awp+0x2062d4 (006062d4)                // [26] \
\
awp+0x206323:
00606323 8bd3            mov     edx,ebx
00606325 8b466c          mov     eax,dword ptr [esi+6Ch]                // TDoc TList at +6c. Contains a number of TLists that point to TTableRow elements
00606328 e8c719e0ff      call    awp+0x7cf4 (00407cf4)                  // Grab element %edx from list which is another TList
0060632d 8bd0            mov     edx,eax
0060632f 8b45f8          mov     eax,dword ptr [ebp-8]
00606332 e8551ae0ff      call    awp+0x7d8c (00407d8c)                  // [27] Extend TList in %eax by %edx
00606337 eb06            jmp     awp+0x20633f (0060633f)
00606339 4b              dec     ebx
0060633a 83fbff          cmp     ebx,0FFFFFFFFh
/
awp+0x16fa8b:
0056fa8b 8b45f0          mov     eax,dword ptr [ebp-10h]                // [28] Empty TList
0056fa8e e89d83e9ff      call    awp+0x7e30 (00407e30)                  // Fetch first element of TList
0056fa93 8945c4          mov     dword ptr [ebp-3Ch],eax                // Store pointer
0056fa96 8b45c4          mov     eax,dword ptr [ebp-3Ch]                // Read pointer
0056fa99 33d2            xor     edx,edx
0056fa9b 899054240000    mov     dword ptr [eax+2454h],edx              // [29] Write NULL to dereferenced pointer

Crash Information

Set breakpoint at construction of empty TList.

0:007> bp awp+16f8c1

Execute instruction that constructs empty TList

0:007> g
Breakpoint 0 hit
eax=00406880 ebx=0c05c701 ecx=0018ef08 edx=00000000 esi=0c0691e0 edi=0c05c72c
eip=0056f8c1 esp=0018eddc ebp=0018ef08 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+0x16f8c1:
0056f8c1 e87e84e9ff      call    awp+0x7d44 (00407d44)
0:000> p
eax=0c03c99c ebx=0c05c701 ecx=0018ef08 edx=00000000 esi=0c0691e0 edi=0c05c72c
eip=0056f8c6 esp=0018eddc ebp=0018ef08 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+0x16f8c6:
0056f8c6 8945f0          mov     dword ptr [ebp-10h],eax ss:002b:0018eef8=00000000

Tlist has been constructed and is empty.

0:000> $$>a<c:/users/user/audit/atlantis/scripts/tlist.dbgscr @eax
[0c03c99c] <type 'structure' name='TList' size=+0x20>
[0c03c99c] (+0) : p_InfoTable_0 : (00406880) 4221056
[0c03c9a0] (+4) : v_length_4 : (00000000) 0
[0c03c9a4] (+8) : v_capacity_8 : (00000001) 1
[0c03c9a8] (+c) : p_field?_c : (00000000) 0
[0c03c9ac] (+10) : p_field?_10 : (00000000) 0
[0c03c9b0] (+14) : p_field?_14 : (00000000) 0
[0c03c9b4] (+18) : p_field?_18 : (00000000) 0
[0c03c9b8] (+1c) : p_items_1c : (0c03c9a8) 201574824

Execute till function that should initialize TList.

0:000> g awp+16f8db
eax=0c05ba80 ebx=0c05c701 ecx=0c03c99c edx=000000aa esi=0c0691e0 edi=0c05c72c
eip=0056f8db esp=0018edd8 ebp=0018ef08 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+0x16f8db:
0056f8db e8f4690900      call    awp+0x2062d4 (006062d4)

Dump out empty TList in TDoc

0:000> $$>a<c:/users/user/audit/atlantis/scripts/tlist.dbgscr poi(@eax+6c)
[099fbac4] <type 'structure' name='TList' size=+0x20>
[099fbac4] (+0) : p_InfoTable_0 : (00406880) 4221056
[099fbac8] (+4) : v_length_4 : (00000000) 0
[099fbacc] (+8) : v_capacity_8 : (00000000) 0
[099fbad0] (+c) : p_field?_c : (00000000) 0
[099fbad4] (+10) : p_field?_10 : (00000000) 0
[099fbad8] (+14) : p_field?_14 : (00000000) 0
[099fbadc] (+18) : p_field?_18 : (00000000) 0
[099fbae0] (+1c) : p_items_1c : (00000000) 0

Step over which should initialize TList

0:000> p
eax=0c03c99c ebx=0c05c701 ecx=000000aa edx=0c03d1b8 esi=0c0691e0 edi=0c05c72c
eip=0056f8e0 esp=0018eddc ebp=0018ef08 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+0x16f8e0:
0056f8e0 8b45fc          mov     eax,dword ptr [ebp-4] ss:002b:0018ef04=0c0691e0

Dump out TList that should've been initialized, but is still empty.

0:000> $$>a<c:/users/user/audit/atlantis/scripts/tlist.dbgscr poi(@ebp-10)
[0c03c99c] <type 'structure' name='TList' size=+0x20>
[0c03c99c] (+0) : p_InfoTable_0 : (00406880) 4221056
[0c03c9a0] (+4) : v_length_4 : (00000000) 0
[0c03c9a4] (+8) : v_capacity_8 : (00000001) 1
[0c03c9a8] (+c) : p_field?_c : (00000000) 0
[0c03c9ac] (+10) : p_field?_10 : (00000000) 0
[0c03c9b0] (+14) : p_field?_14 : (00000000) 0
[0c03c9b4] (+18) : p_field?_18 : (00000000) 0
[0c03c9b8] (+1c) : p_items_1c : (0c03c9a8) 201574824

Execute till function that fetches first element from TList

0:000> g awp+16fa8e
eax=0c03c99c ebx=0c05c701 ecx=0018ef08 edx=0c06949c esi=0c0691e0 edi=0c05c72c
eip=0056fa8e esp=0018eddc ebp=0018ef08 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286
awp+0x16fa8e:
0056fa8e e89d83e9ff      call    awp+0x7e30 (00407e30)

Depiste this fetching a NULL pointer when executed, the pointer is actually uninitialized and is dependent on the state of the previous TList that was destroyed.

0:000> p
eax=00000000 ebx=0c05c701 ecx=0018ef08 edx=0c06949c esi=0c0691e0 edi=0c05c72c
eip=0056fa93 esp=0018eddc ebp=0018ef08 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286
awp+0x16fa93:
0056fa93 8945c4          mov     dword ptr [ebp-3Ch],eax ss:002b:0018eecc=00000000

Uninitialized pointer fetched from TTableRow list is now written to

0:000> g
(830.bdc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=0c05c701 ecx=0018ef08 edx=00000000 esi=0c0691e0 edi=0c05c72c
eip=0056fa9b esp=0018eddc ebp=0018ef08 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+0x16fa9b:
0056fa9b 899054240000    mov     dword ptr [eax+2454h],edx ds:002b:00002454=????????

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

Credit

Discovered by a member of Cisco Talos.