1. Android 的簽名保護(hù)機(jī)制到底是什么?
Android 系統(tǒng)禁止更新安裝簽名不一致的 Apk晃听,如果我們修改了 Apk 又用別的簽名文件簽名唇聘,肯定是不一致的。
我們從簽名工具 autoSign 分析讨韭,看一下 sign.bat 文件內(nèi)容:
-----------------------------------
@ECHO OFF
Echo Auto-sign Created By Dave Da illest 1
Echo Update.zip is now being signed and will be renamed to update_signed.zip
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
Echo Signing Complete
Pause
EXIT
-----------------------------------
看一下 java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk 這行的意義:
以testkey.x509.pem 這個(gè)公鑰文件和 testkey.pk8 這個(gè)私鑰文件對(duì) update.apk 進(jìn)行簽名脂信,簽名后保存為 update_signed.apk。
我們可以看到簽名前和簽名后比較透硝,簽名后的文件中多了一個(gè)文件夾“META-INF”狰闪,里面有三個(gè)文件 MANIFEST.MF 、 CERT.SF 濒生、 CERT.RSA埋泵。
我們通過 jd-gui 工具打開 signapk.jar,找到 main 函數(shù)罪治,通過這個(gè)函數(shù)跟蹤代碼
1.addDigestsToManifest 這個(gè)函數(shù)丽声,遍歷 Apk 中所有文件,對(duì)非文件夾非簽名文件的文件逐個(gè)生成 SHA1 數(shù)字簽名信息觉义,再 base64 編碼雁社。
然后再寫入 MANIFEST.MF 文件中,生成文件如下:
-----------------------------------
Manifest-Version: 1.0
Created-By: 1.0 (Android)
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: AfPh3OJoypH966MludSW6f1RHg4=
Name: AndroidManifest.xml
SHA1-Digest: NaPhUBH5WO7uGk/CfRu/SHsCvW0=
Name: res/drawable-mdpi/ic_launcher.png
SHA1-Digest: RRxOSvpmhVfCwiprVV/wZlaqQpw=
Name: res/drawable-hdpi/ic_launcher.png
SHA1-Digest: Nq8q3HeTluE5JNCBpVvNy3BXtJI=
Name: res/layout/activity_main.xml
SHA1-Digest: kxwMyILwF2K+n9ziNhcQqcCGWIU=
Name: resources.arsc
SHA1-Digest: q7Ystu6WoSWih53RGKXtE3LeTdc=
Name: classes.dex
SHA1-Digest: Ao1WOs5PXMxsWTDsjSijS2tfnHo=
Name: res/drawable-xxhdpi/ic_launcher.png
SHA1-Digest: GVIfdEOBv4gEny2T1jDhGGsZOBo=
-----------------------------------
SHA1 生成的摘要信息晒骇,如果你修改了某個(gè)文件霉撵,Apk 安裝校驗(yàn)時(shí)磺浙,取到的該文件的摘要與 MANIFEST.MF 中對(duì)應(yīng)的摘要不同,則安裝不成功徒坡。
2.接下來對(duì)之前生成的 manifest 使用 SHA1withRSA 算法撕氧, 用私鑰簽名,writeSignatureFile 這個(gè)函數(shù)喇完,最后生成 CERT.SF 文件呵曹,如下:
-----------------------------------
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: pNZ9UXN9GMqTgqAwKD6uEN6aD34=
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: cIga++hy5wqjHl9IHSfbg8tqCug=
Name: AndroidManifest.xml
SHA1-Digest: oRzzLkwuvxC78suvJcAEvTqcjSA=
Name: res/drawable-mdpi/ic_launcher.png
SHA1-Digest: VY7kOF8E3rn8EUTvQC/DcBEN6kQ=
Name: res/drawable-hdpi/ic_launcher.png
SHA1-Digest: stS7pUucSY0GgAVoESyO3Y7SanU=
Name: res/layout/activity_main.xml
SHA1-Digest: Yr3img6SqiKB+1kwcg/Fga2fwcc=
Name: resources.arsc
SHA1-Digest: j1g8I4fI9dM9hAFKEtS9dHsqo5E=
Name: classes.dex
SHA1-Digest: Sci9MmGXNGnZ1d04rCrEEV7MWn4=
Name: res/drawable-xxhdpi/ic_launcher.png
SHA1-Digest: KKqaLh/DVvFp+v1KoaDw7xETvrI=
-----------------------------------
用私鑰通過 RSA 算法對(duì) manifest 里的摘要信息進(jìn)行加密,安裝的時(shí)候只能通過公鑰解密何暮,解密之后才能獲得正確的摘要奄喂,再對(duì)比。
3.最后就是如何生成 CERT.RSA海洼,打開這個(gè)文件看到的是亂碼跨新,說明整個(gè)文件都被編碼加密了,而且這個(gè)文件和公鑰有關(guān)坏逢,從源碼中看出他是通過 PKCS7 將整個(gè)文件加密了域帐。
總結(jié):1.簽名只是對(duì)完整性和簽名發(fā)布機(jī)構(gòu)的校驗(yàn)機(jī)制 2.不能阻止 Apk 被修改,只是簽名無(wú)法保持一致 3.不同私鑰對(duì)應(yīng)著不同的公鑰是整,實(shí)質(zhì)上不同的公鑰就代表了不同的簽名肖揣。
2. Android 系統(tǒng)如何獲取簽名
我們從獲取下面一段代碼開始分析:
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager ;
}
IPackageManager pm = ActivityThread.getPackageManager ();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null ;
}
可以看到 ActivityThread 中方法 getPackageManager 獲取? IPackageManager。
繼續(xù)看 ActivityThread 的代碼:
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager ;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
看源碼知道是通過 ServiceManager.getService(“package”);獲取 PackageManagerService 并得到 IBinder對(duì)象浮入,然后通過? asInterface 函數(shù)取得接口類 IPackageManager 實(shí)例龙优。
然后作為參數(shù)構(gòu)造 ApplicationPackageManager,再看 ApplicationPackageManager 這個(gè)類里的方法:
@Override
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
try {
PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId());
if (pi != null) {
return pi;
}
} catch (RemoteException e) {
throw new RuntimeException( "Package manager has died" , e);
}
throw new NameNotFoundException(packageName);
}
這里的mPM就是上面構(gòu)造函數(shù)傳進(jìn)來的 IPackageManager事秀,就可以調(diào)用 PackageManagerService 的方法了彤断。
下圖是 PackagerManager 靜態(tài)類結(jié)構(gòu)圖:
ok,看完上圖的結(jié)構(gòu)圖易迹,繼續(xù)跟代碼 宰衙,看到這里是 mPM 繼續(xù)調(diào)用的getPackageInfo(代理模式),通過進(jìn)程通信到 PackageManagerService 中執(zhí)行響應(yīng)操作:
@Override
public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
enforceCrossUserPermission(Binder.getCallingUid (), userId, false, "get package info");
// reader
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
if (DEBUG_PACKAGE_INFO)
Log.v (TAG, "getPackageInfo " + packageName + ": " + p);
if (p != null) {
return generatePackageInfo(p, flags, userId);
}
if((flags & PackageManager. GET_UNINSTALLED_PACKAGES ) != 0) {
return generatePackageInfoFromSettingsLPw(packageName, flags, userId);
}
}
return null ;
}
這里 mPackages 是 hashMap睹欲,其調(diào)用put方法的時(shí)機(jī)是在 scanPackageLI 方法中供炼,而 scanPackageLI 的調(diào)用地方是在程序安裝和替換函數(shù)中,還有就是 scanDirLi 中窘疮,代碼略袋哼。
scanDirLi 是用來掃描一些系統(tǒng)目錄的的,在 PackageManagerService 的構(gòu)造函數(shù)中調(diào)用的:
File dataDir = Environment. getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLibInstallDir = new File(dataDir, "app-lib" );
mAsecInternalPath = new File(dataDir, "app-asec" ).getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private" );
上面是掃描的位置↑↑↑↑↑↑↑
PackageManagerService 處理各種應(yīng)用的安裝考余、卸載先嬉、管理等工作,開機(jī)時(shí)由 systemServer 啟動(dòng)此服務(wù)楚堤。就是說之前安裝過的應(yīng)用或者系統(tǒng)應(yīng)用信息都會(huì)在開機(jī)掃描過程中存到 mPackages 這個(gè) hashMap 中疫蔓。開機(jī)后用戶的安裝操作也會(huì)同樣存到這個(gè) hashMap 里面含懊。
繼續(xù)看 getPackageInfo,調(diào)用的 generatePackageInfo 衅胀, 里面調(diào)用的是 PackageParser 中的 generatePackageInfo岔乔,繼續(xù)跟。
這個(gè)函數(shù)的代碼比較長(zhǎng)滚躯,只貼出部分關(guān)鍵代碼:
if ((flags&PackageManager. GET_SIGNATURES ) != 0) {
int N = (p.mSignatures != null) ? p.mSignatures.length : 0;
if (N > 0) {
pi.signatures = new Signature[N];
System.arraycopy (p.mSignatures, 0, pi. signatures, 0 , N);
}
}
return pi;
這段代碼之前主要是做一些復(fù)制的操作雏门,就是 new 一個(gè) PackageInfo,然后把 PackageParser.Package 對(duì)象中的一些內(nèi)容復(fù)制到這個(gè) PackageInfo 中掸掏。
從這段代碼可以看出來茁影,最終得到的 signatures 信息就是中 p(PackageParser.Package )中的成員 mSignatures 中得來的。
好了丧凤,現(xiàn)在就是看這個(gè)PackageParser.Package是從哪來的了募闲,通過跟蹤代碼,installPackageLI 和 scanPackageLI 中的:
final PackageParser.Package pkg = pp.parsePackag (tmpPackageFile, null, mMetrics, parseFlags);
這行代碼只是生成了 pkg愿待,并沒有賦值里面的 mSignatures浩螺,繼續(xù)跟蹤,找到函數(shù) collectCertificatesLI仍侥,找到 pp.collectCertificates(pkg , parseFlags)要出。
PS:最終又進(jìn)行了一系列的跟代碼,找到了 JarVerifier.java 這個(gè)類的 readCertificates 這個(gè)就是用來讀取.RSA 文件的,最終我們看到的里面的代碼是通過 loadCertificates 取得的 certs 賦值給了 pkg.mSignatures农渊。
至此患蹂,獲取簽名的所有邏輯就算是簡(jiǎn)單的過一遍了,以下是簡(jiǎn)略流程圖:(發(fā)現(xiàn) ppt 畫圖比 Windows 畫圖工具好用多了腿时,哈哈)
OK批糟,繞了這么久我們終于找到源頭了,獲取簽名就是在 META-INF 中尋找看铆,并解析徽鼎。試想一下,如果我們修改了這個(gè)函數(shù)弹惦,讓他解析原來正版的 META-INF 中的 CERT.RSA 文件否淤,這樣就可以偽造為真正的簽名了。
那么我們就想到了 HOOK棠隐,(很多人都是從看雪論壇上找到的一篇文章看到的http://bbs.pediy.com/showthread.php?t=186054hook 的原來簡(jiǎn)單來說就是石抡,找到原函數(shù)和新函數(shù)的指針位置,然后兌換內(nèi)容助泽,將新函數(shù)替代原函數(shù)啰扛。
關(guān)于 hook 呢嚎京,網(wǎng)上也有很多框架可以使用,比如:
1.Cydia substrate :http://www.cydiasubstrate.com/
2.Xposed :http://repo.xposed.info/
網(wǎng)上也有很多教程可以看看隐解。
烏云(wooyun)上有一篇很有意思的教程鞍帝,就是利用 hook 進(jìn)行微信運(yùn)動(dòng)作弊,原帖地址:http://drops.wooyun.org/tips/8416
下圖就是我用了上面的方法產(chǎn)生的效果煞茫,還差點(diǎn)被微信部門的人請(qǐng)去喝茶帕涌。
這里用的是 Xposed 框架,原理就是 hook 了手機(jī)的計(jì)步傳感器的隊(duì)列函數(shù)续徽,然后把步數(shù)的返回值每步乘1000返回蚓曼,前提是,你的手機(jī)硬件本身有計(jì)步傳感器功能钦扭,這里微信運(yùn)動(dòng)里面列出了支持的手機(jī)列表:https://kf.qq.com/touch/sappfaq/151013AvyyeQ151013r63qmq.html?platform=15好像最高就是98800了纫版,可能是微信做了步數(shù)限制吧。
我這里用的是小米4聯(lián)通版土全,發(fā)現(xiàn)雖然是1000基數(shù)的加捎琐,但是好像隔了很久才變化,估計(jì)又是 MIUI 做了一些省電策略裹匙,傳感器的采集做了對(duì)齊吧瑞凑?
Xposed 框架,很多玩機(jī)愛好者概页,會(huì)拿它修改一些主題籽御,字體之類的,或者系統(tǒng)界面惰匙,定制自己想要的系統(tǒng)插件等等技掏。然而,也有缺點(diǎn)项鬼,需要手機(jī)root哑梳,而且這個(gè)框架,還有可能讓手機(jī)變磚绘盟,還有的系統(tǒng)可能對(duì)這個(gè)框架支持的不好鸠真,或者不支持。XDA 論壇里面也有很多大神把 Xposed 對(duì)某些機(jī)型做了適配龄毡,大神一般都是說吠卷,如果手機(jī)變磚他們不負(fù)責(zé),哈哈沦零。
正式由于這些框架的諸多不便祭隔,root 等等的問題,于是就有了一些非 root 的 hook 的黑科技路操,比如阿里巴巴的開源框架 Dexposed(https://github.com/alibaba/dexposed)其也是根據(jù) Xposed 框架修改而來的疾渴,不過看 github 上他們也好久沒更新了千贯。
也有像其他個(gè)人寫的和這種比較類似的框架,這里就不介紹了程奠。但是這類框架的缺點(diǎn)就是丈牢,只能在該進(jìn)程下hook,不能全局 hook瞄沙,即只對(duì)這個(gè)進(jìn)程的應(yīng)用起作用己沛,不能對(duì)另一個(gè)應(yīng)用起作用,優(yōu)點(diǎn)是可以 hook 自定義函數(shù)也能hook 系統(tǒng)函數(shù)距境,并且不用 root 和重啟申尼。阿里用這個(gè)框架來打在線熱補(bǔ)丁。
那對(duì)于 App 內(nèi)部簽名校驗(yàn)的就不用再搜相應(yīng)的代碼了垫桂,直接 hook 就一步到位了师幕,android.app.ApplicationPackageManager 這個(gè)類的 getPackageInfo 這個(gè)方法直接把正確的簽名返回就好了,接下來我們就需要把hook的代碼注入到某個(gè) App 里就好了诬滩。
未完待續(xù)霹粥。。疼鸟。后控。。空镜。
歡迎閱讀:手把手教你逆向分析 Android 程序(連載三)