Bluebox的CTO Jeff Forristal在其官?方blog爆出一個漏洞叫做UNCOVERING ANDROID MASTER KEY,大致是不篡改簽名修改android代碼。
Link:http://bluebox.com/corporate-blog/bluebox-uncovers-android-master-key/
剩下就是放出來一張更改基帶字串的圖:
具體細節7月底的blackhat放出。
沒多少天7月8號國外已經有人放出poc來。微博上看到rayh4c說已經搞定。就分析了一下。
POC還沒出來之前,先是看了下android的簽名機制和安裝機制。
簽名機制: 用簡單的話來講就是android把app應用的所有文件都做了sha1(不可逆)簽名,并對這簽名用RSA(非對稱加密算法)的私鑰進行了加密,客戶端安裝驗證時用公鑰進行解密。
從邏輯上看,這簽名機制對完整性和唯一性的校驗是完全沒問題的。主流的很多加密都類似這樣。
安裝機制:
安裝機制則較為復雜。
1.系統應用安裝――開機時完成,沒有安裝界面
2.網絡下載應用安裝――通過market應用完成,沒有安裝界面
3.ADB?工具安裝――沒有安裝界面。
4.第三?方應用安裝――通過SD卡?里的APK?文件安裝,有安裝界面,由packageinstaller.apk應?用處理安裝及卸載過程的界面。
安裝過程:復制APK安裝包到data/app目錄下,解壓并掃描安裝包,把dex?文件(Dalvik字節碼) 保存到dalvik-cache目錄,并data/data目錄下創建對應的應?用數據目錄。
到這里看出在安裝機制上的問題可能性比較大。
回頭看?老外的POC:https://gist.github.com/poliva/36b0795ab79ad6f14fd8
在linux執?行了一遍,出現錯誤。可能是apk的原因。
索性把這poc移植到windows下,先是?用apktool 把要更改的apk給反編譯出來到一個目錄apk_test
然后?又把apk_test打包成?一個新的apk
把原先的apk解壓出來apk_old
把apk_old所有?文件以zip壓縮的?方式加?入新的apk中。我本機以weibo.apk為例:
可見兩者大小發生了變化,apktool在反編譯過程不可避免的出現差異。并且重編譯的apk不含有簽名文件。
按照poc的做法我用批處理導出目錄的文件名到1.txt修改了poc.py
import zipfile
import sys
f=open('1.txt','r')
line=f.readline()
test=[]
while line:
test1=line.replace("\n","")
test.append(test1)
if not line:
break
line=f.readline()
f.close()
z = zipfile.ZipFile("livers.apk", "a")
for i in range(0,len(test)):
print test[i]
z.write(str(test[i]))
z.close()
差不多增大了一倍,放在手機上安裝了一下,成功安裝。查看了下:
出現了多對同名文件。CRC校驗不同,查看了一下,基本上是兩個字節便產生不同。
這里我又測試了只添加簽名文件,或者dex文件等,均不能通過驗證。
可證明其在scan list掃描目錄或者復制文件時候對同名文件處理不當。
證明是否可以進行更改源碼,并能使用原生簽名。我把apk圖標進行了更改。
順便講下一般的反編譯修改:
1. apktool或者其他工具進行反編譯包含smalijava字節碼匯編和xml圖片文件。
2. apkzip解壓。
3. 反編譯dex成java文件。
4. 查找對應修改的smali文件或者xml(一般廣告鏈接)
5. Apktool打包成apk文件
6. 用autosign進行簽名。
這里沒有進行簽名直接借用原來的簽名。
我這里下載的android 2.2的源碼,查找到獲取簽名信息安裝位于frameworks\base\core\java\android\content\pm\PackageParser.java
這個文件,public?boolean?collectCertificates(Package?pkg,?int?flags)
和private?Certificate[]?loadCertificates(JarFile?jarFile,?JarEntry?je,?byte[]?readBuffer)
這個方法是用來獲取簽名信息的。
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry je = (JarEntry)entries.nextElement();
if (je.isDirectory()) continue;
if (je.getName().startsWith("META-INF/")) continue;
Certificate[] localCerts = loadCertificates(jarFile, je,
readBuffer);
。。。。。。
} else {
// Ensure all certificates match.
for (int i=0; i<certs.length; i++) {
boolean found = false;
for (int j=0; j<localCerts.length; j++) {
if (certs[i] != null &&
certs[i].equals(localCerts[j])) {
found = true;
break;
}
}
。。。。。
前面通過黑盒方式,大致推斷出安裝機制就是把重命名文件同時處理了,沒有覆蓋而是:
if (certs[i] != null &&certs[i].equals(localCerts[j])) {
found = true;
break;
}
兩個重名文件都做了驗證,只要有一個通過驗證,就返回驗證通過。
我android研究不多,大多以前玩逆向的底子。大家可以多討論。 歡迎大家留言探討~!
======================================================================================================
沒看到看雪上已經討論的熱火朝天,讀下來來源于看雪的zmworm的原理分析應該是更準確的。
由于ZIP格式允許存在兩個或以上完全相同的路徑,而安卓系統沒有考慮這種場景。
在該情況下,android包管理器校驗簽名取的是最后一個文件的hash,而運行APK加載的dex文件卻是zip的第一個dex文件。??
包管理器驗證簽名驗的是最后一個(名字相同情況下)文件。
1.?解析zip的所有Entry,結果存到HashMap(key為路徑,value為Entry)。
2.?由于HashMap.put在相同key的情況下,會把value更新,導致上述的HashMap在相同路徑下,存儲的一定是最后一個文件的Entry。
?系統加載dex文件,加載的是第一個dex。???
??1.?查找dex的Entry用的是dexZipFindEntry。?
2.?dexZipFindEntry的實現是只要match就返回,所以返回的都是第一個文件。
Zip 可以包含兩個同名文件或者路徑,而其自身的unzip 默認方式是后一個覆蓋前一個。
HashMap.put 的寫法應該文件也直接覆蓋(hash表的沖突處理不當果真出大問題)才算是算是符合zip 的標準。
就是加載dex的方式則是先加載第一個,這樣確實信息不一致。
而我之前黑盒測出來認為android 默認把兩個都加載在簽名驗證順序上出現問題的,未分析到上一層的類。
看雪上也是討論很多帖子得到準確的原理分析,大家共同討論,集思廣益。Hack it, know it too.
持續跟新中。