记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

CVE-2017-0038:GDI32越界读漏洞从分析到Exploit

2017-03-24 14:20

CVE-2017-0038:GDI32越界读漏洞从分析到Exploit。在这篇文章中,我将首先对CVE-2017-0038这个GDI32.dll的Out-of-bound Read漏洞进行分析;随后,我将首先将分享用JS编写一个浏览器可用的Exploit,随后我将和大家一起来看一下这么做的一个限制(为什么无法用浏览器进行Info leak),也是在0patch文章中提到的0xFF3333FF到底是怎么回事;然后,我将和大家分享用C语言完成Exploit,一起来看看真正泄露的内存内容;最后我将把JS和C的Exploit放在github上和大家分享。

调试环境是:

Windows 10 x86_64 build 10240

IE 11

GDI32.dll Version 10.0.10240.16384

\

请大家多多交流,感谢阅读!

CVE-2017-0038 PoC与漏洞分析

如我在第一章分享的测试环境的图片,同样在0patch的文章中也能看到,在浏览器每次加载poc.emf的时候,都会产生不同的图片,这张图片只有左下角的一个小红点是固定不变的,其实除了左下角四个字节,其他的内容都是泄露的内存,这点在后面的分析中我们可以获得。

那么现在我们需要解决的一个问题就是如何加载这个图片,这样我们需要用JS的画布功能来完成对PoC的构造,并且成功加载PoC.emf。

首先,我们定义一个canvas画布,之后通过js的getElementById来获得画布对象,之后我们通过Image()函数来初始化image对象,加载poc.emf,最后,我们通过drawImage来读取poc.emf,将poc.emf打印在画布上。drawImage后两个参数是image在canvas画布上的坐标,这里我们设置为0,0。

\

完成构造后,我们稍微修改一下poc.emf的文件结构,然后打开IE11浏览器,通过Windbg附加进程,并且在gdi32!MRSETDIBITSTODEVICE::bPlay函数位置下断点,这个过程会在poc.emf映射在画布上时发生,允许js执行之后,Windbg命中函数入口。

0:022> x gdi32!MRSETDIBITSTODEVICE::bPlay

00007ff8`2a378730 GDI32!MRSETDIBITSTODEVICE::bPlay =

0:022> bp gdi32!MRSETDIBITSTODEVICE::bPlay

Breakpoint 0 hit

GDI32!MRSETDIBITSTODEVICE::bPlay:

00007ff8`2a378730 48895c2408 mov qword ptr [rsp+8],rbx ss:00000034`93cef6f0={GDI32!MRMETAFILE::bPlay (00007ff8`2a320950)}

0:027> kb

RetAddr:ArgstoChild: Call Site

00007ff8`2a2ff592 : 00007ff8`2a320950 00007ff8`2a2f8ed1 00000000`00000008 ffffffff`8f010c40 : GDI32!MRSETDIBITSTODEVICE::bPlay//到达目标断点

00007ff8`2a2ff0a8 : 0000002c`8f00be8c 00000000`00000000 00000000`00000000 00000000`00000000 : GDI32!PlayEnhMetaFileRecord+0xa2

00007ff8`2a327106 : 00000034`92acee00 00007ff8`2a2ff8f3 00000034`90380680 00000034`93cef988 : GDI32!bInternalPlayEMF+0x858

00007ff8`08650a70 : 00007ff8`08061010 00007ff8`08061010 00000034`93cefa10 00000000`000000ff : GDI32!PlayEnhMetaFile+0x26//关键函数调用PlayEnhMetaFile

现在我们开始单步跟踪这个关键函数的执行流程,之后我放出整个函数的伪代码,相应的注释,我已经写在//后面,首先单步执行,会到达一处参数赋值,在64位系统中,参数是靠寄存器传递的。

0:027> p

GDI32!MRSETDIBITSTODEVICE::bPlay+0x1b:

