V8引擎1 Day漏洞分析与32位环境利用

Posted by GToad on December 3, 2019

最近根据一个1 Day信息Pwn了个国产主流旗舰手机,过程中接触了一下32位V8引擎的Exploit过程,做个笔记吧。

背景

之前的几篇关于V8引擎的漏洞利用环境都是我在x64架构的Ubuntu16.04上编译的,和32位有区别。而目前可能是为了兼容性,市面上大部分电子设备的浏览器功能依然运行的是ARM32的V8引擎,即便是许多ARMv8的64位旗舰手机也是运行32位的。因此最近在对某个手机进行Pwn的过程中就需要把自己的Exploit适配一下。

最近看到有个1 Day漏洞的信息,链接点这里。然后网上也有人写了个针对x64架构的Exploit。不过V8的各种数据结构内部偏移变得很频繁,所以这个Exploit就当作POC来参考一下

PoC

运行如下代码后b和array就能越界访问之后的内存结构了。

array = [];
array.length = 0xffffffff;

b = array.fill(1.1, 0, {valueOf() {
  array.length = 32;
  array.fill(1.1);
  return 0x80000000;
}});

X64下的利用

既然是想exploit运行ARM32版本的某手机系统浏览器,那为啥还要对x64进行利用呢?因为我菜啊,之前几个文章里都只做过x64上的利用,没有弄过ia32或arm32的。因此需要先在x64上把利用思路走通一遍,然后使用ia32位使用相同思路利用一遍,最后在手机上进行arm32的利用调试。

环境搭建

一开始本次的1 Day漏洞验证环境我使用了之前CVE-2018-17463漏洞利用的环境,毕竟这个漏洞是2019年的1 Day,看漏洞信息大致猜测是一个持续时间比较长的漏洞,因此2018年末的版本应该是存在的。不过直接使用之前的漏洞验证环境似乎一直无法复现这个漏洞。

后来发现是由于我之前使用的漏洞验证环境是x64.debug,本次漏洞需要使用release版本才能验证。原因是本次漏洞大致成因是由于length的unsigned类型被错当成了signed,因此大数被当作负数从而导致了后续length处理中产生了OOB问题。而debug版本中存在DCHECK_LE会检测到length变成负数这一现象从而中断引擎的执行。因此本漏洞需要使用release版本或在debug版本中手动去除DCHECK相关检查才能进行验证。

于是我重新编译了x64.release版本,然后成功触发漏洞。

漏洞利用

本漏洞非常容易利用,因为array可以读取下方太多内存空间了,也没什么触发门槛。就用常规思路,先读读object的地址,然后改改arraybuffer的backing_store就可以执行shellcode了。2018以后的V8都用WASM来开辟rwx内存空间进行shellcode的填入。完整exploit如下:

由于是1 day漏洞,exploit暂不公开

最终效果如下:

ia32下的利用

由于已经在x64上完成了一次,因此本阶段就是在原本的exploit上进行修改,使其适合32位架构。测试环境使用ia32.release。

修改偏移

把x64的exploit脚本直接在新的ia32.release版本上运行,发现崩溃。于是开始逐步调试。调试过程中发现,32位的V8内其对象内存地址都为32位,对象的大小基本单位从64位的8 Byte变为4 Byte。因此,对象的地址一般结尾为0xXXXX0、0xXXXX4、0xXXXX8和0xXXXXC。与64位一样,所有代表对象地址的数据都需要+1。因此每个内存对象中的数据大部分结尾为0xXXXX1、0xXXXX5、0xXXXX9和0xXXXXD。

每个数据结构里原本8个Byte表示一个object现在仅需4个,8个Byte可以表示两个object。因此在相同版本下exploit中对对象结构的偏移往往是减半左右。于是对exploit中的各个偏移根据调试内存信息进行修改,使其对应新版本。

修改工具函数

我们在漏洞利用时由于要对ArrayBuffer相应的内存空间进行读写,因此需要使用一些读写函数作为工具。在64位下一般每次读写8个Byte,而在32位下往往需要以4个Byte为基本单位。

同时在exploit的工具函数中往往有代表地址数据的变量,原本类型为BigInt等,现在我们所泄露的地址不再是64位数值,而是32位,因此对相关变量类型也要修改。

修改shellcode

不同架构exploit移植最重要的自然是shellcode。x64的shellcode在32位环境下无法运行,因此需要新的shellcode。在ia32下,我使用了针对x86的shellcode。

最终效果

最终,我成功将x86-64架构的exploit移植到x86-32上。可以从下图中看出已经成功运行了新的shellcode。(虽然shellcode目前还有些问题,但是可以从反馈信息中得知shellcode已经被执行了)

ARM32下的利用

内存数据结构上ARM32和ia32应该相差不大,因此,在完成了ia32上的exploit后就开始着手对目标产品进行测试。目标产品上的V8引擎版本是不确定的,因此可能依然和我们当前32位的exploit不匹配,需要进行实际调试。

实机调试

为了方便测试,我先把目标设备进行解锁并Root了。然后使用Android NDK中提供的gdbserver和gdb对浏览器进程进行调试。其中gdbserver需要运行在不同测试设备上因此存在不同架构的版本分别存放在不同目录下,gdb则是电脑端统一的版本。此处需要注意,如果使用自己原本电脑上版本的gdb会产生一系列问题,例如加载速度非常慢等。

将exploit放在一个html中,在本地Ubuntu(192.168.2.43)中并架设apache。使用手机浏览器进行访问(192.168.2.43/index.html)。这里可以在exploit中插入一些alert语句方便调试关键点。当浏览器在页面中的alert处停止时,使用gdbserver对浏览器进程进行attach,然后电脑端启动gdb进行内存调试。通过alert的输出信息可以获得特定的内存地址信息,通过查看该内存区域内容即可了解目标引擎中的内存偏移情况。

本阶段通过调试使得exploit可以运行到shellcode处即可。

修改Shellcode

由于此时已经可以运行至Shellcode处了,因此需要将exploit中的Shellcode改成适合Android ARM32的。并且shellcode的功能也不再是弹出计算器,而是向我的Ubuntu提供一个shell从而我们进行后续的操作。

通过exploit-db我搜到了一个看着不错的Android ARM32版本shellcode。不过修改参数后直接替换shellcode后并没有达到预期的效果。返回给Ubuntu的shell只持续了一瞬间就断了。

初步怀疑是shellcode的问题,于是便找了个可以在目标手机上运行的ELF文件进行修改,这里我直接用了那个gdbserver,在其主逻辑中用ID pro加入了shellcode,然后发现shellcode可以长时间提供shell。因此,不是shellcode的问题。

于是通过网上的相关资料查找,看到了一些别的安卓攻击中使用的shellcode,发现需要在shellcode前使用fork,否则原浏览器进程会崩溃推出并随之导致shell连接终断。

最终,我在shellcode头部添加fork相关汇编代码即可成功得到shell。

利用效果

最终在获得shell后我们可以以系统浏览器的身份在目标手机系统内进行操作。可以通过该shell查看用户浏览器上的各种信息,各种代理设置。也可以查看SD卡上的内容。甚至,该shell利用am工具可以在设备上可以进一步向应用市场发送特定intent从而进行静默安装。