查看原文
其他

Android native内存检测工具介绍

Sean vivo千镜 2022-11-05

点击上方蓝字关注我们噢~


检测工具不仅可以在验证时发现安全问题,也可以在运用场景中阻断安全问题的发生,对于安全问题检测和攻击拦截非常友好,当然安全检测功能会消耗一定的系统性能。本文将对已集成的部分检测工具进行介绍。


01

背景


Android系统添加了clang的编译特性,使C/C++代码编译时可以添加检测功能,针对内存安全问题进行检测。

Clang 12已经支持了很多类型的检测功能,包括Address Sanitizer、Thread Sanitizer、Memory Sanitizer、Undefined Behavior Sanitizer、Data Flow Sanitizer、Leak Sanitizer等。Android系统集成了部分检测工具能力。



02

检测工具介绍

1.    Address Sanitizer

Address Sanitizer(ASan)用于检测代码中的内存错误,可以检测以下类型的错误:

1.  堆栈和全局变量的越界访问

2.  释放后使用

3.  返回后使用

4.  超出范围使用

5.  双重释放、错误释放

6.  内存泄露 

下面通过一个示例来观察Address Sanitizer是如何检查内存错误的。下文中的代码是一个简单的内存释放后使用的案例:


int main(int argc,int char **argv) {

   int *array = new int[100];

   delete [] array;

   return array[argc];

}

创建UseAfterFree.cc文件,使用clang进行编译并且链接ASan参数-fsanitize=address。命令如下:

$clang++ -fsanitize=address UseAfterFree.cc -o AddressSanitizer.so


将未添加参数和添加参数的程序进行对比,可发现添加参数的程序插入了内存检查逻辑,如下所示:


添加参数的程序

未添加参数的程序

ASan的检测逻辑是通过内存映射进行的。其将内存分为两部分:主内存和影子内存,主内存是程序正常使用的,影子内存用于记录主内存是否可用。主内存每8个字节对应影子内存的一个字节。影子内存的地址是基于主内存计数的基础上偏移0x7FFF8000,计算方式如下:


64-bit:影子内存地址 = (主内存 >> 3) + 0x7FFF8000

32-bit:影子内存地址 = (主内存 >> 3) + 0x20000000

除了内存映射之外,ASan还在被保护的内存对象周围建立中毒状态的内存空间,并对中毒状态的影子内存做特殊标记。


如下图所示,主内存可全部访问,影子内存标记为00;主内存部分可访问,影子内存标记为可访问内存的大小:01-07;堆空间周围中毒状态内存标记为fa;释放后的堆空间标记为fd。还有很多其他的标记状态此处不详细列举。


内存标记示意图

对于检测原理和方式有了进一步的了解,下面对ASan在程序中的插桩代码进行分析。经过未添加参数和添加参数的程序的对比,可以观察到详细的区别。

首先在申请了内存空间后,添加了空间申请是否成功的检查。其次在内存释放后、内存访问前添加了检测逻辑。检测逻辑包含影子内存中的数值和最后一个双字内存空间的前k个字节是否可访问。


ASan检测逻辑说明


由于ASan检测工具性能影响较大,Android10和AArch64上的AOSP master分支已支持HWAddress Sanitizer(硬件辅助Address Sanitizer),此检测工具内存开销更小,代码空间也更小。



2.   Bounds Sanitizer


Bounds Sanitizer (BoundSan) 用于对数组访问的边界检查。BoundSan是基于Undefined Behavior Sanitizer(UBSan)检测工具实现的,UBSan用于检查各种类型的未定义行为,例如使用空指针、有符号整数溢出、浮点类型转换溢出、数组索引边界溢出、枚举类型范围溢出等。

示例代码如下:


int main(int argc, char **argv) {

   int array[8];

   int bound = 10 + argc;

   return array[bound];

}

创建OutOfBounds.cc文件,使用clang编译并连接UBSan参数-fsanitize=bounds。编译命令如下:

clang++ -fsanitize=bounds OutOfBounds.cc -o BoundSan.exe


对比添加参数和未添加参数编译的程序,观察UBSan插入的检测逻辑,如下所示:


添加UBSan参数逻辑


未添加UBSan参数逻辑


相比于ASan提供的检测逻辑,UBSan提供的检测逻辑相对简单。在上图中可以观察到插桩代码仅仅检查了数组索引值是否超出了数组的最大长度,检测到问题后报告对应错误,如图所示:




3.   Integer Overflow Sanitization


Integer Overflow Sanitization (IntSan)检测工具同样基于Undefined Behavior Sanitizer(UBSan)检测工具实现的,用于整数溢出检查,包括有符号和无符号整数溢出检查。

测试代码如下所示:


int sum(int input){

   int k = 0x7fffffff;

   k +=   input;  

   return k;

}


int main(int argc, char **argv){

   int a = sum(argc);

    return a;

}

创建IntegerOverflow.cc,使用clang编译并连接UBSan参数-fsanitize=signed-integer-overflow。编译命令如下:

clang++ -fsanitize=signed-integer-overflow IntegerOverflow.cc -o IntSan.exe


若需要使用无符号溢出检查,则需要把参数变更为unsigned-integer-overflow,若有符号和无符号都需要,可以添加两个参数,用逗号隔开。

对比有无参数编译的程序,如下图所示,观察检测逻辑。


添加IntSan参数逻辑


未添加IntSan参数逻辑


图中红框标记的逻辑即为IntSan添加的溢出检测逻辑,其中if判断逻辑中__OFADD__()是IDA定义的宏,用于生成两数相加溢出的标记。

#define __OFADD__(x, y) invalid_operation // Generate overflow flag for (x+y)



03

总结

Android系统目前已支持了很多安全检测的工具或机制,除了本文介绍的三类,还包括:


堆分配器(Scudo):用于缓解堆相关的漏洞;


控制流完整性(CFI):用于保证程序的原始控制流图不被改变


只执行内存(XOM):用于缓解代码重用攻击;


影子调用堆栈(SCS):目前只支持aarch64,可保护程序返回地址被覆盖。


添加了安全检测功能后,对于系统的性能影响较大,所以在项目中实施安全检测功能时还需要验证和测试,android的配置参数支持黑名单或函数属性来停用安全检测功能,在不合理或者存在性能问题的代码谨慎使用。



参考:
https://source.android.google.cn/devices/tech/debug/fuzz-sanitize
https://clang.llvm.org/docs/index.html
https://github.com/google/sanitizers/wiki



更多精彩阅读

Soot在Android组件NPE拒绝服务检测中的应用
如何用lint扫出不安全代码
如何用OLLVM来保护你的关键代码




长按关注  更多安全技术干货等你发现 


好文!在看吗?点一下鸭!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存