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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
ghostlock-pfem10 — GhostLock (CVE-2026-43499) for OPPO Find X5 Pro (PFEM10) — OPlus watchdog 및 heap-spray detector 리버스 엔지니어링 | Kitploit
도구/GitHubGitHub/imeiplus/ghostlock-pfem10
Android SecurityPrivilege EscalationMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringMobile SecurityPayload DevelopmentBinary Exploitation
GitHubimeiplus/ghostlock-pfem10

ghostlock-pfem10

GhostLock (CVE-2026-43499) for OPPO Find X5 Pro (PFEM10) — OPlus watchdog 및 heap-spray detector 리버스 엔지니어링

4522일 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기

GhostLock — OPPO Find X5 Pro (PFEM10)

English · 中文

build

ColorOS 16 기반 OPPO Find X5 Pro용 GhostLock (CVE-2026-43499) 포팅. uid=0 자식 프로세스와 로드된 kernelsu.ko에 도달하며, 루트 프로세스는 가로채진다.

취약점

CVE-2026-43499 — futex PI use-after-free. remove_waiter()는 current가 requeuer일 때 current->pi_blocked_on을 지우며, 이는 rt_mutex_start_proxy_lock()의 -EDEADLK 롤백 경로에서 발생한다.

remove_waiter @ 0xffffffc0081ed254 — 패치 이전 형태.

디바이스

디바이스OPPO Find X5 Pro (PFEM10)
SoCSM8450 / Adreno 730
OSColorOS 16.0.3.520 (CN01)
커널5.10.236-android12-9-o-gaf2075ad2c06
부트로더잠김, green
VA_BITS39 — KIMAGE_TEXT_BASE = 0xffffffc008000000

상태

단계
Compact waiter 트리거 (CMP_REQUEUE_PI → EDEADLK)동작함
task_struct 유출 (perf)동작함
PI 쓰기 (8바이트; 값 = 0 또는 유효한 커널 주소)동작함
task+0x778 또는 task+0x780 단독 → Uid=root동작함 — 하지만 단일 필드 착지는 태스크를 분기 상태로 남기며, 이는 잠재적인 hard BUG_ON이다. 분기 위험 참조
두 필드에 하나의 값으로 쓰기 (일관된 쌍)❌ 스프레이된 페이지로는 결코 생성되지 않음. 전역 init_cred 별칭(09-14, CONTROL=1)으로만 관찰됨. 러너는 이제 이를 강제함(SAME_VALUE=1); 디바이스에서 실행되지 않음
자격 증명 세탁 (setresgid + setresuid)V12_LAUNDER=1 뒤에 구현됨; 디바이스에서 실행되지 않음
kernelsu.ko 로드됨동작함
루트 프로세스 생존⚠ 확립되지 않음 — 아래 참조
재부팅 메커니즘❌ 확립되지 않음. 하나의 후보(분기)는 이제 배제됨; 아래 참조
착지 기준으로서의 probe_state❌ 잘못됨 — 사용하지 말 것. 세 가지 반례; 아래 표 참조
pstore/ramoops 패닉 채널⚠ 계측기는 존재함; 채널은 결코 검증되지 않음 (아직 null 테스트 없음)
"피해자가 순수 사용자 공간에서 스핀함"⚠ 아직 판독값 없음 — uid.stream이 이제 utime/stime/nvcsw를 기록하므로 확인 가능
pi 측 단일 패스 이중 쓰기⚠ 확립되지 않음; pi.pc/pi.left는 fdset_map.h에서 하드코딩된 0
경로 A (UMH / modprobe_path)STATIC_USERMODEHELPER_PATH=""

"루트 프로세스 생존"에 관하여: evidence/kill.log의 실행들은 uid=0에 도달하고 kernelsu.ko를 로드하며, 실제로 이를 폴링한 실행에서 KernelSU 관리자 프로세스는 120초 동안 생존했고 kernelsu는 여전히 /proc/modules에서 Live 상태였다. 이후 실행에서 동일한 체인은 모듈이 여전히 Live인 상태에서 Android 프레임워크 서비스에 도달할 수 없게 만들었다(Can't find service: package/power/input/phone/wifi). 어떤 [ROOTCHECK-*] 커널 라인도, 어떤 $$sys_call_number@@ 페이로드도 결코 캡처된 적이 없으므로, 이후 실행의 상태 원인은 귀속되지 않는다. evidence/notes.md §2.3, §2.4 및 §7 참조.

오프셋

task_struct

필드오프셋
real_cred / cred0x778 / 0x780
캐시된 syscallno0xdf8
캐시된 uid / euid / gid / egid0xe00 / 0xe08 / 0xe10 / 0xe18

thread_info

필드오프셋
flags0x0
addr_limit0x8
ttbr00x10
preempt_count0x18

cred

필드오프셋필드오프셋
uid0x4cap_inheritable0x28
gid0x8cap_permitted0x30
suid0xccap_effective0x38
sgid0x10cap_bset0x40
euid0x14cap_ambient0x48
egid0x18
fsuid / fsgid0x1c / 0x20

익스플로잇 흐름```

LT perf leak target task_struct → file W7 stage 1 task+0x778 = V (real_cred → private sprayed page; V observed) W7 stage 2 task+0x780 = V (cred) ★ V12_W7_VALUE=V — THE SAME VALUE, not a new page W7 stage 3 V+8 = 0 (LOCAL repair of the page that was installed, ZERO shape) LT child fexecve(memfd of loader) — no execve of a /data path loader ksud late-load → kernelsu ... Live

**2단계에는 1단계의 값을 명시적으로 전달해야 한다.** 1단계와 2단계는 각자
자체적인 spray를 가진 두 개의 독립적인 프로세스이므로, "cred 페이지를 두
슬롯 모두에 기록하라"는 것은 함정이다: 순진하게 읽으면 `(pageA, pageB)`를
생성하고, `commit_creds`는 **포인터**를 비교하기 때문에 두 쓰기가 모두
성공하더라도 그 쌍은 서로 다르다. 이것은 가상의 상황이 아니다 — 정확히
실행 3과 9에서 일어난 일이다:```
run 9   0x778 shot  write value = 0xffffff88679bade0
        0x780 shot  write value = 0xffffff8785d6ade0     <- a different page
run 3   0x778 shot  write value = 0xffffff8787b5ade0
        0x780 shot  write value = 0xffffff881bad2de0     <- a different page

run_bootA.sh는 따라서 V12_W7_VALUE=<stage 1's observed value>로 stage 2를 실행하며, 그 값을 복구할 수 없으면 아예 실행을 거부한다. HOLD는 stage 2보다 오래 살아 있어야 한다. 그렇지 않으면 stage 1의 페이지가 해제되고 재할당되어 "같은 값"이 dangling pointer가 된다. the same-value rule을 참조하라.

부팅당 한 페이지가 복구된다. Stage 3는 V+8을 0으로 만든다. 두 개의 서로 다른 페이지가 있을 경우, 둘 다 0으로 만들면 gid/suid 스탬프(아래)가 지워지고 divergence가 agreement처럼 보이게 되므로, runner는 실제로 설치된 페이지만 복구하고 두 값이 다르면 중단한다.

cred 페이지는 payload.c에 의해 구성된다: 8개의 id 필드 모두 0, 5개의 capability set 모두 full, 그리고 user / user_ns / group_info는 root_user / init_user_ns / init_groups를 가리킨다. Stage 3가 존재하는 이유는 write의 side effect가 설치하는 cred의 cred+8(gid/suid)을 항상 덮어쓰기 때문이다.

init_cred에 관하여 — 명시적 이분법

여기 두 섹션이 서로 모순되곤 했다("절대 전역 init_cred가 아니다" vs "CONTROL=1은 cell 2를 재현한다", 그리고 cell 2는 바로 init_cred이다). 두 진술 모두 서로 다른 역할에 대해 참이다:

  • 타깃으로서 금지됨. init_cred 포인터를 쓰면 side effect가 init_cred+8을 전역적으로 손상시킨다 — init_cred는 모든 커널 스레드가 공유하며, Uid: 0 0 4294967176 0이 바로 그 손상이다. 코드는 V12_ALLOW_INIT_CRED=1이 의도적으로 설정되지 않는 한 이 경로를 거부한다.
  • 유일하게 입증된 일관된 쌍으로서 유지됨. ksud에 도달한 09-14 체인은 두 슬롯 모두에 하나의 고정 주소(0xffffff802a7e0be0)를 썼으므로, 구성상 real_cred == cred이다 — 그래서 execve까지 살아남았다. CONTROL=1이 이를 재현한다. 이는 control이지, 기반으로 삼을 구성이 아니다.

perf leak: PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_CLOCK, PERF_SAMPLE_REGS_INTR, exclude_user=1. [0xffffff8400000000, 0xffffff90000000)를 허용, 투표 ≥ 15%.

write primitive와 그 side effect

UAF는 rb_erase_cached Case 1-left를 통해 구동된다. 이는 하나가 아니라 두 개의 store를 제공한다:``` *(write_target) = write_value // the store you aim *(write_value + 0x08) = write_target // unavoidable side effect

`write_value`는 비트 0이 클리어된 상태에서 8바이트 정렬되어야 합니다 — 이는 `0`이거나 유효한 커널 주소입니다. **이것이 `g_boot_state`를 이 프리미티브로 설정할 수 없는 이유입니다**: `1`이 되어야 하는 바이트는 정렬 요구사항에 의해 하위 비트가 `0`으로 강제되며, `write_value`는 부작용이 발생하는 주소와 동일한 값입니다.

### 부작용은 `write_value`가 가리키는 대상에 기록됩니다

`write_value`는 *저장되는 값*이자 *부작용이 기록되는 주소*(`+8` 위치)입니다. 이를 전역 커널 객체를 가리키게 하면 해당 객체가 손상됩니다.

**W7은 정확히 이렇게 했습니다** — `write_value`를 `init_cred` 별칭을 향하게 하여 — 그리고 이는 읽기에서 확인할 수 있습니다. `out/t5_w7_778.txt`에서:```
shape shift=0 wps=5: in[0]=0xffffff802a7e0be0 (write_value) in[2]=0xffffff8800cdd178 (write_target)
W7[W7] write_target= 0xffffff8800cdd178
Uid:	0	0	4294967176	0

write_value는 init_cred 별칭이었고 write_target은 child_task+0x778이었습니다. init_cred+8은 gid/suid이므로, 부작용으로 0xffffff8800cdd178이 거기에 저장되었습니다: init_cred.gid = 0x00cdd178 그리고 init_cred.suid = 0xffffff88 = 4294967176 — 정확히 위 Uid: 줄의 4번째 awk 필드입니다. init_cred+8을 0으로 만들자 복구되었습니다(out/t5_repair.txt: Uid: 0 0 4294967176 0 → Uid: 0 0 0 0). 이것이 "W7 stage 3"의 전부였습니다.

이 경로는 이제 코드에서 거부됩니다. V12_W7_INIT_CRED=1은 V12_ALLOW_INIT_CRED=1도 함께 설정되지 않으면 설명과 함께 중단되며, W2/W6/LTC 경로는 private cred 페이지가 없을 때 더 이상 init_cred로 폴백하지 않고 대신 중단합니다. 기본값이자 유일하게 온전한 경로는 스프레이된 cred 페이지입니다.

부작용 자체는 피할 수 없습니다: write_value는 반드시 cred 포인터여야 하므로, cred+8은 항상 쓰기 대상으로 덮어써집니다. 선택할 수 있는 것은 그 위치뿐이며 — 그리고 복구는 이제 전역 객체에 대한 쓰기가 아니라 cred_page+8(stage 3)의 로컬 0 쓰기입니다.

잘못된 groups= 출력은 이 증상이 아니라 별개의 증상입니다. 이는 gid와 egid가 깨끗하게 읽힌 실행에서 관찰되었으므로, init_cred+8 부작용에서 비롯될 수 없으며, 가짜 cred 자체의 group_info 필드를 가리킵니다. evidence/notes.md §10.6을 참조하세요.

★ 단일 필드 착륙은 태스크를 분기 상태로 남기며 — 그것은 강성 BUG_ON입니다

이 프리미티브는 패스당 정확히 하나의 주소에 저장합니다. task+0x778 (real_cred)과 task+0x780 (cred)은 서로 다른 두 주소이므로, 0x778만 또는 0x780만 착륙한 쓰기는 태스크를 cred != real_cred 상태로 남깁니다 — 분기 상태입니다.

도구 다운로드