对Exploit Windows 10 in Local Network with WPAD/PAC and JScript 的分析

Project Zero团队在google发表了一篇关于利用WPAD/PAC和JScript来实现对Windows10的代码执行。

链接如下:https://googleprojectzero.blogspot.com/2017/12/apacolypse-now-exploiting-windows-10-in_18.html

360网络安全响应中心也发布了这个报告并翻译了这篇文章。虽然有着文章的翻译,但是对该文的讲述的内容还是让人感觉云里雾里,为此我做了这个分析,不过对我来说文章还是有难度的,所以错误的地方还请指证。

文章的Exploit部分主要分为5个阶段,主要是说明前两个阶段,这也是我要讨论的。

 

整个漏洞的利用流程

使用一个漏洞去泄露一个可控制的内存地址,之后对另一个漏洞的利用形式进行改装来制造一个溢出,最后控制EIP,提升权限,利用ROP等技术来实现代码执行。

 

介绍一下什么是WPAD/PAC

PAC(Proxy Auto-Config)文件定义了浏览器和其他用户如何自动选择适当的代理服务器来访问一个·URL。WPAD可以借助DNS服务或DHCP服务来查询PAC文件的位置。

PAC实际上是一个至少定义了一个JavaScript函数的文件,这也是漏洞利用可行的地方。

 

阶段一

是要泄露一个可控的堆内存地址,这里利用了RegExp.lastParen这个漏洞,POC如下:

var r= new RegExp(Array(100).join('()'));

''.search(r);

alert(RegExp.lastParen);

这个崩溃的发生是因为RegExp.lastPare的访问越界,原因是在JScript RegExp对象偏移0xAC处的RegExpFncObj里有一个缓冲区保存了十对整数,这十对整数表示每次正则匹配的一次索引(start_index和end_index)。但是这里只有10对,一但正则超过10对匹配并且RegExp.lastParen被调用的时候,RegExp.lastParen会使用这个数组的数量作为索引来访问这个缓冲区,造成越界访问。

另外通过RegExp.input设置为整数和定义一个由41个括号所组成的正则,可以使RegExP.lastParen调用时的start_index变为0和end_index变为RegExp.input里的整数值。

整个泄露地址的流程如下:

分配1000 10000-character的字符串(10000 characters == 20000bytes)这样做的目的是为了防止所分配的字符串不会放入LFH里面,LFH接收小于16KB的内存分配,LFH里面块的分配是随机的,这样会影响到地址的定位。)

把分配的每第二个字符串释放掉(这样做防止被释放的堆块合并)

触发这个漏洞

泄露被释放的字符串的地址(得到堆的metadate)

再次分配字符串,确保泄露的指针指向一个字符串(这里分配的内容会在阶段2使用特别的东西进行填充)

 

阶段二

 

使用了另外一个漏洞,这个漏洞是在Array.sort里面,POC如下:

<!-- saved from url=(0014)about:internet -->

<meta http-equiv="X-UA-Compatible" content="IE=8"></meta>

<script language="Jscript.Encode">

var vars = new Array(100);



var arr = new Array(1000);

for(var i=1;i<600;i++) arr[i] = i;



var o = {toString:function() {

for(var i=600;i<1000;i++) {

arr[i] = 1337;

}

}}



function go() {

arr[0] = o;

Array.prototype.sort.call(arr);

}



go();

</script>

Array.sort会调用JsArrayStringHeapSort函数,这个函数会根据输入的数组的元素数量分配两个等长的临时数组,但是在这样这个情况下,当toString()函数回调后,数组的长度增加,这样就发生了溢出。后面作者提出可以通过覆盖Hash表来改进这个漏洞,使其更好达到利用的目的。

在JsArrayStringHeapSort期间,检索下标小于数组长度的每个元素,如果该元素被定义,则会发生下列的操作:

  1. 数组元素将被读进一个VAR并保存在偏移为16的临时数组元素结构中。
  2. 原始的VA被转换为一个string VAR中,指向这个string VAR的指针被写到偏移为0的地方
  3. 偏移为8处,该元素的下标被写入
  4. 偏移40处的值取决于VAR的类型,为0或1

临时数组元素结构体

作者在这部分想找到几个可以利用的指针,我们先来看一下VAR和String VAR的结构可以帮助我们更容易理解这部分的内容:

