Android安全机制:揭开SCUDO的安全防御策略
字数 1844 2025-08-22 12:22:48
Android Scudo分配器安全机制详解
1. Scudo分配器概述
Scudo是Google开发的用户模式内存分配器,基于LLVM Sanitizers的组合分配器,专注于实际安全性。从Android 11开始成为默认分配器。
1.1 主要特点
- 设计用于抵御堆相关漏洞(缓冲区溢出、释放后使用、双重释放)
- 保持良好性能
- 高度模块化和可配置
- 名称"scudo"来自意大利语"shield"(盾牌)
1.2 主要组件
- 主分配器(SizeClassAllocator{3264}):处理≤64KB的分配
- 辅助分配器(MapAllocator):处理>64KB的分配
- TSDRegistryShared(本地缓存):线程特定数据缓存
2. 核心概念与术语
2.1 内存块结构
- Block:由报头(0x10字节)和数据块组成
- 报头包含元数据(8有效字节+8对齐字节)
malloc(n)实际需要(n+0x10)字节
2.2 大小分类(ClassId)
- 分配根据大小分类,每个类有最大块大小
- ClassId从1开始索引
- Android 64位系统的分类示例:
| ClassId | 最大块大小 |
|---|---|
| 1 | 0x20 |
| 2 | 0x30 |
| ... | ... |
| 32 | 0x10010 |
2.3 区域(Region)
- 相同ClassId的分配分组在同一区域
- 区域按ClassId编号索引
- 区域0存储空闲块的TransferBatch列表
2.4 TransferBatch
- 记录同一ClassId的空闲块列表
- 在自由列表或缓存中存储压缩指针
3. 主分配器工作原理
3.1 初始化
- 分配大小为
NumClasses * 2^RegionSizeLog的内存区域 - Android中
RegionSizeLog=28,初始分配约8.8GB - 每个区域封装在两个保护页之间防止溢出
3.2 内存分配流程
malloc()调用allocate()- 根据大小确定ClassId
- 从TSD本地缓存获取块
- 缓存为空时从区域0的TransferBatch补充
- 无可用TransferBatch时生成新批次
- 构建块头并返回标记指针
3.3 块头结构
struct UnpackedHeader {
uptr ClassId : 8;
u8 State : 2; // 分配状态
u8 OriginOrWasZeroed : 2;
uptr SizeOrUnusedBytes : 20;
uptr Offset : 16;
uptr Checksum : 16; // CRC32缩减为16位
};
3.4 TSD本地缓存
- 每个线程有特定数据缓存
- 默认配置:8个TSD,2个默认TSD
- 缓存大小由ClassId决定:
max(1, min(13, (1 << 13) / BlockSize))
3.5 块随机化
- 分配时对块进行洗牌
- 生成4个TransferBatches并随机分配
- 可视化工具可观察分配模式
4. 内存释放机制
4.1 释放流程
free()调用deallocate()- 执行多项健全性检查:
- 指针对齐检查
- 头校验和验证
- 分配状态检查
- 类型/大小匹配检查
- 将指针存入本地缓存
- 缓存满时排空一半到TransferBatch
4.2 隔离机制
- 设计用于延迟实际释放
- Android中禁用隔离(
quarantine_size_kb=0) - 释放的指针可立即重用
5. 辅助分配器
- 比主分配器简单
- 每个分配由
mmap支持 - 分配被保护页包围
- 无隔离缓存,可立即重用指针
6. 安全防御机制分析
6.1 堆溢出防护
- 大溢出(>0x40000)会触及保护页导致崩溃
- 小溢出会覆盖后续块头,释放时检查会失败
- 攻击选择:
- 重新创建有效头(复杂)
- 防止释放溢出的块(更简单)
6.2 释放后使用防护
- Android中隔离禁用,无实际防护
- 释放后仍可访问数据
- 存在利用可能性
6.3 双重释放防护
- 能检测简单双重释放(通过状态位)
- 无法防护"释放-重新分配-释放"场景
- 第二次释放可通过所有检查
6.4 随机分配防护
- 非连续分配增加利用难度
- 每处理4个TransferBatches后重新洗牌
- 攻击者可填充多个批次制造连续分配
7. 总结与评估
Scudo显著提升了Android堆安全级别:
-
优点:
- 有效的堆溢出和简单双重释放防护
- 随机分配增加利用难度
- 良好的性能与安全平衡
-
局限:
- 隔离机制在Android中禁用
- 无法防护所有类型的双重释放
- 有经验的攻击者仍可能找到绕过方法
-
攻击成本:
- 需要精确控制大量内存分配
- 需保留多个数据块并在正确时机释放
- 通常需要额外的攻击原语配合
Scudo虽然不是绝对防护,但大大提高了漏洞利用的门槛和复杂性。