Talos Vulnerability Report

TALOS-2018-0650

Atlantis Word Processor document endnote reference code execution vulnerability

October 1, 2018
CVE Number

CVE-2018-3982

Summary

An exploitable arbitrary write vulnerability exists in the Word document parser of the Atlantis word processor. A specially crafted document can prevent Atlas from adding elements to an array that is indexed by a loop. When reading from this array, the application will use an out-of-bounds index which can result in arbitrary data being read as a pointer. Later, when the application attempts to write to said pointer, an arbitrary write will occur. This can allow an attacker to further corrupt memory, which leads to code execution under the context of the application. An attacker must convince a victim to open a document in order to trigger this vulnerability.

Tested Versions

Atlantis Word Processor 3.0.2.3 Atlantis Word Processor 3.0.2.5

full module list
start    end        module name
00400000 007f0000   awp      C (no symbols)           
    Image path: C:\Program Files (x86)\Atlantis\awp.exe
    Image name: awp.exe
    Browse all global symbols  functions  data
    Timestamp:        Fri Jun 19 15:22:17 1992 (2A425E19)
    CheckSum:         00000000
    ImageSize:        003F0000
    File version:     3.2.5.0
    Product version:  3.2.5.0
    File flags:       0 (Mask 0)
    File OS:          4 Unknown Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0409.04e4

Product URLs

https://www.atlantiswordprocessor.com/en/

CVSSv3 Score

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

CWE

CWE-129: Improper Validation of Array Index

Details

Atlantis’ Word Processor is a traditional word processor that has a number of features, including portability. 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 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. First, at [1], the application will call the function 0x5ab474, which will read a filename from the function’s frame and then write a file handle into the TDoc variable at %ebp-18. After the handle is allocated, the application will read the file into a buffer and then call the function at 0x5ad9aa to verify that the file matches the type that was specified. Once this has been verified to be a Word document (.doc), the application will then call the function at [2] in order to parse the file.

awp+0x1ad81d:
005ad81d 55              push    ebp
005ad81e e851dcffff      call    awp+0x1ab474 (005ab474)    // [1] Open up the file, and return the handle.
005ad823 59              pop     ecx
005ad824 84c0            test    al,al
005ad826 750d            jne     awp+0x1ad835 (005ad835)
...
awp+0x1ad8f2:
005ad8f2 55              push    ebp
005ad8f3 680fd95a00      push    offset awp+0x1ad90f (005ad90f)
005ad8f8 64ff30          push    dword ptr fs:[eax]
005ad8fb 648920          mov     dword ptr fs:[eax],esp
005ad8fe 55              push    ebp
005ad8ff e8d82cfdff      call    awp+0x1805dc (005805dc)    // Reads the file into a local buffer
005ad904 59              pop     ecx
...
awp+0x1ad9a4:
005ad9a4 55              push    ebp
005ad9a5 8b45f0          mov     eax,dword ptr [ebp-10h]    // Pointer to File Format Type index
005ad9a8 8bc3            mov     eax,ebx
005ad9aa e86d3afdff      call    awp+0x18141c (0058141c)    // Verify the file matches the format specified by %eax
005ad9af 59              pop     ecx
005ad9b0 84c0            test    al,al
005ad9b2 0f8592000000    jne     awp+0x1ada4a (005ada4a)
...
awp+0x1ade4d:
005ade4d 8b45e8          mov     eax,dword ptr [ebp-18h]                    // TDoc
005ade50 8b80dc000000    mov     eax,dword ptr [eax+0DCh]
005ade56 83f805          cmp     eax,5
005ade59 776a            ja      awp+0x1adec5 (005adec5)
005ade5b ff248562de5a00  jmp     dword ptr awp+0x1ade62 (005ade62)[eax*4]   // Jump to the correct file format parser
005ade62 7ade            jp      awp+0x1ade42 (005ade42)
...
awp+0x1ade89:
005ade89 55              push    ebp
005ade8a e8259dfeff      call    awp+0x197bb4 (00597bb4)                    // [2] Parse the .doc file
005ade8f 59              pop     ecx
005ade90 8885d7f8ffff    mov     byte ptr [ebp-729h],al
005ade96 eb3a            jmp     awp+0x1aded2 (005aded2)

