1 工具篇
反編譯和回編用到的一些工具:
apktool是解包APK 文件最常用的工具
keytool是一個(gè)Java數(shù)據(jù)證書的管理工具
jarsigner是JDK提供的針對(duì)jar包簽名的通用工具
apksigner是Google官方提供的針對(duì)Android apk簽名及驗(yàn)證的專用工具
zipalign是對(duì)zip包對(duì)齊的工具,使APK包內(nèi)未壓縮的數(shù)據(jù)有序排列對(duì)齊,從而減少APP運(yùn)行時(shí)內(nèi)存消耗
2 調(diào)試包回編操作
通過(guò)apktool d xxx.apk得到反編譯后smali文件和manifest文件,進(jìn)行修改后,利用apktool build命令進(jìn)行重新打包。
2.1 apktool編譯時(shí)錯(cuò)誤
資源文件找不到:
\res\values-v19\styles.xml:11: error: Error: No resource found that matches the given name: attr 'android:actionModeFindDrawable'.
\res\values-v19\styles.xml:10: error: Error: No resource found that matches the given name: attr 'android:actionModeShareDrawable'.
\res\values-v19\styles.xml:21: error: Error: No resource found that matches the given name: attr 'android:actionModeFindDrawable'.
Exception in thread "main" brut.androlib.AndrolibException: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut_util_Jar_7346229083806801174.tmp, p, --forced-package-id, 127, --min-sdk-version, 15, --target-sdk-version, 26, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /tmp/APKTOOL6548347216162541619.tmp, -0, arsc, -0, arsc, -I, /root/.local/share/apktool/framework/1.apk, -S, /root/Desktop/test1/res, -M, /root/Desktop/test1/AndroidManifest.xml]
at brut.androlib.Androlib.buildResourcesFull(Androlib.java:492)
at brut.androlib.Androlib.buildResources(Androlib.java:426)
at brut.androlib.Androlib.build(Androlib.java:305)
at brut.androlib.Androlib.build(Androlib.java:270)
at brut.apktool.Main.cmdBuild(Main.java:227)
at brut.apktool.Main.main(Main.java:75)
Caused by: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut_util_Jar_7346229083806801174.tmp, p, --forced-package-id, 127, --min-sdk-version, 15, --target-sdk-version, 26, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /tmp/APKTOOL6548347216162541619.tmp, -0, arsc, -0, arsc, -I, /root/.local/share/apktool/framework/1.apk, -S, /root/Desktop/test1/res, -M, /root/Desktop/test1/AndroidManifest.xml]
at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:456)
at brut.androlib.Androlib.buildResourcesFull(Androlib.java:478)
... 5 more
Caused by: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut_util_Jar_7346229083806801174.tmp, p, --forced-package-id, 127, --min-sdk-version, 15, --target-sdk-version, 26, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /tmp/APKTOOL6548347216162541619.tmp, -0, arsc, -0, arsc, -I, /root/.local/share/apktool/framework/1.apk, -S, /root/Desktop/test1/res, -M, /root/Desktop/test1/AndroidManifest.xml]
at brut.util.OS.exec(OS.java:95)
at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:450)
... 6 more
錯(cuò)誤原因很清楚,大致是資源文件和一些api版本兼容的問(wèn)題,比如style,manifest一些application屬性等識(shí)別不了。
通過(guò)google有些說(shuō)需要升級(jí)新版本,或者低版本反編譯高版本打包,網(wǎng)上的一些答案我一般是不信的(哈哈哈)。
通過(guò)查apktool的命令幫助,可以通過(guò)-r參數(shù)來(lái)避免resc的反編譯,見(jiàn)下圖。這樣在打包的時(shí)候就不會(huì)重新編譯resc文件包括xml。
usage: apktool
-advance,--advanced prints advance information.
-version,--version prints the version then exits
usage: apktool if|install-framework [options] <framework.apk>
-p,--frame-path <dir> Stores framework files into <dir>.
-t,--tag <tag> Tag frameworks using <tag>.
usage: apktool d[ecode] [options] <file_apk>
-f,--force Force delete destination directory.
-o,--output <dir> The name of folder that gets written. Default is apk.out
-p,--frame-path <dir> Uses framework files located in <dir>.
-r,--no-res Do not decode resources.
-s,--no-src Do not decode sources.
-t,--frame-tag <tag> Uses framework files tagged by <tag>.
usage: apktool b[uild] [options] <app_path>
-f,--force-all Skip changes detection and build all files.
-o,--output <dir> The name of apk that gets written. Default is dist/name.apk
-p,--frame-path <dir> Uses framework files located in <dir>.
那么,我們需要修改xml的時(shí)候還是需要反編譯resc,或者通過(guò)二進(jìn)制寫入hex中。其實(shí)上面編譯失敗的原因是不同level的API造成的。
查看apktool.jar可發(fā)現(xiàn)android-framework.jar:
apktool.jar\brut\androlib\android-framework.jar
而android-framework.jar其實(shí)是用來(lái)指定默認(rèn)的反編譯和編譯的framework(不同手機(jī)和不同版本有不同的framework,但一般都是向下兼容的)。
執(zhí)行apktool命令會(huì)檢查/Users/Quan/Library/apktool/framework/路徑下是否包含1.apk,否則會(huì)通過(guò)android-framework.jar生成1.apk,然后通過(guò)該apk作為framework的路徑。通過(guò)apktool -p可以指定 framework的路徑。
結(jié)論:導(dǎo)致以上編譯失敗的原因很可能是,前期用過(guò)的apktool版本比較低,在編譯api level較高的apk時(shí)候出現(xiàn)資源找不到的情況,我們只需要?jiǎng)h除/Users/Quan/Library/apktool/framework/1.apk然后最新版本的apktool重新執(zhí)行編譯命令就可以。
在重新打包我們的APK后,其實(shí)app是不能夠正常運(yùn)行的,首頁(yè)會(huì)在application初始化中拋出apk簽名錯(cuò)誤的異常,然后退出。
2.2 修改smali的技巧
可以直接修改后或者通過(guò)編寫java文件先生成dex然后通過(guò)反編譯smali之后進(jìn)行在源文件中插入。
javac helloworld.java #編譯生成.class文件
dx --dex --output=helloworld.dex helloworld.class #生成dex
apktool d helloworld.dex #反編譯生成smali
2.3 調(diào)試APK
1.反編譯插入xml android:debuggable="true"
2.修改系統(tǒng)默認(rèn)的配置ro.debuggable=1
此文不贅述。
參考:https://www.bodkin.ren/index.php/archives/533/
3 關(guān)于apk的簽名
3.1 簽名和數(shù)字證書
簽名:
消息發(fā)送方生成一對(duì)公私鑰,消息發(fā)送過(guò)程中:
1)對(duì)要發(fā)送的原始消息提取消息摘要;
2)對(duì)提取的信息摘要用自己的私鑰加密。
數(shù)字證書
一般包含以下一些內(nèi)容:
1)證書的發(fā)布機(jī)構(gòu)(Issuer)
2)證書的有效期(Validity)
3)消息發(fā)送方的公鑰
4)證書所有者(Subject)
5)數(shù)字簽名所使用的算法
6)數(shù)字簽名

