wxvl/doc/2024-11/Android Native内存型漏洞实例.md

14 KiB
Raw Permalink Blame History

Android Native内存型漏洞实例

原创 OPPO安珀实验室 OPPO安珀实验室 2024-11-29 08:30

前言

本文将结合5例Android安全公告公开的Native历史漏洞实例分析C/C++内存型漏洞的产生场景尤其是由C++面向对象特性导致的漏洞场景。

1缺乏长度校验导致的堆溢出

漏洞信息

编号CVE-2023-21118

来源2023年5月安全公告披露

漏洞类型ID信息泄露

影响版本11, 12, 12L, 13

危险等级:高危

漏洞模块libsensor

漏洞分析

本漏洞中会涉及3个Android libutils.so的关键函数先进行解释

第一FlattenableUtils::advance()函数。它是FlattenableUtils类定义的静态成员函数主要目的是将buffer指针向后移动offset长度并将buffer长度size更新为移动指针后buffer的剩余长度。

第二FlattenableUtils::read()函数。主要是将buffer中的内容拷贝到目标value中然后通过上述FlattenableUtils::advance()更新buffer指针位置及长度size。

第三FlattenableUtils::align()函数。用来将输入的数字对齐到N的整数倍N必须是2的幂。

接下来让我们把目光投向漏洞代码本身。

libsensor库中使用unflatten()函数解析传感器参数数据赋值给成员变量。unflatten()函数将调用unflattenString8()函数来解析传入buffer中的传感器名称和供应商等字符串。

unflattenString8()函数首会先调用一次FlattenableUtils::read()函数读取一个4字节长度的uint32_t型变量len也就是接下来要读取的整个字符串的长度。读取的同时也会右移buffer指针并减小size使size变为buffer的剩余长度。通过和len比较可以判断buffer剩余长度是否满足后续拷贝需求。

形参outputString8是mName/mVendor的引用下一步用String8::setTo()函数从buffer截取len长度将字符串拷贝到mName/mVendor中。

紧接着导致越界的步骤出现读取完字符串后会通过FlattenableUtils::advance()函数进行一次4字节对齐。比方说一开始读取的len是1那align的结果就是4advance就会将指针向后移动4字节。

如果buffer本身长度不足剩余长度小于4字节在执行FlattenableUtils::align()时就会将指针移出buffer有效范围造成堆溢出。

修复方法

 
谷歌给出的修复方案是在进行4字节对齐前检查剩余长度是否足够。

在使用如FlattenableUtils::advance()函数进行移动指针时,需要特别留意函数内部是否进行了越界检查。如果函数没有实现,一定要手动检查边界,防止发生溢出。

2. 无符号数回绕导致的堆溢出

漏洞信息

编号CVE-2024-0018

来源2024年1月安全公告

漏洞类型EoP权限提升

影响版本11, 12, 12L, 13, 14

危险等级:高危

漏洞模块libstagefright

漏洞分析

libstagefright库实现的ColorConverter 类用于执行颜色格式之间的转换在构造函数中指定源颜色格式和目标颜色格式后续调用ColorConverter::convert()函数使进入不同格式转换分支。

这例漏洞发生在YUV420格式转换到Y410格式的分支。YUV420的Y表示亮度UV信息包含色彩及其饱和度420采样中每4个Y为一组共用1个UV信息。

由YUV420的采样模式可知输入的正常图像buffer长度应该是4的整数倍。漏洞所在的循环中也确实进行了一次长度校验因为指针每次要右移4个单位长度所以每次比较src.cropWidth()-3来看图像的宽度够不够下次移动指针。

但如果图片的宽度不足3呢由于src.cropWidth()的返回值是一个无符号的size_t类型src.cropWidth()-3将发生无符号数下溢结果变为一个接近2^64的很大的数使得for语句长度判断完全失效后续移动指针时发生越界读、越界写。

修复方法

修复该漏洞的方法是将不等式移项将容易引发无符号数下溢的减法操作转化为左式的加法操作通过x+3与宽度进行比较。

3. 主线程销毁后子线程使用野指针导致UAF

漏洞信息

编号CVE-2023-40131

来源2023年11月安全公告

漏洞类型EoP权限提升

影响版本12, 12L, 13

危险等级:高危

漏洞模块gpuservice

漏洞分析