To parse a .doc file, the application will execute the following function. This will first perform a number of things to figure out how to handle the .doc file. First, the application will check the beginning of the “WordDocument” stream for a 16-bit signature 0xa5ec. Once that is determined, Atlantis can then read from the Fib in the “WordDocument” stream’s header to locate which stream contains table data [3]. If this bit is set [1], then the table can be located in the “1Table” stream. If it is cleared [0], then the table will be located in the “0Table” stream. The correct stream is then opened by the call at [4] or [5]. Finally, the last stream that this function will open is the “Data” stream. This stream is opened at [6].

awp+0x197d0f:
00597d0f 0fb707          movzx   eax,word ptr [edi]
00597d12 3deca50000      cmp     eax,0A5ECh             // Check first word at begining of WordDocument stream for signature
00597d17 0f9445f7        sete    byte ptr [ebp-9]
...
awp+0x197d4d:
00597d4d f6470b02        test    byte ptr [edi+0Bh],2   // [3] Check FibBase.b.fWhichTblStm to determine whether to use the "0Table" or "1Table" stream
00597d51 7531            jne     awp+0x197d84 (00597d84)
...
awp+0x197d53:
00597d53 8d45fc          lea     eax,[ebp-4]
00597d56 e819c7e6ff      call    awp+0x4474 (00404474)
00597d5b 50              push    eax
00597d5c 6a00            push    0
00597d5e 6a10            push    10h
00597d60 6a00            push    0
00597d62 a1080e6700      mov     eax,dword ptr [awp+0x270e08 (00670e08)]    // Reference to string "0Table"
00597d67 8b00            mov     eax,dword ptr [eax]
00597d69 50              push    eax
00597d6a 8b4508          mov     eax,dword ptr [ebp+8]
00597d6d 8b806cffffff    mov     eax,dword ptr [eax-94h]
00597d73 50              push    eax
00597d74 8b00            mov     eax,dword ptr [eax]
00597d76 ff5010          call    dword ptr [eax+10h]                        // [4] Uses IStorage->OpenStream to open the "0Table" stream
00597d79 85c0            test    eax,eax
00597d7b 7d36            jge     awp+0x197db3 (00597db3)
...
awp+0x197d84:
00597d84 8d45fc          lea     eax,[ebp-4]
00597d87 e8e8c6e6ff      call    awp+0x4474 (00404474)
00597d8c 50              push    eax
00597d8d 6a00            push    0
00597d8f 6a10            push    10h
00597d91 6a00            push    0
00597d93 a1a00b6700      mov     eax,dword ptr [awp+0x270ba0 (00670ba0)]    // Reference to string "1Table"
00597d98 8b00            mov     eax,dword ptr [eax]
00597d9a 50              push    eax
00597d9b 8b4508          mov     eax,dword ptr [ebp+8]
00597d9e 8b806cffffff    mov     eax,dword ptr [eax-94h]
00597da4 50              push    eax
00597da5 8b00            mov     eax,dword ptr [eax]
00597da7 ff5010          call    dword ptr [eax+10h]                        // [5] Uses IStorage->OpenStream to open the "1Table" stream
00597daa 85c0            test    eax,eax
00597dac 7d05            jge     awp+0x197db3 (00597db3)
...
awp+0x197db3:
00597db3 8d45f8          lea     eax,[ebp-8]
00597db6 e8b9c6e6ff      call    awp+0x4474 (00404474)
00597dbb 50              push    eax
00597dbc 6a00            push    0
00597dbe 6a10            push    10h
00597dc0 6a00            push    0
00597dc2 a174106700      mov     eax,dword ptr [awp+0x271074 (00671074)]    // Reference to string "Data"
00597dc7 8b00            mov     eax,dword ptr [eax]
00597dc9 50              push    eax
00597dca 8b4508          mov     eax,dword ptr [ebp+8]
00597dcd 8b806cffffff    mov     eax,dword ptr [eax-94h]
00597dd3 50              push    eax
00597dd4 8b00            mov     eax,dword ptr [eax]
00597dd6 ff5010          call    dword ptr [eax+10h]                        // [6] Uses IStorage->OpenStream to open the "Data" stream
00597dd9 eb22            jmp     awp+0x197dfd (00597dfd)

After opening up the required streams, Atlantis will begin to parse required data out of them. At [6], the application will begin to parse various records such as the Dop table (Document Properties), PlcfBkl table (Bookmarks), etc. After parsing this, the application will take the sum of the various ccp fields that are listed. These fields are needed by parsers of the Word Document to determine the location of the different sections of a document. Eventually the application will execute the instruction at [7] and continue parsing more of the file format.

awp+0x197dfd:
00597dfd 55              push    ebp
00597dfe e83549ffff      call    awp+0x18c738 (0058c738)    // [6] Parse the Dop fields out of the table stream
00597e03 59              pop     ecx
...
// Various functions that read an FcLcb from the Fib structure of the WordDocument stream and reads them into a TMemory object
...
awp+0x197fbc:
00597fbc 55              push    ebp
00597fbd 8b574c          mov     edx,dword ptr [edi+4Ch]    // ccpText
00597fc0 035750          add     edx,dword ptr [edi+50h]    // ccpFtn
00597fc3 035754          add     edx,dword ptr [edi+54h]    // ccpHdd
00597fc6 035760          add     edx,dword ptr [edi+60h]    // ccpEdn
00597fc9 8b4734          mov     eax,dword ptr [edi+34h]
00597fcc 034738          add     eax,dword ptr [edi+38h]
00597fcf 03473c          add     eax,dword ptr [edi+3Ch]
00597fd2 034748          add     eax,dword ptr [edi+48h]
00597fd5 e84e47ffff      call    awp+0x18c728 (0058c728)    // Returns %edx depending on 0xa5ec signature or %eax otherwise
00597fda 59              pop     ecx
...
awp+0x198001:
00598001 55              push    ebp
00598002 e80df1ffff      call    awp+0x197114 (00597114)    // [7] Continue parsing the document

Once inside function 0x597114, the application will then enter the loop at [8]. This loop will iterate over the different sections of the document in order to process all of the fields that are defined within a particular section. This is done by parsing the different PLC structures that are defined within the Word Document’s Fib header. On the fifth iteration of this loop, the application will process the PlcfFldEdn field in the Fib. This field is responsible for determining the document fields that are located within the “Endnote” section. Once the position and size of the Plc are determined, at [9] the application will read it into a TMemory object and then eventually enter a loop that processes the CLX records.

awp+0x197374:
00597374 8a1e            mov     bl,byte ptr [esi]          // [8] 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)
...
awp+0x1974b7:
005974b7 8b4508          mov     eax,dword ptr [ebp+8]
005974ba 50              push    eax
005974bb 8b4508          mov     eax,dword ptr [ebp+8]
005974be 8b4008          mov     eax,dword ptr [eax+8]
005974c1 8b9082fbffff    mov     edx,dword ptr [eax-47Eh]   // Read the fib.fibRgFcLcbBlob.97.PlcfFldEdn.fc field
005974c7 8b4508          mov     eax,dword ptr [ebp+8]
005974ca 8b4008          mov     eax,dword ptr [eax+8]
005974cd 8b804afbffff    mov     eax,dword ptr [eax-4B6h]
005974d3 e85052ffff      call    awp+0x18c728 (0058c728)
005974d8 59              pop     ecx
005974d9 8bf8            mov     edi,eax                    // Store the .fc field (position) into %edi
005974db 8b4508          mov     eax,dword ptr [ebp+8]
005974de 50              push    eax
005974df 8b4508          mov     eax,dword ptr [ebp+8]
005974e2 8b4008          mov     eax,dword ptr [eax+8]
005974e5 8b9086fbffff    mov     edx,dword ptr [eax-47Ah]   // Read the fib.fibRgFcLcbBlob.97.PlcfFldEdn.lcb field
005974eb 8b4508          mov     eax,dword ptr [ebp+8]
005974ee 8b4008          mov     eax,dword ptr [eax+8]
005974f1 8b804efbffff    mov     eax,dword ptr [eax-4B2h]
005974f7 e82c52ffff      call    awp+0x18c728 (0058c728)
005974fc 59              pop     ecx
005974fd 8985a890ffff    mov     dword ptr [ebp-6F58h],eax  // Write the .lcb field to a local variable
00597503 e99a000000      jmp     awp+0x1975a2 (005975a2)
...
awp+0x1975d0:
005975d0 8b4508          mov     eax,dword ptr [ebp+8]
005975d3 50              push    eax
005975d4 8b8da890ffff    mov     ecx,dword ptr [ebp-6F58h]  // PlcfFldEdn.lcb size
005975da 8bd7            mov     edx,edi                    // PlcfFldEdn.fc offset
005975dc 8b853891ffff    mov     eax,dword ptr [ebp-6EC8h]  // TMemory object containing Plc for PlcfFldEdn field
005975e2 e89950ffff      call    awp+0x18c680 (0058c680)    // [9] Read the FcLcb data from the selected field into a TMemory object
005975e7 59              pop     ecx
...
awp+0x197a22:
00597a22 46              inc     esi                        // Continue the loop
00597a23 ff8dac90ffff    dec     dword ptr [ebp-6F54h]
00597a29 0f8545f9ffff    jne     awp+0x197374 (00597374)

