在分析本次jscript
组合漏洞之前,先跟随笔者回顾一下近几年的IE 0day
,这里重点关注脚本引擎(vbscript/jscript/jscript9
)相关的0day
:
接着跟随笔者一起看一下vbscript
漏洞与利用相关的一些时间点:
2014年08月,TK在BlackHat USA上做了题为《Write Once, Pwn Anywhere》的演讲,里面提出了一些IE浏览器利用的崭新思路(里面主要以jscript9为例);
2014年11月更新修复了yuange珍藏多年的CVE-2014-6332,于是yuange发布了他写的CVE-2014-6332的利用,公布了一种之前没有用过的利用方法(yuange称为DVE);
2016年05月,CVE-2016-0189这个vbscript 0day被修复,根据后续获得的细节,原始0day里面用到了TK文章里的利用编写方法。2016年06月国外安全公司Theori公开了逆向2016年05月补丁后写出的CVE-2016-0189利用代码,从此这份代码被广泛用于挂马;
2017年02月,CVE-2017-0149这个vbscript 0day被用于实际攻击,微软在2017年03月修补了这个漏洞。在360于2018年底公布这个漏洞的相关细节前,这个漏洞基本上不为人知。根据后续获得的细节,这个漏洞的利用编写方式,除了新增部分Bypass CFG的代码,其手法和CVE-2016-0189的原始利用完全一致;
2018年04月,微软的补丁中修复了一个vbscript模块的远程代码执行漏洞(CVE-2018-1004),如果读者看过ZDI的《IT’S TIME TO TERMINATE THE TERMINATOR》这篇文章,就知道CVE-2018-1004已经涉及到vbscript的|Class_Terminate|回调。此时如果有一个手握类似0day的卖家,知道|Class_Terminate|回调函数即将引起微软的注意,一定非常急于出手,因为他知道这类0day距离被修补不远了;
2018年04月下旬,CVE-2018-8174这个vbscript 0day被用在实际攻击中,这个漏洞和CVE-2018-1004非常相似,同样涉及到|Class_Terminate|回调。相关利用手法和前两个vbscript完全一致,但这次没有Bypass CFG部分的代码,说明攻击目标可能是Windows 7操作系统;
2018年07月补丁后,CVE-2018-8373这个vbscript 0day被用于实际攻击,微软在2018年08月更新中修复了该漏洞。这个漏洞的利用代码同时借助vbscript和jscript9的语言特性来实现相关功能,并且用覆盖栈返回地址的方式Bypass CFG,说明利用编写着对vbscript和jscript9两个脚本引擎的内部实现非常熟悉;
2019年08月,微软进一步在win7/win8上禁用了IE11中的vbscript
再跟随笔者看一下jscript
相关漏洞的一些时间点:
2017年12月,微软修复了多个谷歌报告的jscript漏洞,随后谷歌发布了一篇长文《aPAColypse now: Exploiting Windows 10 in a Local Network with WPAD/PAC and JScript》,详细介绍了Windows 10下利用WPAD协议,借助jscript漏洞进行攻击的方式。作者在文中对此类攻击方式表示担忧,并预测jscript模块中还有其他类似漏洞;
2018年12月,微软修复了一个jscript远程代码执行漏洞CVE-2018-8653,这个漏洞的攻击方式采用的正是一年前谷歌文章中的方法,这次攻击的出现令原作者的担忧一语成谶;
2019年08月,微软进一步在win7/win8上禁用了IE11中的vbscript,此时如果有一个专业的脚本引擎漏洞发现者/写手/卖家,一定会将开发重心进一步切换到jscript;
2019年09月,微软又修复了一个jscript 0day,编号为CVE-2019-1367;从有限的披露信息《Patch Lady – what’s the real risk?》中(文末有参考链接),我们可以知道这个漏洞是通过网页挂马的方式进行攻击的,发送给受害者的是内嵌url的docx文档,并且后续操作也涉及到了WPAD;
2019年11月,微软再次修复一个jscript 0day,编号为CVE-2019-1429,这个漏洞同时被谷歌的Ivan Fratric独立报告给微软,网上目前已有该漏洞的PoC;
根据上述趋势,只要微软不禁用jscript
,接下来还会有jscript
的在野0day
出现。vbscript
和jscript
是微软早期开发的两个脚本引擎,所以安全性问题较多。与vbscript
漏洞的利用细节已经被广泛公开不同,关于jscript
漏洞利用的手法,除了谷歌的上述文章,网上基本没有更多细节,所以jscript
漏洞的调试目前仍然很难上手。
站在防御者的角度,我们必须预测攻击趋势,并且在攻击者实施下一步攻击之前进行有效防御。从这个出发点考虑,我们必须对jscript
系列漏洞的利用方式有所了解。这一步骤完成后,当后面再出现jscript
漏洞时,对漏洞原因/利用方式的分析的时间成本也都会降低。所以,选择一个jscript
漏洞进行一番研究是非常有意义的。
基于上述考虑,笔者最近对《aPAColypse now: Exploiting Windows 10 in a Local Network with WPAD/PAC and JScript》这篇文章中涉及到的漏洞进行了详细调试(谷歌慷慨地公开了相关exploit
代码,不过只针对win10 64位
),根据文章中给出的步骤一步一步在32位
环境上复现了RCE
。在这个过程中同时参考了安恒信息安全研究院另一篇分析文章 (以下称这篇文章为“之前的分析文章”)。
整套利用代码涉及两个漏洞,一个信息泄露漏洞(CVE-2017-11906
)和一个堆溢出漏洞(CVE-2017-11907
),这是一个组合漏洞利用链,整个利用链的编写需要注意许多细节,下面先跟随笔者来详细看一下这两个漏洞,然后讨论这个jscript
漏洞利用链编写中需要注意的地方。
这是jscript
的一个信息泄露漏洞。利用代码中需要借助该漏洞泄露一块稳定的内存地址,用来存储伪造的Jscript Object Memory
结构。我们先来看一下该漏洞的官方PoC
:
我这里直接引用之前的分析文章中的话来描述这个漏洞:
“这个信息泄露漏洞存在于RegExp.lastParen
中,在调用任一RegExp.test、RegExp.exec
或者String.search
后,jscript!RegExpFncObj
中会用数组保存group
信息,这些信息实际上是匹配到的内容的开始位置和结束位置。由于用来保存group
信息的数组大小固定为20*4
(32位
程序中),也就是一共有10
组group
信息,所以当匹配到的group数
量大于10
时,RegExp.lastParen
过大的下标会导致取值时发生越界读操作。数组中保存的起始位置和结束位置能够提供更大的可读空间,所以可以通过控制数组中的值来控制越界读的范围。”
在这个漏洞的具体使用中,我们主要关注的是RegExp.lastParen
函数末尾的一处memcpy
,如下:
memcpy
有3个参数:Dst
,Src
,Size
。当PoC
中的Array(x)
的x
大小满足一定条件时,Dst
对应调用RegExp.lastParen
返回给我们的字符串,Src
为被search
的字符串(假设为str
)首地址,Size
完全可控,可以通过设置RegExp.input
的值去控制Size
,所以只要我们设置的RegExp.input
超出str.length
,即可实现越界读取。关于32位
下Array(X)
内x
的取值,之前的分析文章中已经指出:
“观察jscript!RegExpFncObj
可以发现,如果用31
个空括号组成的RegExp
去搜索,同时在搜索结束后设置RegExp.input
为任意整数,那么RegExp.lastParen
的起始值为0
而结束值则为RegExp.input
设置的整数。”
上述结论在笔者的测试环境中得到了验证。
知道上述关键点后,读者就知道上述漏洞的利用实质是控制memcpy
的相关参数,从而越界读取被search
的字符串后紧邻的数据。由于非LFH
堆的堆块在释放后,头部会存储一些指向下一个堆块的指针。如果我们可以在非LFH
堆中连续申请许多等长字符串,然后释放其中的一半(奇数保留,偶数释放),从而造成大量交替的内存空洞。随后对某个奇数索引对应的字符串进行search
,借助这个信息泄露漏洞,就可以越界读取相邻的处于free
状态的堆块里存储的另一个被free
的堆块指针。读取相应指针后,再立即用设计好的字符串重用之前被释放的字符串。这样泄露出来的指针就指向一块已经被控的内存。
这部分的代码最终如下:
现在跟随笔者打开一次调试器,利用上面的js
代码,我们在上述memcpy
处下一个断点,看一下这3
个参数在调试器中的情况:
读者已经知道,0x624d518
处这块内存被重用后,从第4
字节开始才存储实际字符串内容(因为有长度域和2
个0x00
字节),所以需要加4
,接下来我们要做的就是用精心伪造的数据重新占用0x624d518
这块内存:
上述过程图示如下:
到这里,信息泄露部分的利用就大功告成了。我们通过这个信息泄露漏洞得到了straddress
这块内存,并且里面的数据稳定可控。
这是jscript
中的一个堆溢出漏洞。导致这个漏洞的根源是toString
回调可以打破Array.sort
过程中的“原子操作”。这里也直接用之前的分析文章的话来描述这个漏洞:
“在处理如下(1)
处的调用时,如果Array.sort
输入数组的长度大于Array.length/2
,jscript.dll
会逐个将数组arr
中的元素转化为String
。转化的第一步是分配作为存储转化后String
变量的内存空间,而分配的大小由当前arr
数组中存在的元素的个数决定。转化的第二步是从0
到Array.length
逐个数组转换元素。转换过程中如果元素存在,那么元素会被转化为String
,并写入第一步分配的内存空间中。由于arr[0]
的toString
回调方法的存在,在转换arr[0]
时,(2)
标示位置的代码会被执行,(2)
代码执行后arr
数组中实际存在的元素个数会增加,而第一步分配的内存不会重新分配,于是之后的转换过程中便会导致堆溢出。”
上述过程存在于jscript!JsArrayStringHeapSort
和jscript!JsArrayFunctionHeapSort
两个函数中。开发者在设计这个转换过程,理所应当地认为这里申请内存和紧随的复制是原子操作,不会被打破,但潜在的toString
回调打破了这一原子操作,导致了漏洞的产生。
jscript
这种堆溢出的漏洞如何进行利用呢?谷歌的大牛们给出的思路是利用越界写改写某个jscript
对象的Hashtable
中的相关表项,使之导向之前信息泄露得到的内存中伪造的数据,在此基础上实现任意地址读写。
要构造出任意地址读写原语,我们必须对相关结构在内存中的分布情况,以及分配对象时的堆内存分配情况非常清楚,这些内存相关的问题包括但不限于如下:
这些细节在谷歌的文章和之前的分析文章中都已经有过细节描述。不过谷歌的文章和公开的相关代码侧重于win10 x64
下借助WPAD
进行攻击的细节,相关细节并不能拿来在32位
浏览器利用中直接使用。而之前的分析文章已经将上述1、2、3、5、8
这几个问题描述清楚。所以笔者下面先简要通过上述文献引用1、2、3、5、6、8
的部分细节,随后对4、7
这几点进行补充说明。
1 - VAR结构,jscript
中代表变量的VAR
和vbscript
基本一致,32位
下大小为0x10
,结构如下:
2 - 直接引用之前的分析文章中的一张图来解释之:
3 - 见上图的Object Memory
4 - 后面单独解释
5 - 我们首先要了解jscript!JsArrayStringHeapSort
函数中分配内存时给每一个Array
元素分配空间的大小及内存结构。32位
下,每一个Array
中的元素都会在jscript!JsArrayStringHeapSort
临时分配的内存中(以下将这块内存称为Sort Buffer
)对应一个0x20
的结构,具体结构如下:
再引用之前的分析文章中的一张图进行说明(重点为红色部分):
如果Sort Buffer
和Hashtable
可以被分配在相邻的内存,那么通过堆溢出漏洞就可以实现覆写Hashtable
,并且覆写的数据可以在自定义的toString
回调中进行操控。由于32位
下,每个jscript
对象在初始化时,其Hashtable
的初始大小为0x200
,当对象的元素个数超过512
个时,Hashtable
的大小会被扩大到0x1000
。因为Sort Buffer
的大小为|0x20 * (used_arr_size + 1)|
,所以我们可以定义一个长度足够的数组arr
,初始化其中的前127(used_arr_size)
个元素,就可以使jscript!JsArrayStringHeapSort
中分配的Sort Buffer
大小为0x1000
。
由于低碎片堆中对特定大小的分配存在更大概率的线性关联,所以,如果我们先开启0x1000
的LFH
堆,然后先申请一部分jscript
对象,将其中一部分对象的的元素个数扩大到513
个,这会从LFH
中申请一部分0x1000
大小的内存。随后触发漏洞,往jscript!JsArrayStringHeapSort
传入特定初始化大小的数组arr
(并将其第1
个元素设置为有自定义toString
回调的对象),在jscript!JsArrayStringHeapSort
内部,会继续从LFH
中为Sort Buffer
申请一块大小为0x1000
的内存,随后进入自定义的toString
函数。
在toString
中,先立即将另一部分jscript
对象的元素个数扩大到513
个,此次会再从LFH
中申请一部分0x1000
大小的内存。因为这些内存的申请都位于LFH
内的0x1000
大小桶中,所以Sort Buffer
有很大的概率被0x1000
大小的Hashtable
“左右夹击”着。在toString
的后半部分,对arr
数组剩下的元素进行赋值,并将arr
最后一个元素设置为也具有自定义toString
回调函数的对象,便于后阶段的利用编写。
这样,当代码继续往下执行,已被扩大的arr
中的元素被挨个复制到Sort Buffer
这块内存时,超过|used_arr_size + 1|
那部分的元素就会进行溢出,只要精心控制arr
数组的元素,就可以实现精确覆盖相邻的Hashtable
上的指针。
6 - 已在5
中解释
7 - 后面单独解释
8 - 之前的分析文章中已对这里的细节进行详细描述:
“ 为了达到此目的需要构造5
个假的jScript
成员对象:var1
只包含数据0x1337
,作为寻找被覆盖对象的标识。var2
的type
为0x400c
,表明这是个对象是指针,且偏移8
的位置保存着指向实际对象的指针。设置偏移8
的指针值设置为var1
所在内存位置之前的12 byte
,使得var2
的最后4 byte
和var1
的前4 byte
重合。var3、var4、var5
都是整型,但是最后4 byte
分别为3、8、0x400c
。”
下面来讨论一些谷歌的分析文章和之前的分析文章中没有描述清楚的细节(也就是上述的4,7
两点)。
由前面的分析已知:为了实现精确覆写,我们需要激活LFH
中0x1000
大小的桶,那么如何激活呢?谷歌给出的64位exploit
中是借助如下代码开启0x2000
的LFH
桶:
但是如果我们水平有限,无法确定32位
下lfharrsize
的大小(给jscript!JsFncApply
下断点进行调试会断下非常多的次数,笔者在IDA
中也无法直接看出里面申请内存的计算公式),该如何激活0x1000
大小的LFH
桶呢?一种可行的方式就是先预先创建一些jscript
对象(对象的数量必须超过低碎片堆的激活阈值),并为其增加513
个成员变量,然后随即将这些对象释放,这样就有一批0x1000
大小的Hashtable
被申请和释放:
那么,我们能不能通过申请内存大小为0x1000
的BSTR
字符串来开启LFH
。在笔者的实验环境下是不行的,谷歌的文章中也有类似的提示:
“A String VAR points directly to the character array, which means that, to obtain a String's length, the pointer needs to be decremented by 4 and the length read from there. Note that BSTRs are handled by OleAut32.dll and are allocated on a separate heap (i.e. a different heap than is being used for other JScript objects).
Freeing of BSTRs is also different than for most objects because, instead of directly freeing a BSTR, when SysFreeString is called, it first puts a string in a cache controlled by OleAut32.dll. This mechanism is described in detail in Heap Feng Shui in JavaScript.”
利用上述可行的技巧,我们在内存中看一下LFH
中0x1000
大小的桶在每个阶段的具体分布:
1 - 激活0x1000
大小的LFH
桶,并初始化“左相邻”的0x1000
大小Hashtable
后:
2 - 在jscript!JsArrayStringHeapSort
函数中根据sort
的数组大小申请0x1000
的Sort Buffer
后:
3 - 在arr[0]
的tostring
回调中继续给另一部分jscript
对象增加第513
个成员后:
4 - 触发堆溢出,精确覆写右相邻的Hashtable
通过上述描述,以及在调试器对溢出细节的观察,读者应能比较清晰地了解这个堆溢出漏洞利用的关键细节。
如何构造假的Object Memory
才能使Hashtable
的查找工作保持正常?这个问题其实是比较简单的,只要了解32位
下Object Memory
的结构,就可以精确构造出对应的Fake Object Memory
。在写利用的过程中,通过观察谷歌的exploit
,笔者一开始的几个疑问是:
第1
个问题很简单,了解32位
下Object Memory
的内存结构后就没有任何难度了,改写一下原exploit
的fakeVAR
函数即可。
第2
个问题一开始着实把笔者为难了一番。后来通过观察谷歌的exploit
,笔者推测arrindex
应该是被溢出的arr
的index
,objindex
应该是Hashtable
中原来表项对应Object Memory
中存储的值(即我们在js
代码中初始化jscript
成员对象的值),hash
应该是Hashtable
中原来表项对应Object Memory
中的hash
。而且,多次观察发现,每次实现“左-中-右”的内存相邻后,右相邻的Hashtable
中固定位置存储的指针指向的Object Memory
的value
和hash
都是固定的,这更加验证了前面的猜测。
于是笔者按照上述思路构造了当前调试环境中合适的arrindex,objindex,hash
后,就可以通过查找jscript
对象的成员找到被改写了Hashtable
的jscript
对象了。
以下为笔者某次观察得到的值:
需要注意的是,如果观察到将要覆写的地方的指针为0
,那需要跳过它,接着找下一个符合条件的覆写点,一共找到5
个满足条件的arrindex
即可。至于Fake Object Memory
中的ptr to next memory
域,这个域的伪造一开始着实把笔者为难了一番,后来笔者观察谷歌的exploit
,发现谷歌直接忽略了这个指针,于是笔者实验了一番,发现忽略这个指针并不影响利用代码的执行。
上述细节在调试器中全部观察确认后,即意味着我们已经具备任意地址读取和任意地址写入(由于VAR
为16
字节,所以和vbscript
一样,为受限写)能力,后续的操作就是常规操作了。谷歌的exploit
已经提供了64位
下整套操作函数,笔者稍加改动即完成了这些功能函数在32位
下的移植。
如果要让编写的利用在win10
上也能工作,就需要绕过CFG
。绕过CFG
的最常见思路就是覆写栈上的返回地址。一番调试之后(之前的分析文章里面其实也已经指出了相关偏移),笔者发现jscript
对象在内存中的相关结构如下:
所以只需通过任意地址读取原语拿到pNativeStack
,然后在Native
栈中进行搜索,找到合适的返回地址进行覆写即可。之前的分析文章中也指出:
“由于栈空间中存在关键性数据,一旦改变可能在函数没有返回前产生崩溃,所以在修改栈空间时需要避免修改这些数据。”
所以返回地址也不是随便哪个函数都可以改的,需要仔细观察栈,不断尝试才能找到合适的返回地址进行覆写。
笔者最终覆写的是jscript!CScriptRuntime::Run
函数的返回地址:
经过笔者实验,上述漏洞触发和利用方式无需改动即可从win7 x86
上移植到win10 x86
,但win7
和win10
的Native
栈上对jscript!CScriptRuntime::Run
返回地址的偏移不太一样,读者编写时需要在对应的操作系统上稍做调整。
比较遗憾的是,上述这种简单的覆写方式虽然可以让计算器正常弹出,但弹出计算器后仍会造成崩溃。因为笔者研究这个利用链的目的只是为了写出概念验证的exploit
,所以如何让利用代码结束得优雅一点并不在本次目的内,关于如何更优雅地完成利用,就交给读者自己去完成了。
也许读者认为IE
已经是日薄西山,IE
的漏洞已经不能用来造成广泛的杀伤力。但是谷歌于2017
年底提出的WPAD
的攻击方式,还有2018
年出现的CVE-2018-8174
的0day
攻击方式(Moniker
远程加载vbscript
漏洞),都预示着IE
组件漏洞有着更广泛的触发场景。虽然在2018
年连续的两个vbscript 0day
出现后,微软已经将vbscript
加入了office killbit 的黑名单。但读者很容易注意到黑名单里面没有jscript
和jscript9
。
如果现在有一个jscript/jscript9 0day
,通过Moniker
特性远程进行加载,会执行成功吗?笔者利用此次jscript
漏洞在未打最新补丁的环境上进行了概念验证,发现在win10 + office2016
默认配置下是可以借助Moniker
加载jscript
漏洞并弹出计算器的,如下:
当然,笔者并未在最新win10
全补丁环境上进行这类实验,也许最新版本的win10+office365
上此类攻击已经无法进行,不过也许还可以。
笔者可以预见到的是,只要office
可以用来加载js
漏洞,那office
一定会成为脚本漏洞攻击的重灾区。相关信息安全建设人员如能意识到这个问题,现在就应该着手禁用office
加载远程js
文件这种功能。最简单的就是通过注册表把jscript/jscript9
对应的CLSID
(考虑到之前已有的绕过案例,这类CLSID
应该不止一个)加入killbit
黑名单。
aPAColypse now: Exploiting Windows 10 in a Local Network with WPAD/PAC and JScript
利用WPAD/PAC与JScript实现Windows 10远程代码执行
Issue 1382: Windows: out-of-bounds read in jscript!RegExpFncObj::LastParen
Issue 1383: Windows: heap overflow in jscript.dll in Array.sort
Write Once, Pwn Anywhere
IT’S TIME TO TERMINATE THE TERMINATOR
Patch Lady – what’s the real risk?
Security Settings for COM objects in Office
CVE-2014-6332公开exploit分析
CVE-2016-0189在野exploit分析
CVE-2017-0149在野exploit分析
CVE-2018-8174在野exploit分析
CVE-2018-8373在野exploit分析
CVE-2018-8653在野exploit分析
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!
最后于 2020-7-27 15:15
被银雁冰编辑
,原因: