
CVE-2023-41993 の概念実証エクスプロイト、Safari における WebKit JIT の型混淆であり、ヒープ操作と GetterSetter 混乱を介して addrof/fakeobj プリミティブを提供し、WebContent プロセス内での任意の読み書きを可能にします。
CVE-2023-41993 の PoC エクスプロイト。 addrof/fakeobj までしか書かれていません。 信頼性は高くありません。 より良くしたい場合は、構造体 ID をスプレーしてみてください。
https://po6ix.github.io/POC-for-CVE-2023-41993/pwn.html
複数のリクエストがあったため、GitHub Pages でホストすることにしました。
GitHub にブロックされないことを祈っていてください…
pwn 関数で定義された factor 値がお使いのデバイスに合っていないためです。
そのような場合のために、87 から 1088 の間のランダムな値を使用するようにしました。
そのため、数回リフレッシュするだけで正しい factor 値を見つけることができます。
確率的には 100 回以内で動作するはずです。
成功した場合に表示される情報を送っていただけると助かります。
Safari の WebContent プロセスに対する r/w プリミティブが得られます。
しかし、実際に有用にするには、他のコンポーネントと連鎖させる必要があります。
これについての詳細な writeup が必要かもしれません。 残念ながら、それを書く時間がありません。 そこで、動作を理解できるよう、ここにいくつかのメモを残します。
コミットを見ると、HeapLocation の変更に関するものです。
ノードが同じかどうかを判断するために新しい因子が追加されました。
これにより、GetByOffset や MultiGetByOffset のようなノードが混同される可能性があることが示されています。
しかし、実際にはオフセットのみに関するものです。
異なるオフセットを持つ 2 つの GetByOffset ノードがあるとします。
そのうちの 1 つが CSE され、残りのノードが代わりに使用されます。
基本的にはオフセットの混乱ですが、任意のオフセットへのアクセスが可能になるわけではありません。
なぜなら、この種のノードを CSE するには、LICMPhase によってホイストされる必要があるからです。
書き込み操作を行う種類のノードは、このフェーズではホイストが許可されません。
そのため、PutByOffset や MultiPutByOffset ノードでは同じ混乱は発生しません。
また、GetByOffset がホイストされるとき、safeToExecute 関数が呼び出され、ノードの実行が正当かどうかを確認し、ストレージ容量(インライン/アウトオブライン)未満のオフセットへのアクセスのみを許可します。
したがって、これを悪用するアイデアは GetterSetter でした。
Object.__defineGetter__ を呼び出してプロパティを定義すると、GetterSetter オブジェクトが作成されますが、これはプロパティストレージに保存され、通常はアクセスできません。
しかし、このオフセット操作を使えばアクセス可能です。
その後、Object 関数を呼び出して型混乱を引き起こします。
JSObject* JSCell::toObjectSlow(JSGlobalObject* globalObject) const
{
Integrity::auditStructureID(structureID());
ASSERT(!isObject());
if (isString())
return static_cast<const JSString*>(this)->toObject(globalObject);
if (isHeapBigInt())
return static_cast<const JSBigInt*>(this)->toObject(globalObject);
ASSERT(isSymbol());
return static_cast<const Symbol*>(this)->toObject(globalObject);
}
これにより、GetterSetter を内部値として持つ SymbolObject が作成されます。 これは誤りです。なぜなら、SymbolObject の内部値は GetterSetter ではなく Symbol インスタンスであるべきだからです。
let getterSetter = jitme(1);
let symbolObject = Object(getterSetter);
symbolObject.description; // getter を呼び出す
String Symbol::description() const
{
auto& uid = m_privateName.uid();
return uid.isNullSymbol() ? String() : uid;
}
そして、description ゲッターを呼び出すと、String インスタンスが返されます。 これは、Symbol.m_privateName と GetterSetter.m_getter の間の型混乱です。 このゲッターを呼び出すたびに、m_privateName.m_uid の参照カウンタフィールド(オフセット 0x0)が増加します。 これは非常に便利です。このオフセットは getter 関数の構造体 ID がある場所だからです。 この関数を数回呼び出すことで、JSFunction インスタンスの構造体 ID を変更できます。 多数のプロパティを持つ別の型を用意しておきます。 そして、構造体 ID をそれと一致するように同期させれば、プロパティストレージへの oob 書き込みが可能になります。 これにより、addrof/fakeobj プリミティブが直接得られます。