最新消息:图 床

分析筆記:CVE-2017-0101 整數溢出漏洞

COOL IAM 152浏览 0评论

作者:Leeqwind
作者博客:https://xiaodaozhi.com/exploit/70.html

前面的文章分析了 CVE-2016-0165 整數上溢漏洞,這篇文章繼續分析另一個同樣發生在 GDI 子系統的 CVE-2017-0101 (MS17-017) 整數向上溢出漏洞。分析的環境是 Windows 7 x86 SP1 基礎環境的虛擬機,配置 1.5GB 的內存。

0x0 前言

這篇文章分析了發生在 GDI 子系統的 CVE-2017-0101 (MS17-017) 整數向上溢出漏洞。在函數 EngRealizeBrush 中引擎模擬實現筆刷繪製時,系統根據筆刷圖案位圖的大小以及目標設備表面的像素顏色格式計算應該分配的內存大小,但是沒有進行必要的數值完整性校驗,導致可能發生潛在的整數向上溢出的問題,致使實際上分配極小的內存塊,隨後函數對分配的 ENGBRUSH 對象成員域進行初始化。在整數溢出發生的情況下,如果分配的內存塊大小小於 ENGBRUSH 類的大小,那麼在初始化成員域的時候就可能觸發緩衝區溢出漏洞,導致緊隨其後的內存塊中的數據被覆蓋。

接下來函數調用 SURFMEM::bCreateDIB 分配臨時的位圖表面對象,並在其中對數值的有效性進行再次校驗,判斷數值是否大於 0x7FFFFFFF。但在此時校驗的數值比分配的緩衝區大小數值小 0x84,因此如果實際分配的緩衝區是小於 0x40 字節的情況,那麼在函數 SURFMEM::bCreateDIB 中校驗的數值就將不符合函數 SURFMEM::bCreateDIB 的要求,導致調用失敗,函數向上返回,並在上級函數中釋放分配的 ENGBRUSH 對象。

在上級函數中在釋放先前分配 ENGBRUSH 對象時,如果先前的成員域初始化操作破壞了位於同一內存頁中的下一個內存塊的 POOL_HEADER 結構,那麼在釋放內存時將會引發 BAD_POOL_HEADER 的異常。通過巧妙的內核池內存布局,使目標 ENGBRUSH 對象的內存塊被分配在內存頁的末尾,這樣一來在釋放內存塊時將不會校驗相鄰內存塊 POOL_HEADER 結構的完整性。

利用整數向上溢出導致後續的緩衝區溢出漏洞,使函數在初始化 ENGBRUSH 對象的成員域時,將原本寫入 ENGBRUSH 對象的數據覆蓋在下一內存頁起始位置的位圖表面 SURFACE 對象中,將成員域 sizlBitmap.cy 覆蓋為 0x6 等像素位格式的枚舉值,致使目標位圖表面對象的可控範圍發生改變。通過與位於同一內存頁中緊隨其後的內核 GDI 對象或下一內存頁相同位置的位圖表面對象相互配合,實現相對或任意內存地址的讀寫。

本分析中涉及到的內核中的類或結構體可在《圖形設備接口子系統的對象解釋》文檔中找到解釋說明。

0x1 原理

漏洞存在於 win32k 內核模塊的函數 EngRealizeBrush 中。該函數屬於 GDI 子系統的服務例程,用於根據邏輯筆刷對象在目標表面對象中引擎模擬實現筆刷繪製。根據修復補丁文件對比,發現和其他整數向上溢出漏洞的修復補丁程序類似的,修復這個漏洞的補丁程序也是在函數中對某個變量的數值進行運算時,增加函數 ULongLongToULongULongAdd 調用來阻止整數向上溢出漏洞的發生,被校驗的目標變量在後續的代碼中被作為分配內存緩衝區函數 PALLOCMEM 的緩衝區大小參數。那麼接下來就從這兩個函數所服務的變量着手進行分析。

