
GhostLock réduit à une seule primitive : SELinux désactivé sur Galaxy A17 (BZA5) via futex PI UAF (CVE-2026-43499). Pas de root, pas de patch de cred, pas de rwforge.
Un fork minimal de GhostLock qui ne conserve qu'une seule primitive : désactiver SELinux via CVE-2026-43499 (futex PI UAF).
ghost-hoock running on Samsung A17
ghost-hoock est un fork allégé de l'exploit GhostLock par Mobile Hacking Lab, réduit à une seule primitive :
Une écriture contrainte via futex PI UAF ->
selinux_enforcing = 0.
Pas de root, pas d'écrasement de cred, pas de canal rwforge, pas d'UMH, pas de configfs. Juste le minimum nécessaire pour basculer SELinux en mode permissif sur le noyau vulnérable.
Exemple de sortie sur un 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)
Puis :
$ getenforce
Permissive
getenforce returns Permissive
L'exploit cible CVE-2026-43499 — un use-after-free dans la chaîne rt_mutex PI (Priority Inheritance) du futex du noyau Linux. La chaîne dans ghost-hoock comporte quatre étapes :
Canal auxiliaire temporel contre la table de hachage futex du noyau. Nous martelons FUTEX_WAKE_PRIVATE sur un ensemble de futex en espace utilisateur, mesurons les deltas rdtsc et corrélons les collisions de buckets de hachage. Cela permet de récupérer l'adresse de notre propre mm_struct — la base de la page de spray dont nous aurons besoin plus tard.
Il s'agit de la technique KernelSnitch, reprise telle quelle de l'exploit original.
Nous allouons une grande page slab d'order-3, puis la disposons avec la structure de faux objets utilisée par la route PI :
| Offset | Objet | Rôle |
|---|---|---|
0x0E80 | fake_lock | Faux rt_mutex |
0x0F80 | fake_fops | Fausse table file_operations |
0x1180 | fake_w0 | Faux rt_mutex_waiter utilisé comme arbre cible |
0x1240 | fake_right | Faux nœud droit de l'arbre rb — c'est de là que provient la valeur écrite |
0x1260 | fake_left | Faux nœud gauche de l'arbre rb |
0x1280 | fake_task | Faux task_struct |
La page entière est envoyée via un socket AF_UNIX sous forme de SKB_SEND_SIZE = 2 * ORDER3_SIZE de sendmsg, de sorte que les données du skb atterrissent sur notre page divulguée. Ensuite, nous la libérons dans un ordre contrôlé afin que notre page se retrouve sur un slab partiel par CPU que nous pouvons récupérer.
Trois threads :
FUTEX_WAIT_REQUEUE_PI sur f_wait, ciblant f_pi_target.FUTEX_LOCK_PI sur f_pi_target puis sur f_pi_chain.sched_setattr(tid, SCHED_BATCH, nice=19) sur le TID du waiter, ce qui déclenche rt_mutex_setprio() et force le noyau à parcourir le faux arbre PI.Un quatrième appel depuis le thread principal — FUTEX_CMP_REQUEUE_PI(1, f_pi_target) — lance la requeue. À l'intérieur du noyau, rb_erase() s'exécute contre notre faux arbre.
pselect() / select() copie le fd_set de l'utilisateur dans la pile du noyau puis le parcourt. Nous agençons les bitmaps du fd_set de sorte que les mots que le noyau traite comme des pointeurs d'arbre rb atterrissent sur fake_right et son parent — et le rb_set_parent(child, parent) résultant devient :
*(uint64_t *)target = value | color
Pour mode = 1 (Write 1), target = selinux_enforcing et value = base + 0x100, ce qui s'encode en byte0 = 0, byte1 = 1. Le noyau écrit 0 dans selinux_enforcing[0] — SELinux est désormais permissif.
ret = 6 (au lieu du 9 par défaut) confirme que l'écriture a abouti : le consumer a atteint la cible pendant select(), le réveillant prématurément.
Il s'agit d'un fork de mobilehackinglab/ghostlock-a17 (MIT). Ce qui suit est repris à l'identique de l'exploit amont :
| Composant | Fichier | Notes |
|---|---|---|
| KernelSnitch | src/kernelsnitch/* | Fuite de mm_struct via le timing du hash futex |
| Heap spray | src/spray.c | Structure de faux objets, prepare_skb_payload, prepare_kernel_page |
| Route 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 | Table de symboles extraite de 6.12.23-android16-5-abA175FXXS5BZD2-4k |
| En-tête cible BZA5 | include/target.h | Disposition des adresses, offsets de payload (sous-ensemble W1 uniquement) |
| Offsets de structures runtime | include/runtime_struct_offsets.h | Macros _RSO() pour les champs de task_struct |
Le code auxiliaire (macros pr_*, SYSCHK, pin_to_core, set_limit, set_unbuffer) est également conservé tel quel depuis l'original.
Le GhostLock original obtient un root complet sur l'A17 : il installe un canal R/W physique rwforge, patche cred / real_cred, exécute un helper UMH avec les identifiants init, capture les logs, etc. Dans ghost-hoock, tout ce qui suit la première écriture contrainte a disparu.