AndroidのMediaパイプラインと画像パーサの悪用

Tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

配信: メッセージングアプリ ➜ MediaStore ➜ 特権パーサ

最新のOEMビルドでは、特権メディアインデクサが定期的にMediaStoreを再スキャンして、“AI“や共有機能のための処理を行います。Samsungのファームウェアでは、2025年4月のパッチ以前のバージョンで、com.samsung.ipservice が Quram (/system/lib64/libimagecodec.quram.so) をロードし、WhatsApp(や他のアプリ)がMediaStoreに入れたファイルを自動的に解析します。実際には攻撃者がDNGをIMG-*.jpgに偽装して送り、被害者が「download」(1-click)をタップするのを待つと、ユーザーがギャラリーを開かなくても特権サービスがペイロードを解析します。

$ file IMG-2025-02-10.jpeg
TIFF image data ...
$ exiftool IMG-2025-02-10.jpeg | grep "Opcode List"
Opcode List 1 : [opcode 23], [opcode 23], ...

主なポイント

  • Delivery はシステムのメディア再パース(チャットクライアントではなく)に依存するため、そのプロセスの権限(ギャラリーへの完全な読み書きアクセス、新しいメディアをドロップする能力など)を継承する。
  • MediaStore 経由で到達できる任意の image parser(vision widgets、wallpapers、AI résumé 機能など)は、攻撃者がターゲットにメディアを保存させられればリモートから到達可能になる。

0-click DD+/EAC-3 decoding path (Google Messages ➜ mediacodec sandbox)

Modern messaging stacks は検索/転写のために audio を自動デコードすることもある。Pixel 9 では、Google Messages が着信 RCS/SMS audio をユーザーがメッセージを開く前に /vendor/lib64/libcodec2_soft_ddpdec.so 内の Dolby Unified Decoder (UDC) に渡すため、0-click の攻撃面が media codecs に拡張される。

主なパース制約

  • 各 DD+ syncframe は最大 6 ブロックを持ち、各ブロックは攻撃者制御の skip data を最大 0x1FF バイトまで skip buffer にコピーできる(フレーム当たり約 0x1FF * 6 バイト)。
  • skip buffer は EMDF を走査する:syncword (0xX8) + emdf_container_length (16b) + 可変長フィールド。emdf_payload_size は境界チェックのない variable_bits(8) ループでパースされる。
  • EMDF ペイロードバイトはフレーム毎のカスタムな “evo heap” bump allocator 内に割り当てられ、その後 emdf_container_length により境界付けられた bit-reader からバイト単位でコピーされる。

Integer-overflow → heap-overflow primitive (CVE-2025-54957)

  • ddp_udc_int_evo_mallocalloc_size+extra を 8 バイト境界に揃えるために total_size += (8 - total_size) % total_size を使うが、ラップ検出がない。AArch64 上で 0xFFFFFFFFFFFFFFF9..FF に近い値は極小の total_size に縮む。
  • コピーループは依然として emdf_payload_size から取った論理的な payload_length を使うため、攻撃者バイトは小さくなったチャンクを越えて evo-heap のデータを上書きする。
  • オーバーフロー長は攻撃者が選べる emdf_container_length によって正確に上限が設定される;オーバーフローするバイトは攻撃者制御の EMDF ペイロードデータである。スラブアロケータは各 syncframe ごとにリセットされ、隣接性が予測可能になる。

Secondary read primitive emdf_container_length > skipl の場合、EMDF パースは初期化済み skip バイトを越えて読み取る(OOB read)。単独ではゼロや既知メディアを漏らすだけだが、隣接するヒープメタデータを破損した後は、破損した領域を読み返してエクスプロイトを検証できる。