这里要注意的是偏移为0处是定义的VAR的变量类型,3对应整形,5对应双精度浮点数、8对应字符串

VAR结构

String VAR结构

在产生的临时数组中,如果数组成员是一个字符串,那么结构中偏移为0和8的地方将会有一个指针,当解引用的时候我们可以控制偏移为8处的这个指针。而且如果该数组成员是一个双精度的VAR的话,那么偏移为24,实际上是临时数组偏移为16的地方存储着这个VAR,而VAR偏移为8的地方是一个double VAR的一个实际的双精度的值,那么这个地方则直接在我们控制之下。

剩下就是用一个方法来改写这个漏洞,使这个漏洞得到利用,作者通过研究对象在JScript中的工作原理找到了这个方法。下列这张表说明了JScript中的对象,被调用时的索引形式。

每个对象(更具体的的说,使NameList的JScript对象)都有一个指向Hash表的指针,这个hash表是一个指针数组,每当一个对象的元素成员被访问的时候,该元素名称的hash被计算,然后一个hash最低位的一个指针解引用,该指针指向该对象元素成员的链表,这个链表被遍历,直到找到具有相同名字的元素。hash表介绍完了,下面说一下溢出的具体流程。

  1. 分配和释放大小为8192的内存块,这样会打开LFH来分配8192的内存。这样做使确保溢出所制作的内存块可以分配在LFH中,因为LFH只能分配对应大小的块(<16KB)所以在一定程度上可以保证不会有其它的内存块来影响溢出过程。(这里来做一个计算:LFH分配小于16KB的内存块  16 * 1024 = 16384  16384 / 2 = 8192)
  2. 创建2000个对象,每个对象包含512个成员,这样每个对象都有一个1024字节的hash表,只要像其中一个hash表添加一个元素,该表就会增长到8192个字节。
  3. 将其一组(前1000个对象)对象的成员都添加到513个,这样就会导致1000个8192字节的hash表被分配。
  4. 使用长度为300,包含170个元素的数组触发Array.sort漏洞。JsArrayStringHeapSort函数会分配(170 + 1) * 48(一个VAR对象占24字节,分配两个数组) = 8208bytes。由于LFH的规则,这个对象表将被分配在与8192字节的hash表相同的LFH bucket中。
  5. 将第二组(1000个对象)的成员添加到513个(在toString()方法中实现),确保排序的缓冲区和hash表相邻。随后toString()会向数组里添加更多的元素,使其溢出。

这样的方式制作的溢出会使一个JScript对象的hash表指针被我们控制的数据指针所覆盖,下面使覆盖改指针的具体细节:

  1. 变量1值只包含数字1337。
  2. 变量2使特殊类型的0x400c。这个类型告诉JavaScript实际的VAR是偏移为8的指针指向的,在读取或写入这个变量之前,这个指针会被解引用。在我们的例子中,这个指针指向变量1的前16个字节(也就是说可以修改变量1的类型和值,回忆一下VAR的结构),这实际上意味着变量2的8字节与变量1的8字节重合。
  3. 变量3、变量4、变量5是简单的整数,特殊之处在于最后的8字节的内容为6,8,0x400c。

(变量2的作用是用来修改变量1的类型,可以通过读取变量3,4然后将读取的值写入变量2     :

Cooruptedobject[index2] = corruptedobject[index4]

这样就可以通过将变量1的类型设置成一个字符串,其余的不变就可以读出其内容)

我们可以通过正确的索引(index)来访问被修改的变量1,而且可以通过查看所有对象的变量1来查找哪个对象被破坏,并查看哪个对象拥有特征值1337。

这些东西给了我们几个漏洞利用的方向步骤:

  1. 如果将一个包含指针的变量写入变量1,那么我们可以通过变量2将其类型改变为string,便可以读出该指针的值。
  2. 我们可以通过一个伪造的字符串来泄露一个可以读内存地址上的值,首先将我们想要读取的地址对应的double(VAR)写入变量1,然后通过更改变量1的类型来读取此地址
  3. 或者我可以把一个地址对应的数值写入变量1,然后把变量1的类型更改为1个指针,从而达到写入任意地址。

那么到这里一个POC的流程应该是

首先通过RegExp.lastParen漏洞泄露一个地址,然后该地址作为后续分配的2000个对象的第一个元素的的值,然后触发Array.sort()漏洞,溢出改写变量1的类型,将EIP转到泄露的可控地址,从而达到代码执行。

 

