轉載自:https://bbs.pediy.com/thread-223539.htm
一、背景介紹
近日,Android平台被爆出“核彈級”漏洞Janus(CVE-2017-13156),該漏洞允許惡意攻擊者任意修改Android應用中的代碼,而不會影響其簽名。
衆所周知,Android具有簽名機制。正常情況下,開發者釋出了一個應用,該應用一定需要開發者使用他的私鑰對其進行簽名。惡意攻擊者如果嘗試修改了這個應用中的任何一個檔案(包括代碼和資源等),那麼他就必須對APK進行重新簽名,否則修改過的應用是無法安裝到任何Android裝置上的。但如果惡意攻擊者用另一把私鑰對APK簽了名,并将這個修改過的APK對使用者手機裡的已有應用更新時,就會出現簽名不一緻的情況。是以,在正常情況下,Android的簽名機制起到了防篡改的作用。
但如果惡意攻擊者利用Janus漏洞,那麼惡意攻擊者就可以任意地修改一個APK中的代碼(包括系統的内置應用),同時卻不需要對APK進行重新簽名。換句話說,用這種方式修改過的APK,Android系統會認為它的簽名和官方的簽名是一緻的,但在這個APK運作時,執行的卻是惡意攻擊者的代碼。惡意攻擊者利用這個修改過的APK,就可以用來覆寫安裝原官方應用(包括系統的内置應用)。由此可見,該漏洞危害極大,而且影響的不僅是手機,而是所有使用Android作業系統的裝置。
目前,Google将該漏洞危險等級定義為高,其影響Android 5.1.1至8.0的所有版本。基于多年以來針對移動端漏洞的技術積累和安全對抗,安天移動安全對Janus高危漏洞進行了緊急分析,并釋出技術報告,全文如下。
二、漏洞原理
ART虛拟機在加載并執行一個檔案時,會首先判斷這個檔案的類型。如果這個檔案是一個Dex檔案,則按Dex的格式加載執行,如果是一個APK檔案,則先抽取APK中的dex檔案,然後再執行。而判斷的依據是通過檔案的頭部魔術字(Magic Code)來判斷。如果檔案的頭部魔術字是“dex”則判定該檔案為Dex檔案,如果檔案頭部的魔術字是“PK”則判定該檔案為Apk檔案。
另一方面,Android在安裝一個APK時會對APK進行簽名驗證,但卻直接預設該APK就是一個ZIP檔案(并不檢查檔案頭部的魔術字),而ZIP格式的檔案一般都是從尾部先讀取,是以隻要ZIP檔案尾部的資料結構沒有被破壞,并且在讀取過程中隻要沒有碰到非正常的資料,那麼整個讀取就不會有任何問題。
總而言之,Android在加載執行代碼時,隻認檔案頭,而安裝驗證簽名時隻認檔案尾。
是以隻要構造一個APK,從其頭部看是一個Dex檔案,從其尾部看,是一個APK檔案,就可以實施攻擊。很容易想到,将原APK中的classes.dex抽取出來,改造或替換成攻擊者想要執行的dex,并将這個dex和原APK檔案拼起來,合成一個檔案,就可以利用Janus漏洞。
當然僅僅簡單地将惡意dex放在頭部,原apk放在尾部合起來的檔案還是不能直接用來攻擊。需要稍作修正。對于頭部dex,需要修改DexHeader中的file_size,将其調整為合并後檔案的大小。另外需要修改尾部Zip,修正[end of central directory record]中[central directory]的偏移和[central directory]中各[local file header]的偏移。
當然,Janus漏洞是針對APK檔案的攻擊,是以v1簽名無法抵禦這類攻擊,而v2簽名可以抵禦。
三、漏洞利用
▲圖1 攻擊檔案拼接原理
具體的漏洞利用分為3步:
1. 從裝置上取出目标應用的APK檔案,并構造用于攻擊的DEX檔案;
2. 将攻擊DEX檔案與原APK檔案簡單拼接為一個新的檔案;
3. 修複這個合并後的新檔案的ZIP格式部分和DEX格式部分,修複原理如圖1所示,需要修複檔案格式中的關鍵偏移值和資料長度值。
最後,将修複後的檔案,重命名為APK檔案,覆寫安裝裝置上的原應用即可。
四、漏洞修複
1. Google官方修複方案
對檔案system/core/libziparchive/zip_archive.cc打上如下patch:
2. 修複原理
打開ZIP格式檔案時,多做了一項校驗,也就是檢測檔案的頭部是不是以‘PK’标示打頭。如果是,則進行正常的邏輯,否則認為該檔案不是一個合法的ZIP格式檔案。
五、總結
Android平台上的應用簽名機制是Android安全的基石。Android平台的permission機制完全依賴于應用的簽名,簽名機制一旦突破,所有基于Android permission建構的安全體系将崩潰。而Janus漏洞已經不是Android平台的第一例簽名機制漏洞了,之前由“Bluebox”發現的Master Key漏洞和“安卓安全小分隊”(安天移動安全上海團隊前身)發現的第二個Master Key漏洞都是利用簽名機制的漏洞,其原理是利用Android的代碼中對APK驗證不充分的缺陷,使得應用在安裝時驗證的是原dex,但執行的是另一個dex,進而達到瞞天過海、偷梁換柱的目的。
Janus漏洞的利用在原理上也類似,它将惡意dex和原apk拼接在一起,安裝驗證時驗證的是原apk的dex,而執行時卻是執行惡意dex。修複這類漏洞的原理也很簡單,就是加強安裝時的驗證,避免不合法應被安裝到系統中。針對Janus漏洞,隻需簡單驗證一下apk檔案的頭部是不是‘PK’即可。如果不是,則該apk 檔案一定不是一個正常的apk檔案。
Janus漏洞再一次提示我們,即使像Google這樣的跨國科技企業也難免在簽名驗證這麼關鍵的環節上多次産生漏洞,特别是Janus漏洞從2014年就已經存在,潛伏長達3年之久,并且從Android 5.1-8.0版本都存在這個漏洞。這說明了安全問題有時是極其隐蔽的,暫時未發現安全問題,不代表安全問題不存在;更說明了安全是動态的而不是靜止的。是以安全防護是一個工程化體系化的持久戰,很難通過單次或短期投入就将安全問題一次性解決,在安全方面隻有持續而堅定地投入,一旦發現風險或者漏洞就要以最快的速度及時修複,隻有這樣才能保證系統安全而穩定地運作,最大程度地規避風險和損失。
附錄:技術參考
要了解Janus高危漏洞的原理,首先需要掌握一些基礎知識:ZIP檔案結構、DEX檔案結構和Android APK簽名機制。
1. DEX檔案結構
▲圖2 dex檔案格式
Dex檔案有很多部分組成(如圖2),其中Dex Header最為重要,因為Dex的其他組成部分,都需要通過這個Dex Header中的索引才能找到。
Dex Header内部結構如下:
▲圖3 dex header 結構
在Dex Header中(如圖3),file_size規定了整個dex檔案的總大小。是以,如果想在Dex檔案中隐藏一些額外的資料的話,最簡單地,就可以将這些資料追加到Dex檔案末尾,然後再将file_size調大到合适的值即可。
2. ZIP檔案結構
ZIP檔案可以通過獲得檔案尾部的End of central directory record(EOCD record)擷取central directory,周遊central directory中的每項記錄得到的file data即為壓縮檔案的資料。
▲表1 ZIP檔案結構
如表1,讀取ZIP檔案時,會現從最後一個記錄區end of central directory record中讀取central directory的偏移,然後周遊central directory中的每一項,擷取每個檔案的 local file header,最後通過 local file header 擷取每個檔案的内容。
End of central directory record結構體如下:
▲表2 End of central directory record結構
該結構(如表2)中的offset of start of central directory with respect to the starting disk number,指向了central directory的位置。
Central directory結構體如下:
▲表3 Central directory結構
該結構(如表3)中的relative offset of local header,指向了每個檔案的Local File Header的位置。
是以,如需在ZIP檔案中隐藏一些資料,可将這些資料簡單添加到頭部,然後修改End of central directory record中central directory的偏移。同時修改每個central directory中的Local File Header的偏移即可。
3.Android APK簽名機制
Android APK簽名機制分為兩個版本:v1和v2版本。
兩個版本的簽名差別在于,前者是對APK中的每個檔案進行簽名,如果APK中某個檔案被篡改了,那麼簽名驗證将會通不過;後者則是對整個APK檔案進行簽名,隻要APK檔案的内容發生變化則簽名失效。
很顯然,v2版本要比v1更加嚴格,安全性會高很多。
但遺憾的是,Android從7.0開始才引入v2簽名。之前的所有Android系統隻能驗證v1簽名的app,即使這個app也用V2簽名了。
以下是兩個版本的簽名對比:
▲表4 v1 v2簽名對比
對于android 7.0以上,系統在校驗簽名是會先檢查是否存在V2簽名方案,若存在,則通過V2簽名方案對APK進行校驗,否則使用V1簽名方案對APK進行校驗。
▲圖4 Android v2簽名流程
對于android 7.0以下的系統,不支援V2簽名方案,是以APK在簽名時最好将兩種簽名方案都支援。
例子:https://github.com/TomesVWhite/BuildFakeApk