CVE-2018-3999
An exploitable stack-based buffer overflow vulnerability exists in the JPEG parser of Atlantis Word Processor, version 3.2.5.0. A specially crafted image embedded within a document can cause a length to be miscalculated and underflow. This length is then treated as unsigned and then used in a copying operation. Due to the length underflow, the application will then write outside the bounds of a stack buffer, resulting in a buffer overflow. 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-121: Stack-based Buffer Overflow
Atlantis’ Word Processor is a traditional word processor that is aimed to be both portable, flexible and contains a number of features. This word processor 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 and even has a similar interface. 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 processing a Microsoft Word XML Document, the application has the ability to embed various images within the document. There are a number of image types and these are handled by a class named TDocPicture
. When constructing a TDocPicture
class, the following function will get executed. This function will first check the header of the file to see if it matches a known signature for the PNG, GIF, or JFIF image formats. If the signature is not detected, then the application will determine the actual image type by checking the file extension embedded within the document against a global list. When checking the header, the application will will first check for a PNG signature via a call to @CompareMem
[1]. The application will then check if the signature matches either a GIF or a PNG file. After checking for these signatures, the final check will look for the JFIF signature which is used to identify that the image is in JPEG format. Once identified as a JPEG, at [2] the application will assign an enumeration to a register and then pass it as an argument to [3].
awp+0x1e6820:
005e6820 55 push ebp
005e6821 8bec mov ebp,esp
005e6823 83c4d0 add esp,0FFFFFFD0h
005e6826 53 push ebx
005e6827 56 push esi
005e6828 57 push edi
...
005e68bf 8b15340e6700 mov edx,dword ptr [awp+0x270e34 (00670e34)] // PNG Signature (8 bytes)
005e68c5 8d45e0 lea eax,[ebp-20h]
005e68c8 b908000000 mov ecx,8
005e68cd e8ee4be2ff call awp+0xb4c0 (0040b4c0) // [1] @CompareMem
005e68d2 84c0 test al,al
005e68d4 7404 je awp+0x1e68da (005e68da)
...
005e68da 807de047 cmp byte ptr [ebp-20h],47h // 'G'
005e68de 7510 jne awp+0x1e68f0 (005e68f0)
005e68e0 807de149 cmp byte ptr [ebp-1Fh],49h // 'I'
005e68e4 750a jne awp+0x1e68f0 (005e68f0)
005e68e6 807de246 cmp byte ptr [ebp-1Eh],46h // 'F'
005e68ea 7504 jne awp+0x1e68f0 (005e68f0)
005e68ec b304 mov bl,4
005e68ee eb32 jmp awp+0x1e6922 (005e6922) // Found GIF Signature
...
005e68f0 807de64a cmp byte ptr [ebp-1Ah],4Ah // 'J'
005e68f4 7512 jne awp+0x1e6908 (005e6908)
005e68f6 807de746 cmp byte ptr [ebp-19h],46h // 'F'
005e68fa 750c jne awp+0x1e6908 (005e6908)
005e68fc 807de849 cmp byte ptr [ebp-18h],49h // 'I'
005e6900 7506 jne awp+0x1e6908 (005e6908)
005e6902 807de946 cmp byte ptr [ebp-17h],46h // 'F'
005e6906 7418 je awp+0x1e6920 (005e6920)
...
005e6920 b303 mov bl,3 // [2] Found JFIF Signature
005e6922 8b45dc mov eax,dword ptr [ebp-24h]
005e6925 e8bebfe1ff call awp+0x28e8 (004028e8) // TObject::Free
005e692a 33c0 xor eax,eax
005e692c 8945dc mov dword ptr [ebp-24h],eax
...
005e6941 55 push ebp
005e6942 8bd3 mov edx,ebx // Image type enumeration
005e6944 8bc6 mov eax,esi
005e6946 e80dfeffff call awp+0x1e6758 (005e6758) // [3]
005e694b 59 pop ecx
005e694c 84c0 test al,al
005e694e 7438 je awp+0x1e6988 (005e6988)
The application will assign the image type enumeration to a local variable at [4] inside the function call at 0x5e6758. Later at [5], the application will pass the image type enumeration as an argument to a function call. This function is responsible for constructing an instance of the class specific to the detected image file format. At [6], the application will enter a case statement and will then branch to the case that instantiates the constructor for the image format type specified by the enumeration.
awp+0x1e6758:
005e6758 55 push ebp
005e6759 8bec mov ebp,esp
005e675b 51 push ecx
005e675c 53 push ebx
005e675d 56 push esi
005e675e 8855ff mov byte ptr [ebp-1],dl // [4] Image type enumeration
005e6761 8bf0 mov esi,eax
...
005e67e2 55 push ebp
005e67e3 8a55ff mov dl,byte ptr [ebp-1] // Image type enumeration
005e67e6 8bc6 mov eax,esi
005e67e8 e88bfaffff call awp+0x1e6278 (005e6278) // [5] \
005e67ed 59 pop ecx
005e67ee 8845fe mov byte ptr [ebp-2],al
005e67f1 807dfe00 cmp byte ptr [ebp-2],0
005e67f5 7521 jne awp+0x1e6818 (005e6818)
\
awp+0x1e6278:
005e6278 55 push ebp
005e6279 8bec mov ebp,esp
005e627b 83c4e4 add esp,0FFFFFFE4h
005e627e 53 push ebx
005e627f 56 push esi
005e6280 57 push edi
005e6281 33c9 xor ecx,ecx
005e6283 894de8 mov dword ptr [ebp-18h],ecx
...
005e62a4 33c0 xor eax,eax
005e62a6 8ac2 mov al,dl // [6]
005e62a8 83f809 cmp eax,9
005e62ab 0f879c030000 ja awp+0x1e664d (005e664d)
005e62b1 ff2485b8625e00 jmp dword ptr awp+0x1e62b8 (005e62b8)[eax*4]
005e62b8 e062 loopne awp+0x1e631c (005e631c)
The following case will be branched to when handling a JPEG. This handler will first construct an instance of a TJPEGImage
object at [7] and then store it to a register. Immediately afterward, a method will be called at [8] with the path to the actual image file. This method is a wrapper which will instantiate a TFileStream
object and then proceed to use the object to load the image file for usage by the TDocPicture
object.
awp+0x1e649d:
005e649d b201 mov dl,1
005e649f a150904100 mov eax,dword ptr [awp+0x19050 (00419050)] // TJPEGImage
005e64a4 e8f72fe3ff call awp+0x194a0 (004194a0) // [7]
005e64a9 8bd8 mov ebx,eax // register containing TJPEGImage instance
005e64ab 8b45e4 mov eax,dword ptr [ebp-1Ch]
005e64ae 89584c mov dword ptr [eax+4Ch],ebx
005e64b1 33c0 xor eax,eax
...
005e64b3 55 push ebp
005e64b4 6810655e00 push offset awp+0x1e6510 (005e6510)
005e64b9 64ff30 push dword ptr fs:[eax]
005e64bc 648920 mov dword ptr fs:[eax],esp
005e64bf 8b4508 mov eax,dword ptr [ebp+8] // frame
005e64c2 8b4008 mov eax,dword ptr [eax+8] // caller frame
005e64c5 8b50fc mov edx,dword ptr [eax-4] // path to image file
005e64c8 8bc3 mov eax,ebx // register containing TJPEGImage instance
005e64ca 8b08 mov ecx,dword ptr [eax]
005e64cc ff5138 call dword ptr [ecx+38h] // [8]
Inside this method, the following code will be executed to wrap the loading of the image. The method will first construct a TFileStream
object at [9] and then pass it as an argument to a method specific to each file format parser. The object’s method is called at [10] and is actually responsible for reading the image file contents.
awp+0x1187c:
0041187c 55 push ebp
0041187d 8bec mov ebp,esp
0041187f 51 push ecx
00411880 53 push ebx
00411881 8bd8 mov ebx,eax
00411883 6a20 push 20h
00411885 8bca mov ecx,edx
00411887 a1b86b4000 mov eax,dword ptr [awp+0x6bb8 (00406bb8)] // TFileStream
0041188c b201 mov dl,1
0041188e e8b97dffff call awp+0x964c (0040964c) // [9]
00411893 8945fc mov dword ptr [ebp-4],eax // TFileStream variable
...
004118a4 8b55fc mov edx,dword ptr [ebp-4]
004118a7 8bc3 mov eax,ebx
004118a9 8b08 mov ecx,dword ptr [eax]
004118ab ff5140 call dword ptr [ecx+40h] // [10] TJPEGImage::LoadFromStream
The function TJPEGImage::LoadFromStream
is simply a wrapper around the lower-level JPEG parser. This function will call a couple of functions and then call the function responsible for initializing global JPEG parsing structures at [11]. Inside this function, a TJPEGData
object will be constructed followed by constructing a TMemoryStream
object. Eventually, the function call at [12] will be made. This function allocates space for a global structure that contains function pointers which will be used during JPEG decoding. Once this is complete, the call at [13] will be made, which will immediately make another function call at [14] which will actually process the markers inside a JPEG image.
awp+0x234b8:
004234b8 53 push ebx
004234b9 56 push esi
004234ba 8bda mov ebx,edx
004234bc 8bf0 mov esi,eax
004234be 8bc3 mov eax,ebx
004234c0 e80760feff call awp+0x94cc (004094cc) // TStream::GetSize
004234c5 50 push eax
004234c6 8bc3 mov eax,ebx
004234c8 e8e35ffeff call awp+0x94b0 (004094b0) // TStream::GetPosition
004234cd 5a pop edx
004234ce 2bd0 sub edx,eax
004234d0 8bcb mov ecx,ebx
004234d2 8bc6 mov eax,esi
004234d4 e873000000 call awp+0x2354c (0042354c) // [11] \
004234d9 5e pop esi
004234da 5b pop ebx
004234db c3 ret
\
awp+0x2354c:
0042354c 55 push ebp
0042354d 8bec mov ebp,esp
0042354f 83c4f8 add esp,0FFFFFFF8h
00423552 53 push ebx
00423553 56 push esi
00423554 57 push edi
00423555 894df8 mov dword ptr [ebp-8],ecx
00423558 8bf2 mov esi,edx
0042355a 8945fc mov dword ptr [ebp-4],eax
...
00423598 e8bb74ffff call awp+0x1aa58 (0041aa58) // [12] Allocate global structure containing function pointers
...
004235bd b001 mov al,1
004235bf e87076ffff call awp+0x1ac34 (0041ac34) // [13] \
\
awp+0x1ac34:
0041ac34 e887ffffff call awp+0x1abc0 (0041abc0) // [14]
0041ac39 8bd0 mov edx,eax
The JPEG parser utilized by the application retains the current JPEG marker used for decoding in a global. This is first checked at [15] to determine whether the current state is the “JPG” marker (FF C8). If it’s not, then the application will continue executing and then do some arithmetic at [16] to see if the marker is “SOF9” (FF C9). The function will read the global structure containing the different function pointers that was described previously and then dispatch into the first one similar to to the call at [17] when handling each of these markers. This first function pointer will look at the current JPEG state and then finally call into a function at [18] to process each specific marker.
awp+0x1abc0:
0041abc0 53 push ebx
0041abc1 813d64226700c8000000 cmp dword ptr [awp+0x272264 (00672264)],0C8h // [15]
0041abcb 750f jne awp+0x1abdc (0041abdc)
...
0041abcd e8aefdffff call awp+0x1a980 (0041a980) // handle FFC8 marker
0041abd2 c70564226700c9000000 mov dword ptr [awp+0x272264 (00672264)],0C9h
...
0041abdc a164226700 mov eax,dword ptr [awp+0x272264 (00672264)] // check current marker
0041abe1 0538ffffff add eax,0FFFFFF38h // [16] -0xC8
0041abe6 83e802 sub eax,2
0041abe9 720d jb awp+0x1abf8 (0041abf8)
...
0041abf8 8b1db0236700 mov ebx,dword ptr [awp+0x2723b0 (006723b0)] // global structure containing function pointers
0041abfe ff13 call dword ptr [ebx] // [17] \
0041ac00 8bd8 mov ebx,eax
\
awp+0x1a900:
0041a900 53 push ebx
0041a901 56 push esi
0041a902 8b1db0236700 mov ebx,dword ptr [awp+0x2723b0 (006723b0)] // global structure containing function pointers and jpeg state
0041a908 807b0d00 cmp byte ptr [ebx+0Dh],0
0041a90c 7408 je awp+0x1a916 (0041a916)
...
0041a916 e81df8ffff call awp+0x1a138 (0041a138) // [18] process specific markers
0041a91b 8bf0 mov esi,eax
When processing specific markers, the following code will be executed. This code will perform a number of comparisons in order to narrow down on which function will be used to handle the different markers that can be encountered when parsing a JFIF stream, such as embedded within a JPEG image file format. At [19], application will use an index to dispatch into the handler for the currently parsed “APPx” marker.
awp+0x1a138:
0041a138 53 push ebx
0041a139 56 push esi
0041a13a bb5c226700 mov ebx,offset awp+0x27225c (0067225c) // JFIF stream marker handlers
0041a13f 83bb4001000000 cmp dword ptr [ebx+140h],0
0041a146 7517 jne awp+0x1a15f (0041a15f)
...
0041a15f 8bb340010000 mov esi,dword ptr [ebx+140h]
0041a165 8bc6 mov eax,esi
0041a167 3dd8000000 cmp eax,0D8h // "SOI" marker
0041a16c 7f61 jg awp+0x1a1cf (0041a1cf)
...
0041a1cf 3ddc000000 cmp eax,0DCh // "DNL" marker
0041a1d4 7f20 jg awp+0x1a1f6 (0041a1f6)
...
0041a1f6 2ddd000000 sub eax,0DDh // "DRI" marker
0041a1fb 0f848d000000 je awp+0x1a28e (0041a28e)
0041a201 83c0fd add eax,0FFFFFFFDh
0041a204 83e810 sub eax,10h
0041a207 0f8288000000 jb awp+0x1a295 (0041a295)
...
0041a295 8b8358010000 mov eax,dword ptr [ebx+158h] // "APPx" marker
0041a29b 8bb4b090fcffff mov esi,dword ptr [eax+esi*4-370h]
0041a2a2 ffd6 call esi // [19]
0041a2a4 eb17 jmp awp+0x1a2bd (0041a2bd)
This will then result in the following code being executed by the application to handle the different “APPx” marker types. The first 16 bits after an “APPx” marker will contain the length of the marker. At [20], the application will read the first eight bits, and then assign it to the high byte of a variable that will be referred to as the count
. At [21], the next byte will be read and it will be added to the current value of the count
variable. This composes the 16-bit word, which will be used as the length of the structure following the marker. At this point, the application will perform a signed comparison to see if the count is less than the value of zero. If it is less than or equal to zero, then the block at [22] will be executed which will explicitly set the value of count
to zero.
awp+0x1a448:
0041a448 53 push ebx
0041a449 56 push esi
0041a44a 57 push edi
0041a44b 55 push ebp
0041a44c 83c4e4 add esp,0FFFFFFE4h
0041a44f 8b2d68226700 mov ebp,dword ptr [awp+0x272268 (00672268)] // JPEG structure
0041a455 8b7d00 mov edi,dword ptr [ebp] // JPEG file data
0041a458 8b7504 mov esi,dword ptr [ebp+4] // JPEG leftover size
0041a45b 85f6 test esi,esi
0041a45d 750b jne awp+0x1a46a (0041a46a)
...
0041a46a 4e dec esi
0041a46b 33c0 xor eax,eax
0041a46d 8a07 mov al,byte ptr [edi] // [20] read 8-bits
0041a46f c1e008 shl eax,8 // shift it to the high byte
0041a472 890424 mov dword ptr [esp],eax // count variable
0041a475 47 inc edi
0041a476 85f6 test esi,esi
0041a478 750b jne awp+0x1a485 (0041a485)
...
0041a485 4e dec esi
0041a486 33c0 xor eax,eax
0041a488 8a07 mov al,byte ptr [edi] // [21] read 8-bits
0041a48a 010424 add dword ptr [esp],eax // binary OR it into the low byte of the count variable
0041a48d 47 inc edi
0041a48e 832c2402 sub dword ptr [esp],2 // subtract 2 for length
0041a492 833c240e cmp dword ptr [esp],0Eh
0041a496 7c0a jl awp+0x1a4a2 (0041a4a2) // branch if less than 0xe
...
0041a4a2 833c2400 cmp dword ptr [esp],0 // check if count...
0041a4a6 7e09 jle awp+0x1a4b1 (0041a4b1) // is <= 0
...
0041a4b1 33c0 xor eax,eax // [22] zero out the count
0041a4b3 89442404 mov dword ptr [esp+4],eax
Finally, the application will use the count
variable as a terminator in a loop. At [23], the application will decrease the count by one and then immediately increment it by one when entering the loop. If the 16-bit word following any of the “APPx” markers is zero, this will result in the following loop continuously iterating until the next time the count
variable is zero. At [24], the application does a simple byte-for-byte copy by reading a byte from the %edi
register, and writing it back to the %ebx
register. The decrement for each iteration of the loop is done at [25]. Due to this loop being unbounded and writing into a local buffer, this can result in a stack-based buffer overflow which can lead to code execution under the context of the application. It is also prudent to note, that if this 16-bit length is larger than 0x20 then the stack-based buffer overflow will also occur due to the buffer having a maximum size of 0x20 bytes before overwriting the function’s frame pointer.
awp+0x1a4b7:
0041a4b7 8b442404 mov eax,dword ptr [esp+4] // [23]
0041a4bb 48 dec eax
0041a4bc 85c0 test eax,eax
0041a4be 7225 jb awp+0x1a4e5 (0041a4e5)
0041a4c0 40 inc eax
0041a4c1 89442408 mov dword ptr [esp+8],eax
...
0041a4c5 8d5c240c lea ebx,[esp+0Ch] // destination buffer on the stack
...
awp+0x1a4c9:
0041a4c9 85f6 test esi,esi
0041a4cb 750b jne awp+0x1a4d8 (0041a4d8)
0041a4cd e8e2f0ffff call awp+0x195b4 (004195b4)
0041a4d2 8b7d00 mov edi,dword ptr [ebp] // image data
0041a4d5 8b7504 mov esi,dword ptr [ebp+4] // leftover image size
0041a4d8 4e dec esi
...
0041a4d9 8a07 mov al,byte ptr [edi] // read a byte from the file
0041a4db 8803 mov byte ptr [ebx],al // [24] write into buffer
0041a4dd 47 inc edi
0041a4de 43 inc ebx
0041a4df ff4c2408 dec dword ptr [esp+8] // [25] decrease count variable
0041a4e3 75e4 jne awp+0x1a4c9 (0041a4c9)
(568.7d4): 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=00190000 ecx=000000e0 edx=0b190008 esi=000022d0 edi=0b1915ee
eip=0041a4db esp=0018ea10 ebp=0b18b194 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+0x1a4db:
0041a4db 8803 mov byte ptr [ebx],al ds:002b:00190000=41
0:000> u . L5
awp+0x1a4db:
0041a4db 8803 mov byte ptr [ebx],al
0041a4dd 47 inc edi
0041a4de 43 inc ebx
0041a4df ff4c2408 dec dword ptr [esp+8]
0041a4e3 75e4 jne awp+0x1a4c9 (0041a4c9)
0:000> ? dwo(@ebp+8)
Evaluate expression: 186060684 = 0b170f8c
0:000> ? @ebx-@esp
Evaluate expression: 5616 = 000015f0
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.