順便一提的是,補丁程序在增加校驗函數時遺漏了對 v16 + 0x40 計算語句的校驗,因此攻擊者在已安裝 CVE-2017-0101 漏洞安全更新的操作系統環境中仍舊能夠利用該函數中的整數溢出漏洞。不過那就是另外一個故事了。

補丁前後的漏洞關鍵位置代碼對比:

  v60 = (unsigned int)(v11 * v8) >> 3;
  v49 = v60 * v68;
  v12 = v60 * v68 + 0x44;
  if ( v61 )
  {
    v13 = *((_DWORD *)v61 + 8);
    v14 = *((_DWORD *)v61 + 9);
    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;
  }
  [...]
  v66 = v12 + 0x40;
  v16 = PALLOCMEM(v12 + 0x40, 'rbeG');

補丁前

  if ( ULongLongToULong((_DWORD)a3 * v10, (unsigned int)a3 * (unsigned __int64)(unsigned int)v10 >> 32, &v67) < 0 )
    goto LABEL_54;
  v67 >>= 3;
  if ( ULongLongToULong(v67 * v64, v67 * (unsigned __int64)(unsigned int)v64 >> 32, &a3) < 0
    || ULongAdd(0x44u, (unsigned __int32)a3, &v71) < 0 )
  {
    goto LABEL_54;
  }
  if ( v62 )
  {
    [...]
    v48 = v15 >> 3;
    if ( ULongLongToULong(v48 * v14, v48 * (unsigned __int64)(unsigned int)v14 >> 32, &v65) < 0
      || ULongAdd(v71, v65, &v71) < 0 )
    {
      goto LABEL_54;
    }
  }
  v16 = v71;
  [...]
  v71 = v16 + 0x40;
  v17 = PALLOCMEM(v16 + 0x40, 'rbeG');

補丁后

在 MSDN 網站存在函數 DrvRealizeBrush 的文檔說明。在 Windows 圖形子系統中,通常地 Eng 前綴函數是同名的 Drv 前綴函數的 GDI 模擬,兩者參數基本一致。根據 IDA 和其他相關文檔,獲得函數 EngRealizeBrush 的函數原型如下:

int __stdcall EngRealizeBrush(
    struct _BRUSHOBJ *pbo,       // a1
    struct _SURFOBJ *psoTarget,  // a2
    struct _SURFOBJ *psoPattern, // a3
    struct _SURFOBJ *psoMask,    // a4
    struct _XLATEOBJ *pxlo,      // a5
    unsigned __int32 iHatch      // a6
);

函數 EngRealizeBrush 的函數原型

其中的第 1 個參數 pbo 指向目標 BRUSHOBJ 筆刷對象。數據結構 BRUSHOBJ 用來描述所關聯的筆刷對象實體,在 MSDN 存在如下定義:

typedef struct _BRUSHOBJ {
  ULONG iSolidColor;
  PVOID pvRbrush;
  FLONG flColorType;
} BRUSHOBJ;

結構體 BRUSHOBJ 的定義

參數 psoTarget / psoPattern / psoMask 都是指向 SURFOBJ 類型對象的指針。結構體 SURFOBJ 的定義如下:

typedef struct tagSIZEL {
    LONG cx;
    LONG cy;
} SIZEL, *PSIZEL;

typedef struct _SURFOBJ {
    DHSURF  dhsurf;         //<[00,04] 04
    HSURF   hsurf;          //<[04,04] 05
    DHPDEV  dhpdev;         //<[08,04] 06
    HDEV    hdev;           //<[0C,04] 07
    SIZEL   sizlBitmap;     //<[10,08] 08 09
    ULONG   cjBits;         //<[18,04] 0A
    PVOID   pvBits;         //<[1C,04] 0B
    PVOID   pvScan0;        //<[20,04] 0C
    LONG    lDelta;         //<[24,04] 0D
    ULONG   iUniq;          //<[28,04] 0E
    ULONG   iBitmapFormat;  //<[2C,04] 0F
    USHORT  iType;          //<[30,02] 10
    USHORT  fjBitmap;       //<[32,02] xx
} SURFOBJ;