00007ff8`2a37874b 488bd9 mov rbx,rcx

0:027> r rcx

rcx=0000002c8f00be8c

0:027> dt vaultcli!EMRSETDIBITSTODEVICE 0000002c8f00be8c

+0x000 emr : tagEMR

+0x008 rclBounds : _RECTL

+0x018 xDest : 0n0

+0x01c yDest : 0n0

+0x020 xSrc : 0n0

+0x024 ySrc : 0n0

+0x028 cxSrc : 0n1

+0x02c cySrc : 0n1

+0x030 offBmiSrc : 0x4c

+0x034 cbBmiSrc : 0x28

+0x038 offBitsSrc : 0x74

+0x03c cbBitsSrc : 4

+0x040 iUsageSrc : 0

+0x044 iStartScan : 0

+0x048 cScans : 0x10

这里rcx传递的指针是MRSETDIBITSTODEVICE::bplay的第一个参数,这个参数是一个非常非常非常重要的结构体EMRSETDIBITSTODEVICE,正是对这个结构体中几个成员变量的控制没有进行严格的判断,从而导致了越界读漏洞的发生。

首先我们来看一下EMF文件格式。

\

这里,我对poc.emf进行了修改,修改了cxSrc和cySrc的值,这样在最后向HDC拷贝图像的时候,就不会读取多余的内存信息,这个结构体的变量我们要记录,因为接下来在跟踪函数内部逻辑的时候,会涉及到很多关于这个结构体成员变量的偏移,关于这个结构体变量的解释,可以参照MSDN。

https://msdn.microsoft.com/en-us/library/windows/desktop/dd162580(v=vs.85).aspx

接下来我们继续单步跟踪,首先函数会命中一个叫做pvClientObjGet的函数,这个函数会根据Handle获取EMF头部section的一个标识并对标识进行判断。

0:027> p

GDI32!pvClientObjGet+0x2a:

00007ff8`2a301d5a 488d0daf141400 lea rcx,[GDI32!aplHash (00007ff8`2a443210)]

0:027> p

GDI32!pvClientObjGet+0x31:

00007ff8`2a301d61 83e07f and eax,7Fh

0:027> p

GDI32!pvClientObjGet+0x34:

00007ff8`2a301d64 48833cc100 cmp qword ptr [rcx+rax*8],0 ds:00007ff8`2a443218=0000003492a60080//获取特殊handle object

0:027> p

GDI32!pvClientObjGet+0x39:

00007ff8`2a301d69 488d3cc1 lea rdi,[rcx+rax*8]

0:027> p

GDI32!pvClientObjGet+0x70://获得当前EMF头部section标识 ENHMETA_SIGNATURE

00007ff8`2a301da0 488b4718 mov rax,qword ptr [rdi+18h] ds:00000034`92a60098=0000003492ac5280

0:026> r rax

rax=00000296033d3d30

0:026> dc 296033d3d30 l1

00000296`033d3d30 0000464d MF..

可以看到,最后函数会读取一个名为MF的标识,这个标识就是EMF文件的头部。这里会识别的就是ENHMETA_SIGNATURE结构。

\

返回之后继续单步跟踪,接下来会命中bCheckRecord函数,这个函数主要负责的就是检查EMRSETDIBITSTODEVICE中成员变量的一些信息是否符合要求。

0:027> p

GDI32!MRSETDIBITSTODEVICE::bPlay+0x3f:

00007ff8`2a37876f 498bd6 mov rdx,r14

0:027> p

GDI32!MRSETDIBITSTODEVICE::bPlay+0x42:

00007ff8`2a378772 488bcb mov rcx,rbx

0:027> p

GDI32!MRSETDIBITSTODEVICE::bPlay+0x45:

00007ff8`2a378775 e8a6dbffff call GDI32!MRSETDIBITSTODEVICE::bCheckRecord (00007ff8`2a376320)

0:027> r rcx

rcx=0000002c8f00be8c//检查vaultcli!EMRSETDIBITSTODEVICE结构

可以看到,EMRSETDIBITSTODEVICE结构保存在rcx中,会作为第一个参数传入函数,接下来跟入函数中。

0:027> p//接下来检查tagEMR的nSize

GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x6:

00007ff8`2a376326 448b4104 mov r8d,dword ptr [rcx+4] ds:0000002c`8f00be90=00000078

0:027> dt tagEMR 0000002c8f00be8c

vaultcli!tagEMR

+0x000 iType : 0x50

+0x004 nSize : 0x78

0:026> p

gdi32full!MRSETDIBITSTODEVICE::bCheckRecord+0x2a:

00007ffd`cf88a56a 39442430 cmp dword ptr [rsp+30h],eax ss:000000bb`06b9f650=00000078

0:027> p//与0x78比较检查nSize

GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x6:

00007ff8`2a376326 448b4104 mov r8d,dword ptr [rcx+4] ds:0000002c`8f00be90=00000078

……

0:027> p

GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x16://获得vaultcli!EMRSETDIBITSTODEVICE的cbBmiSrc成员变量值

00007ff8`2a376336 8b4934 mov ecx,dword ptr [rcx+34h] ds:0000002c`8f00bec0=00000028

0:027> p

GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x19:

00007ff8`2a376339 bab0ffffff mov edx,0FFFFFFB0h

0:027> p

GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x1e://与0x0FFFFFFB0作比较,检查cbBmiSrc的上限

00007ff8`2a37633e 3bca cmp ecx,edx

0:027> p

GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x20:

00007ff8`2a376340 7343 jae GDI32!MRSETDIBITSTODEVICE::bCheckRecord+0x65 (00007ff8`2a376385) [br=0]

