Android-安全-签名验证让二次打包变的更难

二次打包的危害性

如果你没有对你的应用做任何的安全保障措施,那么你的应用就非常的危险

首先了解一下什么是二次打包:

二次打包
通过工具apktool、dex2jar、jd-gui、DDMS、签名工具获取源码,嵌入恶意病毒、广告等行为再利用工具打包、签名,形成二次打包应用。

二次打包的一个小演示

这是代码:

 TextView tv = (TextView) findViewById(R.id.tv_test);
 Button btn = (Button) findViewById(R.id.btn_test);
 tv.setText("快看,博主真的好美啊");
 btn.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View v) {
          Toast.makeText(MainActivity.this,"服务器IP:123456,端口号是:0000",Toast.LENGTH_SHORT).show();
     }
 });

1.签名打包为DR_Test.apk,运行如此下图:
Android-安全-签名验证让二次打包变的更难_第1张图片

2.使用apktool 反编译DR_Test.apk

Android-安全-签名验证让二次打包变的更难_第2张图片

3.进入smail文件,修改字符串的内容 “快看,博主真的好美啊” 改为 “快看,天啊,是垃圾广告”,

Android-安全-签名验证让二次打包变的更难_第3张图片

4.改完之后,然后再将修改后的文件,打包成DR_Dirty.apk

Android-安全-签名验证让二次打包变的更难_第4张图片

5.使用Auto_sign工具再次签名DR_Dirty.apk(再次签名肯定和应用的本身签名不同),得到DR_Dirty_signed然后运行

Android-安全-签名验证让二次打包变的更难_第5张图片

这就改变一个显示文本的值,是不是超级简单,也超级危险,所以我们一定要防范这种的二次打包.

防范的思路是:
进入程序就检查签名是不是我们自己的合法的签名,如果不是,就提示盗版信息,或者退出程序

Java层面的签名认证—简单–不安全

    /**
     * 验证是否是合法的签名
     * @return
     */
    private boolean JavaValidateSign(){

        boolean isValidated  = false;
        try {
            //得到签名
            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),PackageManager.GET_SIGNATURES);
            Signature[] signs = packageInfo.signatures;

            //将签名文件MD5编码一下
            String signStr = md5(signs[0].toCharsString());

            //将应用现在的签名MD5值和我们正确的MD5值对比
            return signStr.equals("这里写正确的签名的MD5加密后的字符串");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        return isValidated;
    }

反编译的时候,直接将isValidated修改为true,或者修改接收这个函数的if-else语句条件取个反就行了,就绕过我们的验证,所以不安全

有人说把签名放在NDK层去检验,然后返回结果,这样会不会更安全一点,答案是,只要你用if-else判断,就和上面一样的好破解.

所以,我能想出来的,目前最好的办法,就是:
NDK层判断签名,如果成功,就返回一个指定的字符串Key.这个Key是用来和服务器通信的钥匙,如果没有这个钥匙,就不能获取数据.这样是最好的方法.

#NDK层面的签名认证—复杂–较安全

如果你还没有用过NDK,也不用害怕,只是简单使用,不懂c++也没关系,有百度和Google啊,是吧

AndroidStudio集成NDK

1.下载NDK:NDK Downloads(需要科学上网)

2.Setting -->Project Structure,添加NDK路径,就是上一部下载的NDK,解压之后的路径

Android-安全-签名验证让二次打包变的更难_第6张图片

添加之后你会发现 local.properties多了NDK的引用路径

这里写图片描述

3.gradle.properties添加一句android.useDeprecatedNdk=true

Android-安全-签名验证让二次打包变的更难_第7张图片

开始使用NDK

1.build.gradle中添加ndk配置参数:

defaultConfig {
   ...
   //ndk编译生成.so文件
   ndk {
     moduleName "DRPrincess"         //生成的so文件名称
     abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。
   }
}

2.创建一个Java文件,位置就放在: 你的Moduel/src/main/你的包名下即可

public class DR_JNITest {
    static{
        //引用自己so文件 名称和上一步gradle中的名称保证相同
        System.loadLibrary("DRPrincess");
    }

    // 这个因为要引用c++文件,所以一定要加上native 关键字
    public static  native String getSuccessKey(Object contextObject);
}

3 .Build->Make Project一下,你会发现app/build/intermediates/classes/debug/你的包名 路径下已经有个刚才创建的类的类文件,DR_JNITest.class

