
Root temporário (uid 0) em um ASUS Zenfone 9 com bootloader bloqueado via CVE-2025-21479 + um vazamento de endereço físico baseado em perf. GPLv3.
Código de pesquisa e notas para obter root temporário (uid 0) em um ASUS Zenfone 9 (AI2202) cujo
bootloader não pode ser desbloqueado — porque a ASUS descontinuou sua ferramenta de desbloqueio, removeu a opção de desbloqueio OEM
das Opções do desenvolvedor e recusa terminantemente fastboot oem unlock / flashing unlock no firmware
atual.
Isto não é um desbloqueio de bootloader e não grava nada. É um root em tempo de execução: uma cadeia de primitivas do lado do dispositivo que termina com o processo chamador detendo credenciais de root. Um reboot o apaga.
Verificado em: ASUS Zenfone 9 (AI2202), Android 14, build 34.0304.2004.145, SPL 2024-07-05,
kernel 5.10.205-android12-9-00029-g3f12df86bfdb-ab11799032, SM8475 / Adreno 730, bootloader bloqueado.
Autorização / escopo. Tudo aqui é executado contra um dispositivo que o operador possui, através de uma conexão ADB autorizada. Nenhum servidor de fabricante é atacado, nenhuma chave de assinatura ou token de desbloqueio é contornado, e nada é gravado em qualquer partição. O telefone usado para desenvolvimento é um aparelho reserva, com backup feito e mantido offline. Teste em hardware que você possui e pode se dar ao luxo de perder.
O resultado é uid 0 no contexto SELinux u:r:kernel:s0 (ele herda o SID de init_cred), ou seja,
efetivamente irrestrito. O SELinux precisa estar em Permissive para este passo — sob Enforcing o processo é
morto em vez disso, porque a troca de credenciais contorna o hook do SELinux.
Defina ZF9_SERIAL com o serial do seu dispositivo primeiro (todos os scripts o leem):
export ZF9_SERIAL=<your-device-serial>
# 0. one-time: build and push the device binaries (needs an Android NDK)
# see scripts/ for the exact clang invocations used
adb push cheese_pa call_capset /data/local/tmp/
# 1. full cycle: SELinux -> permissive, patch, run a command as root, restore everything
scripts/root-now.sh id
scripts/root-now.sh sh # root shell
O script sempre restaura o texto original do kernel e o estado do SELinux (protegido por trap), e verifica a restauração por leitura de retorno.
uid=0(root) gid=0(root) context=u:r:kernel:s0,
capset(NULL,NULL) -> 0.gpu fault threshold exceeded 3 faults in 3000 msecs), após o que comandos adicionais falham com
EPERM. O sucesso observado do patch varia entre 13/13 e 2/13 dwords entre execuções.screenrecord em execução. Os scripts iniciam
sua própria carga para cada operação (as leituras também correm), mas nunca executam os internos dessas ferramentas
de forma crua.capset pode executar lixo). Os scripts
escrevem a instrução de entrada por último, verificam cada dword e restauram em caso de falha — mas se uma execução degradar,
reinicie antes de tentar novamente.Regras de segurança que vale a pena manter: leia antes de cada escrita, sempre restaure o que você corrige, nunca deixe uma
entrada de tabela de páginas injetada ativa, não toque na memória física secure/TZ (é fatal), e reinicie para
recuperar um estado degradado. ROADMAP.md tem a lista completa de armadilhas com as evidências por trás de cada uma.
ROADMAP.md durable handoff: verified constants, procedure, gotchas, open paths
STATUS.md current state + verification receipts
src/ pa_leak.{c,h} · cheese.c · cheese_pa.c (workhorse: PROBE/POKE/SELFTEST/ROOT modes)
call_capset.c · host_kallsyms.c (offline symbol resolver)
scripts/ root-now.sh · patch-dwords.sh · demo-root.sh · verify-backup.sh
tools/ btf_offsets.py
Imagens de firmware, pacotes OTA e logs do dispositivo são deliberadamente não commitados (veja .gitignore).
GPLv3 — veja LICENSE.
| Estágio | O que faz | Onde |
|---|
| 1 | CVE-2025-21479 (Adreno KGSL): um pacote SDS é classificado incorretamente como pacote de ringbuffer, permitindo que o userland emita CP_SMMU_TABLE_UPDATE e aponte o TTBR0 da GPU para um endereço físico escolhido pelo atacante | src/cheese.c |
| 2 | Vazamento de endereço físico: perf_event_paranoid = -1 nesta build, então um watchpoint de hardware em uma página que possuímos retorna PERF_SAMPLE_PHYS_ADDR — o endereço físico dessa página. Substitui o vazamento via pagemap do qual o upstream dependia (os PFNs são zerados aqui) | src/pa_leak.{c,h} |
| 3 | Leitura/escrita física arbitrária: constrói a tabela de páginas falsa em um endereço físico conhecido (do estágio 2), então sem loteria de spray e sem caminhadas selvagens | src/cheese_pa.c |
| 4 | Root: resolve símbolos do kernel offline a partir da imagem de firmware, deriva o slide do KASLR no dispositivo, faz patch de __do_sys_capset com um stub commit_creds(&init_cred), chama capset() | src/call_capset.c, scripts/ |