android逆向系列(三)java签名校验与反编译

目录表

  • android逆向系列(一)dex2jar和jd-gui
  • android逆向系列(二)apktool和二次打包
  • android逆向系列(三)java签名校验与反编译

本节主要介绍两种简单的运行时签名校验的方法,和怎么绕开校验进行二次打包的方法;我们知道签名是Android软件的一种有效身份标识,因为签名所使用的秘钥文件是我们所独有的,而当我们app被重新打包后,app的签名信息势必会被篡改,所有我们就可以根据软件运行时签名与发布时签名的相同与否来决定是否需要将app中止运行;那么既然有此方法,为什么我们的软件还是会被破解呢?那是因为破解者在反编译app时,更改了我们的代码逻辑,让我们不能够正确的执行我们的原始校验代码,例如常见的改变if判断,返回错误返回值,校验方法被重新编写等,这都是破解者绕开校验的思路,也是我们在开发中需要考虑的问题。

Java代码签名校验

在这里简单介绍下,纯java逻辑的签名校验方法,Android SDK中提供了检测软件签名的方法,我们可以使用签名对象的hashCode()方法来获取一个Hash值,在代码中比较它的值即可,下面是获取当前运行时的签名信息代码

    public static int getSignature(Context context) {
        PackageManager pm = context.getPackageManager();
        PackageInfo pi;
        StringBuilder sb = new StringBuilder();
        // 获取签名信息
        try {
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signatures = pi.signatures;
            for (Signature signature : signatures) {
                sb.append(signature.toCharsString());
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return sb.toString().hashCode();
    }

接下来我们需要跟我们发布时的签名信息比较,在这里已经把Hash值MD5加密了

        int signature = CommentUtils.getSignature(getApplicationContext());
        if(!MD5Util.getMD5(String.valueOf(signature)).equals("F010AF8CFE611E1CC74845F802666AB4")){
            // 可能被重编译了,需要退出
            android.os.Process.killProcess(android.os.Process.myPid());
        }

Java代码classes.dex的crc32校验

通常重编译apk就是重编译classes文件,而代码重新编译后,生成的classes.dex文件的Hash值就会改变,所以我们可以检查程序安装后classes.dex文件的Hash值来判断软件是否被重新打包过。至于Hash算法MD5和CRC都可以,在这里就直接使用CRC算法获取当前运行的app的crc32值了

    public static long getApkCRC(Context context) {
        ZipFile zf;
        try {
            zf = new ZipFile(context.getPackageCodePath());
            // 获取apk安装后的路径
            ZipEntry ze = zf.getEntry("classes.dex");
            return ze.getCrc();
        }catch (Exception e){
            return 0;
        }
    }

有了当前的crc32值了,那么我们只需要将其与我们app发布时的crc32原始值做比较了,这是我们的java逻辑,R.string.classes_txt的值我们我们可以先随意赋予一个(不影响),随后AndroidStudio开始正式打包

        String srcStr = MD5Util.getMD5(String.valueOf(CommentUtils.getApkCRC(getApplicationContext())));
        if(!srcStr.equals(getString(R.string.classes_txt))){
            // 可能被重编译了,需要退出
            android.os.Process.killProcess(android.os.Process.myPid());
        }

当打包成功后,我们获取apk的classes.dex的crc32值,随后将该crc32值赋予R.string.classes_txt,最后通过AndroidStudio再重新打包即可(因为更改资源文件并不会改变classe.dex的crc32值,改变代码才会)。获取classes.dex的crc32值的方法,我推荐使用 Windows CRC32命令工具 ,提取码: 5xkc,使用方法如下
android逆向系列(三)java签名校验与反编译_第1张图片
Java层面的校验方法都是脆弱的,因为破解者可以直接更改我们的判断逻辑以达到绕开校验的目的,所以我们只能通过增加其破解工作量,来达到一点点防破解的夙愿。我建议将crc32值或签名的hash值进行MD5加密,在代码中使用加密后的值进行比较,防止反编译后的全局搜索;我建议将签名校验与classes.dex校验结合起来使用,先进行签名校验,校验成功后将正确签名hash值作为参数去后台请求classes.dex的crc32值,再与当前运行crc32值进行比较;我建议进行多处校验,每处使用变形判断语句,并与其他判断条件组合使用,以增加破解时的工作量。

绕过Java代码签名校验

在这里我就只以签名校验来对破解思路做个介绍了,让我们在实战以前认认路,磨磨刀。如果反编译步骤还有不了解的可以先去参考上篇博客,首先我们将待编译apk通过apktool生成我们所需要的smali文件,这些文件会根据程序包的层次结构生成相应的目录,程序中所有的类都会在相应的目录下生成独立的smali文件
android逆向系列(三)java签名校验与反编译_第2张图片
然后我们通过dex2jar和jd-gui得到反编译出的java代码(往往都是已混淆的),不熟悉步骤的可以先去查看 上篇博客,这两种方式相辅相成,通过查看Java代码我们可以快速搜索出需要的Android API方法,再通过API方法的位置来定位到相应smali文件的大概位置
android逆向系列(三)java签名校验与反编译_第3张图片
一般java层面的签名校验都离不开signatures来获取签名信息,所以我们可以在jd-gui中全局搜索signatures关键字,找到获取签名的方法,当然如果app在校验失败前有着特殊的Toast提示或者Log信息那就更方便了
android逆向系列(三)java签名校验与反编译_第4张图片
随后打开我们查找到的signatures代码,一般情况下app都会进行多处校验
android逆向系列(三)java签名校验与反编译_第5张图片
随后我们顺藤摸瓜,找到f()方法被调用的地方,在这里就只拿jd-gui来试试水了,我们可以先通过AndroidManifest.xml文件找到Application和主Activity,一般在这里都会进行一些校验和身份状态的判断,改好一处之后,通过运行app,再根据app退出或者卡住的位置来定位下一处校验代码的位置,递归,直到运行成功。
android逆向系列(三)java签名校验与反编译_第6张图片
通过上面的语句我们可以知道,这只是一个简单的equals()比较,之后我们打开相应的smali文件,搜索"F010AF8CFE611E1CC74845F80266",定位签名校验的反编译代码位置

invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;

    move-result-object v0

    invoke-static {v0}, Lcom/lcmhy/c/a/d;->a(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    const-string v1, "F010AF8CFE611E1CC74845F80266"

    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-nez v0, :cond_1

在这里我们只需要将判断语句if-nezv0, :cond_1更改为if-eqz v0, :cond_1翻转逻辑即可,随后我们通过apkTool重新打包并签名,ok,运行成功。

mjzuo博客列表传送阵

你可能感兴趣的:(android随笔)