Android Media Pipelines & Image Parsers का दुरुपयोग

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 बिल्ड नियमित रूप से विशेषाधिकार प्राप्त मीडिया इंडेक्सर चलाते हैं जो “AI” या शेयरिंग फ़ीचर्स के लिए MediaStore को फिर से स्कैन करते हैं। April 2025 patch से पहले के Samsung firmware पर, com.samsung.ipservice Quram (/system/lib64/libimagecodec.quram.so) को लोड करता है और स्वचालित रूप से MediaStore में WhatsApp (या अन्य ऐप्स) द्वारा डाली गई किसी भी फ़ाइल को पार्स कर लेता है। व्यवहार में एक attacker एक DNG भेज सकता है जिसे IMG-*.jpg के रूप में छिपाया गया हो, पीड़ित के “download” (1-click) टैप करने का इंतजार कर सकता है, और विशेषाधिकार प्राप्त सेवा उस payload को पार्स कर देगी भले ही user कभी gallery न खोले।

$ 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 सिस्टम media re-parsing पर निर्भर करता है (chat client नहीं) और इसलिए वह उस प्रक्रिया के permissions विरासत में लेता है (gallery का full read/write access, नया media drop करने की क्षमता, आदि)।
  • MediaStore के माध्यम से पहुँच योग्य कोई भी image parser (vision widgets, wallpapers, AI résumé features, आदि) दूर से पहुँच योग्य बन जाता है यदि attacker किसी लक्ष्य को मीडिया save करने के लिए राज़ी कर सकता है।

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

Modern messaging stacks भी transcription/search के लिए audio को auto-decode करते हैं। Pixel 9 पर, Google Messages आने वाले RCS/SMS audio को user के message खोलने से पहले Dolby Unified Decoder (UDC) में /vendor/lib64/libcodec2_soft_ddpdec.so को सौंप देता है, जिससे 0-click surface media codecs तक बढ़ जाता है।

प्रमुख parse बाधाएँ

  • प्रत्येक DD+ syncframe में अधिकतम 6 blocks होते हैं; प्रत्येक block attacker-controlled skip data के 0x1FF bytes तक को skip buffer में copy कर सकता है (≈ 0x1FF * 6 bytes प्रति frame)।
  • skip buffer को EMDF के लिए स्कैन किया जाता है: syncword (0xX8) + emdf_container_length (16b) + variable-length fields। emdf_payload_size को एक unbounded variable_bits(8) loop के साथ parse किया जाता है।
  • EMDF payload bytes को एक custom प्रति-फ्रेम “evo heap” bump allocator के अंदर allocate किया जाता है और फिर bit-reader द्वारा bounded emdf_container_length से byte-by-byte copy किया जाता है।

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

  • ddp_udc_int_evo_malloc alloc_size+extra को 8 bytes पर align करता है via total_size += (8 - total_size) % total_size बिना wrap detection के। AArch64 पर 0xFFFFFFFFFFFFFFF9..FF के पास के मान छोटे total_size में shrink हो जाते हैं।
  • copy loop अभी भी emdf_payload_size से logical payload_length का उपयोग करता है, इसलिए attacker bytes undersized chunk के पार evo-heap डेटा को overwrite कर देते हैं।
  • Overflow length ठीक वही attacker-चयनित emdf_container_length द्वारा सीमित है; overflow bytes attacker-controlled EMDF payload डेटा होते हैं। slab allocator हर syncframe पर reset होता है, जिससे predictable adjacency मिलती है।

Secondary read primitive यदि emdf_container_length > skipl, तो EMDF parsing initialized skip bytes के पार पढ़ता है (OOB read)। अकेला यह zeros/known media को leak करता है, लेकिन adjacent heap metadata को corrupt करने के बाद यह corrupted region को वापस पढ़ सकता है ताकि exploit validate किया जा सके।

Exploitation recipe

  1. बड़े emdf_payload_size (via variable_bits(8)) वाला EMDF बनाएं ताकि allocator padding wrap करके एक छोटे chunk में आ जाए।
  2. overflow length (≤ total skip data budget) के लिए emdf_container_length सेट करें; overflow bytes को EMDF payload में रखें।
  3. प्रति-फ्रेम evo heap को इस तरह shape करें कि small allocation decoder के static buffer (≈693 KB) या dynamic buffer (≈86 KB) के भीतर target structures से पहले बैठे।
  4. वैकल्पिक रूप से emdf_container_length > skipl चुनें ताकि corruption के बाद skip buffer से overwritten डेटा पढ़कर verify किया जा सके।

