第三章 ARM堆漏洞类型概述
堆漏洞,说白了就是程序在管理动态内存时出了岔子。x86平台上那几样经典漏洞——堆溢出、UAF、Double Free、堆越界读写——到了ARM上,本质没变,但触发条件和利用手法却有不少差异。我这些年做ARM逆向,踩过的坑不少,今天就把这些经验掰开揉碎了讲给你听。
3.1 堆溢出(Heap Overflow)
堆溢出是最基础的堆漏洞。当程序向堆块写入的数据超过了块本身的大小,就会覆盖相邻块的数据。在ARM上,这个问题尤其隐蔽。
ARM上的特殊之处:
- ARM指令长度固定(4字节),但很多编译器为了优化,会在堆块尾部插入对齐填充。这些填充区域往往被开发者忽略,成了溢出攻击的温床。
- ARM的栈帧布局和x86不同,函数返回地址的保存位置有差异,但堆溢出后覆盖函数指针的思路是相通的。
我在项目中遇到过这样一个案例:一个嵌入式设备上的网络服务,接收用户输入后拷贝到堆缓冲区。代码里明明检查了长度,但没考虑ARM的4字节对齐——结果输入刚好卡在对齐边界上,溢出了一个字节。就这一个字节,改写了相邻堆块的size字段,最终导致了任意地址写。
避坑指南:我曾经在逆向一个IoT固件时,发现堆溢出漏洞的触发条件极其苛刻——需要连续发送特定格式的报文。后来我写了个fuzzer,专门针对ARM的对齐特性生成测试用例,效率高了不少。你如果做ARM漏洞挖掘,建议也试试这个思路。
3.2 释放后使用(Use-After-Free, UAF)
UAF,就是内存释放后,指针还在用。ARM上UAF的利用,往往和缓存一致性有关。
| 特性 | x86 | ARM |
|---|---|---|
| 缓存模型 | 强一致性 | 弱一致性(需要内存屏障) |
| UAF检测难度 | 相对容易 | 更难,因为缓存可能掩盖问题 |
| 利用稳定性 | 较高 | 受缓存刷新时机影响 |
为什么会这样?ARM的缓存是写回(write-back)策略,数据修改后不会立即写回主存。你释放了一个堆块,但缓存里可能还留着旧数据。这时候如果另一个线程分配了同一块内存,旧指针读到的可能是缓存里的残影——这就是UAF的变种。
我记得有一次调试一个Android Native库,UAF漏洞在x86模拟器上稳定复现,但到了真机上时好时坏。查了两天才发现,是ARM的缓存一致性搞的鬼。后来我在释放内存后手动加了__sync_synchronize(),才让问题稳定暴露出来。
3.3 双重释放(Double Free)
Double Free,就是同一块内存被释放了两次。ARM上的Double Free,我总结了一个规律:多线程场景下更容易触发。
注意:ARM的原子操作指令(如LDREX/STREX)和x86的CMPXCHG不同,如果代码里没有正确使用内存屏障,两个线程可能同时判断一个堆块“未被释放”,然后都执行free——Double Free就来了。
我见过一个典型的ARM Double Free案例:一个音频播放器,两个线程共用同一个音频缓冲区。线程A释放缓冲区后,线程B因为缓存延迟,还以为缓冲区在用,又释放了一次。结果堆管理器的链表被破坏,下次分配直接崩溃。
3.4 堆越界读(Heap Out-of-Bounds Read)
堆越界读,就是读取了堆块范围之外的数据。ARM上这个漏洞的利用价值,往往在于信息泄露。
- 泄露堆地址:越界读到相邻块的指针,就能算出堆基址,绕过ASLR。
- 泄露libc地址:如果相邻块是空闲块,可能读到unsorted bin的链表指针,进而泄露libc基址。
- ARM的特殊性:ARM的堆管理器(如dlmalloc、jemalloc)在64位模式下,指针占8字节,但很多嵌入式设备还在用32位。越界读时,读到的数据长度不同,泄露的信息量也不同。
我的经验:做ARM堆越界读的利用时,我习惯先搞清楚目标用的是哪个堆管理器。不同管理器的空闲块结构不同,泄露的偏移也不一样。比如ptmalloc的fastbin,空闲块只有fd指针,而unsorted bin有fd和bk两个指针——泄露的信息量差一倍。
3.5 堆越界写(Heap Out-of-Bounds Write)
堆越界写和堆溢出有点像,但更精确——它不一定是从块头开始写,而是写到了块范围之外的任意位置。ARM上,这种漏洞往往和数组索引错误有关。
你想想看,ARM的寻址模式比x86丰富,有前变址、后变址、寄存器偏移等。如果开发者对ARM指令不熟,很容易写出越界的数组访问。比如:
// ARM汇编中常见的越界写模式
LDR R0, [R1, R2, LSL #2] // 读取R1 + R2*4处的值
STR R3, [R0, #4] // 写入R0+4处
// 如果R2被用户控制,且没有边界检查,就是越界写
我在逆向一个ARM路由器固件时,遇到过这样的漏洞:一个处理HTTP请求的函数,用用户提供的索引去访问一个堆上的数组。索引没做负数检查,结果攻击者传入负值,写到了堆块头部的size字段——直接改写了堆管理器的元数据。
3.6 ARM堆漏洞触发特点总结
说了这么多,我画个图帮你理清思路:
核心要点:
- ARM的堆漏洞类型和x86一致,但触发条件受ARM架构特性影响
- 对齐、缓存、原子操作、寻址模式——这四个词是ARM堆漏洞的关键
- 多线程场景下,ARM堆漏洞的触发概率比x86高
- 不同堆管理器的内部结构不同,利用前必须先识别
嗯,这一章的内容就到这儿。记住这些漏洞类型和ARM上的触发特点,后面几章我们会逐个深入,讲具体的利用手法和实战案例。到时候你会发现,理解了这些基础,ARM堆利用其实没那么神秘。