Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
GhostLock-GOT-W29 — HUAWEI MatePad Pro 11 GOT-W29에 대한 CVE-2026-43499 (GhostLock) 연구 | Kitploit
도구/GitHubGitHub/zzzxxxxxxxxxx/ghostlock-got-w29
Privilege EscalationVulnerability AnalysisExploitationReverse EngineeringMobile SecurityBinary Exploitation
GitHubzzzxxxxxxxxxx/ghostlock-got-w29

GhostLock-GOT-W29

HUAWEI MatePad Pro 11 GOT-W29에 대한 CVE-2026-43499 (GhostLock) 연구

저장소 보기
612918일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-43499 (GhostLock) — HUAWEI MatePad Pro 11 GOT-W29

GOT-W29(HarmonyOS 4.0, kernel 4.19.157-perf+)에서의 CVE-2026-43499(rtmutex/futex-PI 스택 UAF, "GhostLock") 권한 상승 연구.

최종 결론

  1. 취약점은 실제이며 트리거 가능: 값 보유 PI 링이 FUTEX_CMP_REQUEUE_PI를 -EDEADLK로 반환하게 만들고, 롤백 경로가 remove_waiter()의 current != waiter->task 정리 버그를 활성화한다(본 커널 kernel/locking/rtmutex.c:1110-1112, 업스트림 수정 3bfdc63936dd), waiter의 pi_blocked_on이 댕글링 상태가 된다(자신의 커널 스택 E-0x1b0 위치의 rt_waiter를 가리킴).
  2. 쓰기 프리미티브는 순수 사용자 공간에서 엔드투엔드 검증됨(QEMU kernel.patched, GDB/KPM 주입 없음): EDEADLK 댕글링 → setsockopt(SOL_IP, MCAST_BLOCK_SOURCE, group_source_req 0x108) 로 fake waiter 전달(copy 착지점 E-0x210, waiter를 buf+0x60에 두어 정확히 E-0x1b0을 덮음) → sched_setattr로 walk 트리거 → rb_erase가 sysctl_bootid에 쓰기, 도구 자체 검증 write_ok=1(boot_id 상위 8바이트 = 0xffffff800b412320).
  3. 초기 "전달 교착" 결론은 철회됨: 기존 스캔은 sock_setsockopt의 0x80 프레임을 setsockopt 체인에 포함시켰다. 디스어셈블리 + QEMU 실측으로 __arm64_sys_setsockopt가 level != SOL_SOCKET에 대해 바로 blr ops->setsockopt 함을 확인, sock_setsockopt를 거치지 않는다. 수정 후 do_ip_setsockopt 복사 깊이 = 0x210(기존 판정 0x290), 기하학적으로 맞음. 배제된 메커니즘: pselect/select, ptrace, import_iovec, ppoll, SCTP(실측 0x158).
  4. 기기 측 남은 작업: MCAST setsockopt의 SELinux 허용 검증(mrx는 EMUI에서 shell 신분으로 동일 캐리어를 사용해 권한 상승에 성공, 가능할 것으로 예상), 실기기 반환 경로/park 동작 재확인(KPM으로 W 직접 관측 가능), 그리고 후속 익스플로잇 체인의 SELinux 적응(메커니즘은 이미 구현됨, --root 참조). HKIP 차이: GOT-W29 커널 심볼 테이블과 이미지에 hkip_*/hypervisor 훅이 없고, 전체 이미지의 97개 hvc 명령어도 cred/cap/SELinux 경로에 없음(KVM/SMCCC/디버그 전송 드라이버뿐), 즉 mrx의 hypervisor xid 보호는 본 기기에 존재하지 않을 가능성이 높다 — 실기기에서는 일반 root 절차로 바로 진행 가능.

기기

항목값
모델HUAWEI MatePad Pro 11 GOT-W29
SoCQualcomm kona (SM8250, Snapdragon 870)
시스템HarmonyOS 4.0 (104.0.0.136)
커널4.19.157-perf+(boot.elf 디스어셈블리 + firmware/symtab.txt)
VA39-bit, 4K pages, KASLR on

취약점과 트리거(최종판)

트리거 전제

  • shell 권한: adb shell 또는 Shizuku 모두 가능.
  • 화웨이의 두 백그라운드 관리 서비스를 끄고, shell 프로세스가 루트 cpuset을 얻도록 한다(그렇지 않으면 트리거 체인이 대상 경로로 진입하지 않음):
pm disable-user com.huawei.powergenie
pm disable-user com.huawei.iaware