Quram’s DNG Opcode Interpreter Bugs

DNG फ़ाइलें तीन opcode सूचियाँ embed करती हैं जो decode के अलग-अलग चरणों पर लागू होती हैं। Quram Adobe के API की नकल करता है, लेकिन इसका Stage-3 handler DeltaPerColumn (opcode ID 11) के लिए attacker-supplied plane bounds पर भरोसा करता है।

Failing plane bounds in DeltaPerColumn

  • Attackers plane=5125 और planes=5123 सेट करते हैं जबकि Stage-3 images केवल planes 0–2 (RGB) एक्सपोज़ करते हैं।
  • Quram opcode_last_plane = image_planes + opcode_planes compute करता है बजाय plane + count के, और यह कभी नहीं चेक करता कि resulting plane range image के अंदर फिट होता है या नहीं।
  • इसलिए loop raw_pixel_buffer[plane_index] पर एक delta लिखता है एक पूरी तरह से नियंत्रित offset के साथ (उदाहरण के लिए, plane 5125 ⇒ offset 5125 * 2 bytes/pixel = 0x2800)। प्रत्येक opcode targeted location में एक 16-bit float value (0x6666) जोड़ता है, जिससे एक सटीक heap OOB add primitive बनता है।

Turning increments into arbitrary writes

  • exploit पहले Stage-3 QuramDngImage.bottom/right को 480 malformed DeltaPerColumn operations से corrupt करता है ताकि भविष्य के opcodes विशाल coordinates को in-bounds मानें।
  • फिर MapTable opcodes (opcode 7) उन fake bounds पर लक्षित किए जाते हैं। सभी zeros वाली substitution table या DeltaPerColumn with -Inf deltas का उपयोग करके attacker किसी भी region को zero कर देता है, फिर अतिरिक्त deltas apply करके exact values लिख देता है।
  • चूंकि opcode parameters DNG metadata के अंदर रहते हैं, payload सैकड़ों हजारों writes encode कर सकता है बिना सीधे process memory को छुए।

Heap Shaping Under Scudo

Scudo allocations को size द्वारा bucket करता है। Quram निम्न objects को identical 0x30-byte chunk sizes के साथ allocate करता है, इसलिए ये एक ही region (heap पर 0x40-byte spacing) में उतरते हैं:

  • Stage 1/2/3 के लिए QuramDngImage descriptors
  • QuramDngOpcodeTrimBounds और vendor Unknown opcodes (ID ≥14, जिसमें ID 23 शामिल है)

Exploit allocations की sequencing करके chunks को deterministic तरीके से place करता है:

  1. Stage-1 Unknown(23) opcodes (20,000 entries) 0x30 chunks spray करते हैं जो बाद में free हो जाते हैं।
  2. Stage-2 उन opcodes को free कर देता है और freed region के अंदर एक नया QuramDngImage रखता है।
  3. 240 Stage-2 Unknown(23) entries free किए जाते हैं, और Stage-3 तुरंत अपने QuramDngImage और उसी आकार के नए raw pixel buffer को allocate करता है, उन spots को reuse करते हुए।
  4. एक crafted TrimBounds opcode सूची 3 में पहले चलता है और एक और raw pixel buffer allocate करता है इससे पहले कि Stage-2 state free हो, जिससे “raw pixel buffer ➜ QuramDngImage” adjacency की गारंटी होती है।
  5. 640 अतिरिक्त TrimBounds entries को minVersion=1.4.0.1 के रूप में चिह्नित किया जाता है ताकि dispatcher उन्हें skip करे, पर उनकी backing objects allocated रहती हैं और बाद में primitive targets बन जाती हैं।

यह choreography Stage-3 raw buffer को सीधे Stage-3 QuramDngImage से पहले रख देती है, इसलिए plane-based overflow descriptor के अंदर fields को flip कर देता है बजाय किसी random state को crash करने के।

Reusing Vendor “Unknown” Opcodes as Data Blobs

