
GhostLock (CVE-2026-43499) — 부트로더가 잠긴 Android 기기를 위한 커널 익스플로잇
부트로더가 잠긴 Android 기기를 위한 커널 익스플로잇. 부트로더를 잠금 해제하거나 부트 이미지를 수정하지 않고 임시 루트 + KernelSU 설치를 달성합니다. 다중 기기 오프셋 테이블을 통한 커널 버전 런타임 자동 감지.
CVE-2026-43499 — Futex PI(우선순위 상속) Use-After-Free
Linux 커널 5.7 ~ 7.1에 영향을 미칩니다. 안정 버전 6.1.175, 6.6.140, 6.12.86에서 수정되었습니다. 2026년 9월 기준 대부분의 Android 기기는 여전히 패치되지 않은 상태입니다.
pselect6 시스템 콜은 fd_set 데이터를 커널 스택에 복사합니다. futex PI 대기자 메커니즘과 결합될 때, 해제된 스택 프레임이 rt_mutex_waiter 구조체로 재사용될 수 있습니다. 이후 PI 체인 순회 중 rb-tree 재조정이 임의의 커널 주소에 제어된 값을 기록합니다.
| 기기 | SoC | 커널 | GKI 브랜치 | SHIFT |
|---|---|---|---|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38 | android16-5 | 0 |
| OnePlus 15 (CPH2745/2747/2749) | SM8850 | 6.12.23 | android16-5 | 0 |
| OnePlus 15 (PLK110) | SM8850 | 6.12.58 | android16-6 | 0 |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23 / 6.12.69 | android16-5 / android16-6 | 0 |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89 | android15-8 | -2 |
| OPPO Pad 4 Pro | SM8750 | 6.6.89 | android15-8 | -2 |
| 기기 | SoC | 커널 | 비고 |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38 | Ace 6T와 동일 커널 |
| OPPO Reno10 Pro+ (CPH2521) | SM8475 | 5.10.236 | 5.10 컴팩트 waiter, waiter word=0 |
| Sony Xperia 1 IV (nagara) | SM8450 | 5.10.218 | 5.10 컴팩트 waiter, waiter word=0 |
| OPPO Find X9 Pro (PLG110) | D9400 | 6.12.23 | MediaTek, android16-5 |
| Vivo X200 Ultra (V2454A) | SM8750 | 6.6.89 | C ashmem, 전통적 훅 |
| Vivo X Fold3 Pro (PD2337) | SM8650 | 6.1.124 | 6.1 컴팩트 waiter, waiter word=3 |
| Vivo T4 | SM8650 | 6.1.145 | 6.1 컴팩트 waiter, waiter word=3 |
pselect 스택 오버레이는 해제된 rt_mutex_waiter가 사용자 제어 가능한 stack_fds 영역(word 0–14) 내에 위치해야 합니다. 위치는 커널 버전이 아닌 컴파일러 PGO/LTO 프로파일에 의해 결정됩니다. 스택 레이아웃을 참조하세요.
| 기기 | SoC | 커널 | 근본 원인 |
|---|---|---|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58 | PGO가 do_futex 제거 → waiter word=14 |
| OnePlus 15 (PLK110) | SM8850 | 6.12.69 | PGO가 do_futex 인라인화 → waiter word=20 |
| OnePlus 12 | SM8650 | 6.1.141 | PGO가 do_futex 인라인화 → waiter word=13/19 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | OPLUS 6.1: waiter word=13 |
| realme RMX5070 | SM6650 | 6.1.141 | OPLUS 6.1: waiter word=13 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | OPLUS 6.1: waiter word=13 |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | 비 OPLUS 6.1: 동일 결과 |
| iQOO Neo 10 CN | SM8650 | 6.1.84 | do_futex 프레임 0xD0 → waiter word=-11 |
| OPPO PKW110 | — | 5.15.180 | do_futex 프레임 0x140 → waiter word=-29 |
| iQOO Z9 5G | — | 5.15.178 | do_futex 프레임이 너무 큼 |
| CPH2763 (OPPO) | — | 6.1.115 | OPLUS 6.1: PGO 인라인화, waiter word=24 |
| iQOO 12 | SM8650 | 6.1.145 | 6.1: waiter word=9, task/lock이 제로화된 영역에 위치 |
6.1 실행 가능성에 관한 참고: OPLUS 6.1 커널은 PGO가
do_futex를 인라인화하기 때문에 일관되게 불가능합니다. 그러나 일부 비 OPLUS 6.1 커널(vivo)은 표준 호출 체인을 유지하여 가능합니다(vivo T4, X Fold3 Pro). 실행 가능성은 기기별로 확인해야 합니다.
기기 기능에 따라 자동으로 선택되는 두 가지 루트 경로:
off_ashmem_misc_fops != 0 필요 (BSS에 정적 miscdevice가 있는 C ashmem).
PI write (mode=4) → redirect miscdevice fops to fake fops
→ configfs r/w → pipe physrw (1-byte precise kernel r/w)
→ SELinux enforcing = 0 (single byte, no policycap corruption)
→ UMH: inject work_struct into system_unbound_wq
→ root script → ksud late-load → KSU installed
사용 가능: OnePlus 13, OPPO Pad 4 Pro, 5.10/6.1 C ashmem 디바이스. Rust ashmem(6.12 GKI)에서는 사용 불가 — 힙 할당 miscdevice.
Write 1 (mode=1) → SELinux enforcing = 0 (8-byte write, corrupts adjacent bytes)
Write 2 (mode=2) → task->cred = init_cred (uid=0, all capabilities)
Root shell → ksud late-load → KSU → SELinux policy fix
W1+W2 이후, 익스플로잇은 SELinux 정책 바이너리의 config 필드(ANDROID_NETLINK_ROUTE + GETNEIGH에 대해 |= 0xC0000000)를 패치하고 /sys/fs/selinux/load를 통해 다시 로드하여 네트워크 연결을 복원합니다.
App (seccomp) → Write 1 → mini-adb TCP → adb shell: full exploit → root
부팅 시 실행되는 런처는 별도의 GhostLock Anchor 앱에서 제공됩니다.
NFDS=320인 경우, core_sys_select는 커널 스택에 256바이트 크기의 stack_fds 버퍼를 할당합니다:
stack_fds: 0 5 10 14 | 15 20 25 29
├─in─┤─out─┤─ex──┤ ├res_in┤res_out┤res_ex┤
◄── USER CONTROLLED ──►│◄── KERNEL ZEROED ──►
익스플로잇은 fd_set 입력 비트맵에 가짜 waiter 필드(task, lock)를 배치합니다. 6.12 중첩 waiter(14 words)의 경우: 최대 허용 가능한 waiter word = 3. 5.10/6.1 컴팩트 waiter(10 words)의 경우: 최대 허용 가능한 waiter word = 7.
waiter 위치는 호출 체인 깊이에 따라 달라집니다:
| 패턴 | 호출 체인 | 가능 여부 |
|---|---|---|
| android16-5 (6.12) | sys_futex → do_futex → fwrpi | ✅ waiter word=2 |
| android16-6 (6.12.58) | sys_futex → do_futex → fwrpi | ✅ waiter word=2 |
| android16-6 (6.12.69 OPLUS) | sys_futex → fwrpi (PGO 인라인됨) | ❌ waiter word=20 |
| android16-6 (X9 Ultra) | sys_futex → fwrpi (PGO 인라인됨) | ❌ waiter word=14 |
| android15-8 (6.6) | sys_futex → do_futex → fwrpi | ✅ waiter word=2 (SHIFT=-2) |
| vivo 6.1 | sys_futex → do_futex → fwrpi | ✅ waiter word=3 |
| OPLUS 6.1 | sys_futex → fwrpi (PGO 인라인됨) | ❌ waiter word=13+ |
| 5.10 OPLUS | sys_futex → do_futex → fwrpi | ✅ waiter word=0 |
모든 커널 쓰기는 linear-map 별칭을 사용합니다. 부트로더가 kernel_phys_load를 선택하며, 이 값은 SoC마다 다릅니다:
| SoC | kernel_phys_load |
|---|---|
| SM8845 (Ace 6T, 15T) | 0xa8000000 |
| SM8750 (OnePlus 13, OPPO Pad 4 Pro) | 0xa8000000 |
| SM8650 (vivo T4, X Fold3 Pro) | 0xa8000000 |
| SM8850 (OnePlus 15, Xiaomi 17) | 0xc7800000 |
잘못된 값은 조용히 실패합니다. 루팅된 기기에서 다음과 같이 읽으십시오:
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... → 0xc7800000
런타임에 재정의: KPHYS=0xc7800000 /data/local/tmp/a/e
/data/local/tmp/a/e # Default (shift=0)
PSELECT_SHIFT=-2 /data/local/tmp/a/e # OnePlus 13 (6.6)
5.10 커널은 0xffffffc008000000을 사용합니다(VA_BITS=39, 6.x 기본값 0xffffffc080000000과 다름). 이는 디바이스 오프셋 항목의 kimage_text_base 필드를 통해 자동으로 처리됩니다.
make NDK_ROOT=/path/to/android-ndk
또는 직접:
NDK=/path/to/android-ndk
"$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android35-clang" \
-O2 -Wall -Isrc/core -Isrc/devices -DTARGET_CONFIG_H=\"target.h\" \
src/core/main.c src/core/util.c src/core/slide.c \
src/core/fops.c src/core/pipe_physrw.c src/core/root.c \
src/core/miniadb.c src/core/umh_root.c \
-o ./ghostlock -fPIE -pie -pthread
GhostLock은 임시 루트를 제공합니다. KernelSU 설치에는 각 KMI 버전에 맞는 kernelsu.ko를 포함하는 ksud가 필요합니다.
| 방법 | 단계 |
|---|---|
| ReSukiSU APK (권장) | ReSukiSU 또는 이 포크를 설치하세요. libksud.so가 포함되어 있습니다. |
| CI 릴리스 | ReSukiSU CI에서 다운로드하세요 |
adb shell mkdir -p /data/local/tmp/a
adb push ./ghostlock /data/local/tmp/a/e
adb shell chmod 755 /data/local/tmp/a/e
adb shell /data/local/tmp/a/e
부팅이 완료되고 ADB가 준비된 후 실행합니다.
boot.img만 있으면 됩니다. 루트 권한도, 디바이스 접근도 필요하지 않습니다.
전체 워크플로는 kallsyms 도구 가이드를 참고하거나, 다음을 실행하세요:
python tools/extract_target.py --kallsyms kallsyms.txt
python tools/extract_btf.py kernel
커널 바이너리에서 futex_wait_requeue_pi를 대상으로 하는 모든 분기 명령을 검색하여 do_futex가 PGO 인라인되었는지 확인합니다. __arm64_sys_futex가 do_futex를 우회하여 이를 직접 호출하는 경우, 해당 기기는 실행 가능성이 낮습니다.