阶段三

这个阶段讲的是CFG的绕过。

简单介绍一下什么是CFG,CFG和我们经常遇到的ASLR和DEP等一样是微软在in Windows 10 and in Windows 8.1 Update 3里推出的新的机制,简单来说就是从汇编层面上保护直接调用函数。

直接调用的地址必须指向一个有效的函数,否则会抛出异常。

更多的CFG绕过方式可以参考本文后提供的链接。这里主要讲文章里的方法。

JScript中有很多简便的方法可以绕过CFG,实际情况是有的返回地址并没有受到CFG的保护,另外JScript中有的对象就拥有指向栈的指针。特别的每一个NameTbl对象(在JavaScript中,所有JScript对象都是从NameTal继承),该对象偏移24的位置保存一个指向CScssion对象的指针。CSession对象偏移80的位置保存着指向靠近栈顶的指针。

因此可以通过任何JScript对象的指针链在可读的情况下来取到一个栈的指针,然后通过任意写入可以覆盖返回地址从而绕过CFG。

 

阶段四

准备好所有攻击的条件,就进入代码执行的准备阶段:

  1. 从任意JScript对象的虚表中读取jscript.dll的地址。
  2. 通过读取jscript.dll中的导入表获取kernel32.dll的地址
  3. 通过读取kernel32.dll中的导入表获取kernelbase.dll的地址
  4. 在kernelbase.dll中寻找我们需要的rop gadgets
  5. 在kernel32.dll的导出表中获取WinExec函数的地址
  6. 根据上文泄露栈地址
  7. 准备好ROP并写入栈中,用最接近我们泄露的栈地址的返回地址来执行ROP

我们将使用的ROP链看起来是这个样子的:

 

[address of RET]  //needed to align the stack to 16 bytes

[address of POP RCX; RET] //loads the first parameter into rcx

[address of command to execute]

[address of POP RDX; RET] //loads the second parameter into rdx

[address of WinExec]

这样子可以执行WinExec调用的命令,如果运行cmd命令,将弹出一个控制台窗口(和WPAD所使用的用户拥有相同的权限),剩下的就是提权的任务了。

 

阶段五

WPAD的service用户会被赋予以下权限:

注意SempersonatePrivilege,这个权限使它允许此服务获得本地系统上的其他用户的权限。并且Impersonate权限表明此服务可以接受本地系统其他用户的请求,并且可能可以代表他们执行一些操作,这样我们只要获取这些用户(包括system)的访问令牌就可以了。

这里作者还提到了另外一个提权的漏洞,原话翻译过来大致是现在的访问令牌很难被获取了,但是并不是不可能。

 

参考链接:

https://www.ibm.com/developerworks/cn/linux/1309_quwei_wpad/

https://googleprojectzero.blogspot.com/2015/06/dude-wheres-my-heap.html

https://msdn.microsoft.com/en-us/library/7ddfax0d(v=vs.100).aspx

https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals.pdf

https://illmatics.com/Understanding_the_LFH.pdf

https://bugs.chromium.org/p/project-zero/issues/detail?id=1383

https://www.blackhat.com/presentations/bh-usa-07/Sotirov/Whitepaper/bh-usa-07-sotirov-WP.pdf

http://sjc1-te-ftp.trendmicro.com/assets/wp/exploring-control-flow-guard-in-windows10.pdf

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

https://www.blackhat.com/docs/us-15/materials/us-15-Zhang-Bypass-Control-Flow-Guard-Comprehensively-wp.pdf

https://improsec.com/blog/bypassing-control-flow-guard-in-windows-10

http://theori.io/research/jscript9_typed_array

http://www.freebuf.com/articles/system/89616.html

https://www.anquanke.com/post/id/85351

http://www.freebuf.com/articles/system/131032.html

https://segmentfault.com/a/1190000007692754

http://blog.csdn.net/duan19920101/article/details/51579136

https://paper.seebug.org/papers/Security%20Conf/Blackhat/2017_asia/asia-17-Li-Cross-The-Wall-Bypass-All-Modern-Mitigations-Of-Microsoft-Edge.pdf

https://github.com/MortenSchenk/RtlCaptureContext-CFG-Bypass

https://googleprojectzero.blogspot.com/2017/12/apacolypse-now-exploiting-windows-10-in_18.html

 

(完)