首页
社区
课程
招聘
[原创]CVE-2018-8353漏洞分析笔记
发表于: 2020-1-9 15:28 13591

[原创]CVE-2018-8353漏洞分析笔记

2020-1-9 15:28
13591

脚本引擎开发者在设计GC(Garbage Collect,简称GC)时追踪指针不善导致的UAF(Use-After-Free)是一类常见的漏洞,这类问题在主流脚本引擎中都比较常见,本文用一个例子来向读者介绍这类漏洞的成因与分析思路。

CVE-2018-8353是谷歌的Ivan Fratric发现的一个jscript漏洞,该漏洞在20188月被修复。这是一个UAF漏洞,Ivan Fratric披露页清晰地描述了该漏洞的成因:

There is a use-after-free vulnerability in jscript.dll related to how the lastIndex property of a RegExp object is handled. This vulnerability can be exploited through Internet Explorer or potentially through WPAD over local network. The vulnerability has been reproduced on multiple Windows versions with the most recent patches applied.

The issue is that lastIndex property of a RegExp object is not tracked by the garbage collector. If you look at RegExpObj::LastIndex you'll see that, on x64, lastIndex gets stored in a VAR at offset 272 (at least in my version), but if you take a look at RegExpObj::ScavengeCore (which gets called by the garbage collector to track various member variables) you'll notice that that offset is not being tracked. This allows an attacker to set the lastIndex property, and after the garbage collector gets trigger, the corresponding variable is going to get freed.

通俗一点说就是RegExp类的lastIndex成员没有被加入GC追踪列表,如果给它赋值,在GC时会导致lastIndex处存储的指针变为悬垂指针。后续再访问lastIndex时,即造成一个典型的Use-After-Free场景。

jscript模块目前已发现多个类似漏洞,例如CVE-2017-11793CVE-2017-11903CVE-2018-0866CVE-2018-0935CVE-2018-8353CVE-2018-8653CVE-2018-8389CVE-2019-1429

本文试图通过CVE-2018-8353一窥这类漏洞的成因,并在此基础上分析谷歌PoC中的信息泄露利用代码。读者将会看到一个GC导致的UAF如何被转化为高质量的信息泄露漏洞。

以下为Ivan Fratric给出的PoC,下一小节将通过该PoC分析漏洞成因。

@0Patch团队已通过补丁分析发现,x86lastIndex位于RegExpObj对象的+A8偏移处,如下:

现在RegExpObj::Create函数内下断点,在RegExpObj对象创建完成后,对其偏移+A8处下一个硬件写入断点,这个偏移处存储一个VAR结构体,此结构体在x86下大小为0x10。重点观察+B0处的数据变化。

为了更清晰地解释成因,笔者并没有开启页堆,但开启了用户模式下堆申请的栈回溯,以下为调试日志:

到这里已经获得一个非常好的UAF,接下来的问题是:如何使用它?

从调试日志中可以看出,用来存储VAR变量的内存块是从GcBlockFactory::PblkAlloc申请的,x86下其申请大小固定为0x648(这篇文章有解释为什么x86下这个大小是0x648):

如果要重用被释放的内存,得在GC后迅速用大小为0x648的内存申请去占用之。如何做到?

一个比较好的方法是借助NameListjscript对象在创建成员变量时,如果成员变量的名称过长(谷歌的文章中说这个长度阈值为4),会在NameList::FCreateVval函数内单独申请内存,以存储对应的成员变量,并且会以第一个成员名称的长度去申请特定大小的内存,而相关计算公式是固定的。

通过逆向调试,可以得到x86下的计算公式:

现在,令alloc_size=0x648,解上述方程,可得到x=0x178(0n376)。于是可以通过下面的代码重用被释放的内存:

在调试器中观察验证重用:

前一小节已经在合适的时机控制了被Free的内存,接下来要通过这个UAF漏洞实现信息泄露,以得到被重用内存的起始地址。

NameList::FCreateVval函数内在申请成员变量名内存时,若成员名长度超过一定值,就会额外申请内存去存储这些名称。第一个成员名可以用来控制申请的内存大小,相关计算过程已经在前面说明。后面的成员名称只要长度合适,就可以在第一个成员名称初始化时申请的内存中使用剩余的部分,从而用来布控内存。

x86环境下,通过逆向NameList::FCreateVval函数,发现每个成员名称前面会额外留0x30大小的空间作为头部,用于初始化各种数据。每次成员名称进行申请时,还会按照下图的计算公式按4字节对齐并保存与返回相关偏移:

整个计算公式比较复杂,但设计思路很简单,笔者在这里描述一遍,读者只要有一些大致思路即可:x86下,第一个成员名初始化时,先申请(2x+0x32)*2+4的内存大小,得到内存后,最前面的4字节有自己的用途,接下来的0x30作为头部使用,用来初始化各种数据,包括本次字符串长度,指向下一个成员名头的指针(这个指针会后面的成员名初始化时被更新)。所以从前面的调试日志也可以看到,第一个成员名从+0x34开始被复制。只要第一次申请的内存空间够,第二个成员名会接着从LABEL_7这里按照base+4+offset的方式进行内存地址获取,然后前0x30又是头部,接着再开始复制,以此类推。

接下来是泄露被重用内存的首地址。

由于被重用的内容之前存储着lastIndex引用的VAR数据,所以只要用长度及内容合适的字符串设计类成员名称,就可以控制指定地址处的VAR结构。

从这里开始笔者使用Ivan Fratric在附件中给出的infoleak.html代码,为便于展示,去除了部分注释:

name1用来申请大小为0x648的内存。name2可调节,用来对齐。name3用来指定类型,以泄露特定偏移处的一个指针,这个后面再会提及。name4用来布控0x1337对应的VAR,用于jscript代码中的条件判断。

上面的小节中只关心了name1,现在开始来具体设计name4,name3,name2

首先得计算垂悬指针指向的VAR结构在被重用内存的偏移值。Ivan Fratric的适配的是x64的版本,原poc在笔者的环境中运行后0x1337对应的i为十进制的115

x64x86的原理一致,笔者以x86的版本进行说明。既然x64环境中对应的i115x32环境中笔者也以115为例进行偏移计算。在上述代码中在第115RegExpObj对象创建时下断点,相关方法在前面UAF小结已经描述,这个偏移很容易计算得到。

笔者的环境中这个偏移每次固定为0x3d8,如下:

现在来设计name,在每个成员名称初始化时,都会有0x30的头部,在这个头部的+0x24处是一个指针(这个指针要到初始化下一个成员名时才会被初始化),指向下一个变量名的0x30头部,下图中字体为红色的即为这些指针。如果能读取其中一个指针,减去其相对内存起始地址的偏移,就可以得到被重用内存的首地址。

下图中字体颜色为橙黄的是被拷贝的成员名称,每个名称最后会多拷贝两个0x00。字体颜色为蓝色的是每个成员名称的实际长度(unicode)。字体颜色为红色上面已经进行解释。字体背景为灰色的一个个0x30内存区域为name2、name3、name4三个成员名的头部。

字体背景为黄色高亮的区域,实验时发现会与name3的值相同(意思就是给33,给55)。后面需要借助这个值来读取它后面偏移8字节的一个红色指针。

因为要泄露某个红色指针,所以x86下必须保证这个红色指针之前8字节处的typelong型,这可以通过设计name3来实现。现在的问题是:VAR与某个特定的lastIndex对应起来?

幸运的是,通过调试观察发现,当连续申请VAR结构时,一个个大小为0x10VAR似乎是从高内存往低内存次第排列。笔者用下图来通俗地解释一下VAR的分布(name2b的数量被用来调节这里的对齐):