Samsung vendor-specific opcode IDs (उदा., ID 23) में high bit सेट छोड़ देता है, जो interpreter को structure allocate करने और execution skip करने का संकेत देता है। exploit उन dormant objects का दुरुपयोग attacker-controlled heaps के रूप में करता है:

  • Opcode list 1 और 2 के Unknown(23) entries contiguous scratchpads के रूप में payload bytes (JOP chain offset 0xf000 पर और shell command 0x10000 पर raw buffer के relative) संग्रहीत करने के लिए काम आते हैं।
  • क्योंकि interpreter सूची 3 को process करते समय अभी भी प्रत्येक object को एक opcode की तरह ट्रीट करता है, किसी एक object’s vtable को बाद में commandeer करना attacker data के execution शुरू करने के लिए पर्याप्त होता है।

Crafting Bogus MapTable Objects & Bypassing ASLR

MapTable objects TrimBounds से बड़े होते हैं, लेकिन एक बार layout corruption होने पर parser खुशी-खुशी extra parameters को out-of-bounds पढ़ लेता है:

  1. linear write primitive का उपयोग करके आंशिक रूप से एक TrimBounds vtable pointer को ओवरराइट करें एक crafted MapTable substitution table के साथ जो lower 2 bytes को पड़ोसी TrimBounds vtable से MapTable vtable पर map कर दे। समर्थित Quram builds के बीच केवल low bytes अलग होते हैं, इसलिए एक single 64K lookup table सात firmware versions और हर 4 KB ASLR slide को हँडल कर सकता है।
  2. TrimBounds के बाकी fields (top/left/width/planes) को patch करें ताकि object बाद में execute होने पर एक मान्य MapTable जैसा व्यवहार करे।
  3. zeroed memory पर fake opcode execute करें। क्योंकि substitution table pointer असल में किसी अन्य opcode के vtable को reference करता है, output bytes libimagecodec.quram.so या उसके GOT से leaked low-order addresses बन जाते हैं।
  4. अतिरिक्त MapTable passes लागू करके उन दो-बाइट leaks को उन gadgets की ओर offsets में बदला जाता है जैसे __ink_jpeg_enc_process_image+64, QURAMWINK_Read_IO2+124, qpng_check_IHDR+624, और libc का __system_property_get entry। attackers प्रभावी रूप से अपने sprayed opcode region के अंदर पूर्ण addresses rebuild कर लेते हैं बिना किसी native memory disclosure API के।

Triggering the JOP ➜ system() Transition

एक बार gadget pointers और shell command opcode spray में stage हो जाने पर:

  1. अंतिम wave के DeltaPerColumn writes Stage-3 QuramDngImage के offset 0x22 पर 0x0100 जोड़ देते हैं, जिससे उसका raw buffer pointer 0x10000 से शिफ्ट हो जाता है और अब attacker command string को reference करता है।
  2. interpreter 1040 Unknown(23) opcodes की tail को execute करना शुरू करता है। पहला corrupted entry इसका vtable forged table (offset 0xf000) से बदल चुका होता है, इसलिए QuramDngOpcode::aboutToApply fake table से qpng_read_data (4th entry) resolve कर लेता है।
  3. chained gadgets यह करते हैं: QuramDngImage pointer load करना, raw buffer pointer की ओर इशारा करने के लिए 0x20 जोड़ना, उसे dereference करना, परिणाम को x19/x0 में कॉपी करना, फिर GOT slots के माध्यम से jump करना जो system के लिए rewritten हैं। क्योंकि raw buffer pointer अब attacker string के बराबर है, अंतिम gadget system(<shell command>) को com.samsung.ipservice के अंदर execute कर देता है।

Notes on Allocator Variants

दो payload families मौजूद हैं: एक jemalloc के लिए tune किया गया, दूसरा scudo के लिए। वे adjacency हासिल करने के लिए opcode blocks के ordering में भिन्न होते हैं लेकिन एक ही logical primitives साझा करते हैं (DeltaPerColumn bug ➜ MapTable zero/write ➜ bogus vtable ➜ JOP)। Scudo की disabled quarantine 0x30-byte freelist reuse को deterministic बनाती है, जबकि jemalloc tile/subIFD sizing के माध्यम से size-class control पर निर्भर करता है।

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 का समर्थन करें