At this point, the application will then process the CLX records in order to identify which “pieces” of the document the fields are in. This is done by reading the CLX location from the Fib, and then iterating through each entry in the piece descriptor table Pcdt. After locating the location of each piece and then calculating its size, the function call at [10] is made with the range of the piece and its size.

awp+0x197882:
00597882 55              push    ebp                        // Frame of the current function
00597883 53              push    ebx                        // Index of ccp in Fib
00597884 8a85bf90ffff    mov     al,byte ptr [ebp-6F41h]    // Pcd.Compressed
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
00597899 8b85b090ffff    mov     eax,dword ptr [ebp-6F50h]  // Clx.Pcdt.PlcPcd.aCP[index + 1]
0059789f 2b85b490ffff    sub     eax,dword ptr [ebp-6F4Ch]  // Clx.Pcdt.PlcPcd.aCP[index + 0]
005978a5 0fafc8          imul    ecx,eax                    // Multiply by 2 if piece is unicode, 1 if piece is ascii.
005978a8 8b5702          mov     edx,dword ptr [edi+2]      // Clx.Pcdt.PlcPcd.aPcd containing piece descriptor
005978ab 8b85b490ffff    mov     eax,dword ptr [ebp-6F4Ch]  // CP position from Clx.Pcdt.PlcPcd.aCP
005978b1 e89ee4ffff      call    awp+0x195d54 (00595d54)    // [10]
005978b6 59              pop     ecx

Once inside the function at 0x595d54, the application will start by calculating the position of the section by using its argument to determine which ccp fields to sum. At [11], the application will grab the index from the function’s argument and at [12] a loop will be entered that will read each ccp and add it to a variable that represents the beginning of the specified section. Each CP from the field described prior will be an offset from this position inside the “WordDocument” stream.

awp+0x195d82:
00595d82 33d2            xor     edx,edx
00595d84 89903c91ffff    mov     dword ptr [eax-6EC4h],edx  // Zero the current ccp position
...
awp+0x195d90:
00595d90 8a450c          mov     al,byte ptr [ebp+0Ch]      // [11] Grab the index of the ccp the field is in
00595d93 48              dec     eax
00595d94 84c0            test    al,al
00595d96 721c            jb      awp+0x195db4 (00595db4)
00595d98 40              inc     eax
00595d99 8b5510          mov     edx,dword ptr [ebp+10h]
00595d9c 8db2d490ffff    lea     esi,[edx-6F2Ch]            // Pointer to field or array containing ccpText 
...
awp+0x195da2:
00595da2 8b5510          mov     edx,dword ptr [ebp+10h]
00595da5 8b0e            mov     ecx,dword ptr [esi]
00595da7 018a3c91ffff    add     dword ptr [edx-6EC4h],ecx  // [12] Add each ccp field to the current ccp position
00595dad 83c604          add     esi,4
00595db0 fec8            dec     al
00595db2 75ee            jne     awp+0x195da2 (00595da2)

After determining the base of the endnote section, the application will then execute the following code. This code will use the CLX descriptor combined with the base of the section in order to read a field from the “WordDocument” stream at [13]. After reading this field information into a large array, the function call at [14] will be executed.

awp+0x196041:
00596041 8b45f0          mov     eax,dword ptr [ebp-10h]    // Pcd position (from Clx)
00596044 3b45e4          cmp     eax,dword ptr [ebp-1Ch]    // Size of CPs
00596047 0f8d640d0000    jge     awp+0x196db1 (00596db1)
...
awp+0x19604d:
0059604d 8b4510          mov     eax,dword ptr [ebp+10h]
00596050 50              push    eax
00596051 807d0800        cmp     byte ptr [ebp+8],0         // Check if Pcd is compressed
00596055 0f94c0          sete    al
00596058 83e07f          and     eax,7Fh
0059605b f76df0          imul    dword ptr [ebp-10h]
0059605e 8b55e8          mov     edx,dword ptr [ebp-18h]    // fc position (from Field)
00596061 0355f0          add     edx,dword ptr [ebp-10h]    // Pcd position
00596064 03c2            add     eax,edx
00596066 e8f5c5ffff      call    awp+0x192660 (00592660)    // [13] Read from the fc
0059606b 59              pop     ecx
0059606c 84c0            test    al,al
0059606e 7413            je      awp+0x196083 (00596083)
...
awp+0x196083:
00596083 55              push    ebp
00596084 e88ff6ffff      call    awp+0x195718 (00595718)    // [14]
00596089 59              pop     ecx

Inside function 0x595718, the function will perform a few calculations (depending on whether the piece descriptor specified the text is compressed or not) in order to locate the correct character position into the current section, the “Endnote” section, for the current field. After doing this, the following code will be executed. At the beginning of this code, the application will calculate a pointer to a TMemory object in a different frame at [15]. This TMemory object actually contains the contents of the Plc field, PlcfendRef, that was read from the document’s Fib earlier. After dereferencing this pointer, the application will then call a function at [16]. Depending on the index that is passed to this, this function may return the size of a TMemory object belonging to PlcfendTxt also read out of the Fib. This size will be later used to calculate the number of iterations for a loop.

awp+0x1957c7:
005957c7 33db            xor     ebx,ebx
005957c9 8b4508          mov     eax,dword ptr [ebp+8]
005957cc 8b4010          mov     eax,dword ptr [eax+10h]
005957cf 8b4008          mov     eax,dword ptr [eax+8]
005957d2 05c8fdffff      add     eax,0FFFFFDC8h
005957d7 8945e8          mov     dword ptr [ebp-18h],eax    // [15] Pointer to PlcfendRef TMemory
005957da 80fb06          cmp     bl,6
005957dd 750d            jne     awp+0x1957ec (005957ec)
...
awp+0x1957ec:
005957ec 80fb07          cmp     bl,7
005957ef 750d            jne     awp+0x1957fe (005957fe)
...
005957fe 80fb08          cmp     bl,8
00595801 750d            jne     awp+0x195810 (00595810)
...
awp+0x195810:
00595810 8b45e8          mov     eax,dword ptr [ebp-18h]    // Pointer to PlcfendRef TMemory
00595813 8b00            mov     eax,dword ptr [eax]
00595815 8945f0          mov     dword ptr [ebp-10h],eax    // [16] PlcfendRef TMemory
00595818 8b4508          mov     eax,dword ptr [ebp+8]
0059581b 50              push    eax
0059581c 8bc3            mov     eax,ebx                    // Index of TList
0059581e e875f7ffff      call    awp+0x194f98 (00594f98)    // [17] Return the size from PlcfendTxt TMemory
00595823 59              pop     ecx
00595824 8945f8          mov     dword ptr [ebp-8],eax      // Store the size

Immediately following the reading of the size from the TMemory object at [18], the size will then be used to calculate the number of CP elements inside the PlcfendTxt field. Once this has been determined, the loop at [19] will be entered. This loop will increase an index for each iteration of the loop and will iterate for the number of CP elements that was calculated from the size of the TMemory object for the PlcfEndTxt field. This index that is incremented will later be used to fetch a pointer from a TList which will then be written to.

