日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747


安卓漏洞 CVE 2017-13287 復(fù)現(xiàn)詳解

 

 

2018年4月,Android安全公告公布了CVE-2017-13287漏洞。

 

與同期披露的其他漏洞一起,同屬于框架中Parcelable對(duì)象的寫入(序列化)與讀出(反序列化)的不一致所造成的漏洞。

 

在剛看到谷歌對(duì)于漏洞給出的補(bǔ)丁時(shí)一頭霧水,

 

在這里要感謝heeeeen@MS509Team在這個(gè)問題上的成果,啟發(fā)了我的進(jìn)一步研究。

 

原理

 

谷歌在Android中提供了Parcelable作為高效的序列化實(shí)現(xiàn),用來支持IPC調(diào)用中多樣的對(duì)象傳遞需求。

但是序列化和反序列化的過程依舊依靠程序員編寫的代碼進(jìn)行同步。

 

那么當(dāng)不同步的時(shí)候,漏洞就產(chǎn)生了。

 

Bundle

傳輸?shù)臅r(shí)候Parcelable對(duì)象按照鍵值對(duì)的形式存儲(chǔ)在Bundle內(nèi),Bundle內(nèi)部有一個(gè)ArrayMap用hash表進(jìn)行管理。

 

反序列化過程如下:

    /* package */ void unparcel() {        synchronized (this) {           final Parcel parcelledData = mParcelledData;           int N = parcelledData.readInt();           if (N < 0) {               return;           }           ArrayMap<String, Object> map = mMap;           try {               parcelledData.readArrayMapInternal(map, N, mClassLoader);           } catch (BadParcelableException e) {           } finally {               mMap = map;               parcelledData.recycle();               mParcelledData = null;           }        }    }

首先讀取一個(gè)int指示里面有多少對(duì)鍵值對(duì)。

    /* package */ void readArrayMapInternal(ArrayMap outVal, int N,        ClassLoader loader) {        if (DEBUG_ARRAY_MAP) {           RuntimeException here =  new RuntimeException("here");           here.fillInStackTrace();           Log.d(TAG, "Reading " + N + " ArrayMap entries", here);        }        int startPos;        while (N > 0) {           if (DEBUG_ARRAY_MAP) startPos = dataPosition();           String key = readString();           Object value = readValue(loader);           outVal.Append(key, value);           N--;        }        outVal.validate();    }

之后的每一對(duì)先是Key的字符串,然后是對(duì)應(yīng)的Value。

    public final Object readValue(ClassLoader loader) {        int type = readInt();        switch (type) {        case VAL_NULL:           return null;        case VAL_STRING:           return readString();        case VAL_INTEGER:           return readInt();        case VAL_MAP:           return readHashMap(loader);        case VAL_PARCELABLE:           return readParcelable(loader);        case VAL_SHORT:           return (short) readInt();        case VAL_LONG:           return readLong();

值內(nèi)部先是一個(gè)int指示值的類型,再存儲(chǔ)實(shí)際值。

當(dāng)Bundle被寫入Parcel時(shí):

    void writeToParcelInner(Parcel parcel, int flags) {        final ArrayMap<String, Object> map;        synchronized (this) {           if (mParcelledData != null) {               if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) {                  parcel.writeInt(0);               } else {                  int length = mParcelledData.dataSize();                  parcel.writeInt(length);                  parcel.writeInt(BUNDLE_MAGIC);                  parcel.appendFrom(mParcelledData, 0, length);               }               return;           }           map = mMap;        }    }

先寫入Bundle總共的字節(jié)數(shù),再寫入魔數(shù),之后是指示鍵值對(duì)數(shù)的N,還有相應(yīng)的鍵值對(duì)。

 

LaunchAnyWhere

 

弄明白Bundle的內(nèi)部結(jié)構(gòu)后,先來看看漏洞觸發(fā)的地方:

安卓漏洞 CVE 2017-13287 復(fù)現(xiàn)詳解

 

這個(gè)流程是AppA在請(qǐng)求添加一個(gè)帳號(hào):

  1. AppA請(qǐng)求添加一個(gè)帳號(hào)
  2. System_server接受到請(qǐng)求,找到可以提供帳號(hào)服務(wù)的AppB,并發(fā)起請(qǐng)求
  3. AppB返回了一個(gè)Bundle給系統(tǒng),系統(tǒng)把Bundle轉(zhuǎn)發(fā)給AppA
  4. AccountManagerResponse在AppA的進(jìn)程空間中調(diào)用startActivity(intent)調(diào)起一個(gè)Activity。

 

在第4步中,如果AppA的權(quán)限較高,比如Settings,那么AppA可以調(diào)用正常App無法調(diào)用的未導(dǎo)出Activity。

 

并且在第3步中,AppB提供的Bundle在system_server端被反序列化,之后system_server根據(jù)之前得到的內(nèi)容再序列化并傳遞給AppA。

 

那么如果對(duì)應(yīng)的傳遞內(nèi)容的序列化和反序列化代碼不一樣,就會(huì)影響到自己以及之后的內(nèi)容的結(jié)果。

 

傳遞的Bundle對(duì)象中包含一個(gè)重要鍵值對(duì){KEY_INTENT:intent},指定了AppA稍后調(diào)用的Activity。

 

如果這個(gè)被指定成Setting中的com.android.settings.password.ChooseLockPassword,就可以在不需要原本鎖屏密碼的情況下重新設(shè)置鎖屏密碼。

 

谷歌在這個(gè)過程中進(jìn)行了檢查,保證Intent中包含的Activity所屬的簽名和AppB一致,并且不是未導(dǎo)出的系統(tǒng)Actiivity。

protected void checkKeyIntent(int authUid, Intent intent) throws SecurityException {    long bid = Binder.clearCallingIdentity();    try {        PackageManager pm = mContext.getPackageManager();        ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, maccounts.userId);        ActivityInfo targetActivityInfo = resolveInfo.activityInfo;        int targetUid = targetActivityInfo.applicationInfo.uid;        if (!isExportedSystemActivity(targetActivityInfo)            && (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid, targetUid))) {            String pkgName = targetActivityInfo.packageName;            String activityName = targetActivityInfo.name;            String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "                + "does not share a signature with the supplying authenticator (%s).";            throw new SecurityException(                   String.format(tmpl, activityName, pkgName, mAccountType));               }    } finally {           Binder.restoreCallingIdentity(bid);    }}

攻擊思路便是在system_server進(jìn)行檢查時(shí)Bundle中的惡意{KEY_INTENT:intent}看不到,但是在重新序列化之后在Setting出現(xiàn),這樣就繞過了檢查。

 

利用

 

首先來看看漏洞所在的代碼

    public static final Parcelable.Creator<VerifyCredentialResponse> CREATOR            = new Parcelable.Creator<VerifyCredentialResponse>() {        @Override        public VerifyCredentialResponse createFromParcel(Parcel source) {            int responseCode = source.readInt();            VerifyCredentialResponse response = new VerifyCredentialResponse(responseCode, 0, null);            if (responseCode == RESPONSE_RETRY) {                response.setTimeout(source.readInt());            } else if (responseCode == RESPONSE_OK) {                int size = source.readInt();                if (size > 0) {                    byte[] payload = new byte[size];                    source.readByteArray(payload);                    response.setPayload(payload);                }            }            return response;        }        @Override        public VerifyCredentialResponse[] newArray(int size) {            return new VerifyCredentialResponse[size];        }    };    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(mResponseCode);        if (mResponseCode == RESPONSE_RETRY) {            dest.writeInt(mTimeout);        } else if (mResponseCode == RESPONSE_OK) {            if (mPayload != null) {                dest.writeInt(mPayload.length);                dest.writeByteArray(mPayload);            }        }    }

仔細(xì)閱讀,會(huì)發(fā)現(xiàn)在mResponseCode為RESPONSE_OK時(shí),

 

如果mPayload為null,那么writeToParcel不會(huì)在末尾寫入0來正確的指示Payload部分的長度。

 

而在createFromParcel中是需要readInt來獲知的,這個(gè)就帶來了序列化與反序列化過程的不一致。

 

可以通過精心構(gòu)造的payload來繞過檢查。

 

難點(diǎn)在于和已經(jīng)有人公開過的CVE-2017-13288和CVE-2017-13315不同,

 

它們是重新序列化之后會(huì)多出來4個(gè)字節(jié)。這里是重新序列化之后會(huì)少4個(gè)字節(jié)。

 

安卓漏洞 CVE 2017-13287 復(fù)現(xiàn)詳解

 

利用String的結(jié)構(gòu),把惡意intent隱藏在String里。上圖每段注釋的括號(hào)里寫了其所占用的字節(jié)數(shù)。

 

在第一次反序列化時(shí),VerifyCredentialResponse內(nèi)部的0還在,惡意intent被包裝在第二對(duì)的Key中。

第二對(duì)的值的類型被制定為VAL_NULL,也就是什么都沒有,常量值為-1。

 

再次序列化時(shí)writeToParcel沒有writeInt(0),所以到達(dá)Setting的Bundle在RESPONSE_OK之后沒有0,原本的String length被視作payload length,調(diào)用readByteArray讀取。

static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jclass clazz, jlong nativePtr)    {    jbyteArray ret = NULL;    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);    if (parcel != NULL) {        int32_t len = parcel->readInt32();        // sanity check the stored length against the true data size        if (len >= 0 && len <= (int32_t)parcel->dataAvail()) {           ret = env->NewByteArray(len);           if (ret != NULL) {               jbyte* a2 = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);               if (a2) {                  const void* data = parcel->readInplace(len);                  memcpy(a2, data, len);                  env->ReleasePrimitiveArrayCritical(ret, a2, 0);               }           }        }    }    return ret;}

再次調(diào)用readInt32讀取長度,之后截取數(shù)組內(nèi)容。相應(yīng)的從Payload length開始的指定長度的內(nèi)容都被視作payload。

 

只要設(shè)置得當(dāng),惡意intent就會(huì)顯露出來成為實(shí)質(zhì)上的第二對(duì)鍵值對(duì)。

 

那么之前作為第二對(duì)值的VAL_NULL怎么辦?之前提過它的常量值是-1,上一對(duì)惡意intent剛結(jié)束,在這里調(diào)用的是readString這個(gè)函數(shù)。

const char16_t* Parcel::readString16Inplace(size_t* outLen) const{    int32_t size = readInt32();    // watch for potential int overflow from size+1    if (size >= 0 && size < INT32_MAX) {        *outLen = size;        const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));        if (str != NULL) {           return str;        }    }    *outLen = 0;    return NULL;}

再次的readInt32,得到-1,直接返回null,長度為0,會(huì)在JNI層中創(chuàng)建一個(gè)空字符串返回到JAVA層。那么就是說:VAL_NULL單獨(dú)作為一個(gè)空字符串被讀取,之后的三個(gè)藍(lán)色塊被視作值。

 

這里因?yàn)橹蟮淖址?23456,所以string_length是6.

 

這個(gè)很關(guān)鍵,因?yàn)樵赟ettings這里被readValue視作type,而6正好是VAL_STRING,也即字符串類型。于是ord('1')= 0x31被視作String length正常使用,正常讀取字符串。

 

