滥用 Android 媒体管线与图像解析器
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 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
投递:消息应用 ➜ MediaStore ➜ 特权解析器
现代 OEM 构建会定期运行特权媒体索引器,这些索引器会为了 “AI” 或分享功能重新扫描 MediaStore。在 2025 年 4 月补丁之前的 Samsung 固件上,com.samsung.ipservice 会加载 Quram (/system/lib64/libimagecodec.quram.so) 并自动解析 WhatsApp(或其他应用)放入 MediaStore 的任何文件。实际上,攻击者可以发送一个伪装成 IMG-*.jpg 的 DNG,等待受害者点击 “download”(一键),即使用户从未打开图库,特权服务也会解析该 payload。
$ 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], ...
关键要点
- 交付依赖于系统媒体的重新解析(而不是聊天客户端),因此继承了该进程的权限(对图库的完全读/写访问、放置新媒体的能力等)。
- 任何通过
MediaStore可达的图像解析器(vision widgets、wallpapers、AI résumé 功能等)如果攻击者能说服目标保存媒体,就会变成远程可达。
0-click DD+/EAC-3 解码路径 (Google Messages ➜ mediacodec sandbox)
现代消息栈也会在转录/搜索前自动解码 audio。在 Pixel 9 上,Google Messages 会在用户打开消息之前将收到的 RCS/SMS audio 交给位于 /vendor/lib64/libcodec2_soft_ddpdec.so 的 Dolby Unified Decoder (UDC),将 0-click 面扩大到媒体 codec。
关键解析约束
- 每个 DD+ syncframe 最多有 6 个 block;每个 block 能将最多
0x1FF字节的攻击者可控 skip data 复制到一个 skip buffer(≈ 每帧0x1FF * 6字节)。 - skip buffer 会被扫描以查找 EMDF:
syncword (0xX8)+emdf_container_length(16b) + 可变长度字段。emdf_payload_size通过一个无界的variable_bits(8)循环解析。 - EMDF payload 字节会在一个每帧自定义的 “evo heap” bump allocator 中分配,然后从受
emdf_container_length限制的位读取器按字节复制。
Integer-overflow → heap-overflow 原语 (CVE-2025-54957)
ddp_udc_int_evo_malloc通过total_size += (8 - total_size) % total_size将alloc_size+extra对齐到 8 字节,没有检测溢出。接近0xFFFFFFFFFFFFFFF9..FF的值在 AArch64 上会缩成很小的total_size。- 复制循环仍然使用来自
emdf_payload_size的逻辑payload_length,因此攻击者字节会覆盖 evo-heap 中该缩小块之后的数据。 - 溢出长度由攻击者可选的
emdf_container_length精确限定;溢出字节就是攻击者控制的 EMDF payload 数据。slab allocator 在每个 syncframe 重置,提供可预测的相邻布局。
次级读取原语
如果 emdf_container_length > skipl,EMDF 解析会读取已初始化 skip 字节之外的数据(OOB read)。单独看它会 leaks 零/已知媒体,但在破坏相邻堆元数据后,它可以读取回被破坏的区域以验证漏洞利用。
利用配方
- 构造 EMDF,使用巨大的
emdf_payload_size(通过variable_bits(8))使 allocator 填充对齐环绕到一个很小的块。 - 将
emdf_container_length设为期望的溢出长度(≤ 总 skip 数据预算);在 EMDF payload 中放置溢出字节。 - 塑造每帧的 evo heap,使小分配位于解码器静态缓冲区(≈693 KB)或为每个 decoder 实例分配一次的动态缓冲区(≈86 KB)内目标结构之前。
- 可选地选择
emdf_container_length > skipl,在破坏后从 skip buffer 读回被覆盖的数据以进行验证。
Quram 的 DNG Opcode 解释器漏洞
DNG 文件在不同解码阶段嵌入三组 opcode 列表。Quram 复制了 Adobe 的 API,但其 Stage-3 对 DeltaPerColumn(opcode ID 11)的处理器信任攻击者提供的平面边界。
在 DeltaPerColumn 中失败的平面边界
- 攻击者设置
plane=5125和planes=5123,尽管 Stage-3 图像只暴露平面 0–2 (RGB)。 - Quram 计算
opcode_last_plane = image_planes + opcode_planes而不是plane + count,并且从未检查结果的平面范围是否适合图像。 - 因此循环会将 delta 写入
raw_pixel_buffer[plane_index],偏移由攻击者完全控制(例如,plane 5125 ⇒ 偏移5125 * 2 bytes/pixel = 0x2800)。每个 opcode 将一个 16-bit float 值(0x6666)加到目标位置,产生一个精确的 heap OOB add 原语。
将增量转为任意写
- 利用首先用 480 个畸形的
DeltaPerColumn操作破坏 Stage-3 的QuramDngImage.bottom/right,使后续的 opcode 将巨大的坐标当作在界内处理。 - 然后将
MapTableopcodes(opcode 7)指向这些伪造的边界。使用全零的替换表或带有-Infdeltas 的DeltaPerColumn,攻击者可以将任一区域清零,然后应用额外的 deltas 来写入精确值。 - 因为 opcode 参数存在于 DNG 元数据中,payload 可以在不直接触碰进程内存的情况下编码数十万次写操作。
Scudo 下的堆塑形
Scudo 按大小对分配进行分桶。Quram 恰好以相同的 0x30 字节块大小分配以下对象,因此它们位于相同区域(堆上以 0x40 字节间隔):
- Stage 1/2/3 的
QuramDngImage描述符 QuramDngOpcodeTrimBounds和 厂商Unknownopcodes(ID ≥14,包括 ID 23)
利用序列化分配以确定性地放置块:
- Stage-1 的
Unknown(23)opcode(20,000 条目)喷洒 0x30 块,之后这些块被释放。 - Stage-2 释放那些 opcode 并在被释放区域内放置一个新的
QuramDngImage。 - 240 个 Stage-2 的
Unknown(23)条目被释放,Stage-3 随即分配其QuramDngImage加上一个同样大小的新 raw pixel buffer,重用这些位置。 - 一个精心构造的
TrimBoundsopcode 在列表 3 中首先运行,并分配另一个 raw pixel buffer,然后释放 Stage-2 状态,保证了“raw pixel buffer ➜ QuramDngImage”的相邻性。 - 另外 640 个
TrimBounds条目被标记为minVersion=1.4.0.1,因此 dispatcher 跳过它们,但它们的 backing objects 仍保持分配,后续成为原语目标。
该编排将 Stage-3 raw buffer 放在 Stage-3 QuramDngImage 之前,因此基于平面的溢出会翻转描述符内部字段,而不是随机崩溃其他状态。
重用厂商“Unknown” Opcodes 作为数据块
Samsung 在厂商特定 opcode ID(例如 ID 23)中保留了高位,这指示解释器 分配 该结构但跳过执行。利用者将这些休眠对象当作攻击者可控的堆块:
- opcode 列表 1 和 2 的
Unknown(23)条目用作连续的 scratchpad 来存放 payload 字节(JOP chain 在相对于 raw buffer 的偏移 0xf000 处,shell 命令在 0x10000)。 - 因为解释器在处理列表 3 时仍然将每个对象视为 opcode,所以之后夺取某个对象的 vtable 就足以开始执行攻击者数据。
构造伪造的 MapTable 对象 & 绕过 ASLR
MapTable 对象比 TrimBounds 更大,但一旦布局损坏到位,解析器就会愉快地从 OOB 读取额外参数:
- 使用线性写原语部分覆盖一个
TrimBounds的 vtable 指针,替换为一个伪造的MapTablesubstitution table,该表将来自相邻TrimBoundsvtable 的低 2 字节映射到MapTablevtable。受支持的 Quram 构建之间只有低字节差异,因此单个 64K 查找表可以覆盖七个固件版本和每个 4 KB 的 ASLR slide。 - 修补
TrimBounds的其余字段(top/left/width/planes),使该对象在稍后执行时表现得像有效的MapTable。 - 在被置零的内存上执行伪造 opcode。因为 substitution table 指针实际引用另一个 opcode 的 vtable,输出字节就会变成来自
libimagecodec.quram.so或其 GOT 的 leaked 低位地址。 - 再次应用
MapTable过程,将这些两字节的 leaks 转换为指向 gadget 的偏移,例如__ink_jpeg_enc_process_image+64、QURAMWINK_Read_IO2+124、qpng_check_IHDR+624和 libc 的__system_property_get条目。攻击者有效地在其喷洒的 opcode 区域内重建完整地址,而无需本地内存泄露 API。
触发 JOP ➜ system() 转换
一旦 gadget 指针和 shell 命令在 opcode 喷洒区就位:
- 最后一轮
DeltaPerColumn写入在 Stage-3QuramDngImage的偏移 0x22 上追加0x0100,将其 raw buffer 指针移动0x10000,使其现在引用攻击者的命令字符串。 - 解释器开始执行尾部的 1040 个
Unknown(23)opcode。第一个被破坏的条目其 vtable 已被替换为位于 0xf000 的伪造表,因此QuramDngOpcode::aboutToApply解析出假表中的qpng_read_data(第 4 项)。 - 链式 gadget 完成的操作:加载
QuramDngImage指针,加0x20指向 raw buffer 指针,解引用它,将结果拷贝到x19/x0,然后通过被重写为system的 GOT 插槽跳转。因为 raw buffer 指针现在等于攻击者字符串,最终 gadget 在com.samsung.ipservice内执行system(<shell command>)。
关于分配器变体的说明
存在两类 payload:一个针对 jemalloc 调优,另一个针对 scudo。它们在 opcode 区块的排序上有差异以实现相邻性,但共享相同的逻辑原语(DeltaPerColumn bug ➜ MapTable zero/write ➜ bogus vtable ➜ JOP)。Scudo 禁用的 quarantine 使 0x30 字节 freelist 重用变得确定性,而 jemalloc 则依赖 tile/subIFD 大小来控制大小类。
参考
- 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 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


