
Exploit de escalonamento de privilégio local direcionado à CVE-2026-43499 para o Xiaomi 17T Pro (warhol) no Android 16 com SoC MediaTek MT6993.
Escalada de privilégios local CVE-2026-43499 (IonStack), portada para o Xiaomi 17T Pro
(warhol) — MediaTek MT6993, Android 16.
Apenas GKI 6.12 / android16. O overlay do waiter, o layout da pilha do
pselecte todos os offsets de struct estão fixados a esse branch, então nada aqui é transferível para 6.6, 6.1 ou 5.10 — esses precisam de uma base construída para eles.generate_target.pyrecusa qualquer outro banner em vez de emitir um header que causaria fault no dispositivo.
Status: funcionando. Verificado no dispositivo em 2026-07-26 a partir de uma inicialização limpa, com ambas as variantes de preloader —
uid=0(root) context=u:r:kernel:s0, SELinux permissivo,suinstalado. O root não é persistente: execute novamente a linhaLD_PRELOADapós cada boot. A corrida do pselect não é 100%: uma execução com falha pode causar panic no telefone, e uma nova tentativa após o reboot é normal. ConsulteWARHOL_PORT.mdpara o registro completo.
Este dispositivo precisa da correção do kernel-MTE. O popsicle upstream padrão não consegue fazer root nele — ele fica em loop em
kernel page retry N/12para sempre. Consulte Kernel MTE.
Os builds vêm em duas variantes de preloader, PRELOADER=retail (padrão) e PRELOADER=eng.
Elas selecionam diretórios de target diferentes para que nenhuma possa sobrescrever os endereços da outra —
consulte Variante de preloader.
Os fatos abaixo foram lidos do pacote full-OTA retail — metadados, o manifesto do payload
A/B e as imagens boot/vendor_boot/dtbo extraídas do payload.bin.
| Item | Valor | Fonte |
|---|---|---|
| Codinome | warhol | pre-device=warhol |
| Fingerprint do build | Xiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keys | post-build |
| Incremental | OS3.0.304.0.WPSJPXM (JP global) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| Patch de segurança | 2026-05-01 | post-security-patch-level |
| Tipo de OTA | A/B (payload.bin, CrAU, 39 partições) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | compatíveis mediatek,mt6993-* no DTB do vendor_boot; cmdline bootopt=64S3,32N2,64N2 |
| Kernel | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | banner lido do boot.img extraído |
| Imagem de boot | header v4, kernel 18,898,125 B, compactado com LZ4-legacy, sem ramdisk | tools/bootinfo.py |
Aqui o kernel importa mais do que o SoC: o warhol está no mesmo branch GKI do
popsicle upstream (android16-5, páginas de 4K), com um patchlevel de diferença — 6.12.38 contra
o 6.12.23 verificado do popsicle. Os offsets de struct ainda precisam ser regenerados por build; apenas
o formato do exploit é reaproveitado. Um build OS3.0.x diferente precisa do seu próprio target.h.
A cadeia do exploit está travada por versão ao branch GKI, não ao SoC. O layout do waiter
do rt_mutex, o overlay de pilha do pselect e os offsets da struct pipe_buffer acompanham o
kernel, então uma base 6.12/android16 supera uma base do mesmo fornecedor em um branch mais antigo.
x-spy/CVE-2026-43499-popsicle é a única implementação pública verificada na linha
GKI 6.12/android16 (Xiaomi 17 / Pro / Ultra, kernel 6.12.23-android16-5), então
source/ foi retirada dela e mantida o mais próxima possível do upstream — com exceção de duas linhas
(consulte Kernel MTE, sem a qual este dispositivo simplesmente não funciona).
A maior parte do delta da MediaTek está limitada à geração de target em tempo de build, em duas partes:
p0_phys_offset e p0_kernel_phys_load de uma partição xbl_config
da Qualcomm, que o warhol não possui. generate_target.py --dtb lê a base da DRAM
do nó /memory do FDT do vendor_boot em vez disso, arredondando para baixo da mesma forma
que arm64_memblock_init faz. O endereço de carga física do kernel é lido da
reserva mb_kernel do preloader com --preloader; o delta resulta em 0 para este
dispositivo, e o lk se recusa a dar boot em um kernel colocado em qualquer outro lugar.boot.img, em vez de um
Image arm64 puro, então o gerador o descompacta antes da análise.WARHOL_PORT.md §2 tem a derivação completa.
O lk da MediaTek não carrega o kernel para uma constante de tempo de compilação — ele procura uma
reserva de DRAM chamada mb_kernel e valida que ela caiu exatamente nela. Essa reserva é feita
pelo preloader, e é por isso que o build do preloader que o telefone está executando é uma
entrada de build aqui em primeiro lugar: é a única coisa fora do boot.img que pode mover
P0_KERNEL_PHYS_LOAD, e um valor errado ali significa um alias errado do mapa linear e um telefone
morto, em vez de um exploit com falha.
Daí duas variantes, mantidas como diretórios de target separados:
PRELOADER= | Preloader no telefone | Diretório de target | Status |
|---|---|---|---|
retail (padrão) | o preloader_<device>.bin de fábrica, byte idêntico ao preloader_raw.img da ROM de fastboot | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ verificado no dispositivo em 2026-07-26 |
eng | o build de engenharia, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ verificado no dispositivo em 2026-07-26 |
make preload # PRELOADER=retail -> out/preload-<device>.so
make PRELOADER=eng preload # -> out/preload-<device>-eng.so
make both # both of the above, and print their sha256
Ambos os artefatos são mantidos lado a lado com nomes distintos. Neste firmware eles saem idênticos byte a byte — esse é o resultado da análise abaixo, não um atalho para contorná-la, então os dois ainda são compilados e nomeados separadamente.
Leia as tabelas de layout de memória dos dois preloaders você mesmo com:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
Neste firmware as duas tabelas são idênticas, mb_kernel.start = 0x80000000 em ambas,
então o target.h do eng sai idêntico byte a byte ao do retail e ambos os builds produzem o
mesmo preload.so.
O que de fato chega ao exploit é a diferença
P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, e seus dois termos não se apoiam em evidências
igualmente sólidas:
P0_KERNEL_PHYS_LOAD — medido a partir da imagem eng, como mb_kernel.start acima.P0_PHYS_OFFSET — foi argumentado em vez de medido, já que é memstart_addr,
que o kernel obtém do nó /memory que o lk emite em tempo de execução a partir do que o
preloader reporta após a inicialização da DRAM, não do FDT estático que o gerador lê.
O argumento foi que os dois builds compartilham todo o caminho da DRAM — mesmas fontes
dramc/emi/mblock/memory_layout, nenhum código de layout de memória entre as strings
exclusivas do eng — e que a única reserva extra do preloader eng, uma security_fe_rsv
dinâmica de 3 MiB com mapping=1, não pode deslocar uma entrada de endereço fixo nem abrir
um buraco no fundo da DRAM. A execução no dispositivo resolveu a questão: os aliases do
mapa linear bateram com o eng, então o termo está correto.Derivação completa em targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
Notas da execução no dispositivo: