Headline
CVE-2018-3981: TALOS-2018-0651 || Cisco Talos Intelligence Group
An exploitable out-of-bounds write exists in the TIFF-parsing functionality of Canvas Draw version 5.0.0. An attacker can deliver a TIFF image to trigger this vulnerability and gain code execution.
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
Discovered by a member of Cisco Talos.