結構體 SURFOBJ 的定義

函數的各個關鍵參數的解釋:

  • 參數 pbo 指向存儲筆刷詳細信息的 BRUSHOBJ 對象;該指針實際上指向的是擁有更多成員變量的子類 EBRUSHOBJ 對象,除 psoTarget 之外的其他參數的值都能從該對象中獲取到。
  • 參數 psoTarget 指向將要實現筆刷的目標表面 SURFOBJ 對象;該表面可以是設備的物理表面,設備格式的位圖,或是標準格式的位圖。
  • 參數 psoPattern 指向為筆刷描述圖案的表面 SURFOBJ 對象;對於柵格化的設備來說,該參數是位圖。
  • 參數 psoMask 指向為筆刷描述透明掩碼的表面 SURFOBJ 對象。
  • 參數 pxlo 指向定義圖案位圖的色彩解釋的 XLATEOBJ 對象。

根據前面的代碼片段可知,在函數 EngRealizeBrush 中存在一處 PALLOCMEM 函數調用,用於為將要實現的筆刷對象分配內存緩衝區,傳入的分配大小參數為 v12 + 0x40,變量 v12 正是在修復補丁中增加校驗函數的目標變量。

根據相關源碼對“補丁前”的代碼片段中的一些變量進行重命名:

  cjScanPat = ulSizePat * cxPatRealized >> 3;
  ulSizePat = cjScanPat * sizlPat_cy;
  ulSizeTotal = cjScanPat * sizlPat_cy + 0x44;
  if ( pSurfMsk )
  {
    sizlMsk_cx = *((_DWORD *)pSurfMsk + 8);
    sizlMsk_cy = *((_DWORD *)pSurfMsk + 9);
    cxMskRealized = 32;
    if ( sizlMsk_cx != 32 && sizlMsk_cx != 16 && sizlMsk_cx != 8 )
      cxMskRealized = (sizlMsk_cx + 63) & 0xFFFFFFE0;
    cjScanMsk = cxMskRealized >> 3;
    ulSizeTotal += (cxMskRealized >> 3) * sizlMsk_cy;
  }
  [...]
  ulSizeSet = ulSizeTotal + 0x40;
  pengbrush = (LONG)PALLOCMEM(ulSizeTotal + 0x40, 'rbeG');

對補丁前的代碼片段的變量重命名

其中變量 ulSizeTotal 對應前面的 v12 變量。分析代碼片段可知,影響 ulSizeTotal 變量值的可變因素有 sizlMsk_cx / sizlMsk_cy / ulSizePat / cxPatRealizedsizlPat_cy 變量。其中變量 sizlMsk_cxsizlMsk_cy 是參數 psoMask 指向的 SURFOBJ 對象的成員域 sizlBitmap 的值。因此還有 ulSizePat / cxPatRealizedsizlPat_cy 變量需要繼續向前回溯,以定位出在函數中能夠影響 ulSizeTotal 變量值的最上層可變因素。


可變因素

EngRealizeBrush 函數伊始,三個 SURFOBJ 指針參數被用來獲取所屬的 SURFACE 對象指針並分別放置於對應的指針變量中。SURFACE 是內核中所有 GDI 表面對象的管理對象類,類中存在結構體對象成員 SURFOBJ so 用來存儲當前 SURFACE 對象所管理的位圖實體數據的具體信息,在當前系統環境下,成員對象 SURFOBJ so 起始於 SURFACE 對象 +0x10 字節偏移的位置。

