Abusing Android Media Pipelines & Image Parsers

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Delivery: Messaging Apps ➜ MediaStore ➜ Privileged Parsers

Τα σύγχρονα OEM builds τρέχουν τακτικά διαδικασίες ευρετηρίασης μέσων με προνόμια που επανασκανάρουν το MediaStore για δυνατότητες “AI” ή κοινής χρήσης. Σε Samsung firmware πριν από το patch του Απριλίου 2025, το com.samsung.ipservice φορτώνει την Quram (/system/lib64/libimagecodec.quram.so) και αναλύει αυτόματα οποιοδήποτε αρχείο το WhatsApp (ή άλλες εφαρμογές) τοποθετεί στο MediaStore. Στην πράξη, ένας επιτιθέμενος μπορεί να στείλει ένα DNG μεταμφιεσμένο ως IMG-*.jpg, να περιμένει το θύμα να πατήσει “download” (1-click), και η υπηρεσία με προνόμια θα αναλύσει το payload ακόμη κι αν ο χρήστης δεν ανοίξει ποτέ την 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], ...

Σημαντικά συμπεράσματα

  • Η παράδοση βασίζεται στην επανα-ανάλυση (re-parsing) των συστημικών media (όχι του chat client) και έτσι κληρονομεί τα δικαιώματα αυτής της διεργασίας (πλήρης πρόσβαση ανάγνωσης/εγγραφής στη συλλογή, δυνατότητα τοποθέτησης νέων πολυμέσων, κ.λπ.).
  • Οποιοσδήποτε image parser προσβάσιμος μέσω του MediaStore (vision widgets, wallpapers, AI résumé features, κ.λπ.) γίνεται απομακρυσμένα προσβάσιμος εάν ο επιτιθέμενος καταφέρει να πείσει το θύμα να αποθηκεύσει media.

0-click DD+/EAC-3 διαδρομή αποκωδικοποίησης (Google Messages ➜ mediacodec sandbox)

Τα σύγχρονα messaging stacks αποκωδικοποιούν επίσης αυτόματα τον ήχο για μεταγραφή/αναζήτηση. Σε Pixel 9, τα Google Messages παραδίδουν εισερχόμενο RCS/SMS audio στον Dolby Unified Decoder (UDC) μέσα στο /vendor/lib64/libcodec2_soft_ddpdec.so πριν ο χρήστης ανοίξει το μήνυμα, διευρύνοντας την 0-click επιφάνεια προς media codecs.

Βασικοί περιορισμοί ανάλυσης

  • Κάθε DD+ syncframe έχει έως 6 blocks· κάθε block μπορεί να αντιγράψει έως 0x1FF bytes από ελεγχόμενα από τον επιτιθέμενο skip data σε έναν skip buffer (≈ 0x1FF * 6 bytes ανά frame).
  • Ο skip buffer σαρώνονται για EMDF: syncword (0xX8) + emdf_container_length (16b) + μεταβλητού μήκους πεδία. Το emdf_payload_size αναλύεται με έναν απεριόριστο βρόχο variable_bits(8).
  • Τα bytes του payload του EMDF δεσμεύονται μέσα σε έναν custom per-frame “evo heap” bump allocator και στη συνέχεια αντιγράφονται byte-προς-byte από έναν bit-reader που οριοθετείται από το emdf_container_length.

Υπερχείλιση-integer → heap-overflow primitive (CVE-2025-54957)

  • Η ddp_udc_int_evo_malloc ευθυγραμμίζει το alloc_size+extra στα 8 bytes μέσω total_size += (8 - total_size) % total_size χωρίς ανίχνευση wrap. Τιμές κοντά στο 0xFFFFFFFFFFFFFFF9..FF συρρικνώνουν το total_size σε πολύ μικρό μέγεθος σε AArch64.
  • Ο βρόχος αντιγραφής εξακολουθεί να χρησιμοποιεί το λογικό payload_length από το emdf_payload_size, οπότε τα bytes του επιτιθέμενου υπεργράφουν δεδομένα του evo-heap πέρα από το υποβαθμισμένο chunk.
  • Το μήκος overflow περιορίζεται με ακρίβεια από το emdf_container_length που επιλέγει ο επιτιθέμενος· τα overflow bytes είναι ελεγχόμενα από τον επιτιθέμενο EMDF payload δεδομένα. Ο slab allocator επαναφέρεται κάθε syncframe, δίνοντας προβλέψιμη γειτνίαση.

Δευτερεύων primitive ανάγνωσης Εάν emdf_container_length > skipl, η ανάλυση EMDF διαβάζει πέρα από τα αρχικοποιημένα skip bytes (OOB read). Αφότου μόνο του leaks μηδενικά/γνωστά μέσα, αλλά μετά τη φθορά του γειτονικού heap metadata μπορεί να διαβάσει πίσω την καταχωρισμένη περιοχή για να επικυρώσει το exploit.

Συνταγή εκμετάλλευσης

  1. Δημιουργήστε EMDF με τεράστιο emdf_payload_size (μέσω variable_bits(8)) ώστε η στοίβα ευθυγράμμισης του allocator να τυλίξει σε μικρό chunk.
  2. Ορίστε emdf_container_length στο επιθυμητό μήκος overflow (≤ συνολικό budget skip data); τοποθετήστε bytes overflow στο EMDF payload.
  3. Σχηματίστε το per-frame evo heap ώστε η μικρή κατανομή να βρίσκεται πριν από στοχευμένες δομές μέσα στο στατικό buffer του decoder (≈693 KB) ή στο δυναμικό buffer (≈86 KB) που δεσμεύεται μία φορά ανά decoder instance.
  4. Προαιρετικά επιλέξτε emdf_container_length > skipl για να διαβάσετε πίσω υπεγραμμένα δεδομένα από τον skip buffer μετά τη φθορά.

Σφάλματα του Quram DNG Opcode Interpreter

Αρχεία DNG ενσωματώνουν τρεις λίστες opcode που εφαρμόζονται σε διαφορετικά στάδια αποκωδικοποίησης. Ο Quram αντιγράφει το API της Adobe, αλλά ο Stage-3 χειριστής του για το DeltaPerColumn (opcode ID 11) εμπιστεύεται attacker-supplied όρια των plane.

Αποτυχία ορίων plane στο DeltaPerColumn

  • Οι επιτιθέμενοι θέτουν plane=5125 και planes=5123 παρόλο που οι εικόνες Stage-3 αποκάλυπταν μόνο planes 0–2 (RGB).
  • Ο Quram υπολογίζει opcode_last_plane = image_planes + opcode_planes αντί για plane + count, και ποτέ δεν ελέγχει αν το προκύπτον εύρος planes χωράει μέσα στην εικόνα.
  • Ο βρόχος επομένως γράφει ένα delta στο raw_pixel_buffer[plane_index] με πλήρως ελεγχόμενο offset (π.χ., plane 5125 ⇒ offset 5125 * 2 bytes/pixel = 0x2800). Κάθε opcode προσθέτει μία 16-bit float τιμή (0x6666) στην στοχευμένη θέση, αποδίδοντας ένα ακριβές heap OOB add primitive.

Μετατροπή των αυξήσεων σε αυθαίρετες εγγραφές

  • Το exploit αρχικά χαλάει τα QuramDngImage.bottom/right του Stage-3 χρησιμοποιώντας 480 κακοσχηματισμένες DeltaPerColumn λειτουργίες ώστε μελλοντικά opcodes να θεωρήσουν τεράστιες συντεταγμένες ως εντός ορίων.
  • Τα MapTable opcodes (opcode 7) κατόπιν στοχεύονται σε εκείνα τα ψεύτικα όρια. Χρησιμοποιώντας έναν substitution πίνακα πλήρη μηδενικών ή ένα DeltaPerColumn με -Inf deltas, ο επιτιθέμενος μηδενίζει οποιαδήποτε περιοχή και στη συνέχεια εφαρμόζει πρόσθετα deltas για να γράψει ακριβείς τιμές.
  • Επειδή οι παράμετροι των opcode ζουν μέσα στα DNG metadata, το payload μπορεί να κωδικοποιήσει εκατοντάδες χιλιάδες εγγραφές χωρίς να αγγίξει άμεσα τη μνήμη της διεργασίας.