Exploitation recipe

  1. variable_bits(8) を使って非常に大きな emdf_payload_size を持つ EMDF を作り、アロケータのパディングをラップさせて小さなチャンクにする。
  2. オーバーフロー長(≤ 全 skip データ予算)として望む emdf_container_length を設定し、オーバーフローバイトを EMDF ペイロードに置く。
  3. フレーム毎の evo heap を整形して、小さな割当がデコーダの static buffer(約 693 KB)またはデコーダインスタンスごとに一度割り当てられる dynamic buffer(約 86 KB)内のターゲット構造の前に来るようにする。
  4. 任意で emdf_container_length > skipl を選び、破損後に skip buffer から上書きしたデータを読み返す。

Quram’s DNG Opcode Interpreter Bugs

DNG ファイルは異なるデコード段階で適用される 3 つの opcode リストを埋め込む。Quram は Adobe の API をコピーしているが、Stage-3 ハンドラの DeltaPerColumn(opcode ID 11)は攻撃者供給の plane bounds を信用している。

DeltaPerColumn における plane 境界チェック失敗

  • 攻撃者は Stage-3 画像が実際には plane 0–2 (RGB) のみを公開しているにもかかわらず、plane=5125planes=5123 を設定する。
  • Quram は opcode_last_plane = image_planes + opcode_planes を計算し plane + count を使うべきところを誤り、結果として得られる plane 範囲が画像内に収まるかどうかをチェックしない。
  • したがってループは raw_pixel_buffer[plane_index] に完全に制御されたオフセットでデルタを書き込む(例:plane 5125 ⇒ オフセット 5125 * 2 bytes/pixel = 0x2800)。各 opcode はターゲット位置に 16-bit float 値(0x6666)を加算するため、正確なヒープ OOB add プリミティブが得られる。

インクリメントを任意書き込みに変える

  • エクスプロイトはまず 480 個の破損した DeltaPerColumn 操作で Stage-3 の QuramDngImage.bottom/right を破壊し、以降の opcode が巨大な座標を in-bounds と扱うようにする。
  • その後 MapTable opcode(opcode 7)を偽の bounds に向ける。全ゼロの substitution table や -Inf デルタを持つ DeltaPerColumn を使って任意領域をゼロ化し、追加のデルタで正確な値を書き込む。
  • opcode パラメータは DNG メタデータ内に存在するため、ペイロードはプロセスメモリに直接触れずに何十万もの書き込みを符号化できる。

Scudo 下でのヒープ形作り

Scudo はサイズでアロケーションをバケット化する。Quram は次のオブジェクトを同じ 0x30 バイトチャンクサイズで割り当てるため、同じ領域(ヒープ上で 0x40 バイト間隔)に落ちる:

  • Stage 1/2/3 の QuramDngImage ディスクリプタ
  • QuramDngOpcodeTrimBounds とベンダーの Unknown opcode(ID ≥14、ID 23 を含む)

エクスプロイトはチャンクを決定論的に配置するために割り当てシーケンスを組む:

  1. Stage-1 の Unknown(23) opcode(20,000 エントリ)で 0x30 チャンクをスプレーし、後で free されるようにする。
  2. Stage-2 でそれら opcode を free し、代わりに新しい QuramDngImage をその解放領域内に置く。
  3. Stage-2 の Unknown(23) 240 エントリを free し、Stage-3 が即座に自身の QuramDngImage と同サイズの新しい raw pixel buffer を割り当ててそれらの場所を再利用する。
  4. 作成した TrimBounds opcode がリスト 3 の先頭で実行され、Stage-2 状態を free する前にさらに別の raw pixel buffer を割り当てさせることで、“raw pixel buffer ➜ QuramDngImage” の隣接を保証する。
  5. 追加の 640 個の TrimBounds エントリは minVersion=1.4.0.1 とマークされてディスパッチャがスキップするが、その裏にあるオブジェクトは割り当てられたまま残り、後にプリミティブ対象となる。

この振付により Stage-3 raw buffer が Stage-3 QuramDngImage の直前に配置され、plane ベースのオーバーフローがランダムな状態をクラッシュさせるのではなくディスクリプタ内のフィールドを反転させる。

