
CVE-2026-43499 GhostLock, Google TV가 탑재된 Chromecast(sabrina)용 루트 익스플로잇
Chromecast with Google TV(sabrina)용 루트 익스플로잇으로, CVE-2026-43499를 통해 Linux 커널의 futex PI(우선순위 상속) 서브시스템에서 발생하는 use-after-free를 악용합니다.
Android 14, 커널 5.15.170(PGO+BOLT+LTO, clang 17)을 실행하는 잠긴 부트로더 기기에서 루트 권한을 획득합니다.
GhostLock은 rt_mutex_start_proxy_lock()의 -EDEADLK 롤백 경로에서 호출되는 remove_waiter()의 버그를 악용합니다. 이 함수는 대기자 태스크의 것이 아니라 requeuer의 current->pi_blocked_on(이미 NULL)을 지웁니다. 대기자의 pi_blocked_on은 절대 지워지지 않아, 커널 스택에 있는 해제된 rt_mutex_waiter를 가리키는 댕글링 포인터가 남습니다.
이후 sched_setattr가 rt_mutex_adjust_pi -> rt_mutex_adjust_prio_chain을 트리거하고, 이는 댕글링 포인터를 따라가 재할당된 힙 페이지의 공격자 제어 데이터 위로 PI 체인을 순회하여 rb_erase를 통한 임의 쓰기 프리미티브를 제공합니다.
| 단계 | 기법 |
|---|---|
| KASLR 유출 | PERF_SAMPLE_IP를 사용한 perf_event_open(TID 게이트) — 최소 커널 IP는 .entry.text(+0x10000)에 있고, _text는 2MB 정렬이며 KASLR 슬라이드는 2MB의 배수이므로 min_ip & ~0x1fffff가 런타임 _text를 정확히 복원 |
| 태스크 구조체 유출 | PERF_SAMPLE_REGS_INTR를 사용한 perf_event_open(TID 게이트, 선형 맵 주소의 모드) |
| 심볼 테이블 검증 | setpriority(-20) 폭풍 중 security_capable()의 레지스터 인자에서 perf-samples로 &init_user_ns를 추출하고 kaslr_base + off_init_user_ns와 대조 — 블라인드 쓰기 전에 실행 중인 커널의 .data/.bss 레이아웃이 오프셋 테이블과 일치함을 증명 |
| mm_struct 유출 | KernelSnitch -- futex 해시 충돌 타이밍 부채널 |
| 힙 스프레이 | SLUB 폐기 안무(memfd-close, CPU-partial 오버플로) + io_uring_setup(256) order-2 페이지 회수 |
| 스택 오버레이 | AF_UNIX SOCK_SEQPACKET sendmsg -- move_addr_to_kernel이 128바이트 sockaddr를 커널 스택에 복사하여 댕글링 대기자의 task/lock/prio 필드를 덮어씀 |
| 순회 트리거 | 단조 nice 사다리(7 -> 14 -> 19)를 사용한 sched_setattr가 PI 체인 순회를 발동하며, 오버레이 라운드마다 한 번씩 실행 |
| 쓰기 프리미티브 | rb_erase Case 1: `{pc=(TARGET-8) |
| SELinux 해제(순회 0) | selinux_state의 8바이트 zero가 enforcing, checkreqprot, initialized 및 policycap[0..4]를 지움 — avc_denied()는 절대 거부하지 않고(enforcing=0) security_compute_av()는 allow-all로 단락(!initialized)되므로, 스왑 후 커널-SID(u:r:kernel)는 더 이상 중요하지 않음 |
| 자격 증명 스왑(순회 1+2) | task->cred = fake_cred 및 task->real_cred = fake_cred(uid=0, caps FULL, 스프레이 페이지에 자체 포함된 가짜 user_namespace; 두 쓰기 모두 필요 — exec 시 commit_creds()는 cred == real_cred가 아니면 BUG_ON 발생) |
| 루트 배터리 | raw-syscall 전용 프로브(uid/SELinux//proc/1//dev/kmsg/wifi/packages//data/data/kallsyms)를 /data/local/tmp/.ghostlock_out에 기록 |
| 루트 셸 | execve("/system/bin/sh") — exec 경로의 commit_creds가 fake_cred를 깨끗한 slab 자격 증명에 복사; 계획된 모든 지우기가 완료된 경우에만 실행 |
rb_right = 0을 사용하는 rb_erase 프리미티브는 임의의 커널 주소에 단일하고 깔끔한 8바이트 제로 저장을 수행한다(부수 저장 없음, 와일드 역참조 없음). selinux_state의 첫 qword를 제로화하면 이중 permissive(enforcing=0 + !initialized → allow-all)가 되어, 커널 SID에도 불구하고 모든 루트 획득 후 파일 I/O를 차단 해제한다security_capable() 경로에서 &init_user_ns를 perf로 유출하고, 이것이 kaslr_base + off_init_user_ns와 일치하지 않으면 cred 전용 흐름으로 중단한다ucounts = NULL을 갖춘 자체 포함 네임스페이스 — cap_capable은 첫 반복에서 일치하고 inc_rlimit_ucounts는 한 번의 루프 후 종료되어, &init_user_ns를 유출할 필요가 없다PERF_SAMPLE_TID 필터는 호출 스레드의 레지스터 스냅샷만 집계되도록 보장하여, 바쁜 시스템 서비스가 모드 투표를 지배하는 것을 방지한다5.15.170-android14-11-gf4a1f03072af (aarch64, PGO+BOLT+LTO, clang 17.0.2)CONFIG_FUTEX_PI=y, CONFIG_IO_URING=y, perf_event_paranoid=-1, SELinux enforcing, panic_on_oops=1, 사용자 네임스페이스 없음export ANDROID_NDK_HOME=/path/to/android-ndk
make -j$(nproc)
Android NDK가 필요합니다(r27에서 테스트됨). 정적으로 링크된 aarch64 바이너리를 생성합니다.
adb push ghostlock /data/local/tmp/
adb shell "cd /data/local/tmp && ./ghostlock --cred"
익스플로잇은 약 25초가 소요됩니다(KASLR/task/layout 유출 + 힙 스프레이 + KernelSnitch 브루트포스 + 3초짜리 오버레이 3회). 성공하면 루트 배터리를 /data/local/tmp/.ghostlock_out에, 마커를 /data/local/tmp/.ghostlock_root에 기록하고, 관대한 커널 아래에서 uid=0으로 /system/bin/sh를 exec합니다.
환경 설정 옵션:
GHOST_SELINUX=0 — SELinux 쓰기를 건너뛰고 검증된 cred 전용 경로(모드 6)를 실행GHOST_SELINUX_FORCE=1 — 기기 심볼 레이아웃 검증이 실패하더라도 SELinux 쓰기를 활성화GHOST_EXEC=0 — 배터리를 기록하고 셸을 exec하는 대신 exit(99)로 종료CRED_ATTEMPTS=n — 실행이 reclaim에 실패했을 때 전체 스프레이 재시도 횟수(기본값 3)이 익스플로잇은 GLM-5.3(Z.ai)을 커널 익스플로잇 컨설턴트로 두고 Claude Opus 4.6(Anthropic)이 이식 및 개발했습니다.
kernel/futex/, kernel/locking/rtmutex.c, lib/rbtree.c이 익스플로잇은 보안 연구 및 교육 목적으로 공개됩니다. 연구자가 소유한 기기를 대상으로 합니다. 소유하지 않은 기기에서 또는 승인 없이 사용하지 마십시오.
MIT