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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
cve-2026-98260-uaf — CVE-2026-98260(posix-cpu-timers exec UAF) 익스플로잇 연구: S9280 Android 14 GKI 6.1 — UAF 트리거 + 제로 쓰기 프리미티브 + SELinux Permissive. 인수인계 문서는 HANDOFF.md에 있음 | Kitploit
도구/GitHubGitHub/nanoturtle1145/cve-2026-98260-uaf
Android SecurityPrivilege EscalationMemory ForensicsVulnerability AnalysisExploitationPapers & ResearchBinary Exploitation
GitHubnanoturtle1145/cve-2026-98260-uaf

cve-2026-98260-uaf

CVE-2026-98260(posix-cpu-timers exec UAF) 익스플로잇 연구: S9280 Android 14 GKI 6.1 — UAF 트리거 + 제로 쓰기 프리미티브 + SELinux Permissive. 인수인계 문서는 HANDOFF.md에 있음

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-98260 — 트리거 전용 PoC (posix-cpu-timers UAF)

취약점

exec: Cleanup POSIX timers right after de_thread() (작성자 Hyunwoo Kim, 2026-09-11 업스트림, Fixes: 55e8c8eb2c7b = 5.7부터).

비 leader 스레드 E에 CLOCK_THREAD_CPUTIME_ID 타이머가 걸려 있음; execve() 안에서 de_thread()가 tid를 교환한 후, 타이머가 PID로 task를 역조회하는 데 실패하고 노드는 여전히 E의 posix_cputimers에 남아 있음. 만약 exec_mmap()의 mmap_read_lock_killable()이 -EINTR을 반환하면 (exec 실패이고 정리 코드는 더 뒤에 있음), do_exit() → exit_itimers()가 k_itimer를 해제하지만 노드는 제거되지 않음; 부모 프로세스가 wait4()로 회수할 때 cleanup_timerqueue() → timerqueue_del()이 이미 해제된 객체에 대해 동작 → UAF.

패치 상태 (2026-10-27 실측으로 각 안정 브랜치 fs/exec.c 확인)

브랜치posixtimer_exec상태
linux-6.1.y없음미수정
linux-5.15.y없음미수정
linux-6.6.y있음수정됨 (6.6.158+)
linux-6.12.y있음수정됨 (6.12.112+)

→ 본 프로젝트 대상 (5.15 / 6.1 라인, 그리고 6.6.158 미만의 6.6)은 전부 영향 범위에 있음.

왜 이 트리거가 "결정적"이고 타이밍에 거는 것이 아닌가

  1. 긴 쓰기 락 윈도우: fragment_vmas()는 memfd + MAP_FIXED로 주소 공간을 --vmas (기본 50000)개의 병합 불가 VMA로 분할함 (각각의 vm_pgoff가 모두 0이고, 인접 주소의 pgoff가 연속되지 않음 → vma_merge 실패). locker 스레드가 반복적으로 mprotect로 전체 구간을 덮어, 수만 개의 VMA를 순회하게 하여 mmap 쓰기 락이 밀리초 단위로 유지됨.
  2. 정밀 동기화 지점: killer는 tgkill(tgid, 이전 tid, 0)이 ESRCH를 반환하는 것으로 "de_thread()가 tid 교환을 완료함"을 판단하고, 이때 비로소 SIGKILL을 보냄.
  3. 피해 스레드는 exec_mmap()에서 반드시 블록됨 — mmap_read_lock_killable() (읽기 락이 쓰기 락에 막힘)에서, 그리고 SIGKILL은 그 뒤 마이크로초 단위로 도달 → 해당 호출이 -EINTR을 반환 → exec 실패. 윈도우 (ms) ≫ 반응 시간 (µs), 따라서 운에 의존하지 않음.

⚠️ 구분에 주의: 트리거는 결정적임; UAF에서 임의 쓰기/cred 변경까지는 posix_timers_cache의 slab 재사용 편성 (order, objects/slab, CPU partial, memcg 귀속)에 달려 있으며, 이는 측정 가능하고 반복 가능한 힙 엔지니어링이지 경쟁 조건이 아님.

빌드

export ANDROID_NDK_HOME=/home/nt/项目/samsung_root_research/06_工具链/android-ndk-r29
make            # 产物 trigger(aarch64 / Android 21+ / 仅依赖 libc、libdl)

테스트 단계

adb push trigger /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/trigger"
adb shell "/data/local/tmp/trigger --check"        # 先看环境
adb shell "/data/local/tmp/trigger; echo exit=\$?"

성공 표시(셋 중 하나만 충족하면 됨):

  • [*] 开始触发가 출력된 후 기기 재부팅/블랙스크린(panic_on_oops=1일 때 가장 직접적)
  • 프로세스가 시그널로 종료됨(exit=137/139), 그리고 이후에도 시스템이 살아 있음
  • dmesg(root 필요)에 cleanup_timerqueue / timerqueue_del / rb_erase / rb_next의 호출 스택이 나타남

실패(미발동) 시 프로그램이 스스로 --attempts회 재시도하고 매 회차 wait4 결과를 출력하며, 마지막에 파라미터 조정을 안내함: --vmas 120000 --lockers 4 --cpu 0.

알려진 제한

  • 발동에 성공하면 거의 반드시 기기가 재부팅됨(이것이 "성공"의 증거), 테스트 전에 저장하지 않은 작업이 없는지 확인할 것.
  • Samsung의 panic_on_oops가 0일 수 있음: 이때는 작업이 종료되고 커널이 손상된 rbtree를 가진 채 계속 실행되므로, 2~3회 연속 실행하여 후속 panic이 나타나는지 관찰할 것을 권장.
  • 본 PoC는 권한 상승 코드를 포함하지 않음(reclaim 안 함, cred 건드리지 않음), 도달 가능성과 결정성 검증에만 사용.

참고

  • 업스트림 패치 https://github.com/torvalds/linux/commit/acb03d3881818581052924a9bbbe92b8741ed448
  • Honor MagicPad3 Pro 전체 체인(6.12.38, uid-0 및 KSU 부팅 포함) https://github.com/woshigenm/CVE-2026-98260
  • vivo/iQOO 6.6.127 이식 노트(회수 오케스트레이션, SLUB 파라미터) https://github.com/janewas/cve-2026-98260-vivo-gki-research

실측 기록(SM-S9280 / e3q / 6.1.162 / Android 17 / 보안 패치 2026-09-05)

테스트 환경: S9280ZHU6EZIF, 커널 6.1.162-android14-11-34343818-abS9280ZHU6EZIF (빌드 2026-09-20), 루팅되지 않음, shell 도메인(uid 2000), SELinux Enforcing.

결론: 발동은 결정적이다(타이밍에 의존하지 않음)

회차관측의미
매 회차부모 프로세스 tgkill(child_tgid, etid, 0)가 ESRCH 반환de_thread()가 tid 교환을 완료함
매 회차이어서 kill(child_tgid, SIGKILL), 자식 프로세스 rc=-9exec가 중단됨(성공한 exec의 rc=0이 아님)
--no-killer 대조exec가 armed에서 종료까지 소요 277 ms(killer 있을 때 287 ms)mmap 쓰기 락이 실제로 exec_mmap()의 mmap_read_lock_killable()을 막았음

5/5 회차 모두 "ESRCH → SIGKILL → exec 실패"에 적중, exec 성공은 단 한 번도 없었음. 이는 de_thread 이후 exec를 중단하는 윈도우가 제어 가능하고 안정적임을 증명함; 윈도우는 locker의 mprotect(6만 VMA, 단일 200–700 ms)에서 오며, 부모 프로세스의 반응은 µs 수준.

겪은 두 가지 핵심 함정(모두 코드에 이미 수정됨)

  1. killer는 반드시 부모 프로세스에 있어야 함: de_thread()는 zap_other_threads()로 같은 프로세스의 다른 스레드를 모두 죽이므로, 자식 프로세스에 작성된 killer는 de_thread 시점에 스스로 먼저 죽어 ESRCH를 영원히 볼 수 없음(exec가 매번 성공하는 것으로 나타남).
  2. reclaim 타이밍: SIGKILL 직후 spray는 무효——그때 자식 프로세스가 아직 죽지 않았고 k_itimer가 아직 RCU로 해제되지 않았음. 올바른 순서는 waitid(WNOWAIT)로 좀비 대기 → RCU 콜백 해제 대기 → 페이지 선점 → 마지막에 wait4로 회수 경로 발동.

아직 미완: UAF를 가시화하기

현재 "해제 후 노드가 여전히 큐에 있음" 단계는 안정적으로 달성했으나, wait4 시점의 rb_erase에 oops가 없음 —— 희생 slab 페이지를 아직 우리 spray가 선점하지 못했음을 의미함(posix_timers_cache의 partial slab이 여전히 캐시되어 페이지가 buddy로 돌아가지 않음). 이는 exploit의 회수 오케스트레이션이며 레이스 문제가 아니므로, vivo 노트의 방식대로: 먼저 k_itimer 자체로 slab을 가득 채우고/비워 전체 페이지 반환을 촉진한 뒤, order-0 캐리어(AIO ring 등)로 split된 반쪽 페이지를 정확히 받아내야 함.

다음 단계 제안: 이 기기를 기준으로 삼아 docs/08-reclaim-choreography.md(vivo 노트)의 사고방식대로 표적 회수를 수행하거나, KASAN이 포함된 자체 빌드 커널로 UAF가 실제로 발생하는지 검증할 것.


전체 체인 이식 가능성(SM-S9280 / 6.1.162 실측 임계값)

공개 체인(Honor uid0.c)은 tracefs의 kmem 이벤트에 의존하여 두 가지 핵심 작업을 수행함:

용도Honor 611/6.12에서 사용하는 것본 기기(Samsung 6.1.162)
피해자 k_itimer 주소 V 찾기(payload의 페이지 내 오프셋 계산)kmem:kmem_cache_alloc이벤트는 존재하지만 events/kmem/*/enable이 0644 root:root라서 shell이 enable할 수 없음(events/sched/*/enable은 쓰기 가능, kmem은 불가)
order-1 slab 페이지가 buddy로 반환되었는지 판정(정확한 페이지 탈취 시점)kmem:mm_page_free위와 동일, 존재하지만 enable 불가
원시 trace 버퍼 읽기per_cpu/cpuN/trace_pipe_raw읽기 가능(-r--r----- root readtracefs)✓
KASLR slide각 trace 이벤트의 static site가능: sched 이벤트는 enable 가능, 프로젝트에 이미 동일한 기법 있음 ✓

결론: 공개 체인은 그대로 이식할 수 없다. 부족한 것은 커널 능력(이벤트는 모두 있음)이 아니라 shell에 kmem 이벤트의 enable 권한이 없다는 점이다.

실행 가능한 대안(개조 필요)

  1. 더 이상 V가 필요 없음: payload를 페이지 내 위치 무관하게 만든다——8KB 페이지 안의 가능한 모든 object 슬롯(k*0x108 + 0x78 + 0x10)마다 target을 한 번씩 기록하면, 피해자 객체가 slab의 어느 슬롯에 떨어지든 rb_erase가 target을 때린다. 이는 「전 슬롯 커버리지」로 kmem_cache_alloc의 주소 발견을 대체하는 것과 같다.
  2. 페이지 탈취를 휴리스틱으로 변경: mm_page_free의 정밀 동기화를 제거한 뒤에는 RCU 유예 기간 후 대량으로 sk_buff 데이터 페이지(AF_UNIX SO_SNDBUF)를 spray하여 방금 반환된 order-1 페이지를 노려야 하므로 성공률이 떨어지며, precheck + 재시도 루프가 필요하다.
  3. KASLR slide: 사용 가능한 sched 이벤트로 수행한다(프로젝트에 이미 SLIDE_TRACEFS_* 메커니즘 있음).
  4. 가장 큰 결손: cred 주소를 어디서 얻을 것인가. 공개 체인은 동일한 kmem 누출로 cred를 얻지만, 본 기기에서는 불가능하다. 선택지:
    • 본 프로젝트에 이미 있는 KernelSnitch / pipe-page 누출 엔진을 재사용한다(작업량은 크지만 기성품);
    • 또는 먼저 제로 쓰기 원시 연산으로 정적 주소(예: selinux_state.enforcing, slide는 알려짐)를 공격하지만, 그것은 SELinux를 끌 수 있을 뿐 uid 0을 얻지는 못한다.

결론: 완전한 체인 = 수일 규모의 이식(먼저 위치 무관 페이지 탈취로 UAF를 가시화한 뒤, cred 누출을 해결), 한 번의 라운드로 통과할 수 있는 일이 아니다.


연구 진행 상황(2026-11-01 기준)

기기: SM-S9280 / 6.1.162-android14-11-34343818-abS9280ZHU6EZIF(One UI 9.0 / Android 17) 경로: UAF → 제로 쓰기 원시 연산 → cred 제로 쓰기 → uid 0. 세 단계로 추진하며, 현재 두 번째 단계에서 막혀 있음.

개요

단계목표상태
1. 트리거posix-cpu-timers UAF를 결정적으로 발생시킴✅ 실기 확인
2. UAF 가시화/제어해제된 객체를 우리가 제어(캐리어 페이지 탈취 or 동일 slab 재사용)✅ 페이지 탈취 실기 성립(P10: victim 페이지가 buddy로 반환되어 우리 측이 받아냄); ⚠ 타이밍 수정 필요: 캐리어 안착이 reap보다 반드시 선행해야 함
3. 권한 상승제로 쓰기로 cred 변경 → uid 0🔶 체인은 구성됨, 실기 미성공

1. 트리거: 결정적으로 성립 ✅

de_thread() 후 exec 실패 → exit_itimers()가 k_itimer를 해제하지만 rbtree 노드는 제거되지 않음 → 부모 프로세스가 reap할 때 timerqueue_del()이 이미 해제된 객체를 조작. 실기 panic 스택이 곧 증거:

pc : rb_next+0x24/0x54        lr : timerqueue_del+0x38/0x74
Call trace: rb_next / posix_cpu_timers_exit+0x78 / release_task+0xec /
            wait_consider_task+0x65c / do_wait / kernel_wait4 / __arm64_sys_wait4

두 가지 핵심 구현 포인트(모두 코드에 이미 반영됨):

  1. locker는 반드시 clone(CLONE_VM|SIGCHLD) 독립 프로세스여야 하며, pthread를 사용할 수 없다 —— zap_other_threads()는 같은 thread group만 죽이므로, pthread locker는 de_thread() 시작 부분에서 죽고, 쓰기 락 해제 → exec 성공 → UAF 없음.
  2. 순서: exec 스레드의 pthread_create와 locker 스택의 mmap은 lockers가 쓰기 락을 획득하기 전에 완료되어야 한다.

二、UAF → 가시화: 페이지 탈취가 실기에서 성립 ✅ (다음 장애물은 타이밍)

도구 다운로드