Android-安全-签名验证让二次打包变的更难_第8张图片

  1. 在Termial窗口
    输入 cd app/src/main
    输入javah -d jni -classpath 自己编译后的class文件的绝对路径
    例如:javah -d jni -classpath D:\Android\WorkSpace\DR_TestAppDemo\app\build\intermediates\classes\debug dr.dr_testappdemo.DR_JNITest
    (注意debug后的空格)

    这两个操作的意思是在main文件下创建 jni文件夹.里面已经自动生成了dr_dr_testappdemo_DR_JNITest.h文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class dr_dr_testappdemo_DR_JNITest */

#ifndef _Included_dr_dr_testappdemo_DR_JNITest
#define _Included_dr_dr_testappdemo_DR_JNITest
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     dr_dr_testappdemo_DR_JNITest
 * Method:    getSuccessKey
 * Signature: (Ljava/lang/Object;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_dr_dr_1testappdemo_DR_1JNITest_getSuccessKey
  (JNIEnv *, jclass, jobject);

#ifdef __cplusplus
}
#endif
#endif

5.jni文件夹新建c++文件,名字可以随便取,我的是test.cpp
里面引用刚才创建的.h文件#include "dr_dr_testappdemo_DR_JNITest.h"
创建.h文件中的方法,保证方法名称,返回值,参数列表一样.

下面是签名验证的逻辑,c++语言反射Java获取签名信息,然后和定义的合法签名做比对,比对成功返回key,失败返回error

//
// Created by Administrator on 2017/1/13.
//
#include 
#include 
#include 
#include "dr_dr_testappdemo_DR_JNITest.h"

/**
 *这个key是和服务器之间通信的秘钥
 */
const char* AUTH_KEY = "服务器通信秘钥";

/**
 * 发布的app 合法签名的签名字符串
 * signature[0].toCharString()得到
 * 
 */
const char* RELEASE_SIGN = "这是合法的签名字符串";

/**
 * 发布的app 合法签名的HashCode
 * signature[0].hashCode()得到
 */
const int RELEASE_SIGN_HASHCODE = 123456789;


JNIEXPORT jstring JNICALL Java_dr_dr_1testappdemo_DR_1JNITest_getSuccessKey
        (JNIEnv *env, jclass jclazz, jobject contextObject){

    jclass native_class = env->GetObjectClass(contextObject);
    jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject pm_obj = env->CallObjectMethod(contextObject, pm_id);
    jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的 ID
    jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jclass native_classs = env->GetObjectClass(contextObject);
    jmethodID mId = env->GetMethodID(native_classs, "getPackageName", "()Ljava/lang/String;");
    jstring pkg_str = static_cast(env->CallObjectMethod(contextObject, mId));
// 获得应用包的信息
    jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 获得 PackageInfo 类
    jclass pi_clazz = env->GetObjectClass(pi_obj);
// 获得签名数组属性的 ID
    jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
    jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
    jobjectArray signaturesArray = (jobjectArray)signatures_obj;
    jsize size = env->GetArrayLength(signaturesArray);
    jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
    jclass signature_clazz = env->GetObjectClass(signature_obj);

    //第一种方式--检查签名字符串的方式
    jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;");
    jstring str = static_cast(env->CallObjectMethod(signature_obj, string_id));
    char *c_msg = (char*)env->GetStringUTFChars(str,0);

    if(strcmp(c_msg,RELEASE_SIGN)==0)//签名一致  返回合法的 key,否则返回错误
    {
        return (env)->NewStringUTF(AUTH_KEY);
    }else
    {
        return (env)->NewStringUTF("error");
    }

    //第二种方式--检查签名的hashCode的方式
    /*
    jmethodID int_hashcode = env->GetMethodID(signature_clazz, "hashCode", "()I");
    jint hashCode = env->CallIntMethod(signature_obj, int_hashcode);
    if(hashCode == RELEASE_SIGN_HASHCODE)
    {
        return (env)->NewStringUTF(AUTH_KEY);

    }else{
        return (env)->NewStringUTF("error");
    }
     */
}

6.Activity中调用c++方法

实际上是通过第一步我们定义的java方法调用的

 String key = DR_JNITest.getSuccessKey(MainActivity.this);

代码地址

我的GitHub : https://github.com/DRPrincess/DR_TestNDKSignatureCheckDemo

#参考博客

Android APK加固技术方案调研

Android studio 编译C生成.so文件

Android JNI NDK C++ so本地验证 获取应用签名


欢迎关注个人微信公众号,最新的博客,好玩的事情,都会在上面分享,期待与你共同成长。

你可能感兴趣的:(Android-安全-签名验证让二次打包变的更难)