標籤:

探索影響Android的6個內核漏洞

了解安全漏洞的人都知道,除了被冠以CVE外,其它所謂的漏洞都並非真正意義上的漏洞。今天我就給你介紹一些路由器和Android手機中出現的6個真正漏洞,且屬於內核漏洞,其中就包括Google的Pixel(XL)和Nexus 5x。

在過去的兩年時間裡,我花了很多時間來審核各種Android內核漏洞,截止目前我總共發現了8個遠程漏洞,不過由於只有6個漏洞被公布了,所以為了保證安全,我只能先談談這6個,目前它們都已經被修復了。

查找漏洞的方法

我發現的漏洞出現在Qualcomm/Atheros開發的qcacld無線驅動程序中,像Pixel系列手機就用的是這種驅動。與Broadcom 的無線單晶元系統(SoC)不同的是,Qualcomm SoC只實現了部分的SoftMAC,這意味著一些MAC層管理服務(MACSublayerManagementEntity,MLME)在主機軟體中就被進行了處理,而不是在硬體或SoC固件中。正因為如此,處理任何類型的802.11管理框架的源代碼都必須在驅動程序中進行。

說起來容易做起來難,要想在qcacld驅動程序中發現漏洞可沒有想像的容易。為了向你展示其中的複雜程度,你可以看看以下的代碼部分。

scotty@hacktheplanet:~/android/msm/drivers/staging/qcacld-2.0$ find . -name * | xargs wc -ln 177 ./wcnss/inc/halCompiler.hn 826 ./wcnss/inc/wlan_nv.hn 690986 totaln

正如你所看到的,這個單一的無線驅動程序有近69萬行代碼。所以,我的計劃是從無線晶元組的固件中尋找一些中斷,以便主機驅動程序可以隨時通知具有漏洞的數據包和框架。這樣我就可以通過跟蹤調用鏈來發現一個漏洞,不過通過這種方法,我無法確定主驅動器的真正入口,所以最終我放棄了該方法。還好在不懈的努力下,我知道如何對一個無線驅動實施grep。由於我知道有進行取消認證(de-authentication)的框架,所以我就從這裡開始入手。

我很快就把範圍鎖定在了CORE/MAC/src/pe/lim/limProcessDeauthFrame.c:96:,在確定範圍後,我就對該目錄進行了深挖,看看還能發現什麼。

正如你所看到的,有大量的文件專門用於管理框架,這也證明了我鎖定的範圍是正確的。不過在對這段代碼進行審核之前,我還要確定它們已經被使用了。為此,我往其中輸入了一個pr_err("%s: HELLO WORLD!n", __func__);,確定了在802.11管理框架的主函數處理程序中,這段代碼正在使用中。

漏洞常識

在數據包固件通知後的呼叫鏈中,我就將進入以下這個函數。

在這個函數中,我將驗證數據包是否是一個管理框架,為此我將解析子類型並調用正確的管理處理程序。