隨後,參數 psoPattern 指向的 SURFOBJ 對象的成員域 sizlBitmap 存儲的位圖高度和寬度數值被分別賦值給 sizlPat_cxsizlPat_cy 變量,並將寬度數值同時賦值給 cxPatRealized 變量。參數 psoTarget 對象的成員域 iBitmapFormat 存儲的值被賦給參數 psoPattern (編譯器導致的變量復用,本應是名為 iFormat 之類的局部變量),用於指示目標位圖 GDI 對象的像素格式。根據位圖格式規則,像素格式可選 1BPP(1) / 4BPP(2) / 8BPP(3) / 16BPP(4) / 24BPP(5) / 32BPP(6) 等枚舉值,用來指示位圖像素點的色彩範圍。

  pSurfTarg = SURFOBJ_TO_SURFACE(psoTarget);
  pSurfPat = SURFOBJ_TO_SURFACE(psoPattern);
  pSurfMsk = SURFOBJ_TO_SURFACE(psoMask);
  cxPatRealized = *((_DWORD *)pSurfPat + 8);
  psoMask = 0;
  psoPattern = (struct _SURFOBJ *)*((_DWORD *)pSurfTarg + 0xF);
  sizlPat_cy = *((_DWORD *)pSurfPat + 9);
  [...]
  sizlPat_cx = cxPatRealized;

函數 EngRealizeBrush 伊始代碼片段

函數隨後根據目標位圖 GDI 對象的像素格式,將變量 ulSizePat 賦值為格式枚舉值所代表的對應像素位數,例如 1BPP 格式的情況就賦值為 132BPP 格式的情況就賦值為 32,以此類推。

與此同時,函數根據目標位圖 GDI 對象的像素格式對變量 cxPatRealized 進行繼續賦值。根據 IDA 代碼對賦值邏輯進行整理:

  1. 當目標位圖 GDI 對象的像素格式為 1BPP 時:
    如果 sizlPat_cx 值為 32 / 16/ 8 其中之一時,變量 cxPatRealized 被賦值為 32 數值;否則變量 cxPatRealized 的值以 32 作為初始基數,加上變量 sizlPat_cx 的值並以 32 對齊。

  2. 當目標位圖 GDI 對象的像素格式為 4BPP 時:
    如果 sizlPat_cx 值為 8 時,變量 cxPatRealized 被賦值為 8 數值;否則變量 cxPatRealized 的值以 8 作為初始基數,加上變量 sizlPat_cx 的值並以 8 對齊。

  3. 當目標位圖 GDI 對象的像素格式為 8BPP / 16BPP / 24BPP 其中之一時:
    變量 cxPatRealized 的值以 4 作為初始基數,加上變量 sizlPat_cx 的值並以 4 對齊。

  4. 當目標位圖 GDI 對象的像素格式為 32BPP 時:
    變量 cxPatRealized 被直接賦值為變量 sizlPat_cx 的值。

接下來,函數將變量 cxPatRealized 的值與變量 ulSizePat 存儲的目標位圖對象的像素位數相乘並右移 3 比特位,得到圖案位圖新的掃描線的長度,並將數值存儲在 cjScanPat 變量中。

在 Windows 內核中處理位圖像素數據時,通常是以一行作為單位進行的,像素的一行被稱為掃描線,而掃描線的長度就表示的是在位圖數據中向下移動一行所需的字節數。位圖數據掃描線的長度是由位圖像素位類型和位圖寬度決定的,位圖掃描線長度和位圖高度的乘積作為該位圖像素數據緩衝區的大小。

函數隨後計算 cjScanPatsizlPat_cy 的乘積,得到新的圖案位圖像素數據大小,與 0x44 相加並將結果存儲在 ulSizeTotal 變量中。此處的 0x44ENGBRUSH 類對象的大小,將要分配的內存緩衝區頭部將存儲用來管理該筆刷實現實體的 ENGBRUSH 對象。

這裡的新的圖案位圖像素數據大小,是通過與邏輯筆刷關聯的圖案位圖對象的高度和寬度數值,和與設備關聯的目標表面對象的像素位顏色格式數值計算出來的,在函數後續為引擎模擬實現畫刷分配新的位圖表面對象時,該數值將作為新位圖表面對象的像素數據區域的大小。

接下來函數還判斷可選的參數 psoMask 是否為空;如果不為空的話,就取出 psoMask 對象的 sizlBitmap 成員的高度和寬度數值,並依據前面的像素格式為 1BPP 的情況,計算掩碼位圖掃描線的長度和掩碼位圖數據大小,並將數據大小增加進 ulSizeTotal 變量中。

在調用函數 PALLOCMEM 時,傳入的分配內存大小參數是 ulSizeTotal + 0x40,其中的 0x40ENGBRUSH 結構大小減去其最後一個成員 BYTE aj[4] 的大小,位於 ENGBRUSH 對象後面的內存區域將作為 aj 數組成員的後繼元素。函數對 ulSizeTotal 變量增加了兩次 ENGBRUSH 對象的大小,多出來的 0x44 字節在後面用作其他用途,但我並不打算去深究,因為這不重要。

在函數 PALLOCMEM 中最終將通過調用函數 ExAllocatePoolWithTag 分配類型為 0x21 的分頁會話池(Paged session pool)內存緩衝區。

內存緩衝區分配成功后,分配到的緩衝區被作為 ENGBRUSH 對象實例,並將緩衝區指針放置在 pbo 對象 +0x14 字節偏移的成員域中:

  pengbrush = (LONG)PALLOCMEM(ulSizeTotal + 0x40, 'rbeG');
  if ( !pengbrush )
  {
LABEL_43:
    HTSEMOBJ::vRelease((HTSEMOBJ *)&v70);
    return 0;
  }
LABEL_44:
  bsoMaskNull = psoMask == 0;
  *((_DWORD *)pbo + 5) = pengbrush;

分配的緩衝區地址存儲在 pbo 對象的成員域

依據以上的分析可知,在函數中能夠影響 ulSizeTotal 變量值的最上層可變因素是:

  • 參數 psoPattern 指向的圖案位圖 SURFOBJ 對象的成員域 sizlBitmap 的值
  • 參數 psoMask 指向的掩碼位圖 SURFOBJ 對象的成員域 sizlBitmap 的值
  • 參數 psoTarget 指向的目標位圖 SURFOBJ 對象的成員域 iBitmapFormat 的值

在獲得 ulSizeTotal 變量最終數值的過程中,數據進行了多次的乘法和加法運算,但是沒有進行任何的數值有效性校驗。如果對涉及到的這幾個參數成員域的值進行特殊構造,將可能使變量 ulSizeTotal 的數值發生整數溢出,該變量的值將變成遠小於應該成為的值,那麼在調用函數 PALLOCMEM 分配內存時,將會分配到非常小的內存緩衝區。分配到的緩衝區被作為 ENGBRUSH 對象實例,在後續對該 ENGBRUSH 對象的各個成員變量進行初始化時,將存在發生緩衝區溢出、造成後續的內存塊數據被覆蓋的可能性,嚴重時將導致操作系統 BSOD 的發生。

0x2 追蹤

上一章節分析了漏洞的原理和成因,接下來將尋找一條從用戶態進程到漏洞所在位置的觸發路徑。通過在 IDA 中查看函數 EngRealizeBrush 的引用列表,發現在 win32k 中僅對該函數進行了少量的引用。

Logical Brush Types
https://msdn.microsoft.com/en-us/library/windows/desktop/dd145038(v=vs.85).aspx

ICM-Enabled Bitmap Functions

https://msdn.microsoft.com/en-us/library/windows/desktop/dd144990(v=vs.85).aspx

Windows Color System

https://msdn.microsoft.com/en-us/library/windows/desktop/dd372446(v=vs.85).aspx

DrvRealizeBrush function

https://msdn.microsoft.com/en-us/library/windows/hardware/ff556273(v=vs.85).aspx

GDI Support Services

https://docs.microsoft.com/en-us/windows-hardware/drivers/display/gdi-support-services

sensepost / gdi-palettes-exp

https://github.com/sensepost/gdi-palettes-exp

GDI Support for Palettes

https://docs.microsoft.com/en-us/windows-hardware/drivers/display/gdi-support-for-palettes


转载请注明:IAMCOOL » 分析筆記:CVE-2017-0101 整數溢出漏洞

0 0 vote
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x