Σχηματισμός heap υπό Scudo

Το Scudo ομαδοποιεί τις δεσμεύσεις κατά μέγεθος. Ο Quram τυχαίνει να δεσμεύει τα ακόλουθα αντικείμενα με ταυτόσημα 0x30-byte chunk sizes, επομένως προσγειώνονται στην ίδια περιοχή (0x40-byte κενά στη στοίβα):

  • Περιγραφείς QuramDngImage για Stage 1/2/3
  • QuramDngOpcodeTrimBounds και vendor Unknown opcodes (ID ≥14, συμπεριλαμβανομένου του ID 23)

Το exploit ακολουθεί σειρές δεσμεύσεων για να τοποθετήσει deterministically chunks:

  1. Stage-1 Unknown(23) opcodes (20,000 εγγραφές) ψεκάζουν 0x30 chunks που απελευθερώνονται αργότερα.
  2. Το Stage-2 απελευθερώνει αυτά τα opcodes και τοποθετεί ένα νέο QuramDngImage μέσα στην απελευθερωμένη περιοχή.
  3. 240 Stage-2 Unknown(23) εγγραφές απελευθερώνονται, και το Stage-3 αμέσως δεσμεύει το QuramDngImage του μαζί με ένα νέο raw pixel buffer του ίδιου μεγέθους, επαναχρησιμοποιώντας αυτές τις θέσεις.
  4. Ένα κατασκευασμένο TrimBounds opcode τρέχει πρώτο στη λίστα 3 και δεσμεύει ακόμη ένα raw pixel buffer πριν απελευθερωθεί η κατάσταση του Stage-2, εξασφαλίζοντας την γειτνίαση “raw pixel buffer ➜ QuramDngImage”.
  5. 640 επιπλέον TrimBounds εγγραφές επισημαίνονται minVersion=1.4.0.1 ώστε ο dispatcher να τις παραλείψει, αλλά τα υποκείμενα αντικείμενα παραμένουν δεσμευμένα και αργότερα γίνονται primitive targets.

Αυτή η χορογραφία τοποθετεί το raw buffer του Stage-3 αμέσως πριν από το Stage-3 QuramDngImage, έτσι το overflow βάσει planes αναστρέφει πεδία μέσα στον περιγραφέα αντί να προκαλέσει τυχαίο crash.

Επαναχρησιμοποίηση vendor “Unknown” Opcodes ως Data Blobs

Η Samsung αφήνει το high bit ενεργό στα vendor-specific opcode IDs (π.χ. ID 23), το οποίο δίνει εντολή στον interpreter να δεσμεύσει τη δομή αλλά να παραλείψει την εκτέλεση. Το exploit καταχράται αυτά τα αδρανή αντικείμενα ως ελεγχόμενους από τον επιτιθέμενο χώρους:

  • Οι εγγραφές Unknown(23) στη λίστα opcode 1 και 2 λειτουργούν ως συνεχή scratchpads για αποθήκευση bytes payload (JOP chain στη μετατόπιση 0xf000 και μια shell εντολή στο 0x10000 σχετική με το raw buffer).
  • Επειδή ο interpreter εξακολουθεί να αντιμετωπίζει κάθε αντικείμενο ως opcode όταν επεξεργάζεται η λίστα 3, η κατάληψη του vtable ενός αντικειμένου αρκεί αργότερα για να αρχίσει η εκτέλεση attacker data.

Κατασκευή ψευδών MapTable αντικειμένων & παράκαμψη ASLR