至此Settings側(cè)正常讀取完畢,惡意intent被讀取并執(zhí)行。

 

假String的構(gòu)造

 

之前略過了包含惡意intent的假String的具體padding過程,這里展開:

 

String_length(4) + Payload_length(4) + PADDING(Size + 16) + EVIL_INTENT(Size) + PADDING(8)String_length = Payload_length = (4 + 4 + Size + 16 + Size + 8) / 2 – 1 = Size + 15

 

這里先給出公式,Size在這里就是Evil_intent部分的長度,String_length和Payload_length在Setting側(cè)都被視作payload的長度使用,故相同。

 

從兩個(gè)視角去審視這個(gè)公式:

 

  1. system_server側(cè)

對(duì)于system_server來說,從String_length開始的部分就是單純的一個(gè)字符串,那么它先讀取String_length并套用readString16Inplace中的公式。

 

它會(huì)從String_length之后讀取$ 2(1 + Size + 15)=2Size + 32 $,正好包括總長。

 

  1. Settings側(cè)

對(duì)于Settings來說,從Payload_length之后會(huì)直接截取對(duì)應(yīng)長度的內(nèi)容作為數(shù)組,即Payload_length之后$Size + 15$,

 

因?yàn)镻arcel底層的操作對(duì)4向上湊整,所以正好露出EVIL_INTENT。

這樣就可以達(dá)成效果。

 

結(jié)果

POC: https://github.com/FXTi/CVE201713287POC

 

總結(jié)

 

在IPC這塊就算谷歌引入了AIDL這種方式來規(guī)定接口,哪怕只是中間所用到的類的序列化過程出現(xiàn)一點(diǎn)失誤都會(huì)造成如此嚴(yán)重的漏洞。

 

可見安全編程以及代碼審計(jì)的必要性,沒準(zhǔn)以后還會(huì)有類似機(jī)理的漏洞被發(fā)掘出來。

 

作者:FXTi

轉(zhuǎn)載自 https://www.anquanke.com/post/id/197710

分享到:
標(biāo)簽:漏洞
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績?cè)u(píng)定