反编译工具apktool使用问题

1 工具篇

反编译和回编用到的一些工具:

apktool是解包APK 文件最常用的工具

keytool是一个Java数据证书的管理工具

jarsigner是JDK提供的针对jar包签名的通用工具

apksigner是Google官方提供的针对Android apk签名及验证的专用工具

zipalign是对zip包对齐的工具,使APK包内未压缩的数据有序排列对齐,从而减少APP运行时内存消耗

2 调试包回编操作

通过apktool d xxx.apk得到反编译后smali文件和manifest文件,进行修改后,利用apktool build命令进行重新打包。

2.1 apktool编译时错误

资源文件找不到:
\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 

错误原因很清楚,大致是资源文件和一些api版本兼容的问题,比如style,manifest一些application属性等识别不了。
通过google有些说需要升级新版本,或者低版本反编译高版本打包,网上的一些答案我一般是不信的(哈哈哈)。
通过查apktool的命令帮助,可以通过-r参数来避免resc的反编译,见下图。这样在打包的时候就不会重新编译resc文件包括xml。

usage: apktool
 -advance,--advanced   prints advance information.
 -version,--version    prints the version then exits
usage: apktool if|install-framework [options] 
 -p,--frame-path    Stores framework files into .
 -t,--tag           Tag frameworks using .
usage: apktool d[ecode] [options] 
 -f,--force              Force delete destination directory.
 -o,--output        The name of folder that gets written. Default is apk.out
 -p,--frame-path    Uses framework files located in .
 -r,--no-res             Do not decode resources.
 -s,--no-src             Do not decode sources.
 -t,--frame-tag     Uses framework files tagged by .
usage: apktool b[uild] [options] 
 -f,--force-all          Skip changes detection and build all files.
 -o,--output        The name of apk that gets written. Default is dist/name.apk
 -p,--frame-path    Uses framework files located in .


那么,我们需要修改xml的时候还是需要反编译resc,或者通过二进制写入hex中。其实上面编译失败的原因是不同level的API造成的。
查看apktool.jar可发现android-framework.jar:
apktool.jar\brut\androlib\android-framework.jar

而android-framework.jar其实是用来指定默认的反编译和编译的framework(不同手机和不同版本有不同的framework,但一般都是向下兼容的)。
执行apktool命令会检查/Users/Quan/Library/apktool/framework/路径下是否包含1.apk,否则会通过android-framework.jar生成1.apk,然后通过该apk作为framework的路径。通过apktool -p可以指定 framework的路径。
结论:导致以上编译失败的原因很可能是,前期用过的apktool版本比较低,在编译api level较高的apk时候出现资源找不到的情况,我们只需要删除/Users/Quan/Library/apktool/framework/1.apk然后最新版本的apktool重新执行编译命令就可以。

在重新打包我们的APK后,其实app是不能够正常运行的,首页会在application初始化中抛出apk签名错误的异常,然后退出。

2.2 修改smali的技巧

可以直接修改后或者通过编写java文件先生成dex然后通过反编译smali之后进行在源文件中插入。
javac helloworld.java #编译生成.class文件
dx --dex --output=helloworld.dex helloworld.class #生成dex
apktool d helloworld.dex #反编译生成smali

2.3 调试APK

1.反编译插入xml android:debuggable="true"
2.修改系统默认的配置ro.debuggable=1
此文不赘述。
参考:https://www.bodkin.ren/index.php/archives/533/

3 关于apk的签名

3.1 签名和数字证书

签名:
消息发送方生成一对公私钥,消息发送过程中:
1)对要发送的原始消息提取消息摘要;
2)对提取的信息摘要用自己的私钥加密。
数字证书
一般包含以下一些内容:
1)证书的发布机构(Issuer)
2)证书的有效期(Validity)
3)消息发送方的公钥
4)证书所有者(Subject)
5)数字签名所使用的算法
6)数字签名


反编译工具apktool使用问题_第1张图片
签名和验证过程

3.2 APK签名工具

jarsign是Java本生自带的一个工具,可以对jar进行签名
signapk是后面专门为了Android应用程序apk进行签名的工具,7.0之后强制要求使用该工具进行签名
区别:

  • 使用的签名文件不一样
    jarsign工具签名时使用的是
    signapk工具签名时使用的是pk8,x509.pem文件
  • 生成的签名文件名不一样
    jarsign的签名文件名包含了keystore的别名


    反编译工具apktool使用问题_第2张图片
    jarsign签名的产物

signapk固定的名字,默认为CERT


反编译工具apktool使用问题_第3张图片
signapk签名的产物
  • 使用的默认签名算法不一样
    jarsign默认使用sha-256做摘要算法
    signapk 默认使用sha-1做摘要算法

3.3 keystore和pk8,x509.pem可互相转换

在线转换地址:https://myssl.com/cert_convert.html

3.4 APK签名后文件

signapk签名apk之后会包含以下文件,位于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= 对资源和源码文件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= 
//对MANIFEST.MF SHA1+base64

Name: AndroidManifest.xml
SHA1-Digest: hgUK+DK9MfGkBwlilMAQGpievZ8= 
//对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+私钥加密)公钥信息和发布机构信息

3.5 查看证书信息

通过openssl工具可以查看rsa文件,该文件包含了摘要算法、加密算法、签名和公钥等信息。
如下:


反编译工具apktool使用问题_第4张图片
证书的内容

4 项目中APK的签名验证

验证应用的签名证书信息和release包真实的签名是否一致,因为私钥不可能被人获取。这也是app store上认领app的方式。

5 通过xposed绕过签名验证机制

由于目标的apk有在初始化进行证书hash的认证,我需要通过以下方法绕过签名验证机制。

两方式:
1.修改Signature的方法
so中可以用IDA进行搜索,通过IDA分析so库中签名散列值。
通过hook android.content.pm.Signature#hashCode方法的返回值,将其置为和通过逆向得到的值一致,从而达到了绕过签名验证的机制。
2.Hook系统的PMS服务

public static void hookPms(Context context, String signed, int hashCode){
    try{
        // 获取全局的ActivityThread对象
        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);
        // 准备好代理对象, 用来替换原始的对象
        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对象
        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;
    //应用正确的签名信息
    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.GET_SIGNATURES){
               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.GET_SIGNATURES);
        if (packageInfo.signatures != null) {
           Log.d("", "sig:"+packageInfo.signatures[0].toCharsString()+
                    "hashcode:"+packageInfo.signatures[0].hashCode());
        }
    } catch (Exception e2) {
    }
}

该方法不需要借助xposed也无需root,将方法插入application首行(通过smali来插入),然后编译成apk。因为获取签名服务的在Security#init中所以我们可以在此之前hook 住pms,从而修改签名的信息。Hook部分的代码参考Android Hook技术实践。

你可能感兴趣的:(反编译工具apktool使用问题)