因为代码片段较长,这里我列举了一些片段,主要就是对结构体中的一些成员变量进行检查,比如头部的tagEMR,会检查tagEMR中的nSize,后续还会检查cbBmiSrc(BitmapInfo大小)等等。

随后继续单步跟踪,会到达bClipped这个函数,这个函数的主要功能就是对EMRSETDIBITSTODEVICE结构体偏移0x8位置的成员,也就是_RECTL进行检查,_RECTL主要是负责这个图像的上下左右边界。

0:026> p//传递rbx+8地址值,是个_RECTL对象

gdi32full!MRSETDIBITSTODEVICE::bPlay+0x4e:

00007ffd`cf88dfae 488d5308 lea rdx,[rbx+8]

0:026> p

gdi32full!MRSETDIBITSTODEVICE::bPlay+0x52:

00007ffd`cf88dfb2 488bcd mov rcx,rbp

0:026> p

gdi32full!MRSETDIBITSTODEVICE::bPlay+0x55:

00007ffd`cf88dfb5 e822caffff call gdi32full!MF::bClipped (00007ffd`cf88a9dc)

0:026> r rdx

rdx=0000029606a4a0e4

0:026> dt _RECTL 0000029606a4a0e4

vaultcli!_RECTL

+0x000 left : 0n0

+0x004 top : 0n0

+0x008 right : 0n15

+0x00c bottom : 0n15

0:027> r rcx

rcx=0000003492ac5280

0:027> dt _RECTL 0000003492ac5280+8c//这里偏移+8c是由于pvClientObjGet获取的对象偏移+8c存放的是比较值,具体在函数里体现

vaultcli!_RECTL

+0x000 left : 0n-1

+0x004 top : 0n-1

+0x008 right : 0n17

+0x00c bottom : 0n17

在rcx寄存器,也就是第一个参数中存放的是上下左右的界限,而第二个参数则是我们当前图像的RECTL,我们来看一下bClipped检查的伪代码。

知识来源: www.2cto.com/article/201703/617220.html

阅读:123804 | 评论:0 | 标签:exp 漏洞

想收藏或者和大家分享这篇好文章→复制链接地址

“CVE-2017-0038:GDI32越界读漏洞从分析到Exploit”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云