3.2 APK簽名工具
jarsign是Java本生自帶的一個(gè)工具,可以對(duì)jar進(jìn)行簽名
signapk是后面專門為了Android應(yīng)用程序apk進(jìn)行簽名的工具,7.0之后強(qiáng)制要求使用該工具進(jìn)行簽名
區(qū)別:
- 使用的簽名文件不一樣
jarsign工具簽名時(shí)使用的是
signapk工具簽名時(shí)使用的是pk8,x509.pem文件 -
生成的簽名文件名不一樣
jarsign的簽名文件名包含了keystore的別名
jarsign簽名的產(chǎn)物
signapk固定的名字,默認(rèn)為CERT

- 使用的默認(rèn)簽名算法不一樣
jarsign默認(rèn)使用sha-256做摘要算法
signapk 默認(rèn)使用sha-1做摘要算法
3.3 keystore和pk8,x509.pem可互相轉(zhuǎn)換
在線轉(zhuǎn)換地址:https://myssl.com/cert_convert.html
3.4 APK簽名后文件
signapk簽名apk之后會(huì)包含以下文件,位于META-INF。
1.MANIFEST.MF
Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.0.1
Name: AndroidManifest.xml
SHA1-Digest: OOwo62rpBBm58uyA9DaVke/pjYM= 對(duì)資源和源碼文件sha1散列,base64加密
Name: META-INF/android.arch.core_runtime.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
Name: META-INF/android.arch.lifecycle_extensions.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
Name: META-INF/android.arch.lifecycle_livedata-core.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
Name: META-INF/android.arch.lifecycle_livedata.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
Name: META-INF/android.arch.lifecycle_runtime.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
Name: META-INF/android.arch.lifecycle_viewmodel.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=
Name: META-INF/proguard/com.rong360.cccredit.base.comInputWidget.BaseV
iewHolder.pro
SHA1-Digest: 2A1Gn5+G2UZfIRuOe+B8eayD3h4=
Name: META-INF/proguard/com.rong360.cccredit.base.view.BaseItemView.pr
o
SHA1-Digest: 5NlorzmAOWo2s54IOAzJ6fpbRso=
Name: META-INF/rxjava.properties
SHA1-Digest: vKB1Ac/XQ8wiPI/th9N8DZ/+T9Y=
Name: assets/getui_popup_bg.9.png
SHA1-Digest: 0i2ug9zD61+mUJM+VRU0su+rZBA=
Name: assets/getui_popup_close.png
SHA1-Digest: 3PxcPZ1vRVg/shO9Q8m4kCl+++0=
Name: assets/home_producing.json
SHA1-Digest: F7l2niyrW1QfhVDBJwGIfu9OIqU=
Name: assets/loading_blue.json
SHA1-Digest: aARfFQziHhwO3GhIK9Yy1aowXYc=
2.CERT.SF
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: wMk2wph8ittVFJEdaSigTm0sCiU=
//對(duì)MANIFEST.MF SHA1+base64
Name: AndroidManifest.xml
SHA1-Digest: hgUK+DK9MfGkBwlilMAQGpievZ8=
//對(duì)MANIFEST.MF中塊+2次回車 SHA1+base64
Name: META-INF/android.arch.core_runtime.version
SHA1-Digest: OPQCkzMXJVPQryHeMowVNZmfRMw=
Name: META-INF/android.arch.lifecycle_extensions.version
SHA1-Digest: 6wRGJv7GN0UPnHsBck6G9DEz/wQ=
Name: META-INF/android.arch.lifecycle_livedata-core.version
SHA1-Digest: TSBGEIW1zN2n2sraHWcuRYSO8JU=
Name: META-INF/android.arch.lifecycle_livedata.version
SHA1-Digest: 2SW0lUzWE/vv2diFzKyGpyt9JW8=
Name: META-INF/android.arch.lifecycle_runtime.version
SHA1-Digest: yMBVn3OtS/Z9YTo02MlcvU6yfFM=
Name: META-INF/android.arch.lifecycle_viewmodel.version
SHA1-Digest: 5aVTTikyBvFlGq287kN/q6fe0kA=
3.CERT.RSA
證書文件,包含了.sf文件的簽名信息(sha256+私鑰加密)公鑰信息和發(fā)布機(jī)構(gòu)信息
3.5 查看證書信息
通過(guò)openssl工具可以查看rsa文件,該文件包含了摘要算法、加密算法、簽名和公鑰等信息。
如下:

4 項(xiàng)目中APK的簽名驗(yàn)證
驗(yàn)證應(yīng)用的簽名證書信息和release包真實(shí)的簽名是否一致,因?yàn)樗借€不可能被人獲取。這也是app store上認(rèn)領(lǐng)app的方式。
5 通過(guò)xposed繞過(guò)簽名驗(yàn)證機(jī)制
由于目標(biāo)的apk有在初始化進(jìn)行證書hash的認(rèn)證,我需要通過(guò)以下方法繞過(guò)簽名驗(yàn)證機(jī)制。
兩方式:
1.修改Signature的方法
so中可以用IDA進(jìn)行搜索,通過(guò)IDA分析so庫(kù)中簽名散列值。
通過(guò)hook android.content.pm.Signature#hashCode方法的返回值,將其置為和通過(guò)逆向得到的值一致,從而達(dá)到了繞過(guò)簽名驗(yàn)證的機(jī)制。
2.Hook系統(tǒng)的PMS服務(wù)
public static void hookPms(Context context, String signed, int hashCode){
try{
// 獲取全局的ActivityThread對(duì)象
Class<!--?--> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod =
activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 獲取ActivityThread里面原始的sPackageManager
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);
// 準(zhǔn)備好代理對(duì)象, 用來(lái)替換原始的對(duì)象
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<!--?-->[] {Class.forName("android.content.pm.IPackageManager") },
new PmsHookBinderInvocationHandler(sPackageManager, signed, hashCode));
// 替換掉ActivityThread里面的 sPackageManager 字段
sPackageManagerField.set(currentActivityThread, proxy);
// 替換 ApplicationPackageManager里面的 mPM對(duì)象
PackageManager pm = context.getPackageManager();
Field mPmField = pm.getClass().getDeclaredField("mPM");
mPmField.setAccessible(true);
mPmField.set(pm, proxy);
}catch (Exception e){
}
}
public class PmsHookBinderInvocationHandler implements InvocationHandler{
private Object base;
//應(yīng)用正確的簽名信息
private String SIGN;
private int hashCode = 0;
public PmsHookBinderInvocationHandler(Object base, String sign, int hashCode) {
try {
this.base = base;
this.SIGN = sign;
this.hashCode = hashCode;
} catch (Exception e) {
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("getPackageInfo".equals(method.getName())){
Integer flag = (Integer)args[1];
if(flag == PackageManager.<em>GET_SIGNATURES</em>){
Signature sign = new Signature(SIGN);
if(hashCode != 0){
try{
Class<!--?--> clazz = sign.getClass();
Field mHaveHashCodeF = clazz.getDeclaredField("mHaveHashCode");
mHaveHashCodeF.setAccessible(true);
mHaveHashCodeF.set(sign, true);
Field mHashCodeF = clazz.getDeclaredField("mHashCode");
mHashCodeF.setAccessible(true);
mHashCodeF.set(sign, hashCode);
}catch(Exception e){
}
}
PackageInfo info = (PackageInfo) method.invoke(base, args);
info.signatures[0] = sign;
return info;
}
}
return method.invoke(base, args);
}
}
private void getSignature() {
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(
getPackageName(), PackageManager.<em>GET_SIGNATURES</em>);
if (packageInfo.signatures != null) {
Log.d("", "sig:"+packageInfo.signatures[0].toCharsString()+
"hashcode:"+packageInfo.signatures[0].hashCode());
}
} catch (Exception e2) {
}
}
該方法不需要借助xposed也無(wú)需root,將方法插入application首行(通過(guò)smali來(lái)插入),然后編譯成apk。因?yàn)楂@取簽名服務(wù)的在Security#init中所以我們可以在此之前hook 住pms,從而修改簽名的信息。Hook部分的代碼參考Android Hook技術(shù)實(shí)踐。
