
CVE-2018-4248: libxpcの文字列シリアライズ処理における範囲外読み取り
xpc-string-leak は、libxpc における領域外メモリ読み取りの概念実証エクスプロイトです。このエクスプロイトは、脆弱性を利用して、task_for_pid-allow エンタイトルメントを持つサンドボックス化されていないルートプロセスである diagnosticd から、ヒープメモリの領域外読み取りを行います。
macOS 10.13.5 および iOS 11.4 において、関数 _xpc_string_deserialize は、_xpc_string_create で XPC 文字列オブジェクトを作成する前に、逆シリアル化された文字列が適切な長さであることを確認しません。これにより、XPC 文字列が別の XPC メッセージにシリアル化された場合、Heartbleed スタイルのヒープ領域外読み取りが発生する可能性があります。
以下は、IDA を使用して逆コンパイルされた _xpc_string_deserialize の実装です。
OS_xpc_string *__fastcall _xpc_string_deserialize(OS_xpc_serializer *xserializer)
{
OS_xpc_string *xstring; // rbx@1
char *string; // rax@4
char *contents; // [rsp+8h] [rbp-18h]@1
size_t size; // [rsp+10h] [rbp-10h]@1 MAPDST
xstring = 0LL;
contents = 0LL;
size = 0LL;
if ( _xpc_string_get_wire_value(xserializer, (const char **)&contents, &size) )
{
if ( contents[size - 1] || (string = _xpc_try_strdup(contents)) == 0LL )
{
xstring = 0LL;
}
else
{
xstring = _xpc_string_create(string, size - 1);
LOBYTE(xstring->flags) |= 1u;
}
}
return xstring;
}
_xpc_string_deserialize はまず _xpc_string_get_wire_value を呼び出して、文字列ヘッダーによって報告された文字列データへのポインタとシリアル化されたサイズを取得します。次に、_xpc_string_deserialize は報告されたサイズの末尾にヌル終端があることを確認しますが、重要なことに、データ内のそれより前の場所にヌル終端がないかどうかは確認しません。最後に、ヒープ上に文字列のコピーを作成し、_xpc_string_create を使用して OS_xpc_string オブジェクトを作成します。
以下は、_xpc_string_create の逆コンパイルされたコードです。
OS_xpc_string *__fastcall _xpc_string_create(const char *string, size_t length)
{
OS_xpc_string *xstring; // rax@1
xstring = (OS_xpc_string *)_xpc_base_create(&OBJC_CLASS___OS_xpc_string, 16LL);
if ( (((_DWORD)length + 4) & 0xFFFFFFFC) + 4 < length )
_xpc_api_misuse("Unreasonably large string");
xstring->wire_length = ((length + 4) & 0xFFFFFFFC) + 4;
xstring->string = string;
xstring->length = length;
return xstring;
}
_xpc_string_create は _xpc_string_deserialize から渡された length の値を信頼し、OS_xpc_string オブジェクトの適切なフィールドを設定します。この時点で、逆シリアル化された文字列の length フィールドは、割り当てられた文字列データよりも大きい可能性があります。
理論的には、xpc_string_get_length を使用して文字列の長さを取得するサービスでメモリ破壊を引き起こすために使用できる可能性がありますが、このパターンは一般的ではないようです。より強力ではないが、より実用的なエクスプロイト戦略は、文字列を再シリアル化して送り返させることで、被害プロセスのメモリへの Heartbleed スタイルのウィンドウを得ることです。
以下は _xpc_string_serialize の実装です。
void __fastcall _xpc_string_serialize(OS_xpc_string *string, OS_xpc_serializer *serializer)
{
int type; // [rsp+8h] [rbp-18h]@1
int size; // [rsp+Ch] [rbp-14h]@1
type = *((_DWORD *)&OBJC_CLASS___OS_xpc_string + 10);
_xpc_serializer_append(serializer, &type, 4uLL, 1, 0, 0);
size = LODWORD(string->length) + 1;
_xpc_serializer_append(serializer, &size, 4uLL, 1, 0, 0);
_xpc_serializer_append(serializer, string->string, string->length + 1, 1, 0, 0);
}
シリアル化中は OS_xpc_string の length パラメータが信頼されるため、多くのバイトがヒープからシリアル化されたメッセージに読み込まれます。逆シリアル化された文字列が報告された長さよりも短かった場合、メッセージはヒープデータの領域外で埋められることになります。
それでも、XPC メッセージの一部をクライアントに返信する XPC サービスにのみエクスプロイトが制限されますが、これははるかに一般的です。たとえば、macOS および iOS では、diagnosticd は有望な候補であり、サンドボックス化されておらず、ルートであり、task_for_pid 権限を持っています。Diagnosticd は診断メッセージ(たとえば、os_log によって生成されたメッセージ)を処理し、それらに関心のあるクライアントにストリーミングする責任があります。独自の診断ストリームを受信するように登録し、期待されるよりも短い文字列を含む診断メッセージを送信することで、diagnosticd のヒープ内の一部のデータのスナップショットを取得でき、プロセス内でのコード実行を容易にするのに役立ちます。
ビルドするには、make を実行します。さまざまなビルドオプションについては、Makefile の上部を参照してください。
コマンドラインでリークサイズを指定してエクスプロイトを実行します:
$ ./xpc-string-leak 0x40
0x2000000000000000 0xe00007ff39bf0992
0x00007fff56858570 0x00007fff7ed23d0e
0x0000000000000000 0x0000000000000000
0x00007fff7ed52be2 0x00007fff7ed29ed6
リークサイズは 8 の倍数で、かつ 16 以上である必要があります。
xpc-string-leak コードはパブリックドメインとしてリリースされています。礼儀として、このコードを参照または使用する場合は、私に帰属表示をするようお願いします。
このバグは 2018 年初頭(1月または2月)に発見しましたが、5月まで調査を忘れていました。5月9日に Apple に問題を報告し、CVE-2018-4248 が割り当てられ、7月9日に iOS 11.4.1 および macOS 10.13.6 で修正されました。
Brandon Azad