將APK重命名為zip文件,然后可以看到有個META-INF的文件夾,里面有三個文件,分別名為MANIFEST.MF、CERT.SF和CERT.RSA,這些就是使用signapk.jar生成的簽名文件。
1、 MANIFEST.MF文件:
程序遍歷update.apk包中的所有文件(entry),對非文件夾非簽名文件的文件,逐個生成SHA1的數字簽名信息,再用Base64進行編碼。具體代碼見這個方法:
#!java
private static Manifest addDigestsToManifest(JarFile jar)
關鍵代碼是
#!java
for (JarEntry entry: byName.values()) {
String name = entry.getName();
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
(stripPattern == null ||!stripPattern.matcher(name).matches())){
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
md.update(buffer, 0, num);
}
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
attr.putValue("SHA1-Digest", base64.encode(md.digest()));
output.getEntries().put(name, attr);
}
}
之后將生成的簽名寫入MANIFEST.MF文件。關鍵代碼如下:
#!java
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
2、 生成CERT.SF文件:
對前一步生成的Manifest,使用SHA1-RSA算法,用私鑰進行簽名。關鍵代碼如下:
#!java
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureFile(manifest,
new SignatureOutputStream(outputJar, signature));
3、 生成CERT.RSA文件:
生成MANIFEST.MF沒有使用密鑰信息,生成CERT.SF文件使用了私鑰文件。那么我們可以很容易猜測到,CERT.RSA文件的生成肯定和公鑰相關。 CERT.RSA文件中保存了公鑰、所采用的加密算法等信息。核心代碼如下:
#!java
je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(signature, publicKey, outputJar);
在程序中獲取APK的簽名時,通過signature方法進行獲取,如下:
#!java
packageInfo = manager.getPackageInfo(pkgname,PackageManager.GET_SIGNATURES);
signatures = packageInfo.signatures;
for (Signature signature : signatures) {
builder.append(signature.toCharsString());
}
signature = builder.toString();
所以一般的程序就是在代碼中通過判斷signature的值,來判斷APK是否被重新打包過。
在講簽名繞過的方式前,需要先明確DEX校驗和簽名校驗:
1.將apk以壓縮包的形式打開刪除原簽名后,再簽名,安裝能夠正常打開,但是用IDE(即apk改之理,會自動反編譯dex)工具二次打包,卻出現非正常情況的,如:閃退/彈出非正版提示框。可以確定是dex文件的校驗
2、將apk以壓縮包的形式打開刪除原簽名再簽名,安裝之后打開異常的,則基本可以斷定是簽名檢驗。如果在斷網的情況下同樣是會出現異常,則是本地的簽名檢驗;如果首先出現的是提示網絡沒有連接,則是服務器端的簽名校驗.
獲取簽名信息和驗證的方法都寫在android的java層。實例如下:
1、使用APKIDE反編譯APK,不做任何操作,然后直接回編譯,安裝后運行,提示如下:
2、在APKIDE中搜索signatures(或者搜索錯誤提示),定位到簽名驗證的代碼處。
3、此處就是獲取簽名的,然后找程序判斷簽名的地方,進行修改,如下圖,if-nez是進行判斷的地方,將ne修改為eq。即if-eqz v2, :cond_0。則程序就可以繞過本地的簽名交易。
將關鍵代碼放到so中,在底層獲取簽名信息并驗證。因為獲取和驗證的方法都封閉在更安全的so庫里面,能夠起到一定意義上的保護作用。實例如下:
1、使用APKIDE反編譯APK,不做任何操作,然后直接回編譯,安裝后運行,程序直接退出,無任何提示。
2、在APKIDE中搜索signatures(或者搜索錯誤提示),定位到簽名驗證的代碼處。
3、使?用JD-GUI打開AppActivity,可以看到,此處是獲取包名,然后進?行MD5計算。
4.在程序中搜索getSignature,發現并沒有調?用此函數的地?方,猜測在so?文件中,搜索loadLibrary。
5.在代碼中可以查找,可以找到調?用的是libcocos2dcpp.so
6.使?用IDA打開libcocos2dcpp.so,然后搜索getSiganture,找到調?用此函數的地方。
從代碼中可以看到,此函數調?用了org.cocos2dx.cpp.AppActivity.getSignature
7、查看F5代碼,發現此函數是判斷簽名的函數,然后我們雙擊此函數的調?者,部分代碼如下。
8、從上圖可以看出,只需要修改BEQ loc_11F754,讓其不跳轉到jjni——>error,就可以繞過簽名校驗。 查看HEX,使?010editor跳到0011F73E,修改D0為D1。成功繞過簽名校驗。
在android的java層獲取簽名信息,上傳服務器在服務端進行簽名然后返回驗證結果。
如下圖,網絡驗證時,如果網絡沒連接,一般都會提示錯誤。
既然是網絡驗證,肯定要把驗證信息發送到服務端,然后進行驗證,先看個簡單的實例,下次會有個難度大的。
1、手機配置好抓包,然后抓包。第一種圖是正常的APK的時候的數據包,第二個圖是反編譯的APK的數據包,通過對比,發現cookie中的public_key不一樣,那么我們替換一下,發現可以正常使用APK的功能了。
2、將正確的public_key添加到APK中。打開反編譯的代碼,搜索signatures,定位到簽名的代碼。
可以看到,代碼將signatures的值傳遞到V4中,然后傳遞到Utils->mPublicKey函數中,于是我們將正確的public_key傳給V4。
然后重新打包,重新安裝就可以了。
java層的校驗很容易就可以破解掉,在so層實現校驗相對來說分析會更難點,而網絡驗證,如果僅僅是字符串的比較,那么也很容易破解掉。
碼子碼的太累了。。
后面還有幾篇正在寫的文章,包括so分析等等。
摘抄: http://www.blogjava.net/zh-weir/archive/2011/07/19/354663.html