OpenSSLX509Certificate反序列化漏洞(CVE-2015-3825)成因分析

0x00 序


序列化 (Serialization),是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。使用者可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

Android也有许多场景使用序列化进行数据传递,如App间/内的对象传递、Binder通信的数据传递等等,一般涉及跨进程、跨权限。序列化/反序列也是程序/接口的一个输入,存储区的内容或序列是可被随机填充,如果使用时验证不完整,也会导致安全漏洞。在Android系统中,可通过序列化/反序列化漏洞实现App拒绝服务、提升权限等攻击。

0x01 漏洞成因


这个Android序列化漏洞(),影响Android4.3及Android5.1版本,也就是Jelly Bean、KitKat、棒棒糖和Android M预览版1,波及55%的Android设备。可在受影响的设备上提权到system权限,也就意味着攻击者可以通过替换目标应用的apk接管受害者手机上的任意应用。这个漏洞是由的IBM安全团队Or Peles和Roee Hay在USENIX 2015大会上的议题《ONE CLASS TO RULE THEM ALL 0-DAY DESERIALIZATION VULNERABILITIES IN ANDROID》【1】。

2.1 PoC构造

Paper作者没放出Exploit也没放出PoC,根据这篇paper我们可以知道,漏洞出在OpenSSLX509Certificate(全包名路径为com.android.org.conscrypt.OpenSSLX509Certificate)类,OpenSSLX509Certificate类满足:

1)OpenSSLX509Certificate是可序列化的,因为他继承自可序列化的Certificate类;
2)它有一个finalize()方法,并且有调用native的方法(libjavascrypto.so中),参数field mContext,long型(实际为指针类型);
3)OpenSSLX509Certificate也没有实现特定的反序列化方法(readObject和readResolve);

其中mContext就是要找的可被攻击控制的指针。

我对CVE-2014-7911的POC进行了改造,首先定义类com.android.org.conscrypt.ApenSSLX509Certificate,如下:

注意包名为com.android.org.conscrypt,然后在同包名下创建一个MainActivity.java,对ApenSSLX509Certificate进行调用:

和CVE-2014-7911 PoC一样,向“android.os.IUserManager”的service发送请求前,修改类名:


 

类似CVE-2014-7911的分析,我们也对service.jar加一些日志信息输出,在Android 4.4.2的AVD中,安装、运行PoC,我们看到:

也是强制类型转换导致异常,与CVE-2014-7911的强制转换为java.io.Serializable导致的异常不同,因为传入的object本身不是序列化的对象,致使类型转换失败。CVE-2015-3825是将com.android.org.conscrypt.OpenSSLX509Certificate强制转换为java.lang.String[]而产生的异常。

验证PoC过程中,在Android 4.4.2 AVD,只触发了“Error writing application restrictions list”异常,但是GC资源回收没被触发。

在Android 5.1.1 AVD,可以通过重复发送n次的“TRANSACTION_setApplicationRestrictions”请求可以触发GC回收资源,最后导致system_server的crash:

2.2 异常分析

这里基于Android 5.1.1 AVD上的分析。

上面说到,“TRANSACTION_setApplicationRestrictions”请求发出后,导致一个异常,然后GC回收资源。

从源代码分析,GC调用OpenSSLX509Certificate. finalize():

然后调用NativeCrypto.X509_free()方法,该方法在NativeCrypto.java定义如下:

最终是在libjavacrypto.so中实现的,该函数定义在org_conscrypt_NativeCrypto.cpp文件中:

NativeCrypto_X509_free函数最后调用的X509_free是OpenSSL库提供的接口,关于如何找到该函数实现请参考附录一。

根据上面分析得到信息,在动态调试时,我们在libjavacrypto.so:: NativeCrypto_X509_free函数中下断,

下断点后,有时会碰到单步执行异常,笔者使用的一个办法供参考:设置该lib库的所有内存节属性为可写的。

在j_j_X509_free中单步步入,到libcrypto.so: ASN1_item_free函数,

sub_56420即为asn1_item_combine_free函数,定义为:

我们继续分析这个函数,

如分号后的备注所写,这段代码将初始相关变量:将&mContext存入R10,combine存入R2,it存入R5,然后验证参数的合法性。代码继续,获取aux->asn1_cb存入R9中:

继续,接下来调用asn1_do_lock函数:

此时整理asn1_do_lock函数调用时参数:R0是上面R10存储的&mContext,R1为-1,R2为上面R5存储的it。下面进入asn1_do_lock函数继续分析,取出it->funcs放入R2:

再取it->funcs即aux的ref_offset放入R3中,然后计算(char*)mContext+aux->ref_offset的存入R12:

接下来是调用CRYPTO_add_lock函数:

进一步分析CRYPTO_add_lock函数,读取R7地址的内容再加R1(R1=-1,这里也就是减1操作),然后再存入R1地址中:

调试时aux->ref_offset的值为0x10,参考x509_st结构,我们猜测(char*)mContext+0x10为mContext-> references,用记录对象引用次数,管理内存的引用。再看源码tasn_fre.c (external/openssl/crypto/asn1/)【4]的asn1_item_combine_free方法:

当asn1_do_lock返回为0,即mContext-> references为0时,才调用asn1_cb函数释放资源。

继续CRYPTO_add_lock的反汇编代码分析,由于我们在Java层传入的是一个非法地址0x7f7f7f7f,所以导到内存写异常。

Google的修复方法【2】是给mContext成员添加transient修饰符,使其不被序列化。

0x03 总结


在对象序列化时,指针成员的序列化较易存在安全风险,如CVE-2014-7911中的mOrgue,CVE-2015-3825中的mContext。本漏洞(CVE-2015-3825)中由于mContext是可序列化的,而它指向的又是X509结构的指针,当传入的序列化对象在反序列化产生异常时,系统调用GC回收资源,即mContext->references减1,这里mContext是可控制的,便可导致有限制的内存任意写(多次减1)漏洞。

0x04 参考


【1】 https://www.usenix.org/system/files/conference/woot15/woot15-paper-peles.pdf
【2】https://android.googlesource.com/platform/external/conscrypt/+/edf7055461e2d7fa18de5196dca80896a56e3540
【3】 https://github.com/Purity-Lollipop/platform_external_conscrypt/commit/edf7055461e2d7fa18de5196dca80896a56e3540
【4】 https://android.googlesource.com/platform/external/openssl/+/android-5.1.1_r13/crypto/asn1/tasn_fre.c

0x05 附录


5.1 如何找到那个叫X509_free的函数

在OpenSSL代码中怎么搜X509_free也搜索不到真正的代码实现,这是因为OpenSSL中用了一堆宏、宏嵌套定义部分函数、结构,X509_free就在其中一个。细细看代码才发现X509_free是在crypto/asn1/x_x509.c文件中由IMPLEMENT_ASN1_FUNCTIONS定义的:

顺藤摸瓜找出下面几个嵌套的宏:

映射到X509的定义,可以翻译如下:

转载自:http://drops.wooyun.org/papers/10235    原文作者:没羽@阿里移动安全