Abusando dos Pipelines de Mídia do Android e Analisadores de Imagem
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Entrega: Apps de Mensageria ➜ MediaStore ➜ Parsers privilegiados
Builds OEM modernos executam regularmente indexadores de mídia privilegiados que reescaneiam MediaStore para recursos de “AI” ou compartilhamento. No firmware Samsung anterior ao patch de abril de 2025, com.samsung.ipservice carrega Quram (/system/lib64/libimagecodec.quram.so) e analisa automaticamente qualquer arquivo que o WhatsApp (ou outros apps) coloque em MediaStore. Na prática, um atacante pode enviar um DNG disfarçado como IMG-*.jpg, aguardar a vítima tocar em “download” (1-click), e o serviço privilegiado irá analisar o payload mesmo que o usuário nunca abra a galeria.
$ 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], ...
Principais conclusões
- A entrega depende do reparseamento de mídia do sistema (não do cliente de chat) e, portanto, herda as permissões desse processo (acesso total de leitura/escrita à galeria, capacidade de adicionar nova mídia, etc.).
- Qualquer parser de imagem acessível via
MediaStore(vision widgets, wallpapers, recursos de resumo/AI, etc.) torna-se alcançável remotamente se o atacante conseguir convencer a vítima a salvar mídia.
0-click DD+/EAC-3 decoding path (Google Messages ➜ mediacodec sandbox)
Stacks de mensagens modernos também decodificam automaticamente áudio para transcrição/busca. No Pixel 9, Google Messages entrega áudio RCS/SMS recebido ao Dolby Unified Decoder (UDC) dentro de /vendor/lib64/libcodec2_soft_ddpdec.so antes do usuário abrir a mensagem, ampliando a superfície 0-click para codecs de mídia.
Principais restrições de parsing
- Cada DD+ syncframe tem até 6 blocks; cada block pode copiar até
0x1FFbytes de skip data controlada pelo atacante para um skip buffer (≈0x1FF * 6bytes por frame). - O skip buffer é escaneado por EMDF:
syncword (0xX8)+emdf_container_length(16b) + campos de tamanho variável.emdf_payload_sizeé parseado com um loopvariable_bits(8)sem limite. - Os bytes do payload EMDF são alocados dentro de um bump allocator por-frame customizado chamado “evo heap” e então copiados byte-a-byte a partir de um bit-reader limitado por
emdf_container_length.
Integer-overflow → heap-overflow primitive (CVE-2025-54957)
ddp_udc_int_evo_mallocalinhaalloc_size+extraa 8 bytes viatotal_size += (8 - total_size) % total_sizesem detecção de wrap. Valores próximos de0xFFFFFFFFFFFFFFF9..FFencolhem para umtotal_sizeminúsculo em AArch64.- O loop de cópia continua a usar o logical
payload_lengthdeemdf_payload_size, de modo que bytes do atacante sobrescrevem dados do evo-heap além do chunk subdimensionado. - O comprimento do overflow é precisamente limitado por
emdf_container_lengthescolhido pelo atacante; os bytes do overflow são dados do payload EMDF controlados pelo atacante. O slab allocator é reiniciado a cada syncframe, proporcionando adjacência previsível.
Primitivo de leitura secundário
Se emdf_container_length > skipl, o parsing EMDF lê além dos skip bytes inicializados (OOB read). Isoladamente, ele leaks zeros/mídia conhecida, mas após corromper metadados de heap adjacentes ele pode ler de volta a região corrompida para validar o exploit.
Receita de exploração
- Forjar um EMDF com enorme
emdf_payload_size(viavariable_bits(8)) de modo que o padding do allocator dê wrap reduzindo o chunk. - Definir
emdf_container_lengthpara o comprimento de overflow desejado (≤ orçamento total de skip data); colocar os bytes de overflow no payload EMDF. - Moldar o evo heap por-frame para que a alocação pequena fique antes das estruturas alvo dentro do buffer estático do decoder (≈693 KB) ou do buffer dinâmico (≈86 KB) alocado uma vez por instância do decoder.
- Opcionalmente escolher
emdf_container_length > skiplpara ler de volta dados sobrescritos do skip buffer após a corrupção.
Quram’s DNG Opcode Interpreter Bugs
Arquivos DNG embutem três listas de opcodes aplicadas em diferentes estágios de decodificação. Quram replica a API da Adobe, mas seu handler de Stage-3 para DeltaPerColumn (opcode ID 11) confia em limites de plano fornecidos pelo atacante.
Limites de plano inválidos em DeltaPerColumn
- Atacantes definem
plane=5125eplanes=5123mesmo que imagens Stage-3 exponham apenas planos 0–2 (RGB). - Quram calcula
opcode_last_plane = image_planes + opcode_planesem vez deplane + count, e nunca verifica se o intervalo de planos resultante cabe dentro da imagem. - O loop, portanto, escreve um delta em
raw_pixel_buffer[plane_index]com um offset totalmente controlado (ex.: plane 5125 ⇒ offset5125 * 2 bytes/pixel = 0x2800). Cada opcode adiciona um valor float de 16 bits (0x6666) ao local alvo, gerando um primitivo preciso de adição OOB no heap.
Transformando incrementos em writes arbitrários
- O exploit primeiro corrompe
QuramDngImage.bottom/rightdo Stage-3 usando 480 operaçõesDeltaPerColumnmalformadas para que opcodes futuros tratem coordenadas enormes como in-bounds. - OpCodes
MapTable(opcode 7) são então direcionados a esses limites falsos. Usando uma tabela de substituição de todos zeros ou umDeltaPerColumncom deltas-Inf, o atacante zera qualquer região, então aplica deltas adicionais para escrever valores exatos. - Como os parâmetros de opcode residem nos metadados DNG, o payload pode codificar centenas de milhares de writes sem tocar diretamente na memória do processo.
Modelagem de heap sob Scudo
Scudo agrupa alocações por tamanho. Quram por acaso aloca os seguintes objetos com tamanhos de chunk idênticos de 0x30 bytes, então eles caem na mesma região (espaçamento de 0x40 bytes no heap):
- Descritores
QuramDngImagepara Stage 1/2/3 QuramDngOpcodeTrimBoundse opcodes vendorUnknown(ID ≥14, incluindo ID 23)
A sequência de alocações do exploit coloca chunks deterministically:
Unknown(23)opcodes Stage-1 (20.000 entries) sprayam chunks 0x30 que depois são liberados.- Stage-2 libera esses opcodes e coloca um novo
QuramDngImagedentro da região liberada. - 240 entries
Unknown(23)Stage-2 são liberados, e Stage-3 aloca imediatamente seuQuramDngImagemais um novo raw pixel buffer do mesmo tamanho, reutilizando esses slots. - Um
TrimBoundscraftado roda primeiro na lista 3 e aloca mais um raw pixel buffer antes de liberar o estado do Stage-2, garantindo adjacência “raw pixel buffer ➜ QuramDngImage”. - 640
TrimBoundsadicionais são marcadosminVersion=1.4.0.1para que o dispatcher os pule, mas seus objetos de backing continuam alocados e depois viram alvos primitivos.
Essa coreografia coloca o raw buffer do Stage-3 imediatamente antes do QuramDngImage do Stage-3, de modo que o overflow baseado em planos altera campos dentro do descritor em vez de travar estados aleatórios.
Reutilizando opcodes vendor “Unknown” como blobs de dados
A Samsung deixa o high bit set em IDs de opcode vendor-specific (ex.: ID 23), o que instrui o interpretador a alocar a estrutura mas pular a execução. O exploit abusa desses objetos dormentes como heaps controlados pelo atacante:
- Entradas
Unknown(23)das listas 1 e 2 servem como scratchpads contíguos para armazenar bytes do payload (cadeia JOP em offset 0xf000 e um comando shell em 0x10000 relativo ao raw buffer). - Como o interpretador ainda trata cada objeto como um opcode quando a lista 3 é processada, tomar o vtable de um objeto é suficiente para começar a executar dados do atacante.
Construindo objetos MapTable falsos & contornando ASLR
Objetos MapTable são maiores que TrimBounds, mas uma vez que a corrupção de layout acontece, o parser felizmente lê parâmetros extras fora dos limites:
- Use o linear write primitivo para sobrescrever parcialmente um ponteiro de vtable de
TrimBoundscom uma tabela de substituiçãoMapTableforjada que mapeia os 2 bytes inferiores a partir da vtable de umTrimBoundsvizinho para a vtable doMapTable. Apenas os bytes baixos diferem entre builds Quram suportados, então uma única tabela de lookup de 64K pode cobrir sete versões de firmware e cada slide ASLR de 4 KB. - Corrija o restante dos campos de
TrimBounds(top/left/width/planes) para que o objeto se comporte como umMapTableválido quando executado depois. - Execute o opcode falso sobre memória zerada. Como o ponteiro da tabela de substituição referencia na realidade a vtable de outro opcode, os bytes de saída tornam-se leaked low-order addresses de
libimagecodec.quram.soou do seu GOT. - Aplique passes
MapTableadicionais para converter esses vazamentos de dois bytes em offsets para gadgets como__ink_jpeg_enc_process_image+64,QURAMWINK_Read_IO2+124,qpng_check_IHDR+624, e a entrada__system_property_getda libc. Os atacantes efetivamente reconstruem endereços completos dentro da sua região de opcode spray sem APIs nativas de disclosure de memória.
Disparo da transição JOP ➜ system()
Uma vez que os pointers de gadget e o comando shell estejam stageados dentro do spray de opcodes:
- Uma onda final de writes
DeltaPerColumnadiciona0x0100ao offset 0x22 doQuramDngImagedo Stage-3, deslocando seu ponteiro de raw buffer por 0x10000 de modo que agora refira a string de comando do atacante. - O interpretador começa a executar o tail de 1040 opcodes
Unknown(23). A primeira entry corrompida tem sua vtable substituída pela tabela forjada em offset 0xf000, de modo queQuramDngOpcode::aboutToApplyresolveqpng_read_data(a 4ª entrada) a partir da tabela falsa. - Os gadgets encadeados realizam: carregar o ponteiro
QuramDngImage, somar 0x20 para apontar ao ponteiro do raw buffer, desreferenciar, copiar o resultado parax19/x0, então pular através de slots GOT reescritos parasystem. Como o ponteiro do raw buffer agora aponta para a string do atacante, o gadget final executasystem(<shell command>)dentro decom.samsung.ipservice.
Notas sobre variantes de alocador
Existem duas famílias de payloads: uma ajustada para jemalloc e outra para scudo. Elas diferem em como os blocos de opcode são ordenados para alcançar adjacência, mas compartilham os mesmos primitivos lógicos (DeltaPerColumn bug ➜ MapTable zero/write ➜ vtable falsa ➜ JOP). A quarentena desabilitada do Scudo torna a reutilização da freelist de 0x30 bytes determinística, enquanto jemalloc depende de controle de size-class via tile/subIFD sizing.
References
- Project Zero – A look at an Android ITW DNG exploit
- Project Zero – Pixel 0-click: CVE-2025-54957 in Dolby UDC
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