ベンダーの “Unknown” Opcode をデータブロブとして再利用する

Samsung はベンダー固有 opcode ID(例えば ID 23)で高位ビットをセットしたままにすることがあり、これによりインタプリタは構造体を allocate するが実行はスキップする。エクスプロイトはこれら休止中のオブジェクトを攻撃者制御のヒープとして悪用する:

  • Opcode リスト 1 と 2 の Unknown(23) エントリはペイロードバイトを格納する連続したスクラッチパッドとして機能する(raw buffer 相対でオフセット 0xf000 に JOP チェーン、0x10000 にシェルコマンドなど)。
  • インタプリタはリスト 3 を処理するときに各オブジェクトを opcode として扱い続けるため、後で一つのオブジェクトの vtable を掌握すれば攻撃者データの実行を開始できる。

偽の MapTable オブジェクトの作成と ASLR 回避

MapTable オブジェクトは TrimBounds より大きいが、レイアウト破損が起きるとパーサは喜んで境界外の追加パラメータを読み取る:

  1. 線形書き込みプリミティブを使い、TrimBounds の vtable ポインタの下位バイトを隣接する TrimBounds vtable から MapTable vtable へマッピングするような改造済み MapTable 置換テーブルで部分上書きする。サポートされる Quram ビルド間では下位バイトのみが異なるため、単一の 64K ルックアップテーブルで 7 つのファームウェア版と全ての 4 KB ASLR スライドに対応できる。
  2. 残りの TrimBounds フィールド(top/left/width/planes)をパッチして、後で実行されたときにオブジェクトが有効な MapTable のように振る舞うようにする。
  3. ゼロ化されたメモリ上で偽 opcode を実行する。置換テーブルポインタが実際には別の opcode の vtable を参照しているため、出力バイトは libimagecodec.quram.so またはその GOT からの低位アドレスが leaked される。
  4. 追加の MapTable パスを適用して、これらの 2 バイトのリークを __ink_jpeg_enc_process_image+64, QURAMWINK_Read_IO2+124, qpng_check_IHDR+624, libc の __system_property_get エントリなどのガジェットへ向かうオフセットに変換する。攻撃者はネイティブのメモリ開示 API を使わずにスプレー領域内でフルアドレスを再構築することに成功する。

JOP ➜ system() 移行のトリガ

ガジェットポインタとシェルコマンドが opcode スプレー内に準備されると:

  1. 最後の一連の DeltaPerColumn 書き込みが Stage-3 QuramDngImage のオフセット 0x22 に 0x0100 を加え、その raw buffer ポインタを 0x10000 だけシフトして攻撃者コマンド文字列を指すようにする。
  2. インタプリタは 1040 個の Unknown(23) opcode の末尾を実行し始める。最初の破損したエントリはオフセット 0xf000 の偽テーブルで vtable が置換されているため、QuramDngOpcode::aboutToApply は偽テーブルの 4 番目のエントリ(qpng_read_data)を解決する。
  3. チェインされたガジェットは次を行う:QuramDngImage ポインタをロードし、raw buffer ポインタを指すように 0x20 を加え、それをデリファレンスして結果を x19/x0 にコピーし、GOT スロットを通って system に書き換えられた箇所へジャンプする。raw buffer ポインタが攻撃者文字列を指すため、最終ガジェットは com.samsung.ipservice 内で system(<shell command>) を実行する。

アロケータ変種についての注記

ペイロードは 2 系統ある:jemalloc 用に調整されたものと scudo 用のもの。これらは隣接性を達成するための opcode ブロックの順序が異なるが、論理プリミティブ(DeltaPerColumn バグ ➜ MapTable ゼロ/書き込み ➜ 偽 vtable ➜ JOP)は同一である。Scudo の quarantine 無効化により 0x30 バイトの freelist 再利用が決定論的になる一方、jemalloc は tile/subIFD サイズ制御によるサイズクラスで動作する。

References

Tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする