0%

漫步安卓物理内存:CVE-2025-21479 提权实录

背景

在大多数用户的印象里,GPU(图形处理器)是游戏流畅、视频绚丽的保障,是沉浸在虚拟世界背后的无名英雄。然而,在现代移动计算架构,尤其是在安卓生态中,GPU的角色早已超越了“图形画师”的范畴。它通过诸如OpenCL、Vulkan等通用计算框架,深度参与到机器学习、图像处理、甚至安全计算等关键任务中,成为了SoC(系统级芯片)中与CPU平起平坐的“第二颗大脑”。

正是这种权限与复杂性的与日俱增,使其成为了安全攻防的新前线。传统的安全模型高度聚焦于加固CPU和操作系统内核,却往往忽视了GPU这个拥有独立驱动、固件(Firmware)和强大DMA(直接内存访问)能力的“特权邻居”。一旦这个邻居的“家门”被撬开,攻击者获得的将是一个绕过所有主流内核防护机制的绝佳跳板。

近年来,GPU安全漏洞呈现显著上升趋势。正如Google Project Zero在2022年的博客中提到的那样:

There were 7 Android in-the-wild 0-days detected and disclosed this year. Prior to 2021 there had only been 1 and it was in 2019: CVE-2019-2215.

For the 7 Android 0-days they targeted the following components:
Qualcomm Adreno GPU driver (CVE-2020-11261CVE-2021-1905CVE-2021-1906)
ARM Mali GPU driver (CVE-2021-28663CVE-2021-28664
Upstream Linux kernel (CVE-2021-1048CVE-2021-0920)

5 of the 7 0-days from 2021 targeted GPU drivers.

在2022年的7个在野0day中,有5个是关于GPU漏洞的,比例高达70%。且GPU漏洞的危害性往往极高,正如我们将要深入剖析的CVE-2025-21479一样,一旦被利用将引发从用户态直通物理内存操作的本地权限提升。

这个存在于高通Adreno GPU固件中的逻辑缺陷,正是一个典型的“核弹级”案例。它允许攻击者实现任意物理地址的读写操作。这意味着,攻击者不再需要与复杂的软件 mitigations(如KASLR、DEP、CFI)缠斗,而是直接“改写物理内存”。接下来,让我们一起揭开这场“上帝模式”漏洞的利用之旅。

Catch the bug from diff

高通的6月bulletin 为我们带来了3个漏洞:CVE-2025-21479,CVE-2025-21480,CVE-2025-27038。但可惜没有一个注明了commit修复链接,这给漏洞研究带来了挑战。但没多久,就有一名大佬 zhuowei 在 github 公开了他的漏洞poc和分析过程。

zhuowei 在文中提到,他通过Freedreno的 afuc 反汇编器逆向了三星S24的前后两个版本的GPU固件,并发现了一些端倪:

1
2
3
Galaxy S24 firmware: gen70900_sqe.fw
April update (S921USQU4BYD9): v675
May update (S921USQS4BYE4): v676

这两个固件仅有一处差异,即每次使用寄存器 $12 时,mask 从 0x3(0b11) 变成 0x7(0b111),多了1个bit,而这个$12代表着IB level。

1
2
3
4
5
6
7
8
        0163: b80300a4  CP_ME_INIT:
0163: b80300a4 fxn355:
0163: b80300a4 cread $03, [$00 + 0x0a4]
- 0164: 2a440003 and $04, $12, 0x3
+ 0164: 2a440007 and $04, $12, 0x7
0165: 98641813 ushr $03, $03, $04
0166: c860004a brne $03, b0, #l432
0167: 01000000 nop

自高通Adreno A7xx系列GPU起,其硬件引入了一个新的指令缓冲区(IB)层级,将原有的四级架构(RB、IB1、IB2、SDS)扩展为五级(RB、IB1、IB2、IB3、SDS)。关键问题在于,用于权限检查的微码逻辑未随之更新。微码通过 当前层级值 AND 3 的位运算来判定当前所处级别,若结果为0(即RB级),则允许执行特权指令。

在未打补丁的A7xx硬件上,当于第4级SDS执行指令时,计算 4 AND 3 得到结果0。这导致微码产生致命误判,错误地认为当前处于最高权限的RB级别,从而违规批准了特权指令的执行。

利用过程始于对 CP_SET_DRAW_STATE 指令的调用。此指令能让用户态应用在SDS层级上提交并执行命令序列。由于CVE-2025-21479漏洞的存在,当这个由用户控制的命令序列在SDS层级被执行时,GPU微码会错误地授予其RB层级的最高特权。攻击者可以滥用 CP_SMMU_TABLE_UPDATE 特权指令修改TTBR0寄存器对GPU的一级页表进行重配置。通过植入一个精心构造的恶意页表,攻击者可以完全掌控GPU对系统物理地址空间的映射关系。从而GPU沦为攻击者向任意物理内存地址发起读写攻击的武器,为后续获取内核权限奠定了决定性基础。

构建内核物理地址读写原语

成功利用 CP_SMMU_TABLE_UPDATE 获得配置页表的能力后,攻击的下一个阶段是构建一个稳定可靠的内核物理地址读写原语。其核心在于构造并定位一个受控的页表,并利用GPU指令实现任意物理内存的访问。

为TTBR0构建受控页表

攻击的首要步骤是准备一个恶意的一级页表,并将其物理地址配置到TTBR0寄存器中。该页表将负责定义GPU的虚拟地址到物理地址的映射关系。

内核物理地址读写的第一步,是构造出GPU的TTBR0页表,其中0x1234567841414141处就是TTBR0一级页表的物理地址:

1
2
3
4
5
6
7
// 构造用于执行 CP_SMMU_TABLE_UPDATE 的指令序列
*drawstate_cmds++ = cp_type7_packet(CP_SMMU_TABLE_UPDATE, 4);
drawstate_cmds += cp_gpuaddr(drawstate_cmds, 0x1234567841414141); // 目标TTBR0页表物理地址 (P)
*drawstate_cmds++ = 0x42424242; // 参数1
*drawstate_cmds++ = 0x43434343; // 参数2
drawstate_cmds += cp_wait_for_me(drawstate_cmds);
drawstate_cmds += cp_wait_for_idle(drawstate_cmds);

然而,用户态无法直接获知自身内存的物理地址。为解决此问题,采用以下策略:

  1. 大量喷射匿名内存页:以此占据大量的物理页面,提高猜测命中概率。
  2. 物理地址猜测:选取多个高概率命中的候选物理地址 P 作为假定的页表地址。
  3. 精心设计虚拟地址映射:选择一个虚拟地址 V(例如 0x40403000),使其在各级页表行走中的索引均相同。此设计允许通过修改单个页表页 (P) 即可完成从 V 到任意目标物理地址 P_target 的映射,极大简化了页表操作。

定位受控页表

为确认哪个被喷射的页面其物理地址恰好为猜测的 P,需进行以下验证:

  1. 通过GPU特权指令向虚拟地址 V 写入一个特定魔数 x。若 P 此时确实是我们的匿名页,则GPU可以通过TTBR找到V对应的P 并向其中写入魔数。
1
2
3
*drawstate_cmds++ = cp_type7_packet(CP_MEM_WRITE, 3);
drawstate_cmds += cp_gpuaddr(drawstate_cmds, 0x40403000); // 目标虚拟地址 V
*drawstate_cmds++ = 0x42424242; // 欲写入的魔数值 x
  1. 在用户态遍历所有喷射的匿名页,检查其中内容。若发现某个页面包含魔数 x,则可断定该页的物理地址即为 P。至此,我们获得了一个已知其物理地址 (P) 和虚拟地址的可读写内存页。

实现任意物理地址写

一旦确定了页表页 P,写入任意物理地址 P_target 的过程变得直接:

修改映射:在已定位的页表页 P 中,修改虚拟地址 V 对应的页表项,使其指向目标物理地址 P_target
执行写入:再次使用 CP_MEM_WRITE 指令向虚拟地址 V 写入数据。GPU会根据恶意页表,将数据实际写入物理地址 P_target

实现任意物理地址读

GPU指令集未提供直接的内存读取指令,因此读取操作需要通过内存拷贝指令 CP_MEM_TO_MEM 间接实现。有两种策略:

  1. 拷贝至TTBR0页 (P):将目标物理地址 P_target 的内容拷贝至已控制的物理页 P 的某个偏移处。随后,在用户态直接读取该匿名页的对应虚拟地址,即可获得 P_target 的数据。

    • 风险:此操作会覆盖页表页的部分内容,需谨慎管理拷贝偏移量,或在执行后重新修复页表项。
  2. 使用专用接收页 (P3):更安全的方法是喷射并定位另一个物理页 P3 作为专用的数据接收缓冲区。将 P_target 的数据拷贝至 P3,然后在用户态读取 P3 对应的虚拟地址。这避免了污染关键的页表结构。

此外,CP_MEM_TO_MEM指令只能拷贝4或8字节的内存,若需要拷贝16字节,则需要执行两遍该指令。由于漏洞只存在于A7xx设备,不妨直接使用A6xx引入的CP_MEMCPY指令,它实现了两个地址指定length的对拷,效率更高。

1
<value name="CP_MEMCPY" value="0x75" variants="A6XX-"/>

物理基址探测与内核信息泄露

在成功构建物理地址读写原语后,攻击者便具备了直接探查和篡改内核运行时数据的能力,这是实现最终提权的关键跳板。

定位内核物理内存基址

绝大多数安卓设备的内核物理内存加载基址在每次启动时是固定的。通过查询设备信息或利用已知的通用偏移(例如 0xA8000000, 0x80080000 等),攻击者可以从此地址开始直接读取内存,从而将内核的代码和数据段完整地dump下来。这份原始内存镜像足以让攻击者进行离线分析,精确计算出该特定内核版本中关键数据结构(如 selinux_state)、函数地址以及防护机制(如 KASLR 偏移)的偏移量,进而编写出最终的利用载荷。

对于少数实施了内核物理地址空间布局随机化(PhyASLR)的厂商(如三星),其随机化强度通常较弱。正如研究员Pan Zhenpeng和Jheng Bing Jhong在其研究中指出的:

“Samsung’s PhyASLR is aligned with 0x10000 and quite weak, by checking the instructions of _stext, can bypass it within 20 times.”

因此,通过遍历数个按0x10000对齐的候选地址并验证其内容是否包含有效的内核指令签名,即可快速爆破出随机化后的实际基址。

直接内存dump的效率瓶颈

然而,通过 CP_MEM_TO_MEM或是CP_MEMCPY 进行大规模数据转储存在显著性能瓶颈:

  1. 规模限制:该指令一次操作最多拷贝一页(0x1000字节)的数据。若要传输更多数据,必须准备大量的专用物理页( P4、P5、…)作为接收缓冲区,并进行多次迭代。
  2. 缓存同步开销:每次内存操作后,都必须同步CPU与GPU缓存,以确保数据一致性,这引入了巨大的性能开销。

完整泄露一个50MB大小的内核镜像需要数万次这样的操作,效率捉襟见肘。

策略转变: targeting PTE 页表

鉴于直接dump效率低下,且此时尚未获取到内核虚拟地址布局的具体信息(如 swapper_pg_dir),我转变了利用策略:转而在物理内存中扫描寻找存有用户空间页表项(PTE)的页表页。

通过修改这些PTE项,可以重新映射用户态虚拟地址,从而绕过传统的内存读取指令,以更高的效率实现大规模数据泄露。这为后续更复杂的利用步骤提供了一个高效的数据通道。

关于PTE页表的攻击,Nicolas Wu在Dirty Pagetable研究中做出了杰出贡献,这里不在过多展开。

文中提到,给PTE用的内存分配自MIGRATE_UNMOVABLE free_area,而用户的匿名页分配自MIGRATE_MOVABLE free_area,这两者会让相同的虚拟地址非常难以复用。不过现在的场景是希望系统复用相同的物理地址,实际测试发现这似乎并没有那么困难。

“The root cause for this is that the physical pages allocated by anonymous mmap() usually come from the MIGRATE_MOVABLE free_area of the memory zone, while user page tables are usually allocated from the MIGRATE_UNMOVABLE free_area of the memory zone. This makes heap spray difficult to succeed. The research presented by Yong Wang explained such difficulty very well.“

假设一台拥有8GB物理内存的设备,在攻击初期剩余约6GB可用内存。在定位TTBR0页的步骤中,通过喷射匿名页已消耗约2GB(记为 M0​),系统仍剩余约4GB可用内存。可以通过检查/proc/meminfo来查看系统内存的实时状态。

操作流程如下:
0. 预留PTE喷射内存:通过fork创建 N 个worker,每个worker通过mmap()预留大量(例如 0xfe00 个)2MB的虚拟地址空间区间,但暂不进行读写操作。此步骤仅保留虚拟地址范围,并未分配物理内存或创建PTE。
- 选择 0xfe00 是为了规避 /proc/sys/vm/max_map_count的系统限制(通常为 0xFFFA)。
- 每个2MB映射最终将对应一个PTE页,因此此预备步骤为后续最多喷射254MB的PTE页做好了地址空间准备。

  1. 耗尽Movable内存:分配大量匿名页(M1​,约3GB),将系统中剩余的空闲内存几乎耗尽。可通过监控 /proc/meminfo 中的 MemAvailable 字段确认。
  2. 释放非关键内存:释放之前用于定位TTBR0页的非关键匿名页(M0’,约2GB)。此操作释放d的内存使 MemAvailable 回升约2GB。
  3. 喷射PTE页:按顺序激活所有worker,通过读取每个2MB区间的首个字节来触发页错误。内核在处理错误时,会为该虚拟地址区间分配并初始化一个PTE页。持续此过程,直到系统可用内存再次耗尽(甚至开始使用交换分区),此时大量PTE页已被喷射到物理内存中。
  4. 扫描定位PTE页:利用已有的物理内存读写能力,在已知的TTBR0页附近扫描具有PTE页特征(如特定格式的页表项)的物理页。测试证明,此方法能高效发现PTE页:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[+] main.c:1612 [STEP] searching ttbr0 page ....
[*] main.c:349 guessing ttbr0 phyaddr: 0xa67e0000 ...
[+] main.c:432 found ttbr0 phys: 0xa67e0000, virt: 0x7215e3d000
[+] main.c:1617 [STEP] searching leak page ...
[*] main.c:1622 guessing leak page 0xa67e1000 ...
[+] main.c:484 found leak page, phys: 0xa67e1000, virt: 0x7215e3e000
[+] main.c:1536 [STEP] searching pte page ...
[*] main.c:906 search_pte_page dump 0xae7e0000:
00000000 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00000010 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
[*] main.c:906 search_pte_page dump 0xa77e0000:
00000000 - C3 5F 98 AA 00 00 60 01 00 00 00 00 00 00 00 00 | ._....`.........
00000010 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
[+] main.c:912 found PTE page!!!

一旦定位到PTE页,即可构建一个高性能的数据泄露引擎:

  1. 建立映射:修改该PTE页中的第一项,使其指向该PTE页自身的物理地址。这意味着,某个特定的用户态虚拟地址(由worker的mmap地址决定)将被映射到这个PTE页的物理内存。
  2. 轮询检测:让所有worker检查其各自2MB区间的头8个字节。哪个worker读到了我们写入的特定魔数(即自指向的PTE值),就证明其虚拟地址空间映射到了目标PTE页。
1
2
3
4
5
6
7
[+] main.c:1568 [STEP] searching modified pte ...
[+] main.c:1576 build evil pte: 0x600000a77e0f43
[*] main.c:1579 searching ...
[*] pte_spray.h:473 found pte check val:
00000000 - 43 0F 7E A7 00 00 60 00 00 00 00 00 00 00 00 00 | C.~...`.........
00000010 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
[+] main.c:1585 found pte page at virt 0x1faa400000, worker 11
  1. 大规模泄露:通过修改这个受控的PTE页,可以将其余PTE项指向任意目标物理地址。随后,直接读取对应的用户态虚拟地址即可一次性获取连续2MB的内核物理内存数据,效率相比单次0x1000字节的拷贝提升了数个数量级。

通过构造恶意的PTE页表,现在可以一次读取连续的2MB内存,大大提升内核镜像信息泄露的速度。
也可以通过mmap文件并修改page属性来修改文件的page cache,正如PAN ZHENPENG 和 JHENG BING JHONG 在他们的slide中所做的那样,也可以通过改文件page cache以注入代码到init来泄露内核信息:

Obtain kernel offsets without firmwares - Combine together:

  1. Hijack init to chmod 0777 for dir /data/local/tmp, thus make root user can create/write the file under this dir (or setuid later to skip this)
  2. Read target offsets info and copy contents into /data/local/tests dir
  3. Transition our context to u:r:shell:s0 and cp boot.img from /data/local/tests to /data/local/tmp
  4. Untrusted_app can get the offsets from /data/local/tmp
  5. (Optional) get offsets from boot.img

Example Demo:

带着内核符号攻克内核

在获取了内核信息并建立了稳定的物理内存读写原语后,实现本地权限提升便成为可能。具体的利用路径则取决于目标设备所启用的内核防护机制。

策略一:直接代码段修补(针对默认防护)

在缺乏额外强内核保护(如Samsung KNOX, Huawei HKIP)的标准安卓系统上,最直接的方法是篡改内核代码段(__text)本身。

思路如下:

  1. 重映射代码段为可写:通过修改GPU的TTBR0页表中对应内核代码段物理地址的页表项(PTE),将其内存属性从“只读”更改为“可写”。这绕过了CPU侧的内存管理单元(MMU)对只读页的硬件保护。
  2. 修补关键安全函数:直接在内核镜像中定位并修补关键的安全检查函数。常见的修补目标包括:
    • 禁用SELinux:将 selinux_state 中的 enforcing 字段置0,或修改 selinux_enforcing 指针。
    • 提权系统调用:修补 setresuidsetresgidcapset 等函数的实现,使其跳过权限检查逻辑,直接成功返回。
  3. 触发提权:完成修补后,在用户态直接调用被篡改的系统调用,即可获得root权限。此方法直接、高效,但依赖于对代码段的写入能力。

策略二: data only attack

在搭载了KNOX、HKIP或类似强内核完整性保护机制的设备(如三星、华为旗舰机型)上,上述方法通常失效。这些保护机制会监控内核代码段和只读数据段的完整性,任何修改都会触发系统重启。因此,利用策略必须转向攻击其保护范围的盲区。

在默认保护的系统上,我们可以通过修改GPU的TTBR0页表项中PTE项所描述的的页属性将内核代码段对应的内存重映射可写,进而修补内核镜像中关键的函数保护点。如关闭selinux,或是patch setresuid、setresgid、capset等关键安全函数,之后用户态便可直接调用这些修补后的syscall提权。

而在有额外保护的机型上(如三星、华为等)由于KNOX、HKIP等保护的存在,将代码段重映射为RW并不可行,仅能在内核data段和修改文件page cache中寻找提权的机会。

正如DARKNAY在《「AVSS研报」iOS•Android•鸿蒙安全对抗能力初评报告-内核篇》中所指出的:

HarmonyOS 4.2版中HKIP机制与RKP机制保护能力相当,其保护了代码段、只读数据段、内核重要结构体等不被篡改。但是对于应用程序页表缺乏保护,攻击者可以通过修改应用程序页表任意读写未被HKIP保护的物理地址,该利用方法也被验证在Pixel、Samsung Galaxy上同样可行。

在HarmonyOS NEXT中,可以看到HKIP提供了多种保护机制,但是在实际研究过程中发现,除了代码段、只读数据段以及内核页表存在HKIP保护外,其余重要结构体并未被保护

以下为部分HKIP机制与RKP机制中的防御能力覆盖范围比较:

这份评估揭示了此类防护体系的一个关键弱点:其保护重心在于内核自身的静态完整性,而对于动态的用户空间映射机制策略数据结构的保护存在不足。这为高级利用提供了两条清晰的路径:

  1. 操纵应用程序页表:正如我们之前构建PTE攻击链所做的那样,HKIP/RKP通常不保护用户进程的页表。攻击者可以继续利用此原语,篡改用户态PTE来达成劫持用户态应用的目的
  2. 篡改SELinux策略:SELinux的策略强制执行依赖于内核内存中的策略数据库。这些策略数据通常不被HKIP/RKP机制视为需要保护的“只读数据”或“代码”。因此,攻击者可以篡改数据以禁用selinux。

攻克SELinux:精准绕过强制策略

在获取内核物理地址读写能力后,绕过SELinux成为提权路径上的关键一步。最直观的思路是修改 selinux_state 结构中的 enforcing 字段。然而,在目标设备上,内核编译时未启用 CONFIG_SECURITY_SELINUX_DEVELOP 选项,导致该字段不存在,此路径无效。

1
2
$ adb shell zcat /proc/config.gz | grep CONFIG_SECURITY_SELINUX_DEVELOP
# CONFIG_SECURITY_SELINUX_DEVELOP is not set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct selinux_state {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
bool disabled;
#endif
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
bool enforcing;
#endif


#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
static inline bool enforcing_enabled(struct selinux_state *state)
{
return READ_ONCE(state->enforcing);
}
#else
static inline bool enforcing_enabled(struct selinux_state *state)
{
return true;
}
#endif
策略一:禁用SELinux初始化状态(已弃用)

深入分析SELinux的权限检查函数 security_compute_av,发现其存在一个初始化状态检查:若 selinux_initialized(state) 返回 false,则直接跳转至 allow 标签,授予所有权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
avc_has_perm
avc_has_perm_noaudit
avc_compute_av
security_compute_av

// --------------

void security_compute_av(struct selinux_state *state, ...)
{
...
if (!selinux_initialized(state))
goto allow;
...
allow:
avd->allowed = 0xffffffff; // 允许所有操作
goto out;
}

static inline bool selinux_initialized(const struct selinux_state *state)
{
return smp_load_acquire(&state->initialized); // 读取 initialized 字段
}

因此,将 selinux_state->initialized 字段修改为 0 即可全局禁用SELinux。然而经测试,此举会导致系统出现一些副作用,故弃用此方案。

策略二:操纵策略库实现恒允访

我们转而采用一种更精准、副作用更小的方案。其核心在于利用 security_compute_av 函数中另一处逻辑,通过控制策略数据使所有权限检查恒返回允许。

关键逻辑位于 unmap_class() 调用之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void security_compute_av(struct selinux_state *state,
u32 ssid,
u32 tsid,
u16 orig_tclass,
struct av_decision *avd,
struct extended_perms *xperms)
{
struct selinux_policy *policy;
struct policydb *policydb;
...
policy = rcu_dereference(state->policy);
policydb = &policy->policydb;
...
tclass = unmap_class(&policy->map, orig_tclass);
if (unlikely(orig_tclass && !tclass)) { // 条件A:orig_tclass有效但tclass为0
if (policydb->allow_unknown) // 条件B:策略允许未知
goto allow; // selinux bypass
goto out;
}
...
allow:
avd->allowed = 0xffffffff;
goto out;
}

static u16 unmap_class(struct selinux_map *map, u16 tclass)
{
if (tclass < map->size)
return map->mapping[tclass].value;

return tclass;
}

该代码段的逻辑是:如果一个orig_tclass在经过 unmap_class() 后结果为0,系统策略配置为允许未知对象类(policydb->allow_unknown == true),则授予全部权限。

这为实现绕过提供了两条清晰的修改路径:

  1. 启用未知类允许:修改selinux_state->policy->policydb->allow_unknown1(true)。
  2. 破坏类映射表:修改 selinux_state->policy->map->mapping 指向的映射数组,确保对于任何有效的 orig_tclass,函数 unmap_class 都返回 0。这可以通过篡改 selinux_mapping 结构中的 value 字段实现。
1
2
3
4
5
6
7
8
9
10
11
12
/* Mapping for a single class */
struct selinux_mapping {
u16 value; /* policy value for class */
unsigned int num_perms; /* number of permissions in class */
u32 perms[sizeof(u32) * 8]; /* policy values for permissions */
};

/* Map for all of the classes, with array size */
struct selinux_map {
struct selinux_mapping *mapping; /* indexed by class */
u16 size; /* array size of mapping */
};

此方法仅篡改策略数据,不干扰SELinux的状态机,有效避免了直接禁用初始化所带来的副作用。

向 init 注入 reverse shell shellcode

将init用到的共享库用只读方式映射到内存空间,控制PTE修改页表项为读写即可修改文件page cache,从而劫持init的执行流。由于共享库会被很多进程使用,而修改page cache后的变化会应用到所有进程,shellcode需要额外判断一下调用者是否为init,并在fork后执行反弹shell代码。

example:

最后是完整提权演示:

总结与思考

CVE-2025-21479从GPU固件的逻辑缺陷到最终的本地提权,攻击者需要精准地操控内存分配、利用硬件特性绕过软件保护、并深刻理解内核安全子系统的内部机理。这一过程充分展现了现代漏洞利用技术的高度复杂性和精巧性。

然而,正如我们所看到的,即便拥有如此强大的原始能力——任意物理地址读写——在实际攻击中仍面临重重障碍:需要对抗内存分配器的隔离策略,需要爆破或扫描以定位关键数据,需要精心设计才能稳定地篡改策略而非导致系统崩溃。这一切都清晰地指向一个结论:当今安卓系统的防御体系已经日趋成熟,攻守双方的力量对比正在发生根本性的转变。防御方通过多年的积累,构建起一个由硬件隔离(如TrustZone)、内核完整性保护(如RKP/HKIP)、运行时监控(如SELinux)、和沙箱隔离组成的多层次、纵深化防御体系。攻击者往往需要连续突破多个互不信任的安全层,其门槛和不确定性已被极大地提高。一个漏洞,即便强如CVE-2025-21479,也更多是作为一把开启后续攻击链的“钥匙”,而并非一锤定音的终极武器。

CVE-2025-21479既是对过去攻击技术的总结,也是对未来防御的启示。它证明,尽管攻击技术依然在向更高精尖的方向发展,但防御体系已经构筑了足够坚固的壁垒,使得攻击的成本和代价变得极其高昂。安全不再是单点技术的比拼,而是整个系统生态、供应链响应速度、以及攻防思维理解的全面竞争。移动安全进入了一个新的阶段,在这个阶段中,防御者正逐渐夺回主导权,但丝毫的松懈都可能让局势逆转。这场永恒的博弈,将继续驱动着技术创新,迈向更安全的数字未来。

参考