
Local root exploit for CVE-2025-21479 (Adreno KGSL) on iQOO Neo8 (SM8475) - physical memory r/w, disables SELinux, spawns root shell
Local privilege escalation proof-of-concept for CVE-2025-21479, targeting the Qualcomm Adreno GPU (KGSL) on the vivo V2301A (iQOO Neo8 / PD2301, Snapdragon 8+ Gen 1 / SM8475, Adreno A730, Android 14, GKI kernel 5.10).
In principle, the exploit drives the GPU's SMMU through KGSL's CP_SMMU_TABLE_UPDATE command to obtain arbitrary physical memory read/write capabilities, then disables SELinux, locates kernel symbols, and obtains a root shell — all without flashing or relying on an unlocked bootloader.
For security research only on devices you own or are authorized to use.
and $12, 0x3) in the Adreno A730 (eureka_panther) microcode is flawed and misclassifies Self-Draw-State (SDS) packets as RingBuffer (RB). Therefore, by submitting a forged CP_SMMU_TABLE_UPDATE inside an SDS block from userspace, you can overwrite the GPU's TTBR0 and make the GPU page table point to any physical page — from then on the GPU can read/write arbitrary physical memory.stabilize_rw hardens the mapping into a 2 MB self-mapping region, supporting fast large-batch reads (fast_pread_bytes etc.).selinux_state.enforcing field in physical memory. Then dump the kernel image from the fixed physical base address 0xA8000000 (default cap 43 MB) — SM8475 has no physical KASLR — and parse the kallsyms structures directly from the raw dump (endianness is auto-detected), so virtual KASLR does not matter either.__do_sys_capset with an AArch64 shellcode (prepare_kernel_cred(0) → commit_creds(), and disable seccomp). The forked child calls capset() → becomes uid 0 → restores the overwritten kernel code → the child re-executes the command you specify (default sh) in the u:r:shell:s0 domain. The kernel code ends up byte-for-byte identical to before the run.Full device information can be found in device_info.txt.
├── source/
│ ├── cheese.c # 漏洞利用主程序(include 了下面两个文件)
│ ├── kallsyms_lookup.c # 针对原始内核映像的 kallsyms 解析器
│ └── adrenaline.h # KGSL ioctl / 命令定义(作者 hawkes)
├── exploit # 预编译 arm64 二进制(动态链接,minSdk 21,
│ # NDK r30-beta2)
├── device_info.txt # 用于调参的目标设备信息
└── README.md
Note: cheese.c #includes kallsyms_lookup.c and adrenaline.h; at compile time the three files need to be in the same directory.
Any recent Android NDK (r25+) works, for example:
# 静态链接(自包含、任意 API 等级可运行;体积较大)
aarch64-linux-android34-clang -o cheese source/cheese.c -static
# 动态链接(体积小,依赖设备自带的 libc;API 等级需匹配设备)
aarch64-linux-android34-clang -o exploit_dyn source/cheese.c
# 若要兼容 API 21+ 设备,请改用 aarch64-linux-android21-clang
adb push exploit /data/local/tmp/
adb shell
chmod +x /data/local/tmp/exploit
/data/local/tmp/exploit
Click for detailed tutorial (includes a one-click tool)
Upon success you get a uid=0 shell and SELinux is disabled for this session. After gaining privileges, the process re-executes argv[1..] (default sh). Each run shows a countdown ("pte wait 3 second") and 43 MB kernel read progress ("Read X / 43 MB").

$(find /data/app -type f -name "libksud.so" | grep resukisu) late-load --kmi android12-5.10
Device-specific constants are at the top of source/cheese.c:
KERNEL_PHYS_BASE — kernel physical base address (default 0xA8000000, SM8475)IDMAP_PG_DIR_OFFSET — idmap_pg_dir offset (default 0x245a000)selinux_state_offset — offset used to locate selinux_state (default 0x729030; actual physical address = idmap_pg_dir physical address + this offset)gPhyAddrs[] — candidate physical base addresses tried in orderDEFAULT_KERNEL_SIZE_MB — fallback / cap for kernel readsPrerequisites: Adreno A730/A7xx series GPU (affected firmware), a kernel whose kallsyms can be parsed from the physical dump, and no physical KASLR.
can't get GPU r/w — GPU initialization failed; wait a bit, if it still fails try CHEESE_ATTEMPT=1..n or manually specify CHEESE_PHYADDR; for debugging a single attempt add CHEESE_NO_RETRY=1.CHEESE_READ_FULL_KERNEL=1 to read the full image (combined with CHEESE_KALLSYMS_MAX_MB to raise the cap), then consider CHEESE_FORCE_MANUAL_PATCHFINDER=1.cheese exploit this tool is based onadrenaline.h KGSL header, providing ioctl interface definitionsBased on zhuowei/cheese (original repository has no license). For full technical details see the security bulletin.
This repository is for educational and security research purposes only. Use it only on devices you own or are authorized to use. The author is not responsible for any misuse. Using this tool may void warranties or violate applicable terms of service.
| Model | vivo V2301A (iQOO Neo8, codename PD2301, board taro) |
| SoC | SM8475 (Snapdragon 8+ Gen 1) + Adreno 730 |
| Android | 14 (UP1A.231005.007, SDK 34, qssi-user) |
| Kernel | 5.10.198-gki-gfb2f5774872c |
| SELinux | Enforcing (disabled at runtime by this tool) |
| Variable | Description |
|---|
CHEESE_PHYADDR=<phys> | Force the kernel physical base address (skips the built-in gPhyAddrs probe table). |
CHEESE_ATTEMPT=<n> | Use gPhyAddrs[n] instead of gPhyAddrs[0]; if GPU initialization fails, maybe_retry automatically increments and retries. |
CHEESE_SWAPPER_PG_DIR_OFF=<off> | Skip the swapper_pg_dir scan and use this absolute offset directly. |
CHEESE_READ_FULL_KERNEL=1 | Read the full kernel image (default read cap is DEFAULT_KERNEL_SIZE_MB = 43 MB). |
CHEESE_KALLSYMS_MAX_MB=<MB> | Override the kallsyms read cap. |
CHEESE_FORCE_MANUAL_PATCHFINDER=1 | Force the manual patchfinder fallback (init_cred, prepare_kernel_cred). |
CHEESE_SKIP_GPU=1 | Skip the GPU read/write phase (debugging only). |
CHEESE_NO_RETRY=1 | Exit immediately on failure instead of retrying with a different index. |