所以,在x86下,如果找到了0x1337对应的regexps[i].lastIndex,就可以通过读取regexps[i+5].lastIndex来泄露相关指针,减去固定偏移就得到被重用内存的起始地址了。如下:

到这里已经将这个UAF漏洞转为了信息泄露,泄露出一块(aaa...部分)完全可控的内存的首地址。如果读者之前看过笔者之前的一篇文章,就会明白这里已经将CVE-2018-8353转换为和CVE-2017-11906具有相同功能的信息泄露漏洞。

此类信息泄露漏洞与其他堆溢出漏洞一起使用可以实现RCE。笔者将这个漏洞的利用代码稍加改动,并配合CVE-2017-11907一起使用,可以在未打补丁的机器上实现RCE

考虑到CVE-2018-8653CVE-2019-1429这类在野0day的利用方式,应该是用了更高级的利用手法,通过UAF直接实现了任意地址读写,通过单个UAF即可实现RCE,并不需要其他漏洞进行辅助。

这类漏洞后面一定还会出现,请大家做好防范工作。

Issue 1587: Windows: use-after-free in JScript in RegExp.lastIndex
Garbage Collection Internals of JScript

感谢ty1337指出NameList::FCreateVval函数逆向部分表述的一处错误,原文已修正

 
 
 
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-7-27 15:10 被银雁冰编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (7)
雪    币:
活跃值: (94)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
Mark
2020-1-9 21:19
0
雪    币: 508
活跃值: (2383)
能力值: ( LV12,RANK:356 )
在线值:
发帖
回帖
粉丝
3
跟着大佬的脚步学习
2020-1-10 09:56
0
雪    币: 32805
活跃值: (64261)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
2020-1-11 15:55
0
雪    币: 5317
活跃值: (3393)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
5
支持,调试此类漏洞比较捉急的是WinDbg和IDA经常下不到符号。
2020-1-15 16:25
0
雪    币: 192
活跃值: (136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6

逆向NameList::FCreateVval函数,图片上的解释是存在错误的,计算v12的+4是因为申请的内存的第一个dword会指向上一次申请内存的地址,offset_next是向后(扩大)4字节对齐。

最后于 2020-7-27 09:38 被ty1337编辑 ,原因: 增加图片
2020-7-26 20:48
0
雪    币: 9662
活跃值: (4603)
能力值: ( LV15,RANK:800 )
在线值:
发帖
回帖
粉丝
7
ty1337 逆向NameList::FCreateVval函数,图片上的解释是存在错误的,计算v12的+4是因为申请的内存的第一个dword会指向上一次申请内存的地址,offset_next是向后(扩大)4字节对 ...

我检查了一下,你是对的,~(~(v10 + 3) | 3语句的作用是按4字节补齐,感谢指出错误。不过我在win7/win10 x86下都调试确认了一下,从代码设计来看,namelist+0xC的地方会保存当前申请得到的内存基址,下次会接着取出NameList对象这个地方的指针。然而每次初始化第一个成员时,进入NameList::FCreateVval函数传入的都是一个新的NameList对象,其+0xC处为0,所以每次malloc得到地址并初始化后,起始4字节都为0,在这份利用代码中并不会出现一个指向上次申请地址的指针。

最后于 2020-7-27 15:04 被银雁冰编辑 ,原因:
2020-7-27 14:19
0
雪    币: 192
活跃值: (136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
银雁冰 ty1337 逆向NameList::FCreateVval函数,图片上的解释是存在错误的,计算v12的+4是因为申请的内存的第一个dword会指向上一次申请内存 ...

嗯,利用代码中确实不存在,可以用下面这段代码来观察

	var name1 = Array(377).join('a');
	var name2 = 'bbbbbbbbbbb';
	var obj = {};
	alert('1');
	obj[name2] = 1;
	obj[name1] = 1;
2020-7-27 19:57
0
游客
登录 | 注册 方可回帖
返回