多线程环境下,子线程可能需要使用到主线程的上下文,如果不进行线程管理就可能发生内存释放后访问的问题。

在这个2023年公布的高危漏洞中GpuService类构造函数将this指针传递给初始化函数。为了提升效率这些初始化函数使用detach()函数创建子线程。

使用detach()函数创建的子线程时子线程会驻留后台运行但是主线程不会自动监控其生命周期等待它执行完。所以当调用GpuService类的构造函数后立即析构就会导致子线程接受的this指针丢失成为野指针后续子线程中通过该指针调用成员变量、成员函数都属于UAF。

修复方法

通过智能指针创建子线程,并在析构函数中等待所有子线程运行结束即可解决这个问题。

从这个实例我们可以发现由于C++很多指针、内存、线程的管理已经对开发者透明了,导致我们自己在实现类时会习惯性忽视这些因素。实践中如果涉及多线程、指针及内存生命周期管理等操作应该格外小心。

4. Vector未加锁产生条件竞争导致UAF

漏洞信息

编号CVE-2021-0437

来源2021年4月安全公告

漏洞类型EoP权限提升

影响版本8.1, 9, 10, 11

危险等级:高危

漏洞模块drm

漏洞分析

这是一例由Vector类型内存管理模式引发的条件竞争漏洞。在DrmPlugin类的setPlayPolicy()函数中会对KeyedVector型成员变量mPlayPolicy进行加项。

KeyedVector是Android封装的一个特殊Vector子类和STL库的vector、map类型一样它也是一种可以在原分配内存不够用时自动扩容的动态数组。当前的容量不满足添加数据后所需容量时KeyedVector对象会创建一段新的((3*x + 1) / 2)长度内存,拷贝数据后释放掉原来的内存空间。

这样的模式虽然使用起来非常方便但会引发一个问题在未加锁的情况下多线程访问Vector如果一个线程的加项操作使Vector重新分配空间刚好另一个线程已经获取了指向旧内存空间的指针那它后续使用的将是一个野指针这样就会产生UAF。

这个实例中的KeyedVector、std::vector、std::map以及其它所有存在类似内存管理机制的对象多线程访问时都会存在UAF风险。

修复方法

就像上文提到的,访问这类自动管理内存分配的对象时,一定要加锁,避免产生条件竞争。

5. 超出作用域形成野指针导致UAF

漏洞信息

编号CVE-2020-0008

来源2020年1月安全公告

漏洞类型ID信息泄露

影响版本8.0、8.1, 9, 10,

危险等级:高危

漏洞模块bt

漏洞分析

这一类错误比较容易忽视是由于作用域误判导致的UAF。

BtAddrString()是一个封装好的函数用于将蓝牙地址转换为std::string对象返回。

我们看这句字符串指针赋值的代码由于BtAddrString()函数返回的string对象是在栈上的而且在红框代码调用后没有专门声明变量储存该string对象所以它的生命周期只在这一行分号就结束了立刻被析构掉。addr指针指向的内存被释放于是成了野指针后续函数使用时就会产生UAF。

修复方法

声明string对象存储返回值存储后对象生命周期和所在函数生命周期一致后续使用就不会出问题。

总结

本文分析了5个Android高危历史漏洞。

前两例堆溢出漏洞主要成因是在进行指针移动前未进行恰当的长度校验导致指针移出buffer有效范围。在封装移动指针操作、使用封装的移动指针操作时需要明确相关函数是否进行了长度校验、校验条件能否限制所有的非正规情况发生。

后三例内存释放后访问漏洞的情况更为复杂。第一多线程在提升程序运行效率的同时也需要开发者更注重线程同步。无论是主线程销毁使子线程使用的对象指针失效还是Vector类型自动分配内存导致旧数据指针失效都会导致程序面临极大风险。第二需要明确临时变量的作用域避免指针在实际使用前变为野指针。编程时应当关注临时变量的生命周期防止产生上文实例中的危险行为。

相关链接

Android安全公告https://source.android.google.cn/docs/security/bulletin


往期推荐

· Android Memory Tagging Extension (MTE) 的深度研究与应用

· ODC24 安全生态分论坛OPPO构建端云协同技术守护AI时代隐私安全

· Parcelable和Bundle的爱恨情仇

·

 TensorFlow Lite文本分类在Android上的应用


·
 浅谈Android BLE蓝牙安全隐私问题