awp+0x195827:
00595827 8b7df8          mov     edi,dword ptr [ebp-8]      // [18] Read size from PlcfendTxt TMemory object
0059582a c1ef02          shr     edi,2                      // Convert it to a number of CPs
0059582d 83ef02          sub     edi,2                      // Subtract 2 from it
00595830 85ff            test    edi,edi
00595832 0f8c59040000    jl      awp+0x195c91 (00595c91)
00595838 47              inc     edi
00595839 33f6            xor     esi,esi
...
awp+0x19583b:
0059583b 8b4508          mov     eax,dword ptr [ebp+8]      // [19] Loop that uses number of elements
0059583e 50              push    eax
0059583f 8bc3            mov     eax,ebx
00595841 e8f6f7ffff      call    awp+0x19503c (0059503c)
...
awp+0x195c89:
00595c89 46              inc     esi                        // Index that is increased for each loop
00595c8a 4f              dec     edi                        // Sentinel for loop
00595c8b 0f85aafbffff    jne     awp+0x19583b (0059583b)

Inside the prior described loop, the application will then enter a case statement that will process different lists depending on the case. After executing the case statement, the application will then execute the following code before continuing the loop. As a safety at [20], the application will incorrectly check that the index is not larger than the number of elements described by the PlcfendTxt. However, as will be shown later this index should be checked against the number of the PlcfendRef elements. At [21], the current invalid index will then be passed to the function 0x5955a4.

awp+0x195a1c:
00595a1c 8b45f8          mov     eax,dword ptr [ebp-8]      // Read PlcfendTxt size
00595a1f c1e802          shr     eax,2
00595a22 83e802          sub     eax,2                      // Convert to a count
00595a25 3bf0            cmp     esi,eax                    // [20] Check that index is not larger than PlcfendTxt
00595a27 0f8d5c020000    jge     awp+0x195c89 (00595c89)
awp+0x195a2d:
00595a2d 55              push    ebp
00595a2e 8b4508          mov     eax,dword ptr [ebp+8]
00595a31 8b4010          mov     eax,dword ptr [eax+10h]
00595a34 8b4008          mov     eax,dword ptr [eax+8]
00595a37 8b4008          mov     eax,dword ptr [eax+8]
00595a3a 8b8058f9ffff    mov     eax,dword ptr [eax-6A8h]   // TPar object
00595a40 b11e            mov     cl,1Eh
00595a42 8bd6            mov     edx,esi                    // Index that comes from loop iterations
00595a44 e85bfbffff      call    awp+0x1955a4 (005955a4)    // [21]
00595a49 59              pop     ecx
00595a4a e93a020000      jmp     awp+0x195c89 (00595c89)

Inside the function 0x5955a4, the following code will eventually get executed. At [22], the application will fetch a TList that collected the TField objects referenced by the PlcfendRef field. Due to the application not checking the invalid index against the number of elements in this TList, the function call at [23] which fetches an element from a Tlist will return a pointer outside the bounds of the list. This function will actually return an invalid pointer which then gets stored at [24]. Later at [25], this pointer will get read and then used to write a TField to at [26]. This results in the invalid pointer being written to which results in an arbitrary write which can lead to code execution under the context of the application.

awp+0x1955ee:
005955ee 8b5508          mov     edx,dword ptr [ebp+8]          // Caller's frame
005955f1 8b5208          mov     edx,dword ptr [edx+8]
005955f4 8b5210          mov     edx,dword ptr [edx+10h]
005955f7 8b5208          mov     edx,dword ptr [edx+8]
005955fa 8b848210fdffff  mov     eax,dword ptr [edx+eax*4-2F0h] // [22] TList of TFields
00595601 8b55f8          mov     edx,dword ptr [ebp-8]          // Invalid index from caller's loop
00595604 e8eb26e7ff      call    awp+0x7cf4 (00407cf4)          // [23] Fetch an index from a TList
00595609 894350          mov     dword ptr [ebx+50h],eax        // [24] Write result to member of TField
...
awp+0x19563b:
0059563b 8b4350          mov     eax,dword ptr [ebx+50h]        // [25] Read invalid result pointer
0059563e 895858          mov     dword ptr [eax+58h],ebx        // [26] Write %ebx to invalid pointer

Crash Information

Set a breakpoint when the index %edx is larger than the length of the TList

0:000> bp 595604 "j (@edx >= dwo(@eax+4)) ; g"

0:000> g
...
eax=0bdab800 ebx=0beda740 ecx=0018771e edx=00000021 esi=00000021 edi=00000015
eip=00595604 esp=0018772c ebp=00187740 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
awp+0x195604:
00595604 e8eb26e7ff      call    awp+0x7cf4 (00407cf4)

Output the fields belonging to the TList. The length of the TList is 0x15.

0:000> $$>a<c:/users/user/audit/atlantis/scripts/TList.dbgscr @eax
[0bdab800] <type 'structure' name='TList' size=+0x20>
[0bdab800] (+0) : p_InfoTable_0 : (00406880) 4221056
[0bdab804] (+4) : v_length_4 : (00000015) 21
[0bdab808] (+8) : v_capacity_8 : (00000019) 25
[0bdab80c] (+c) : p_field?_c : (0bda6db0) 198864304
[0bdab810] (+10) : p_field?_10 : (0bda7020) 198864928
[0bdab814] (+14) : p_field?_14 : (0c110718) 202442520
[0bdab818] (+18) : p_field?_18 : (0bdbef1c) 198962972
[0bdab81c] (+1c) : p_items_1c : (0bda21d8) 198844888

Output the index that is used. The index is 0x21.

0:000> ? @edx
Evaluate expression: 33 = 00000021

Dump out members of the TList

0:000> dc poi(0bdab800+1c)
0bda21d8  0bda6db0 0bda7020 0c110718 0bdbef1c  .m.. p..........
0bda21e8  0bdc0f94 0bdba618 0bdab1f8 0bd9d4f0  ................
0bda21f8  0bda8338 0bda216c 0be2ed70 0be32214  8...l!..p...."..
0bda2208  0be33c14 0be33c80 0be37340 0be648a0  .<...<..@s...H..
0bda2218  0be6d004 0be748e8 0be6de84 0be684a4  .....H..........
0bda2228  0be89190 0a6b6f77 6b6c6177 6f772c73  ....wok.walks,wo
0bda2238  770a736b 000000da 0055ee94 0bdcba80  ks.w......U.....
0bda2248  00000000 00000000 00000000 00000000  ................

Dump out the item pointed to by the index

0:000> dc poi(0bdab800+1c) + 21*4
0bda225c  000000f0 00000001 00000000 00000000  ................
0bda226c  00000000 00000000 00000000 00000000  ................
0bda227c  00000000 00000000 00000000 00000000  ................
0bda228c  00000000 00000009 ffffffff 00000000  ................
0bda229c  00000000 00000000 00000000 00000000  ................
0bda22ac  0bdba684 0bdba698 00000000 00000000  ................
0bda22bc  00000000 00000000 00000000 00000000  ................
0bda22cc  00000000 00000000 00000000 00000000  ................

Continue execution…

0:000> g
(3f8.9bc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=000000f0 ebx=0beda740 ecx=0018771e edx=00000021 esi=00000021 edi=00000015
eip=0059563e esp=0018772c ebp=00187740 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
awp+0x19563e:
0059563e 895858          mov     dword ptr [eax+58h],ebx ds:002b:00000148=????????  

0:000> ub .
awp+0x195622:
00595622 8b5210          mov     edx,dword ptr [edx+10h]
00595625 8b5208          mov     edx,dword ptr [edx+8]
00595628 8b848210fdffff  mov     eax,dword ptr [edx+eax*4-2F0h]
0059562f 8b55f8          mov     edx,dword ptr [ebp-8]
00595632 e8bd26e7ff      call    awp+0x7cf4 (00407cf4)
00595637 85c0            test    eax,eax
00595639 7406            je      awp+0x195641 (00595641)
0059563b 8b4350          mov     eax,dword ptr [ebx+50h]

Dump out the contents of pointer that was dereferenced.

0:000> dc @ebx+50 L8
0beda790  000000f0 00000000 00000000 00000000  ................
0beda7a0  00000000 00000000 00000000 00000000  ................

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 a pointer to a TField.

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.