Skip to content
Exploit for Use After Free in Linux Linux_Kernel

Exploit for Use After Free in Linux Linux_Kernel

Sploitus September 14, 2026

> A minimal fork of [GhostLock]( that keeps **only one primitive**: turning off SELinux via CVE-2026-43499 (futex PI UAF).

- [What was kept from the original](#what-was-kept-from-the-original)

- [Limitations and risks](#limitations-and-risks)

`ghost-hoock` is a **stripped-down fork** of the [GhostLock]( exploit by [Mobile Hacking Lab]( reduced to a single primitive:

> **One constrained write via futex PI UAF -> `selinux_enforcing = 0`.**

No root, no `cred` overwrite, no rwforge channel, no UMH, no configfs. Just the minimum needed to flip SELinux into permissive mode on the vulnerable kernel.

Example output on a **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: SELinux === target=0xffffff800277e560 mode=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

The exploit targets **CVE-2026-43499** — a use-after-free in the Linux kernel's `futex` PI (Priority Inheritance) `rt_mutex` chain. The chain in `ghost-hoock` is four steps:

Timing side-channel against the kernel's futex hash table. We hammer `FUTEX_WAKE_PRIVATE` on a set of user-space futexes, measure `rdtsc` deltas, and correlate hash-bucket collisions. This recovers the address of our own `mm_struct` — the base of the spray page we later need.

This is the [KernelSnitch]( technique, taken verbatim from the original exploit.

We allocate a large `order-3` slab page, then lay it out with the fake-object layout used by the PI route:

| `0x0E80` | `fake_lock` | Fake `rt_mutex` |

| `0x0F80` | `fake_fops` | Fake `file_operations` table |

| `0x1180` | `fake_w0` | Fake `rt_mutex_waiter` used as target tree |

| `0x1240` | `fake_right` | Fake rb-tree right node — **this is where the write value comes from** |

| `0x1260` | `fake_left` | Fake rb-tree left node |

| `0x1280` | `fake_task` | Fake `task_struct` |

The whole page is sent through an `AF_UNIX` socket as `SKB_SEND_SIZE = 2 * ORDER3_SIZE` of `sendmsg`, so the skb data lands on our leaked page. Then we free it in a controlled order so that our page ends up on a per-cpu partial slab we can reclaim.

- **waiter** — enters `FUTEX_WAIT_REQUEUE_PI` on `f_wait`, targeting `f_pi_target`.

- **owner** — holds `FUTEX_LOCK_PI` on `f_pi_target` and then on `f_pi_chain`.

- **consumer** — spins calling `sched_setattr(tid, SCHED_BATCH, nice=19)` on the waiter's TID, which triggers `rt_mutex_setprio()` and forces the kernel to walk the fake PI tree.

A fourth call from the main thread — `FUTEX_CMP_REQUEUE_PI(1, f_pi_target)` — kicks off the requeue. Inside the kernel, `rb_erase()` runs against our fake tree.

`pselect()` / `select()` copies the user's `fd_set` into kernel stack and later walks it. We arrange the `fd_set` bitmaps so that the words the kernel treats as rb-tree pointers land on `fake_right` and its parent — and the resulting `rb_set_parent(child, parent)` becomes:

For `mode = 1` (Write 1), `target = selinux_enforcing` and `value = base + 0x100`, which encodes as `byte0 = 0, byte1 = 1`. The kernel writes `0` to `selinux_enforcing[0]` — SELinux is now permissive.

`ret = 6` (instead of the default `9`) confirms the write landed: the consumer hit the target during `select()`, waking it early.

This is a fork of [**mobilehackinglab/ghostlock-a17**]( (MIT). The following is taken **1:1** from the upstream exploit:

| **KernelSnitch** | `src/kernelsnitch/*` | mm_struct leak via futex hash timing |

| **Heap spray** | `src/spray.c` | fake-object layout, `prepare_skb_payload`, `prepare_kernel_page` |

| **PI route + pselect** | `src/route.c` | `prepare_pselect_fdsets`, `do_pselect_fake_lock_route`, `consumer_thread`, `waiter_thread`, `owner_thread` |

| **BZA5 offsets** | `include/offsets_bza5.h` | Symbol table extracted from `6.12.23-android16-5-abA175FXXS5BZD2-4k` |

| **BZA5 target header** | `include/target.h` | Address layout, payload offsets (W1-subset only) |

| **Runtime struct offsets** | `include/runtime_struct_offsets.h` | `_RSO()` macros for `task_struct` fields |

Auxiliary code (`pr_*` macros, `SYSCHK`, `pin_to_core`, `set_limit`, `set_unbuffer`) is also kept as-is from the original.

The original GhostLock achieves **full root** on the A17: it installs a rwforge physical R/W channel, patches `cred` / `real_cred`, runs a UMH helper with init creds, captures logs, and more. In `ghost-hoock`, everything past the **first constrained write** is gone.

| Removed file | Why it existed in the original |

| `rwforge_a17.c` | Marching-forger physical R/W channel via pipe_buffers |

| `pipe_physrw.c`, `pipe_reclaim.c` | Pipe-buffer reclaim -> arbitrary kernel read/write |

| `root.c` | `cred` / `real_cred` overwrite, `su` install, SELinux SID patching |

| `umh_root.c`, `wq_umh_root()` (in `main.c`) | Running an init-creds helper from a forged kernel workqueue item |

| `slide.c` | KASLR leak via `boot_id` oracle — **not needed on BZA5, KASLR is off** |

| `try_cfi_stage()` (in `fops.c`) | CFI-friendly configfs stage used to bootstrap the root path |

| `run_rwforge()`, `run_bootid_oracle()`, `rwforge_root_and_capture()` | The whole root pipeline |

| `install_embedded_su()`, `install_embedded_wallpaper()` | Root-install helpers |

| Write 2 (cred), `patch_cred_*`, `patch_task_seccomp` | Post-W1 credential takeover |

The original GhostLock remains **more complete and powerful** than this fork. `ghost-hoock` is not a replacement — it is a **minimal PoC** for one narrow task: turning SELinux off.

### On-device (clang, Termux or adb shell)

Requires `clang` and `make` in `$PATH`. Tested on Termux; also works via `adb shell` if the toolchain is present.

git clone

/path/to/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \

-D_GNU_SOURCE -D__ARM=1 -DTARGET_CONFIG_H='"target.h"' \

src/main.c src/spray.c src/route.c -o ghost-hoock

# ghost-hoock: ELF 64-bit LSB pie executable, ARM aarch64, ...

# copy the binary somewhere readable from the shell context

--attempts N number of W1 attempts (default: 20)

--no-drain skip slab_drain before each W1 attempt

· GHOSTLOCK_CORE — 0..N. CPU core the consumer thread is pinned to. Default: 0.

· KPHYS — 0x.... Kernel physical load address, if it differs from P0_KERNEL_PHYS_LOAD.

· PREPARE_SLABS — 4..64. Number of slab pages prepared during spray. Default: 32.

· PSELECT_SHIFT — -14..14. fd_set word shift. Debug only.

· FOPS_MAX_ATTEMPTS — 4..72. Max prepare_kernel_page attempts for the FOPS payload.

· RWF_DEBUG — any value. Print debug lines from payload construction.

- **Device:** Samsung Galaxy A17 SM-A175F (BZA5) — the target this offset table was extracted for.

- **Kernel:** `6.12.23-android16-5-abA175FXXS5BZD2-4k`.

- **Context:** must be run from the `shell` SELinux context (`u:r:shell:s0`), not from an app.

- **Permissions:** nothing special — no root required. The whole point is to disable SELinux *without* root.

> **Porting:** other devices/kernels need their own offset table. Add a new `OFFSETS_ENTRY(...)` in `include/offsets_bza5.h` with symbol offsets extracted from `vmlinux`/`kallsyms` for that build, then rebuild.

- **Kernel panics are possible.** The fork inherits the original's risk: a wrong `page_base` or a write that lands on unrelated memory will crash the kernel. The fork has *less* surface than the original (no rwforge, no cred patch, no UMH), so it is statistically safer, but not 100% bulletproof.

- **KASLR is off on BZA5.** The exploit relies on `slide = 0`. There is **no KASLR-leak** in this fork. If you port it to a KASLR-enabled kernel, you must bring back `slide.c` from the original.

- **SELinux only.** The fork does not give root. It only writes `0` to `selinux_enforcing`. If you need root, use the full [ghostlock-a17](

- **Single write.** Only Write 1 (`selinux_enforcing = 0`) is retained. Do not try to extend it into Write 2 or the rwforge pipeline without understanding the PI route deeply.

- **Requires the write to land within ~20 attempts.** If the first W1 attempt misses, the loop retries. On a fresh boot with a mostly-idle system, it typically lands on attempt 1.

│ ├── ghost_hoock.h # shared header, API

│ ├── offset.h # TARGET_CONFIG_H dispatcher

│ ├── offsets_bza5.h # symbol offsets (BZA5 only)

│ ├── runtime_struct_offsets.h # dynamic struct offsets (_RSO macros)

│ └── target.h # BZA5 addresses, payload layout

│ ├── main.c # CLI, offset selection, W1 loop

│ ├── spray.c # KernelSnitch + heap spray + ashmem

│ ├── route.c # PI route + pselect constrained write

│ └── kernelsnitch/ # mm_struct leak (from upstream)

**MIT** — same as the upstream [ghostlock-a17]( See `LICENSE`.

This fork retains the original copyright notice from `mobilehackinglab` (2026) and adds the fork authors on top, as required by the MIT terms.

The project is published strictly for **security research on your own device**. Running it against a device you do not own is illegal in most jurisdictions.

- [**Mobile Hacking Lab**]( — original [ghostlock-a17]( exploit, on which this fork is built.

- [**IAIK KernelSnitch**]( — `mm_struct` leak technique.

- Original CVE-2026-43499 researchers — for reverse-engineering the futex PI UAF.

- Upstream exploit:

- This fork:

Built for research. Tested on a single physical device. Use at your own risk.

Extracted Entities

Malware (1)

Platforms (1)