
MT6985 MediaTek Dimensity 9300 (vivo PD2241)용 CVE-2026-43499 익스플로잇 어댑터
| 항목 | 값 |
|---|
| 기기 | vivo PD2241 (Dimensity 9200 / MT6985), Android 15 |
| 펌웨어 | PD2241_A_15.2.10.2.W10.V000L1 |
| 커널 | 5.15.178-android13-8-gfb31f5bdd612-dirty |
| Bootloader | 잠김 (ro.boot.flash.locked=1) |
| SELinux | Enforcing |
| panic_on_oops | 켜짐 → 모든 커널 OOPS = 즉시 재부팅 |
| Exploit | CyberMeowfia — CVE-2026-43499 (IonStack) |
| 소스 트리 | android_15.0_kernel_MT6985 (5.15.178) — 기기 버전과 불일치 (소스는 android15 GKI, 기기는 android13 GKI) |
arch/arm64/include/asm/memory.h + include/linux/fs.h + android/abi_gki_aarch64.xml에서 추출:
KIMAGE_TEXT_BASE = 0xffffffc008000000, VA_BITS=39, DIRECT_MAP=256GBlayout-offset-in-bits)iopoll 없음 (android13 GKI), IOCTL=0x48, open=0x68atomic_t usage = 4 바이트, uid=0x04slab_cache=0x18핵심 발견: 소스는 android15 GKI, 기기는 android13 GKI — task_struct 오프셋이 0x40~0x88 바이트 차이 나므로 소스를 그대로 참조할 수 없음.
OTA zip (8.3GB)
→ payload.bin (8.2GB)
→ payload_dumper → boot.img (96MB, v4 header)
→ LZ4 압축 해제 → Image (50MB ARM64)
→ kallsyms-finder → 187810 심볼
두 펌웨어 버전 (15.2.7.6 / 15.2.10.2)에서 심볼 추출 — 같은 심볼이 두 버전 간 10KB~200KB 차이, 반드시 올바른 버전을 사용해야 함.
# rt_mutex_adjust_pi 내부:
LDR x21, [x19, #0x8b0] → pi_blocked_on = 0x8b0 (android15 값, frankel 아님)
이로써 task_struct 레이아웃이 frankel의 android13이 아닌 android15 브랜치임을 확인.
NDK r29, make PROJECT=android_15.0_kernel_MT6985 → preload.so (150KB).
[+] preload starting pid=25414
[+] p0 profile ... 모든 심볼 정상 로드
[-] KernelSnitch mm_struct leak failed ← 가끔 이 줄 없음 (드물게 성공)
[+] slide child context route=pselect ← slide KASLR leak 자식 프로세스 시작
[커널 panic] ← rt_mutex_adjust_prio_chain+0x1b0
크래시 명령어 (capstone):
ldar w8, [x27] ; x27 = waiter->lock ([x28, #0x38]에서 로드)
; x27 값이 쓰레기 → 페이지 테이블 매핑 없음 → translation fault
; → die() → panic → 재부팅
KernelSnitch는 전체 exploit의 진입점 — futex 해시 버킷의 타이밍 차이로 mm_struct 주소를 유출:
MT6985는 CONFIG_KASAN_HW_TAGS=y → 커널이 MTE 태그로 slab 할당을 표시
mm_struct의 포인터에 KASAN tag 포함 → futex_hash가 tagged pointer 기반으로 계산
하지만 bruteforce는 direct map (untagged 주소)을 스캔 → 계산된 hash가 불일치
MTE tag 순회 (0-14 총 15종)를 추가해도 VA_BITS=39 시스템에서
tag 비트 (bit56-59)가 부호 확장 비트와 겹침 → 일부 tag 조합이 유효하지 않은 주소 생성 → 누락
Pixel 기기는 KASAN_HW_TAGS가 없어 이 메커니즘이 동작. MTK는 불가.
CONFIG_PANIC_ON_OOPS가 결정타Pixel: 커널 OOPS → dump_stack → 계속 실행 → exploit 재시도 가능
MT6985: 커널 OOPS → die() → panic() → 즉시 재부팅 → 시행착오 여지 없음
π 체인 파괴가 조금만 어긋나도 전체 붕괴, Pixel에서는 어긋나도 "이번엔 실패, 다른 주소로 재시도"일 뿐.
또한 bootloader가 잠겨 있음 (flash.locked=1) → 이 옵션을 제거한 커스텀 커널을 플래시할 수 없음.
소스 트리: 5.15.178 android15 GKI
기기: 5.15.178-android13 (vivo vendor)
메이저 버전 번호가 모두 5.15.178이지만 GKI 브랜치가 다름 (android13 vs android15), task_struct/cred 등 핵심 구조체 레이아웃이 불일치. frankel/android15 두 오프셋 세트 사이를 반복 전환하다가 결국 디스어셈블리로 확정.
| 변경 | 목적 | 결과 |
|---|---|---|
THRESHOLD_MULT 10→5→3 | 충돌 감지 임계값 낮춤 | <5는 오탐 과다 |
APPENDED_FUTEXES 4096→8192 | hash 체인 차이 확대 | 효과 없음 |
REPEAT_MEASUREMENT/AVERAGE | 샘플링 정밀도 증가 | 효과 없음 |
MTE=1 | bruteforce가 태그 순회 | 더 느려지고 오히려 크래시 감소 |
MM_STRUCT_SZ 0x500→0x400 | mm_struct 스텝 수정 | 필수, ABI 실제 992 바이트 |
IDENTITY_END 64GB→256GB | 스캔 범위 확대 | 너무 느림 (MTE 순회), 여전히 불일치 |
| TASK offsets: android15↔frankel | 올바른 오프셋 확정 | 디스어셈블리로 android15 확인 |
| FOPS offsets: android15↔frankel | android13에 iopoll 없음 | frankel 사용 |
exploit/targets/android_15.0_kernel_MT6985/target.h 내:
| 카테고리 | 신뢰도 | 검증 방식 |
|---|---|---|
| 메모리 레이아웃 | 정확 | memory.h 계산 + kallsyms _text 검증 |
| 심볼 오프셋 (22개) | 정확 | 15.2.10.2 boot.img에서 추출 |
| task_struct 오프셋 | 정확 | ABI XML + capstone 디스어셈블리 (pi_blocked_on=0x8b0) |
| FOPS 오프셋 | 오류 (2026-07-31 수정됨) | 원래 frankel에서 복사 (android13에 iopoll 없음); 소스 트리 ABI에는 실제로 iopoll@0x30, ioctl=0x50, open=0x70 — VERIFICATION.md 참조 |
| CRED 오프셋 | 정확 (검증됨) | ABI XML: uid=0x04, securebits=0x24, caps=0x28, security=0x78 |
어셈블리는 통과, 실행도 됨, 마지막 1km에서 패배.
CONFIG_PANIC_ON_OOPS 제거 — 커스텀 커널 플래시 (bootloader 잠금 해제 필요) 또는 기본적으로 이 옵션이 꺼진 MT6985 기기 확보/proc/self/pagemap — 본 기기에서 제한됨 (전부 0 반환)/proc/mtk_*) — 존재하나 추가 분석 필요symbols/kallsyms_PD2241_15.2.10.2.txt — 완전한 15.2.10.2 심볼 테이블, 후인이 바로 사용 가능device_config.txt — 기기 실제 커널 설정, vendor가 무엇을 변경했는지 확인 가능exploit/targets/android_15.0_kernel_MT6985/target.h — 구조체 오프셋 검증 완료scripts/server_compile.py — 자동 컴파일, 파라미터 변경 후 재컴파일 빠름tar -xf는 대용량 zip에서 실패할 수 있음, Python zipfile 또는 수동 압축 해제 사용update_metadata_pb2.py가 protobuf 5.x를 요구, runtime_version import 줄을 수동으로 삭제해야 함./preload.so 직접 실행 시 segfault: 반드시 /system/bin/linker64 /data/local/tmp/preload.so 사용layout-offset-in-bits는 컴파일러가 계산한 값, 수동으로 5000바이트 세는 것보다 100배 정확CONFIG_ANDROID_VENDOR_OEM_DATA=y, CONFIG_SCHED_INFO=y, CONFIG_RSC_* → 표준 GKI에서 이탈boot.img → kernel.bin → LZ4 압축 해제 → Image → kallsyms-finder → 심볼 테이블07/28 CyberMeowfia 저장소 + MT6985 소스 다운로드
07/29 소스 분석 (memory.h, fs.h, ABI XML, 각종 struct)
펌웨어 언패킹 (payload.bin → boot.img → Image)
심볼 추출 (kallsyms-finder → 187810 심볼)
다회 컴파일 + 다회 크래시 + 디스어셈블리 검증
자동 재시도 5회 → 전부 실패
본 글 작성
-------------------------------------------
총계: ~68M 토큰, root 셸 0개
2026-07-29, 살아서 이야기를 전하다
android_15.0_kernel_MT6985.tar.gz(5.15.178 소스 트리 + ABI XML) 확보 후
전량 교차 검증 수행, 상세 내용은 VERIFICATION.md 참조. 요약:
target.h 수정 완료, exploit 내장 leak_kernel_base() 실기기 자체 검증으로
폴백.KSNITCH_MTE_ENABLED=1이 실제로
동작하도록 수정(기존 util.c는 mte=0 하드코딩);rt_mutex_adjust_prio_chain+0x1b0은 pselect/pi 체인 단계에
위치하며 FOPS 자체 검증보다 앞섬; 위 수정 후 재실기 검증 가치 있음.기기(PD2241, compiler251203103903) 실기 8+회 테스트:
MM_STRUCT_SZ=0x400(기존 0x500은 스캔
그리드 오정렬로 오탐만 발생) + MTE tag 0..15 순회(기기 mm 포인터 tag가
변함) + 위조 주소 untag. 이제 실제 mm_struct를 안정적으로 찾음.rt_mutex_adjust_prio_chain+0x1b0에서 크래시: pselect/pi 체인
타이밍 레이스(위조 waiter가 커널 스택의 올바른 오프셋에 안착하지 못함).
vivo RSC 스케줄러가 futex/pi 경로를 변경하여 해당 레이스를 근본적으로
깨뜨렸을 가능성. slide 스택 정렬은 SLIDE_SHIFT 환경 변수로 파라미터화했으나
스캔 완료 못함.최종 선택: 유료 bootloader 잠금 해제로 하향(더 이상 이 경로에 의존하지 않음).