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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
配信: メッセージングアプリ ➜ 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_mallocはalloc_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
variable_bits(8)を使って非常に大きなemdf_payload_sizeを持つ EMDF を作り、アロケータのパディングをラップさせて小さなチャンクにする。- オーバーフロー長(≤ 全 skip データ予算)として望む
emdf_container_lengthを設定し、オーバーフローバイトを EMDF ペイロードに置く。 - フレーム毎の evo heap を整形して、小さな割当がデコーダの static buffer(約 693 KB)またはデコーダインスタンスごとに一度割り当てられる dynamic buffer(約 86 KB)内のターゲット構造の前に来るようにする。
- 任意で
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=5125とplanes=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 と扱うようにする。 - その後
MapTableopcode(opcode 7)を偽の bounds に向ける。全ゼロの substitution table や-Infデルタを持つDeltaPerColumnを使って任意領域をゼロ化し、追加のデルタで正確な値を書き込む。 - opcode パラメータは DNG メタデータ内に存在するため、ペイロードはプロセスメモリに直接触れずに何十万もの書き込みを符号化できる。
Scudo 下でのヒープ形作り
Scudo はサイズでアロケーションをバケット化する。Quram は次のオブジェクトを同じ 0x30 バイトチャンクサイズで割り当てるため、同じ領域(ヒープ上で 0x40 バイト間隔)に落ちる:
- Stage 1/2/3 の
QuramDngImageディスクリプタ QuramDngOpcodeTrimBoundsとベンダーのUnknownopcode(ID ≥14、ID 23 を含む)
エクスプロイトはチャンクを決定論的に配置するために割り当てシーケンスを組む:
- Stage-1 の
Unknown(23)opcode(20,000 エントリ)で 0x30 チャンクをスプレーし、後で free されるようにする。 - Stage-2 でそれら opcode を free し、代わりに新しい
QuramDngImageをその解放領域内に置く。 - Stage-2 の
Unknown(23)240 エントリを free し、Stage-3 が即座に自身のQuramDngImageと同サイズの新しい raw pixel buffer を割り当ててそれらの場所を再利用する。 - 作成した
TrimBoundsopcode がリスト 3 の先頭で実行され、Stage-2 状態を free する前にさらに別の raw pixel buffer を割り当てさせることで、“raw pixel buffer ➜ QuramDngImage” の隣接を保証する。 - 追加の 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 より大きいが、レイアウト破損が起きるとパーサは喜んで境界外の追加パラメータを読み取る:
- 線形書き込みプリミティブを使い、
TrimBoundsの vtable ポインタの下位バイトを隣接するTrimBoundsvtable からMapTablevtable へマッピングするような改造済みMapTable置換テーブルで部分上書きする。サポートされる Quram ビルド間では下位バイトのみが異なるため、単一の 64K ルックアップテーブルで 7 つのファームウェア版と全ての 4 KB ASLR スライドに対応できる。 - 残りの
TrimBoundsフィールド(top/left/width/planes)をパッチして、後で実行されたときにオブジェクトが有効なMapTableのように振る舞うようにする。 - ゼロ化されたメモリ上で偽 opcode を実行する。置換テーブルポインタが実際には別の opcode の vtable を参照しているため、出力バイトは
libimagecodec.quram.soまたはその GOT からの低位アドレスが leaked される。 - 追加の
MapTableパスを適用して、これらの 2 バイトのリークを__ink_jpeg_enc_process_image+64,QURAMWINK_Read_IO2+124,qpng_check_IHDR+624, libc の__system_property_getエントリなどのガジェットへ向かうオフセットに変換する。攻撃者はネイティブのメモリ開示 API を使わずにスプレー領域内でフルアドレスを再構築することに成功する。
JOP ➜ system() 移行のトリガ
ガジェットポインタとシェルコマンドが opcode スプレー内に準備されると:
- 最後の一連の
DeltaPerColumn書き込みが Stage-3QuramDngImageのオフセット 0x22 に 0x0100 を加え、その raw buffer ポインタを 0x10000 だけシフトして攻撃者コマンド文字列を指すようにする。 - インタプリタは 1040 個の
Unknown(23)opcode の末尾を実行し始める。最初の破損したエントリはオフセット 0xf000 の偽テーブルで vtable が置換されているため、QuramDngOpcode::aboutToApplyは偽テーブルの 4 番目のエントリ(qpng_read_data)を解決する。 - チェインされたガジェットは次を行う:
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
- Project Zero – A look at an Android ITW DNG exploit
- Project Zero – Pixel 0-click: CVE-2025-54957 in Dolby UDC
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


