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 主要组件

  1. 主分配器(SizeClassAllocator{3264}):处理≤64KB的分配
  2. 辅助分配器(MapAllocator):处理>64KB的分配
  3. 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 内存分配流程

  1. malloc()调用allocate()
  2. 根据大小确定ClassId
  3. 从TSD本地缓存获取块
    • 缓存为空时从区域0的TransferBatch补充
    • 无可用TransferBatch时生成新批次
  4. 构建块头并返回标记指针

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 释放流程

  1. free()调用deallocate()
  2. 执行多项健全性检查:
    • 指针对齐检查
    • 头校验和验证
    • 分配状态检查
    • 类型/大小匹配检查
  3. 将指针存入本地缓存
    • 缓存满时排空一半到TransferBatch

4.2 隔离机制

  • 设计用于延迟实际释放
  • Android中禁用隔离(quarantine_size_kb=0)
  • 释放的指针可立即重用

5. 辅助分配器

  • 比主分配器简单
  • 每个分配由mmap支持
  • 分配被保护页包围
  • 无隔离缓存,可立即重用指针

6. 安全防御机制分析

6.1 堆溢出防护

  • 大溢出(>0x40000)会触及保护页导致崩溃
  • 小溢出会覆盖后续块头,释放时检查会失败
  • 攻击选择:
    1. 重新创建有效头(复杂)
    2. 防止释放溢出的块(更简单)

6.2 释放后使用防护

  • Android中隔离禁用,无实际防护
  • 释放后仍可访问数据
  • 存在利用可能性

6.3 双重释放防护

  • 能检测简单双重释放(通过状态位)
  • 无法防护"释放-重新分配-释放"场景
  • 第二次释放可通过所有检查

6.4 随机分配防护

  • 非连续分配增加利用难度
  • 每处理4个TransferBatches后重新洗牌
  • 攻击者可填充多个批次制造连续分配

7. 总结与评估

Scudo显著提升了Android堆安全级别:

  1. 优点

    • 有效的堆溢出和简单双重释放防护
    • 随机分配增加利用难度
    • 良好的性能与安全平衡
  2. 局限

    • 隔离机制在Android中禁用
    • 无法防护所有类型的双重释放
    • 有经验的攻击者仍可能找到绕过方法
  3. 攻击成本

    • 需要精确控制大量内存分配
    • 需保留多个数据块并在正确时机释放
    • 通常需要额外的攻击原语配合

Scudo虽然不是绝对防护,但大大提高了漏洞利用的门槛和复杂性。

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 块头结构 3.4 TSD本地缓存 每个线程有特定数据缓存 默认配置:8个TSD,2个默认TSD 缓存大小由ClassId决定: 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虽然不是绝对防护,但大大提高了漏洞利用的门槛和复杂性。