switch (fc.type)n {n case SIR_MAC_MGMT_FRAME:n {n // Received Management framen switch (fc.subType)n {n case SIR_MAC_MGMT_ASSOC_REQ:n // Make sure the role supports Associationn if (LIM_IS_BT_AMP_AP_ROLE(psessionEntry) ||n LIM_IS_AP_ROLE(psessionEntry))n limProcessAssocReqFrame(pMac, pRxPacketInfo, LIM_ASSOC, psessionEntry);n else {n // Unwanted messages - Log errorn limLog(pMac, LOGE, FL("unexpected message received %X"),limMsg->type);n }n break;n case SIR_MAC_MGMT_ASSOC_RSP:n limProcessAssocRspFrame(pMac, pRxPacketInfo, LIM_ASSOC,psessionEntry);n break;n case SIR_MAC_MGMT_REASSOC_REQ:n // Make sure the role supports Reassociationn if (LIM_IS_BT_AMP_AP_ROLE(psessionEntry) ||n LIM_IS_AP_ROLE(psessionEntry)) {n limProcessAssocReqFrame(pMac, pRxPacketInfo, LIM_REASSOC, psessionEntry);n } else {n // Unwanted messages - Log errorn limLog(pMac, LOGE, FL("unexpected message received %X"),limMsg->type);n }n break;n case SIR_MAC_MGMT_REASSOC_RSP:n limProcessAssocRspFrame(pMac, pRxPacketInfo, LIM_REASSOC,psessionEntry);n break;n case SIR_MAC_MGMT_PROBE_REQ:n limProcessProbeReqFrame_multiple_BSS(pMac, pRxPacketInfo,psessionEntry);n break;n case SIR_MAC_MGMT_PROBE_RSP:n if(psessionEntry == NULL)n limProcessProbeRspFrameNoSession(pMac, pRxPacketInfo);n elsen limProcessProbeRspFrame(pMac, pRxPacketInfo, psessionEntry);n break;n

一旦正確的處理程序被調用,我就需要將數據包從原始的over-the-wire(over-the-air)表單解析為我可以使用的c-style結構。

這個轉換髮生在巨人文件dot11f.c中,這是編譯器生成的文件並處理轉換。

下面是一個通過c-style結構體傳遞原始位元組的例子:

有趣的是,在我從線程結構解析到c-style結構之後,我還必須進一步到另一個c-style結構中解析它。 dot11f c-style結構中的每個欄位都可以在80211管理框架中發揮作用,例如,如果管理框架可以包含一個功能列表,那麼在dot11f「out」結構中就將會有一個結構用於函數。由於驅動程序可能用不到這些功能,因此它就用不到一個大型結構,而只需要一個將80211框架轉化成很小的結構。這樣,我就只能為驅動程序實際使用的部分分配內存了,進一步的細化處理會在以下函數中進行。

在細化之後,驅動程序開始使用數據,不過這個過程里漏洞的發現而遠不相關,下面就是數據包的數據流演示圖。

第一個漏洞(CVE-2017-11013)

現在我已經對高層次和一般數據流程進行了一個大概的了解,接下來我來介紹第一個也是最標準的漏洞。

第一個漏洞存在於dot11f.c文件中,正如我上面提到的那樣,它負責將over-the-wire數據包解析為c-style結構。當dot11f.c文件獲得一個over-the-wire數據包時,數據包是由一系列信息元素(IE)構成的,其中每個IE都是類型長度值(TLV)。 IE包含一個位元組標籤,一個位元組長度和最多255個位元組的值。

標籤會被標準化,以方便表示特定類型的元素。

dot11f.c代碼包含許多IE定義,這些定義代表了數據包中可能包含的內容。例如,假設我用手機訪問家裡的網路,那麼在協議鏈中的某個時刻,訪問點將返回給我一個關聯響應。當手機獲得關聯響應數據包時,驅動程序最終會通過調用dot11fUnpackAssocResponse來立即調用UnpackCore。

現在,讓我把調用分解成一些UnpackCore。pCtx是驅動程序的狀態結構,pBuf是TLV的over-the-wire列表,nBuf表示的是pBuf的總位元組數,FFS_AssocResponse是一個必須在pBuf中的強制IE的列表,而IES_AssocResponse則是一個在pBuf中的可選IE列表。由於pFrm是我要用到的「out」結構,這意味著解析的IE將被翻譯成tDot11fAssocResponse結構的一部分。

現在,我會將大量的結構定義進行轉儲,這對於理解第一個漏洞是非常重要的。讓我們一起來看一下IES_AssocResponse結構的定義。請注意,這只是數據包可以包含的部分可選IE列表:

現在看看「out」結構的定義tDot11fAssocResponse:

對於tDot11fAssocResponse結構的每一部分,IES_AssocResponse數組中都有相應的tIEDefn結構。現在將tIEDefn結構翻譯成英語,讓我們更好的理解「out」結構。

typedef struct sIEDefn {n tANI_U32 offset;n n /* This is the offset into the out structuren * where we will place the parsed data.n * So for instance the out structure cann * Contain tDot11fIEExtSuppRates ExtSuppRates;n * We would do:n * .offset = offsetof(tDot11fAssocResponse, ExtSuppRates)n */n tANI_U32 presenceOffset;n /* Each member in the out structure has a presentn * flag which gets set if the parser saw the IE inn * the raw packet buffer and parsed data into then * Out member.n */n n tANI_U32 countOffset;n /* Some IEs can be sent more than once in the rawn * tlv packet. This offset represents where in then * Out structure the count for how many IEs of thisn * type weve parsed lives. So for example, in then * Assoc response go above and look forn * tDot11fIERICDataDesc RICDataDesc[2];n * and right above youll see:n * tANI_U16 num_RICDataDesc;n * So well have a:n * .countOffset = offsetof(tDot11fAssocResponse, num_RICDataDesc);n */n const char *name;n /* self explanatory, name of the IE were parsing */n n tANI_U16 arraybound;n /* If the parser accepts multiple IEs in a single packetn * of the same type, like we showed for RICDataDesc[2],n * this value represents how many IEs we can accept.n * So for a Assoc response frame there are 2 array boundn * vars:n * tDot11fIERICDataDesc RICDataDesc[2];n * tDot11fIEWMMTSPEC WMMTSPEC[4];n * Go back up to the picture of the Assoc Resp out frame andn * Youll see above each one there is a num_* representingn * once again how many of theses IEs weve parsed (count offset).n * So, for the RICDataDesc arraybound would = 2,n * and for WMMTSPEC arraybound = 4. Everything else wouldn * be 0.n */n n tANI_U16 minSize;n /* The minimum size of the IE */n tANI_U16 maxSize;n /* Maximum size of the IE */n n tANI_U16 sig;n /* This is a unique number representing the IE, usedn * internally for the parser. Well see more about itn * later. Its just used as a case in a huge switchn * statement to get us to the right parser functionn */n unsigned char oui[5];n unsigned char noui;n /* So along with the eid below the IE can have a OUIn * which is mostly used with tag # 221 which is then * Vendor tag. If we want to support multiple tagn * Number 221s each with different types well passn * along an OUI which is just more bytes which representn * a specific type of 221. noui is just how many OUIn * bytes we should look for.n */n tANI_U8 eid;n /* eid is just the TLV Tag. Dunno why they didnt call itn * "tag"n */n n tFRAMES_BOOL fMandatory;n} tIEDefn;n

要注意的是,對於IES_AssocResponse列表來說,它是原始數據包中的可選IE。由於其中的每部分都是我上面描述的tIEDefn。所以會舉幾個例子,來映射它們上面的定義,以確保你了解它是如何設置的。請注意,為了方便理解,我刪除了大部分的定義,因為它太大了。

static const tIEDefn IES_AssocResponse[] = {n {n offsetof(tDot11fAssocResponse, SuppRates),n offsetof(tDot11fIESuppRates, present),n 0,n "SuppRates",n 0,n 2,n 14,n SigIeSuppRates,n {0, 0, 0, 0, 0},n 0,n DOT11F_EID_SUPPRATES,n 1,n},n {n offsetof(tDot11fAssocResponse, ExtSuppRates),n offsetof(tDot11fIEExtSuppRates, present),n 0,n "ExtSuppRates",n 0,n 3,n 14,n SigIeExtSuppRates,n {0, 0, 0, 0, 0},n 0,n DOT11F_EID_EXTSUPPRATES,n 0,n },n...n...n...n {n offsetof(tDot11fAssocResponse, RICDataDesc),n offsetof(tDot11fIERICDataDesc, present),n offsetof(tDot11fAssocResponse, num_RICDataDesc),n "RICDataDesc",n 2,n 2,n 550,n SigIeRICDataDesc,n {0, 0, 0, 0, 0},n 0,n DOT11F_EID_RICDATADESC,n 0,n},n...n...n...n {n offsetof(tDot11fAssocResponse, WMMTSPEC),n offsetof(tDot11fIEWMMTSPEC, present),n offsetof(tDot11fAssocResponse, num_WMMTSPEC),n "WMMTSPEC",n 4,n 63,n 63,n SigIeWMMTSPEC,n {0, 80, 242, 2, 2},n 5,n DOT11F_EID_WMMTSPEC,n 0,n},n...n...n}n

至此,我已理解了解析器使用的一些結構定義,現在,就讓我來看看UnpackCore中的相關解析器代碼。

while (nBufRemaining)n {n if (1 == nBufRemaining)n {n FRAMES_LOG0(pCtx, FRLOGE, FRFL("This frame reports "n "only one byte remaining after its fixed fields.n"));n status |= DOT11F_INCOMPLETE_IE;n FRAMES_DBG_BREAK();n goto MandatoryCheck;n}n pIe = FindIEDefn(pCtx, pBufRemaining, nBufRemaining, IEs);n

因此,我把FindIEDefn定義的理解是這樣的:

在高層次上,FindIeDefn會遍歷整個可選的IE列表,在本文所舉的例子中,IES_AssocResponse會查看當前索引處的緩衝區是否包含IE的標記。所以函數將從IES_AssocResponse數組的索引0開始,最終會找到代表eid的tIEDefn結構。

static const tIEDefn* FindIEDefn(tpAniSirGlobal pCtx,n tANI_U8 *pBuf,n tANI_U32 nBuf,n const tIEDefn IEs[])n{n const tIEDefn *pIe;n (void)pCtx;n pIe = &(IEs[0]);n /* Start on the 0th index of our list of Optional IEs:n * IES_AssocResponsen */n while (0xff != pIe->eid)n {n /* While we havent reached the end of the IES_AssocResponse list */n if (*pBuf == pIe->eid)n {n/* If the raw packet at the current index has the current tag */n /* If there is no extra noui bytes to check return */n if (0 == pIe->noui) return pIe;n /* If the tags match and there is extra OUI data to compare lets compare */n if ( ( nBuf > (tANI_U32)(pIe->noui + 2) ) &&n ( !DOT11F_MEMCMP(pCtx, pBuf + 2, pIe->oui, pIe->noui) ) )n return pIe;n }n /* The raw packet didnt match this IE definition, lets try the next one */n ++pIe;n }n/* This IE isnt recognized for this type of frame, return NULL */n return NULL;n}n

在FindIEDefn調用之後會立即解包內核:

pIe = FindIEDefn(pCtx, pBufRemaining, nBufRemaining, IEs);n/* So at this point lets just assume we have a proper pIe.n * lets say we saw a tDot11fIEWMMTSPEC IE.n */n/* Extract the TAG from the Packet */n eid = *pBufRemaining++; --nBufRemaining;n/* Extract the Length from the packetn len = *pBufRemaining++; --nBufRemaining;n/* If we did find the IE Definition in the raw packet,n * well do some validation checks.n */n if (pIe && pIe->noui)n{n if (pIe->noui > nBufRemaining)n {n FRAMES_LOG3(pCtx, FRLOGW, FRFL("IE %d reports "n "length %d, but it has an OUI of %d bytes.n"),n eid, len, pIe->noui);n FRAMES_DUMP(pCtx, FRLOG1, pBuf, nBuf);n FRAMES_LOG2(pCtx, FRLOG1, FRFL("Weve parsed %d by"n "tes of this buffer, and show %d left.n"), pBufRemaining - pBuf, nBufRemaining);n status |= DOT11F_INCOMPLETE_IE;n FRAMES_DBG_BREAK();n goto MandatoryCheck;n }n pBufRemaining += pIe->noui;n nBufRemaining -= pIe->noui;n len -= pIe->noui;n }n/* If the TLV reports a Length greater than the amount of datan* left in our buffer we will bail out. This will preventn* an OOB read.n*/n if (len > nBufRemaining)n {n FRAMES_LOG3(pCtx, FRLOGW, FRFL("IE %d reports length %"n "d, but there are only %d bytes remaining in this"n " frame.n"), eid, len, nBufRemaining);n FRAMES_DUMP(pCtx, FRLOG1, pBuf, nBuf);n FRAMES_LOG2(pCtx, FRLOG1, FRFL("Weve parsed %d by"n "tes of this buffer, and show %d left.n"), pBufRemaining - pBuf, nBufRemaining);n status |= DOT11F_INCOMPLETE_IE;n FRAMES_DBG_BREAK();n goto MandatoryCheck;n }n /* If our call to FindIEDefn did find an IE we recognize inn * The packet lets start parsing it:n if (pIe)n {n /* do some more size validations */n if (nBufRemaining < pIe->minSize - pIe->noui - 2U)n {n FRAMES_LOG3(pCtx, FRLOGW, FRFL("The IE %s must be "n "at least %d bytes in size, but there are only"n "y %d bytes remaining in this frame.n"),n pIe->name, pIe->minSize, nBufRemaining);n FRAMES_DUMP(pCtx, FRLOG1, pBuf, nBuf);n status |= DOT11F_INCOMPLETE_IE;n FRAMES_DBG_BREAK();n goto MandatoryCheck;n }n elsen {n /* Some weird check that doesnt really do anything exceptn * complain in dmesg.n */n if (len > pIe->maxSize - pIe->noui - 2U){n FRAMES_LOG1(pCtx, FRLOGW, FRFL("The IE %s reports "n "an unexpectedly large size; it is presumably "n "more up-to-date than this parser, but this wa"n "rning may also indicate a corrupt frame.n"),n pIe->name);n FRAMES_DUMP(pCtx, FRLOG1, pBuf, nBuf);n }n/* If our IE element is array bound, like I explained above:n * If our frame type can accept multiple IEs of the same typen * in the same packet we will grab the current Count of hown * Many weve already parsed and store it in countOffset.n * On the first iteration it will be 0, second time we seen * the same IE it will be 1, etc etc. If our IE elementn * is not array bound countOffset will become 0.n */n countOffset = ( (0 != pIe->arraybound) * ( *(tANI_U16* )(pFrm + pIe->countOffset)));n switch (pIe->sig) {n/* Now we switch on the signature which will take us to the correct parsingn * function.n * Ive listed both cases for the two Array bound signatures in ourn * Association response below:n */n n case SigIeRICDataDesc:n //reset the pointers back since this is a container IE and it doesnt have its own EID and Len.n pBufRemaining -= 2; nBufRemaining += 2;n if ( pIe && pIe->noui ) {n pBufRemaining -= pIe->noui;n nBufRemaining += pIe->noui;n len += pIe->noui;n }n status |= GetContainerIesLen(pCtx, pBufRemaining, nBufRemaining, &len, IES_RICDataDesc);n if (status != DOT11F_PARSE_SUCCESS && status != DOT11F_UNKNOWN_IES ) break;n status |= dot11fUnpackIeRICDataDesc(pCtx, pBufRemaining, len, ( tDot11fIERICDataDesc* )(pFrm + pIe->offset + sizeof(tDot11fIERICDataDesc)*countOffset) );n break;n ...n ...n ...n /* in the case of a SigIeWMMTSPEC IE well call dot11fUnpackIeWMMTSPEC() */n case SigIeWMMTSPEC:n status |= dot11fUnpackIeWMMTSPEC(pCtx, pBufRemaining, len, ( tDot11fIEWMMTSPEC* )(pFrm + pIe->offset + sizeof(tDot11fIEWMMTSPEC)*countOffset) );n /* Lets break this call down a bit more:n * dot11fUnpackIeWMMTSPEC(pCtx,n * pBufRemaining,n * len,n * (tDot11fIEWMMTSPEC*)(pFrm + pIe->offset + sizeof(tDot11fIEWMMTSPEC) * countOffset));n *n *n * The most interesting portion of this call is the last parameter.n * pFrm is the start -in memory- of our Out structure tDot11fAssocResponse.n * pIe->offset, like we described above is the amount of bytesn * into tDot11fAssocResponse where the tDot11fIEWMMTSPEC membern * lives. The second half of the math:n * sizeof(tDot11fIEWMMTSPEC) * countOffset will calculaten * the index into the array. Remember for the WMMTSPECn * it looks like this in the out structure:n * tDot11fIEWMMTSPEC WMMTSPEC[4];n * So there are 4 slots in the array we can save.n * So like I said above the first time we do this well save inton * slot 0 because count offset will be 0.n */n break;n ...n ...n ...n default:n FRAMES_LOG1(pCtx, FRLOGE, FRFL("INTERNAL ERROR"n ": I dont know about the IE signature %d"n "-- this is most likely a framesc bug.n"),n pIe->sig);n FRAMES_DBG_BREAK();n return DOT11F_INTERNAL_ERROR;n }n/* If we are array bound well add one to our num_*n * in the case of WMMTSPEC the below math will take usn * to the location in memory where the num_WMMTSPECn * inside the tDot11fAssocResponse lives and well addn * one to it.n */n if (pIe->arraybound) (++( *(tANI_U16*)(pFrm + pIe->countOffset) ));n/* At this point if there is remaining IEs in the inputn * buffer well jump back up to the top of this while loopn * try and find another IE defn and parse the rest of the packet.n */n }n }n}n

現在,我提供的這些信息,足以讓你發現漏洞了。如果你還沒有理解,我可以再給你一些提示:當我在關聯響應中發送另一個wmtspec IE時,會發生什麼?它將如何解析,在第二次迭代中會存儲哪個索引,接下來,不斷地發送WMMTSPEC IE的結果又是什麼?

解析代碼時,存在的問題是我從來沒有驗證countOffsetnum_對arraybound的影響。還記得上面我說過,countOffsetnum_表示目前已經分析的元素數,而arraybound則表示我有多少個可用的插槽。所以我可以發送15個WMMTSPEC IE,這樣解析代碼將繼續不斷地解析它們,並將它們存儲在0->14的插槽中。請注意,我只有4個WMMTSPECS插槽:

typedef struct sDot11fAssocResponse{n tDot11fFfCapabilities Capabilities;n tDot11fFfStatus Status;n tDot11fFfAID AID;n tDot11fIESuppRates SuppRates;n tDot11fIEExtSuppRates ExtSuppRates;n tDot11fIEEDCAParamSet EDCAParamSet;n tDot11fIERCPIIE RCPIIE;n tDot11fIERSNIIE RSNIIE;n tDot11fIERRMEnabledCap RRMEnabledCap;n tDot11fIEMobilityDomain MobilityDomain;n tDot11fIEFTInfo FTInfo;n tANI_U16 num_RICDataDesc;n tDot11fIERICDataDesc RICDataDesc[2];n tDot11fIEWPA WPA;n tDot11fIETimeoutInterval TimeoutInterval;n tDot11fIEHTCaps HTCaps;n tDot11fIEHTInfo HTInfo;n tDot11fIEWMMParams WMMParams;n tDot11fIEWMMCaps WMMCaps;n tDot11fIEESERadMgmtCap ESERadMgmtCap;n tDot11fIEESETrafStrmMet ESETrafStrmMet;n tDot11fIEESETxmitPower ESETxmitPower;n tANI_U16 num_WMMTSPEC;n tDot11fIEWMMTSPEC WMMTSPEC[4];n tDot11fIEWscAssocRes WscAssocRes;n tDot11fIEP2PAssocRes P2PAssocRes;n tDot11fIEVHTCaps VHTCaps;n tDot11fIEVHTOperation VHTOperation;n tDot11fIEExtCap ExtCap;n tDot11fIEOBSSScanParameters OBSSScanParameters;n tDot11fIEQosMapSet QosMapSet;n} tDot11fAssocResponse;n

所以,如果我發送了15個WMMTSPEC IE,那多餘的11個將會溢出到WscAssocRes,P2PAssocRes,VHTCaps等處,直到我在tDot11fAssocResponse結構之外進行攻擊。

這個漏洞之所以如此之標準,是因為你可以針對不同類型的內存。由於該漏洞存在於IE的通用解析代碼中,因此每個「out」結構都分配在不同的位置,比如,有些分配在堆棧中,有些分配在.bss中。所以你就會有很多可以溢出的地方,此時你只需要找到一個具有數組IE的802.11管理數據包,並查看「out」結構被分配的位置。你甚至可以從.bss和堆中找到這些溢出,稍後我會對此詳細解釋。這個漏洞對於一個真正的近端內核遠程代碼執行來說是一個很好的攻擊目標,因為你已經控制了數據,且有多種可以溢出的位置。

以下是我在UnpackCore中偶然發現這段代碼時,所出現的漏洞。

case SigIeNeighborReport:n if (countOffset < MAX_SUPPORTED_NEIGHBOR_RPT) {n status |= dot11fUnpackIeNeighborReport(pCtx, pBufRemaining, len, ( tDot11fIENeighborReport* )(pFrm + pIe->offset + sizeonf(tDot11fIENeighborReport)*countOffset) );n } else {n status |= DOT11F_BUFFER_OVERFLOW;n }n break;n

我很驚訝地看到,對於這個IE類型,研發人員已經發布了一個補丁來修復了內存漏洞。不過,我還是想看看這個問題在什麼情況下得到了修復,為此我執行了git blame命令,並且發現了其中的問題:

可以看到,在2014年10月14日,Qualcomm曾有機會修復整個漏洞。不過,當時並沒有人注意到這個IE漏洞。所以這也提醒了開發人員,如果是嵌入式系統或驅動程序或C或C ++代碼,那編寫代碼時一定要格外注意。

我上面說過,這個漏洞之所以很標準,是因為有一些內存區域也存在溢出。這樣,我就可以在BSS,堆和棧中也找到相應的問題。對於棧,我現在可以做到將其粉碎(smash the stack)並控制其IP,具體方法請點此。

但在我的結構和棧cookie之間沒有任何指針或者任何有用的東西,不過,.bss中也可以發生溢出,這可能會有幫助。在一些BSS分配中,有一個巨大的VosContext結構,我可以把它溢出。另外,我還可以溢出BSS段,但還不足以深入到VoScontext。由於這種方法不會破壞什麼結構,所以稍後我會在.bss和堆中複製我的溢出結構。

從上面可以看出,解析代碼溢出了ar. ricdatadesc[],由於我可以控制溢出的數量,因此我也可以控制ar.num_RICDataDesc。這意味著,我可以控制有多少RicDataDescs插入&pAssocRsp->RICData [cnt],其中pAssocRsp是我分配的堆。所以使用這個簡單的技巧,我可以從bss和堆中溢出受控數據。

第二個漏洞(CVE-2017-9714)和第三個漏洞:遠程內核DoS(無限循環)

雖然這兩個漏洞可能不會讓那些專門尋找漏洞利用或內存損壞的人高興起來,但對我來說卻是非常有意義的,因為它們是整數溢出漏洞。

當我將手機置於AP模式進行網路共享時,會有一些其他的管理數據包被打開以供驅動程序解析,如關聯請求,身份驗證請求等。AP模式(ACCESS POINT MODE)就是無線熱點模式,是無線AP的默認工作模式,這種AP模式下無線網卡能找到AP,並通過AP接入區域網中。如果手機處於AP模式,並發送關聯請求,我可以發送一些名為「opaque RSN data」或「WPA Opaque data」的信息。雖然我不知道它們是什麼,但是你可以把它發送出去,這正是我所關心的。

如果我發送了這些信息,UnpackCore中的80211解析代碼將把它很好地放置到pAssocReq變數中,並將pAssocReq ->rsnPresent設置為1。最終,這條信息會到達/lim/limProcessAssocReqFrame.c:

tSirRetStatusnsirConvertAssocRespFrame2Struct(tpAniSirGlobal pMac,ntANI_U8 *pFrame,n tANI_U32 nFrame,n tpSirAssocRsp pAssocRsp)n{n /* pAssocRsp is allocated on the heap by the function that calls us */n static tDot11fAssocResponse ar;n tANI_U32 status;n tANI_U8 cnt =0;n // Zero-init our [out] parameter,n vos_mem_set( ( tANI_U8* )pAssocRsp, sizeof(tSirAssocRsp), 0 );n // delegate to the framesc-generated code,n status = dot11fUnpackAssocResponse( pMac, pFrame, nFrame, &ar);n ...n ...n ...n#ifdef WLAN_FEATURE_VOWIFI_11Rn if (ar.num_RICDataDesc) {n for (cnt=0; cnt < ar.num_RICDataDesc; cnt++) {n if (ar.RICDataDesc[cnt].present) {n vos_mem_copy( &pAssocRsp->RICData[cnt], &ar.RICDataDesc[cnt],n sizeof(tDot11fIERICDataDesc));n }n }n pAssocRsp->num_RICData = ar.num_RICDataDesc;n pAssocRsp->ricPresent = TRUE;n }n#endifn}n

當我打包一些RSN數據時,我將調用dot11fUnpackIeRSN並將長度/數據傳遞給該函數。現在,解包函數的任務就是將原始位元組轉換為一個c-style的結構,將結果保存到基於棧的Dot11IERSN變數中。

讓我來看一下dot11fUnpackIeRSN的函數到底執行了什麼內容:

你可以看到通過framesntohs調用從原始緩衝區中提取了pwise_cipher_suite_count。在我確認pwise_cipher_suite_count小於4之後,它會立即進行驗證。如果它比較大,我會取消當前標誌並返回DOT11F_SKIPPED_BAD_IE;,所以,如果它們被正確解析,就會返回一個漏洞,那如果沒有被正確解析呢?

如果回過頭來看看我如何調用dot11fUnpackIeRSN,那你會看到以下方法。

if(pAssocReq->rsn.length) {n // Unpack the RSN IEn dot11fUnpackIeRSN(pMac, &pAssocReq->rsn.info[0], pAssocReq->rsn.length, &Dot11fIERSN);n

看到什麼漏洞了嗎?由於調用函數不會檢查dot11fUnpackIeRSN的返回值。如果要繼續使用代碼,就要調用limCheckRxRSNIeMatch。

看到第一個循環,你是否能推斷出為什麼我可以進行無限循環?如果不知道,我可以給你一些提示,比如,什麼是pwise_cipher_suite_count的類型?

由於我完全控制了pwise_cipher_suite_count且它是一個u16(我用framesntohs(frames-network-to-host-short)提取它)且在循環中的count變數(i)類型是u8,所以我可以進行無限循環。如果我把pwise_cipher_suite_count為31337 dot11funpackiersn發送,則循環將失敗,所以永遠不會將pwise_cipher設置為0,由於我從不檢查返回值並不斷進入limCheckRxRSNIeMatch。因此,一旦進入for循環,則pwise_cipher_suite_count就是u16(31337),u8最大值為255,所以u8(i)從255一直循環到0,由於寬度限制,顯然永遠不會達到31337。所以for循環一直在該範圍循環,直到watchdog啟動並在一段時間後攻擊內核。

我在上面說過,你可以發送WPA不透明數據,這樣,一個未檢查的返回值會導致u8/u16無法與無限循環進行匹配。

第四個漏洞:堆緩衝區溢出(CVE-2017-9714)

802.11管理框架的一部分可以包括一些QoS類型信息,你可以將它們單獨發送,也可以將其附加到802.11關聯響應中。具體來說就是,如果我附加一個QoSMapset元素在我的關聯響應中,就可能會導致一些損壞。

正如你在上面的例子中看到的那樣,我設置了num_dscp_exceptions,然後驗證長度小於60.一旦我確認開始使用調用鏈,就會以sirConvertAssocRespFrame2Struct結尾。在這個函數結束時,我會執行以下操作。

if ( ar.QosMapSet.present )n {n pAssocRsp->QosMapSet.present = 1;n ConvertQosMapsetFrame( pMac, &pAssocRsp->QosMapSet, &ar.QosMapSet);n limLog( pMac, LOG1, FL("Received Assoc Response with Qos Map Set"));n limLogQosMapSet(pMac, &pAssocRsp->QosMapSet);n }n

我通過調用ConvertQosMapsetFrame(pMac,&pAssocRsp->QosMapSet,&ar.QosMapSet);從一個結構轉換到另一個結構。

如果光看這個函數,它是沒有任何意義的,這時如果你再次查看dot11fUnpackIeQosMapSet函數,你可以看到,我可以將可以將0 -> 60位元組的數據放到dscp_exception數組中。對於該數組中存儲的數據,我將在pDst->num_dscp_exceptions中設置其大小。

現在再來看一下ConvertQosMapsetFrame函數:

if (dot11fIE->num_dscp_exceptions > 58)n dot11fIE->num_dscp_exceptions = 58;n Qos->num_dscp_exceptions = (dot11fIE->num_dscp_exceptions - 16)/2;nfor (i = 0; i < Qos->num_dscp_exceptions; i++)n

正如你在上面的例子中看到的,如果> 58,則我會設置最大值,但是沒有最小值檢查。那麼,如果我發送14個num_dscp_exceptions,會怎麼樣呢?由於會產生整數演算法(integer math),所以我能得到(INT_MIN + 1)或0xfffffffe。當我將int(INT_MIN + 1)除以2時,便得到INT_MIN。當我將一個完整的32位INT_MIN放入u8時,我會得到了255(我只是取了int的底部位元組( bottom byte ))。

因此,通過發送14個num_dscp_exceptions,我可以欺騙代碼,將255存儲到Qos->num_dscp_exceptions中,然後在其上循環。

typedef struct sSirQosMapSetn{n tANI_U8 present;n tANI_U8 num_dscp_exceptions;n tANI_U8 dscp_exceptions[21][2];n tANI_U8 dscp_range[8][2];n} tSirQosMapSet, *tpSirQosMapSet;n

你可以在上面看到有42個用於dscp_exceptions的插槽,所以為了方便,我刪除了這個結構的末端並將其放入臨近的堆對象中。

第五個漏洞(CVE-2017-11014):另一個堆溢出

由於Qcacld驅動程序支持roaming操作框架,所以我認為它適用於IEEE 802.11k協議規範。這個操作框架旨在幫助發現具有Neighbor AP的802.11,而不必進行自我掃描。這個操作之所以很受歡迎,是因為它是為運行功率受限設備而設計的,這樣在查找時它們就不必連續掃描網路,而僅僅對當前關聯的Neighbor AP進行詢問,並且AP將執行掃描並返回信息。

roaming文件夾是什麼文件夾?

Windows系統下的roaming文件夾,是用於存放程序數據的文件夾,如QQ音樂,QQ號碼的登陸信息等,在我們使用電腦程序時時,這個文件夾因為緩存程序數據的原因,體積會不斷膨脹。

有趣的是,有時驅動甚至不需要詢問這些數據。訪問點可以只通過發送roaming操作框架,qcacld就會進行開始解析。

所以讓我試著發送一個roaming操作框架,看看會發生什麼?

首先我會進入limProcessActionFrame()函數:

由於我正在發送無線電測量請求,我將輸入__limProcessRadioMeasureRequest:

像所有的漏洞和數據包一樣,我會選擇dot11fUnpack函數,將其解析為我的c-style結構。我將把下面的可選IE數組傳遞給UnpackCore。請注意,IE數組描述了解析器期望的可能在數據包中的元素類型。

在這個漏洞中,我將重點關注可以使用此數據包發送的APChannel報告函數。

offsetof(tDot11fIEMeasurementRequest, measurement_request.Beacon.APChannelReport),n offsetof(tDot11fIEAPChannelReport, present),n offsetof(tDot11fIEMeasurementRequest, measurement_request.Beacon.num_APChannelReport),n "APChannelReport" , 2, 3, 53, SigIeAPChannelReport)n

正如你所看到的,有一個num_APChannelReport函數,所以我可以使用這個結構中的第一個漏洞(CVE-2017-11013),但前提是它是被修復了的。根據其定義,我可以發送2個AP信道報告(字元串之後的數字是arraybound),現在看看我如何解析AP信道報告:

正如你所看到的,我將信道數量限制在50,因為pDst結構只有50個插槽。

這樣我就發送了2個AP信道報告,每個信道數量限制在50。現在我將展開調用堆棧並將其返回到__limProcessRadioMeasureRequest,在這個函數中,我將調用rrmProcessRadioMeasurementRequest( pMac, pHdr->sa, &frm, psessionEntry ); ,&from包含我的解析框架,或者是在解析內核中提到的pDst。

在rrmProcessRadioMeasurementRequest里,我看到了下面的代碼(為了方便介紹,我跳過了一些不相關的內容)。

你可以看到我調用了rrmProcessBeaconReportReq,並且我輸入的參數之一是&rRMReq->MeasurementRequest [i]。由於我輸入的結構包含了我的2個AP報告,所以現在就讓我解析一下該函數(為了方便介紹,我跳過了一些代碼)。

在上面的代碼中,我遍歷2個AP信道報告,並將信道列表存儲到pChanList中。在每個列表之後,我都將指針向前進行了移動,以便更深入到目標數組中。請注意,由於每個AP信道報告發送了50個信道。所以在首次複製之後,我將把指針向上移動50個位元組。看看tANI_U8 *pChanList = pSmeBcnReportReq->channelList.channelNumber;,這個信道號數組看起來如下所示。

讓我看看信道號數組是如何對SIR_ESE_MAX_MEAS_IE_REQS進行數據類型定義的:

所以我將100個位元組(每個ap報告50個)存儲到一個配置為8位元組的數組中。

第六個漏洞(CVE-2017-11015):另一個堆溢出

如果我在AP模式下向手機發送特製的身份驗證框架,我將登陸到sirConvertAuthFrame2Struct,並立即調用dot11fUnpackAuthentication。對於認證數據包,我可以附加一些特定的TLV:

static const tIEDefn IES_Authentication[] = {n {offsetof(tDot11fAuthentication, ChallengeText), offsetof(tDot11fIEChallengeText, present), 0, "ChallengeText" , 0, 3, 255, SigIeChallengeText, {0, 0, 0, 0, 0}, 0, DOT11F_EID_CHALLENGETEXT, 0, },n {offsetof(tDot11fAuthentication, RSNOpaque), offsetof(tDot11fIERSNOpaque, present), 0, "RSNOpaque" , 0, 8, 255, SigIeRSNOpaque, {0, 0, 0, 0, 0}, 0, DOT11F_EID_RSNOPAQUE, 0, },n {offsetof(tDot11fAuthentication, MobilityDomain), offsetof(tDot11fIEMobilityDomain, present), 0, "MobilityDomain" , 0, 5, 5, SigIeMobilityDomain, {0, 0, 0, 0, 0}, 0, DOT11F_EID_MOBILITYDOMAIN, 0, },n {offsetof(tDot11fAuthentication, FTInfo), offsetof(tDot11fIEFTInfo, present), 0, "FTInfo" , 0, 84, 222, SigIeFTInfo, {0, 0, 0, 0, 0}, 0, DOT11F_EID_FTINFO, 0, },n {offsetof(tDot11fAuthentication, TimeoutInterval), offsetof(tDot11fIETimeoutInterval, present), 0, "TimeoutInterval" , 0, 7, 7, SigIeTimeoutInterval, {0, 0, 0, 0, 0}, 0, DOT11F_EID_TIMEOUTINTERVAL, 0, },n {offsetof(tDot11fAuthentication, RICDataDesc), offsetof(tDot11fIERICDataDesc, present), offsetof(tDot11fAuthentication, num_RICDataDesc), "RICDataDesc" , 2, 2, 550, SigIeRICDataDesc, {0, 0, 0, 0, 0}, 0, DOT11F_EID_RICDATADESC, 0, },n {0, 0, 0, NULL, 0, 0, 0, 0, {0, 0, 0, 0, 0}, 0, 0xff, 0, }, };n

對我來說,我最感興趣的是挑戰字串 (Challenge Text) ,不過請注意,挑戰字串僅在WEP加密方案中有用。無論手機的AP是否啟用了WEP,你都可以將其添加到身份驗證框架中,並讓驅動程序解析它,如下所示,UnpackCore會調用挑戰文本的特定解析函數:

tANI_U32 dot11fUnpackIeChallengeText(tpAniSirGlobal pCtx, tANI_U8 *pBuf, tANI_U8 ielen, tDot11fIEChallengeText *pDst)n{n tANI_U32 status = DOT11F_PARSE_SUCCESS;n (void) pBuf; (void)ielen; /* Shutup the compiler */n if (pDst->present) status = DOT11F_DUPLICATE_IE;n pDst->present = 1;n pDst->num_text = (tANI_U8)( ielen );n if (ielen > 253){n pDst->present = 0;n return DOT11F_SKIPPED_BAD_IE;n }n DOT11F_MEMCPY(pCtx, pDst->text, pBuf, ( ielen ) );n (void)pCtx;n return status;n} /* End dot11fUnpackIeChallengeText. */n

正如你所看到的,他們將挑戰字串的數量限制在253個位元組。當這個函數完成UnpackCore和dot11fUnpackAuthentication後,我將返回到sirConvertAuthFrame2Struct,開始執行以下操作:

// & "transliterate" from a tDot11fAuthentication to a tSirMacAuthFrameBody...n pAuth->authAlgoNumber = auth.AuthAlgo.algo;n pAuth->authTransactionSeqNumber = auth.AuthSeqNo.no;n pAuth->authStatusCode = auth.Status.status;n if ( auth.ChallengeText.present )n {n pAuth->type = SIR_MAC_CHALLENGE_TEXT_EID;n pAuth->length = auth.ChallengeText.num_text;n vos_mem_copy( pAuth->challengeText, auth.ChallengeText.text, auth.ChallengeText.num_text );n }n

截至目前,一切看起來都很正常,但由於pAuth是我的身份驗證轉換函數的一個參數,所以我需要看看這個pAuth結構是什麼樣的。

sirConvertAuthFrame2Struct(tpAniSirGlobal pMac,n tANI_U8 *pFrame,n tANI_U32 nFrame,n tpSirMacAuthFrameBody pAuth)n

它的類型是tpSirMacAuthFrameBody,如下所示:

typedef __ani_attr_pre_packed struct sSirMacAuthFrameBodyn{n tANI_U16 authAlgoNumber;n tANI_U16 authTransactionSeqNumber;n tANI_U16 authStatusCode;n tANI_U8 type; // = SIR_MAC_CHALLENGE_TEXT_EIDn tANI_U8 length; // = SIR_MAC_AUTH_CHALLENGE_LENGTHn tANI_U8 challengeText[SIR_MAC_AUTH_CHALLENGE_LENGTH];n} __ani_attr_packed tSirMacAuthFrameBody, *tpSirMacAuthFrameBody;n

最後,我需要看看SIR_MAC_AUTH_CHALLENGE_LENGTH是多大:

#define SIR_MAC_AUTH_CHALLENGE_LENGTH 128n

所以挑戰字串有足夠的空間容納128位元組的字元數組,但是請記住,在原始數據包解析時,我使用了最大長度——253位元組,所以memcpy函數如下所示。

vos_mem_copy( pAuth->challengeText, auth.ChallengeText.text, auth.ChallengeText.num_text );n

可以看出,我複製的位元組比結構分配多了125個位元組。

本文翻譯自:pleasestopnamingvulnerabilities.com ,如若轉載,請註明原文地址: 4hou.com/mobile/8396.ht 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

Hydra - Password Crack Tool
快訊:英國議會電郵網路遭到持續攻擊
收銀系統的 POS 機是怎麼被黑客攻擊控制,並盜竊資料的?
評估內網的web應用意義大嗎? ?
無需Ptrace就能實現Linux進程間代碼注入

TAG:信息安全 |