
4.19.152-perf+ Android 커널용 GhostLock(CVE-2026-43499) 어댑터
잠금 부트로더가 적용된 OnePlus/OPPO/realme 기기를 위한 커널 익스플로잇입니다. 부트로더를 잠금 해제하거나 부트 이미지를 수정하지 않고도 루트 권한 획득 및 KernelSU 설치를 달성합니다. 다중 기기 오프셋 테이블을 통한 커널 버전 런타임 자동 감지 기능을 제공합니다.
CVE-2026-43499 — Futex PI (우선순위 상속) Use-After-Free
Linux 커널 2.6.39 ~ 7.1에 영향을 미칩니다. 메인라인 7.1(커밋 3bfdc63936dd)에서 수정되었습니다. Android GKI 6.12.x는 여전히 취약합니다.
pselect6 시스템 콜은 fd_set 데이터를 커널 스택에 복사합니다. futex PI 대기자(waiter) 메커니즘과 결합하면, 해제된 스택 프레임이 rt_mutex_waiter 구조체로 재할당될 수 있습니다. 이후 PI 체인 탐색 중 발생하는 rb-tree 리밸런싱이 제어된 값을 임의의 커널 주소에 기록합니다.
| 기기 | SoC | 커널 | 상태 |
|---|---|---|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14275539 | 작동 |
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14552068 | 작동 |
| OnePlus 15 (CPH2749) | SM8850 | 6.12.23-...-ab14541642 | 작동 |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23-...-abogki463945075 | 작동 |
| Xiaomi 17 (pudding) | SM8850 | 6.12.69-...-abogki514973465 | 작동 (2026년 8월 업데이트) |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89-...-abogki446052083 | 작동 (PSELECT_SHIFT=-2) |
| OPPO Pad 4 Pro | SM8750 | 6.6.89-...-ab14358676 | 작동 (PSELECT_SHIFT=-2) |
| 기기 | SoC | 커널 | 비고 |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38-...-ab14552068 | Ace 6T와 동일한 커널. QEMU 검증: SP diff=-64. |
pselect 스택 오버레이는 해제된 rt_mutex_waiter가 stack_fds 버퍼의 사용자 제어 가능 영역에 위치할 때만 동작합니다. 대기자가 위치하는 곳은 커널 버전이 아니라 컴파일러 출력(PGO + LTO)에 의해 결정됩니다. 자세한 내용은 Stack Layout을 참조하세요.
| 기기 | SoC | 커널 | 사유 |
|---|---|---|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58-android16-6 | PGO가 do_futex 프레임을 제거 → SP diff=+32, waiter word=14. 안전한 시프트가 없음. |
| OPPO Find X7 | — | 6.1.157 | 6.1 GKI: waiter가 word 13에 위치 (모든 6.1 OPLUS/GKI 기기) |
| realme RMX5070 | SM6650 | 6.1.141 | 6.1 GKI: waiter가 word 13에 위치 |
| realme RMX3852 | SM8635 | 6.1.141 | RMX5070과 동일한 6.1 브랜치 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | 동일한 6.1 브랜치 |
| OnePlus 12 | SM8650 | 6.1.141 | 6.1 GKI: do_futex가 PGO로 인라인됨, waiter word=13/19 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | 동일한 6.1 브랜치 |
| OPPO PKW110 | — | 5.15.180 | do_futex 프레임 0x140 (정상의 4.5배) → waiter word=-29, 도달 불가 |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | 6.1 GKI: waiter가 word 13에 위치 (OPLUS 아님, 동일한 결과) |
| iQOO Z9 5G | — | 5.15.178 | do_futex 프레임이 너무 커서 waiter 도달 불가. OPLUS 기기가 아님 (vivo) |
기기 기능에 따라 자동으로 선택되는 두 가지 루트 경로:
off_ashmem_misc_fops != 0 필요 (BSS에 정적 miscdevice가 있는 C ashmem).
PI write (mode=4) → redirect miscdevice fops to fake fops (via W0 pi_tree)
configfs r/w established
→ pipe physrw (1-byte precise kernel r/w)
→ SELinux enforcing = 0 (single byte, no policycap corruption)
→ UMH: inject work_struct into system_unbound_wq
kernel executes /data/local/tmp/a/e --umh as UID 0
→ root script → ksud late-load → KSU installed
경로 B 대비 장점:
selinux_state.policycap을 손상시키지 않음 (OnePlus 13의 네트워크 문제 해결)현재 사용 가능한 기기: OnePlus 13 (커널 6.6, C ashmem). Rust ashmem 기기(6.12 GKI)에서는 사용 불가 — miscdevice가 힙에 할당되어 컴파일 시 주소를 예측할 수 없음.
UMH 오프셋 또는 C ashmem misc_fops를 사용할 수 없을 때 사용됩니다.
Write 1 (mode=1) → SELinux enforcing = 0
(low byte of kernel ptr = 0x00, 8-byte write)
Write 2 (mode=2) → task->cred = init_cred
(uid=0, all capabilities)
Root shell → ksud late-load (KernelSU LKM)
→ su -c load_policy (fix SELinux policycap)
→ dynamic manager registration
App (seccomp) → Write 1 (no perf needed)
→ mini-adb connect TCP (port from /data/local/tmp/a/adb_port, default 5555)
→ adb shell: full exploit (perf works, no seccomp)
→ root → KSU → network fix
BOOT_COMPLETED → BootCompletedReceiver
├─ su available → skip (soft reboot / already rooted)
└─ no root → GhostlockService → setsid exploit --bootstrap
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 ──►
익스플로잇은 가짜 waiter 필드(task, lock)를 fd_set 입력 비트맵에 기록합니다. 이 방식이 동작하려면 waiter의 task 및 lock 필드가 제어 가능한 영역(word 0-14)에 있어야 합니다.
Ace 6T ✅ (waiter at word 2):
░░████████████████░░│░░░░░░░░░░░░░░░░░░
▲waiter t l │
task/lock controllable
RMX5070 ❌ (waiter at word 13):
░░░░░░░░░░░░░████│██████████████░░░░░░
▲ │ t l
waiter task/lock ZEROED
실현 가능 규칙: waiter word + 11(rt_waiter_node의 lock 오프셋)이 14 이하여야 합니다. 최대 실현 가능 waiter word는 3입니다.
waiter 위치는 SoC 브랜치마다 다른 컴파일러의 스택 프레임 레이아웃(PGO + LTO + BOLT 최적화 프로파일)에 의해 결정됩니다. 동일한 커널 버전이라도 SoC마다 레이아웃이 다를 수 있습니다.
모든 커널 쓰기는 이미지의 선형 매핑(linear-map) 별칭을 통해 이루어집니다:
data_addr(x) = PAGE_OFFSET + (kernel_phys_load - PHYS_OFFSET) + (x - KIMAGE_TEXT_BASE)
부트로더가 kernel_phys_load를 선택하므로 SoC마다 값이 달라지며 boot.img나 DT에는 포함되지 않습니다. struct kernel_offsets의 기기별 필드이며, 0이면 target.h 기본값을 사용합니다.
| SoC | kernel_phys_load |
|---|---|
| SM8845 (Ace 6T, 15T) | 0xa8000000 |
| SM8750 (OnePlus 13, OPPO Pad 4 Pro) | 0xa8000000 |
| SM8850 (OnePlus 15, Xiaomi 17) | 0xc7800000 |
잘못된 값은 조용히 실패합니다 — 쓰기가 매핑된 RAM에는 여전히 이루어지므로 크래시가 없고 효과도 없습니다. PSELECT_SHIFT 문제로 오인하지 마세요. 동일 모델의 루팅된 기기에서 값을 확인하세요 (Kernel code는 _stext에서 시작하며 _text는 0x10000 더 낮습니다):
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... -> 0xc7800000
커널에 따라 waiter가 제어 가능한 영역 내의 서로 다른 위치에 배치됩니다. PSELECT_SHIFT를 사용해 조정하세요:
# Default (Ace 6T + OnePlus 15, 6.12): shift=0
/data/local/tmp/a/e
# OnePlus 13 (6.6): shift=-2
PSELECT_SHIFT=-2 /data/local/tmp/a/e
# Override kernel_phys_load for new SoCs (when /proc/iomem is not accessible):
KPHYS=0xc7800000 /data/local/tmp/a/e
check_feasibility.py의 waiter word는 신뢰할 수 없습니다. 프레임 산술은 정확하지만 0-저장(zero-store)에서 추론하는 구조체 오프셋은 정확하지 않습니다 (OnePlus 15에서는 word 3을 주지만 실제 측정값은 word 2입니다). 잘못된 시프트는 추측할 때마다 커널 패닉을 유발하므로, 루팅된 기기에서 직접 측정하세요:
echo 'p:ds do_select fdsin=+0(%x1)' >> /sys/kernel/tracing/kprobe_events
echo 'p:rw rt_mutex_wait_proxy_lock waiter=%x2' >> /sys/kernel/tracing/kprobe_events
# trigger FUTEX_CMP_REQUEUE_PI, then:
# PSELECT_SHIFT = ((waiter & 0x3fff) - (fdsin & 0x3fff)) / 8 - 2
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 설치는 ksud에 의존합니다 — 각 KMI 버전에 대한 kernelsu.ko 모듈이 내장된 바이너리입니다. 루트 스크립트는 기기에서 ksud를 찾아 ksud late-load --kmi android16-6.12를 호출합니다.