메커니즘: PI 링(waiter→target→owner→chain→waiter)을 만들어 FUTEX_CMP_REQUEUE_PI가 EDEADLK 롤백을 타게 하고, 롤백 중 remove_waiter(lock, waiter)가 waiter->task가 아닌 current(requeue 스레드)로 정리한다: rt_mutex_dequeue는 실제 waiter 노드를 제거하지만 waiter의 pi_blocked_on은 정리되지 않는다(current->pi_blocked_on = NULL), 커널 스택 rt_waiter를 가리키는 댕글링 포인터가 된다.

값 보유 링(probe 링, 실측 안정적으로 -EDEADLK):

  • waiter: FUTEX_LOCK_PI(chain)로 chain 보유; FUTEX_WAIT_REQUEUE_PI(wait, 0, target)
  • owner: FUTEX_LOCK_PI(target)로 target 보유; FUTEX_LOCK_PI(chain) 블록
  • main: FUTEX_CMP_REQUEUE_PI(wait, 1, target) → -EDEADLK
  • waiter 타임아웃 반환(WAIT_REQUEUE_PI 2s) 후 댕글링 pi_blocked_on이 스택에 남음

주의: 기존 "self-own" 트리거, edeadlk_probe variant 11의 -EDEADLK는 모두 owner==task 조기 반환(rtmutex.c:1035)으로 댕글링 포인터를 만들지 않으며 무효.

성과

KASLR 누출(perf_event_open, shell 신분으로 가능)

perf_event_paranoid=-1에서 perf_event_open(PERF_SAMPLE_IP, exclude_user=1)로 커널 텍스트 클러스터를 샘플링, 알려진 심볼 오프셋에 정렬해 slide 획득:

samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000    runtime _stext=0xffffff9487280800

도구: tools/perf_kaslr.c.

EDEADLK 트리거(댕글링 포인터 생성)

[M] CMP_REQUEUE_PI ret=-1 errno=35 (EDEADLK!)
[W] WAIT_REQUEUE_PI ret=-1 errno=110 (ETIMEDOUT)  ← waiter 반환, pi_blocked_on 댕글링

도구: tools/edeadlk_probe.c.

쓰기 프리미티브와 전달 체인(QEMU 엔드투엔드 검증, 주입 없음)

rt_mutex_adjust_prio_chain의 walk가 댕글링 pi_blocked_on 위치의 fake rt_waiter를 읽음:

  • [3] next_lock == waiter->lock(lock = empty_zero_page의 독립 슬롯, 아래 참조)
  • [5] raw_spin_trylock(&lock->wait_lock) 제로 락 성공
  • [7] rt_mutex_dequeue → rb_erase 단일 좌자식 경로: *(rb_left) = rb_parent_color → 값 0xffffff800b412320(&loggers[0][1])을 sysctl_bootid + 8에 쓰기
  • [9] ownerless 깨끗한 반환

owner-ful 페이지 / prepare_skb_payload / kernelsnitch 불필요. fake rt_mutex는 walk 간에 같은 슬롯을 재사용할 수 없다: walk 종료 시 waiters.root/leftmost가 락 안에 이번 waiter를 가리키는 상태로 남고, 두 번째 walk가 옛 노드를 읽으면 이전 라운드에서 해제된 커널 스택을 역참조해 rt_mutex_top_waiter()의 BUG_ON(w->lock != lock)을 트리거한다 (QEMU 실측: --verify-all의 두 번째 테스트가 바로 BUG). 구현상 매 attempt마다 empty_zero_page + n*0x20을 취한다(총 128개 슬롯, SLIDE_LOCK_STRIDE/SLOTS), 자연히 전부 0이고 서로 오염되지 않는다.

QEMU(kernel.patched, nokaslr, --probe-cycle --verify-all) 도구 자체 검증 출력:

4.483 237 [+] slide write boot_id raw=00000000000000002023410b80ffffff lo=0 hi=ffffff800b412320 exp_lo=0 exp_hi=ffffff800b412320 ok=1
4.547 1   [+] slide write test PASSED
6.583 240 [+] slide leaf boot_id raw=00000000000000002023410b80ffffff lo=0 hi=ffffff800b412320 exp_lo=0 exp_hi=0 ok=1
6.636 1   [+] slide leaf test PASSED
8.671 243 [+] slide read boot_id raw=100000002401000030d74c0b80ffffff lo=0000012400000010 hi=ffffff800b4cd730 exp_lo=0000012400000010 exp_hi=ffffff800b4cd730 ok=1
8.724 1   [+] slide read test PASSED
10.811 1  [+] slide restore test PASSED
10.811 1  [+] slide verify all PASSED

(매 attempt의 requeue=-1/35 trigger=0/0 wait=-1/110 carrier=-1/22 일치: EDEADLK, walk 성공, hrtimer 타임아웃, 캐리어 복사 성공 후 가짜 주소 그룹 때문에 EINVAL 반환.)

주의: 검증 대상은 boot_id + 8에 쓴다: proc_do_uuid()는 bootid[8] == 0일 때 전체 UUID를 재생성하므로, 하위 8바이트에 쓰면 검증 읽기 자체에 덮어써진다(QEMU 실측 확인).

쓰기 프리미티브 형태(--verify-all 전면 자체 점검)

형태fake waiter tree 세 단어효과
pointer(rb_erase Case 2: 단일 좌자식)pc=value, right=0, left=target*(target) = value(64비트 전체, 색상 비트 보존); 부작용 *(value+8) = target, 따라서 value는 읽기 가능해야 하고 그 +8은 희생 가능해야 함
leaf(Case 1: 자식 없음)pc=target-8, right=0, left=0*(target) = 0; 깨끗한 쓰기 한 번(부작용 없음), pc 하위 비트=0 → RED → rebalance 미트리거, 하지만 __rb_change_child가 *(target-8)을 한 번 읽음

읽기 프리미티브(mrx-w09의 boot_id 채널 이식)

random_table[4].data(= &sysctl_bootid, link 0xffffff800b4cd730; .rela.dyn에서 유일하게 addend=0xffffff800b7f8b64인 R_AARCH64_RELATIVE로 확인)를 쓰기 프리미티브로 대상 주소 A로 바꾼 뒤, /proc/sys/kernel/random/boot_id를 읽으면 [A, A+16)을 반환한다. 주의:

  • proc_do_uuid()는 ((u8 *)A)[8] == 0일 때 16바이트 UUID를 재생성한다(그리고 A를 덮어씀), 읽기 창의 byte 8은 반드시 0이 아니어야 함;
  • 포인터 쓰기의 부작용이 A+8 = .data 슬롯 주소에 쓰므로, 읽어들인 상위 절반은 항상 그 슬롯 주소가 된다; 정확히 16바이트를 읽어야 할 때는 이 위치를 피해야 함.

--verify-read 자체 점검: .data를 boot_id ctl_table 항목 +0x10(maxlen=16, mode=0444 → 상수 0x0000012400000010)으로 가리켜 읽고, restore 단계로 sysctl_bootid로 되돌린다.

엔드투엔드 권한 상승(uid 2000 → 0, QEMU 검증됨)

--escalate(기기 shell 경로와 동일, 전 과정 RT 없음):

  1. perf로 current 누출: PERF_COUNT_SW_CPU_CLOCK + PERF_SAMPLE_REGS_INTR (arm64 33개 레지스터), 트리거는 nanosleep(1µs) 사용(단일 태스크일 때 sched_yield는 __schedule에 진입하지 않음). __schedule 내 x27 취득: mrs x27, sp_el0는 +0x40에 있지만, +0x68의 ldr w8, [x27, 0x50]!가 x27을 0x50 올리므로 되돌려야 함; 후보는 64바이트 정렬 요구.
  2. comm 검증: 읽기 프리미티브로 후보 task+0x990을 읽어 "ghostloc"과 같을 때만 계속 (읽기 부작용이 comm 상위 8바이트를 망가뜨리지만 프로세스 이름 표시에만 영향).
  3. cred 포인터 쓰기: task+0x980(real_cred) / task+0x988(cred) ← init_cred (0xffffff800b42e9c0), 이후 본 프로세스 getuid()==0.
  4. 복구: 포인터 쓰기 부작용이 init_cred+8(gid)을 대상 주소로 덮어쓰므로, leaf 제로 쓰기로 복원; 마지막으로 boot_id .data를 sysctl_bootid로 되돌림.

QEMU 실측(--no-rt --attempts 4, uid 2000에서 시작):

escalate: candidate 1/1 task=ffffffc07a373240 (n=12)
slide read8 boot_id ... lo=636f6c74736f6867 ...  ok=1        # "ghostloc" 확인
slide escalate-cred attempt 1 ok=1 ... target=ffffffc07a373bc8
slide child pid=246 uid=0 ...                                # 이후 자식 프로세스는 이미 root
init: ghostlock_exe exited status=0x0 (SUCCESS)              # wrapper 독립 종료 코드 표시

실패 모드(flaky): 몇 attempt마다 한 번씩 walk가 rt_mutex_adjust_prio_chain의 raw_spin_trylock 재시도 루프에 걸린다(fake waiter가 반환 경로에 clobber됨), 해당 attempt가 타임아웃되는 형태; --attempts N으로 재시도(기기 측도 mrx와 동일: 성공할 때까지 재시도).

후속 익스플로잇 체인(--root, 최종 단계 메커니즘은 QEMU에서 테스트 가능)

도구 다운로드