Τα MapTable αντικείμενα είναι μεγαλύτερα από τα TrimBounds, αλλά μόλις επέλθει η φθορά της διάταξης, ο parser ευχαρίστως διαβάζει επιπλέον παραμέτρους εκτός ορίων:

  1. Χρησιμοποιήστε το linear write primitive για να μερικώς υπεγράψετε έναν pointer vtable του TrimBounds με έναν κατασκευασμένο substitution πίνακα MapTable που αντιστοιχίζει τα χαμηλότερα 2 bytes από το γειτονικό vtable TrimBounds στο vtable του MapTable. Μόνο τα χαμηλά bytes διαφέρουν μεταξύ των υποστηριζόμενων Quram builds, οπότε ένας ενιαίος 64K lookup πίνακας μπορεί να χειριστεί επτά εκδόσεις firmware και κάθε 4 KB ASLR slide.
  2. Επιδιορθώστε τα υπόλοιπα πεδία του TrimBounds (top/left/width/planes) ώστε το αντικείμενο να συμπεριφέρεται σαν έγκυρο MapTable όταν εκτελεστεί αργότερα.
  3. Εκτελέστε το ψεύτικο opcode πάνω σε μηδενισμένη μνήμη. Επειδή ο pointer του substitution table στην πραγματικότητα αναφέρεται στο vtable ενός άλλου opcode, τα output bytes γίνονται leaked low-order addresses από το libimagecodec.quram.so ή το GOT του.
  4. Εφαρμόστε επιπλέον περάσματα MapTable για να μετατρέψετε αυτές τις δι-πλευρικές διαρροές σε offsets προς gadgets όπως __ink_jpeg_enc_process_image+64, QURAMWINK_Read_IO2+124, qpng_check_IHDR+624 και τη libc __system_property_get είσοδο. Οι επιτιθέμενοι ουσιαστικά ανασυστήνουν πλήρεις διευθύνσεις μέσα στην ψεκαζόμενη περιοχή opcode χωρίς εγγενείς native memory disclosure APIs.

Ενεργοποίηση της μετάβασης JOP ➜ system()

Μόλις οι pointers προς gadgets και η shell εντολή τοποθετηθούν μέσα στο opcode spray:

  1. Ένα τελικό κύμα DeltaPerColumn εγγραφών προσθέτει 0x0100 στη μετατόπιση 0x22 του Stage-3 QuramDngImage, μετατοπίζοντας το pointer του raw buffer κατά 0x10000 ώστε τώρα να αναφέρεται στη συμβολοσειρά εντολής του επιτιθέμενου.
  2. Ο interpreter αρχίζει να εκτελεί την ουρά από 1040 Unknown(23) opcodes. Η πρώτη κατεστραμμένη εγγραφή έχει το vtable της αντικαταστημένο με τον παραποιημένο πίνακα στη μετατόπιση 0xf000, έτσι QuramDngOpcode::aboutToApply επιλύει qpng_read_data (την 4η εγγραφή) από τον ψεύτικο πίνακα.
  3. Τα αλυσιδωτά gadgets κάνουν: φορτώνουν τον pointer QuramDngImage, προσθέτουν 0x20 για να δείξουν στον pointer του raw buffer, τον αποαποστηχούν, αντιγράφουν το αποτέλεσμα σε x19/x0, και μετά πηδούν μέσω GOT slots που έχουν ξαναγραφεί σε system. Επειδή ο pointer του raw buffer τώρα δείχνει στη συμβολοσειρά του επιτιθέμενου, το τελικό gadget εκτελεί system(<shell command>) μέσα στο com.samsung.ipservice.

Σημειώσεις για παραλλαγές allocator

Υπάρχουν δύο οικογένειες payload: μία ρυθμισμένη για jemalloc και μία για scudo. Διαφέρουν στο πώς παραγγέλνονται τα blocks των opcode για να επιτευχθεί η γειτνίαση, αλλά μοιράζονται τα ίδια λογικά primitives (σφάλμα DeltaPerColumn ➜ MapTable zero/write ➜ bogus vtable ➜ JOP). Η απενεργοποιημένη quarantine του Scudo κάνει την επαναχρησιμοποίηση freelist 0x30-byte ντετερμινιστική, ενώ ο jemalloc βασίζεται σε έλεγχο size-class μέσω tile/subIFD sizing.

References

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks