
Xiaomi 17(pudding) 대상 특정 Android CVE-2026-98260 연구용 포트
Xiaomi 17(코드명
pudding)의 특정 커널 빌드를 대상으로 한 Android AArch64 익스플로잇 체인: 일반 ADB shell에서 시작하여 순차적으로 SELinux를 Permissive로 설정하고, 현재 프로세스의 UID/GID를 0으로 만들고, 완전한 Linux capabilities를 채우며, 현재 부팅 주기 내에서 root 서비스를 제공한다.
이 프로젝트는 CVE-2026-98260-popsicle-android(Xiaomi 17 Pro Max / popsicle)를 참고하여 이식한 것이며, 처음부터 독립적으로 구현한 연구가 아닙니다.
비주 스레드의 execve()는 먼저 de_thread()를 실행하여 스레드 신원을 교환한다. 만약 exec가 그 이후에 실패하면(예: exec_mmap()이 SIGKILL에 의해 중단되어 -EINTR을 반환), 태스크는 POSIX timer를 정리하기 전에 종료된다: k_itimer는 해제되지만, 그 timerqueue 노드는 여전히 살아 있는 태스크의 트리에 남아 있다. 이후 부모 프로세스가 해당 태스크를 회수할 때, cleanup_timerqueue()가 이 해제된 노드를 다시 조작하여 use-after-free가 발생한다.
익스플로잇은 이 UAF를 제한된 8바이트 제로 쓰기로 전환한 뒤, SELinux → UID/GID → 완전한 capabilities의 세 단계로 진행한다. 입문자를 위한 전체 설명은 docs/VULNERABILITY.md를 참조하라.
| 항목 | 실측값 |
|---|---|
| 기기 | Xiaomi 17, 모델 25113PN0EC, 코드명 pudding |
| 시스템 | Xiaomi HyperOS 4 OS4.0.5.0.XPCCNXM, Android 17 |
| 커널 | 6.12.69-android16-6-g43b5ab7e247d-abogki561813157-4k |
| 페이지 크기 | 4 KiB |
| 시작점 | 권한이 부여된 일반 ADB shell, UID 2000, SELinux Enforcing, bootloader 잠금 |
| 성공 결과 | UID/GID 0, SELinux Permissive, CapPrm/CapEff/CapBnd=000001ffffffffff |
| 수명 주기 | root 서비스는 이번 부팅 이후 새로 열리는 ADB shell에서 사용 가능; 재부팅 후에는 다시 실행해야 함 |
성공 후의 독립 검증(새로 연 ADB shell):
uid=0(root) gid=0(root)
CapInh: 000001ffffffffff
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
getenforce: Permissive
이것은 영구 root가 아닙니다. boot/init_boot 또는 어떤 파티션에도 기록되지 않으며, bootloader는 잠금 상태로 유지됩니다. 재부팅 후 SELinux는 Enforcing으로 돌아가고 root 서비스는 사라집니다.
성공률을 보장하지 않습니다. 실측에서 9회 부팅 중 한 번 완전한 A→B→C를 달성했습니다. 세 단계의 통과 조건은 서로 독립적입니다:
| 단계 | 디바이스 측 프로그램 | 통과 조건 |
|---|---|---|
| 1. SELinux | exploit-android-perf | getenforce가 Permissive이고, 상태 워드의 나머지 필드는 예상대로 유지됨 |
| 2. 신원 | exploit-android-perf-chain | 동일 프로세스의 UID/GID가 0으로 변경되고, 이어서 fullcap을 exec함 |
| 3. 완전한 권한 | exploit-android-key-ptr + pc98260_rootd.c | 완전한 capabilities, root 서비스가 새 shell에서 접근 가능 |
어느 한 단계라도 맞지 않으면 [PARKED]를 출력하고 위험 상태를 유지하며, 반드시 재부팅해야 하고 이번 부팅 내에서 강제로 재시도할 수 없습니다.
--aio 16384는 본 기기의 튜닝 값입니다(성공한那次는 3903번째 perf 메타데이터 페이지에서 적중).
| 위치 | popsicle | pudding |
|---|---|---|
LINK_SELINUX_STATE | 0xffffffc08289aba0 | 0xffffffc0827f9ba0 |
LINK_GETPID | 0xffffffc080074eac | 0xffffffc080102e10 |
| timer 주소 누출 | alloc_posix_timer 반환 프로브 | 해당 심볼이 인라인되어, posix_cpu_timer_create 진입점 $arg1로 변경 |
PERF_MAX | 8192 | 16384(페이지 적중 기회 향상) |
근거와 증거는 docs/ADAPTATION.md를 참조하세요.
Android NDK가 필요합니다. 저장소의 bin/은 NDK 26.3.11579264 / API 34로 빌드되었습니다:
powershell -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -NdkHome "$env:LOCALAPPDATA\Android\Sdk\ndk\26.3.11579264" -ApiLevel 34
-VerifyReference는 새 산출물과 bin/의 SHA-256을 하나씩 비교합니다. bin/과 동일한 툴체인을 사용해야만 바이트 단위로 동일해집니다. NDK 버전을 바꿔도 실행은 가능하지만 해시는 달라집니다.
산출물:
| 산출물 | 입력 소스 |
|---|---|
exploit-android-perf | src/exploit-android-perf.c |
exploit-android-perf-chain | src/exploit-android-perf-chain.c |
exploit-android-key-ptr | src/exploit-android-key-ptr.c + src/pc98260_rootd.c |
pc98260-su | src/pc98260_su.c |
빌드는 휴대폰에 연결하지 않고, 기기에도 쓰지 않습니다.
USB 디버깅이 켜져 있고 권한이 부여된 기기를 연결한 뒤, platform-tools를 PATH에 추가합니다:
.\开始98260临时ROOT.bat
또는 디바이스를 명시적으로 지정합니다:
powershell -NoProfile -ExecutionPolicy Bypass -File .\run_98260.ps1 -Serial <ADB序列号> -Reboot
호스트 스크립트는 기종, 시스템 증분 버전, 전체 커널 문자열, boot ID와 네 개 바이너리의 해시를 검증합니다. 또한 각 단계를 시작하기 전에 /sys/kernel/tracing/tracing_on을 0으로 설정합니다(일부 HyperOS 빌드의 miuibpf는 이를 1로 유지하므로, 이 경우 익스플로잇 체인이 실행을 거부합니다). [PARKED]가 나타나면 스크립트가 중지되고 재부팅을 안내합니다.
성공 후에는 다음과 같이 root를 사용할 수 있습니다:
adb shell -t /data/local/tmp/pc98260-su
adb shell /data/local/tmp/pc98260-su -c "id"
익스플로잇 체인은 "대상 타이머가 위치한 페이지"를 정확히 회수할 것을 요구합니다. 동일한 대상 페이지를 관찰할 수 없거나, 포인터 쓰기 후 검증이 일치하지 않으면, 프로그램은 관련 zombie/매핑을 유지한 채 중단하여, dangling 노드가 여전히 커널에 의해 참조되고 있는 상황에서 무작위로 회수하는 것을 방지합니다. 이때는 새로운 휴대폰 부팅을 다음 시도의 경계로 삼아야 합니다.
이 저장소에는 다음이 포함되지 않습니다: 기기 백업, boot/init_boot 이미지, 파티션 이미지, payload.bin, 펌웨어에서 추출한 커널/ELF, 원시 실행 로그, ADB 시리얼 번호 또는 boot ID. logs/와 build/는 Git에서 무시됩니다. bin/에는 네 개의 임시 권한 상승 체인 바이너리만 있습니다.
/data/local/tmp/pc98260-root.sock을 리스닝합니다; 이번 부팅 내내 기기 전체가 완전히 노출됩니다.