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

来源:互联网 发布:工业机器人技术编程 编辑:程序博客网 时间:2024/06/06 12:20

转载:http://blog.csdn.net/qq_32452623/article/details/54351364

二次打包的危害性

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

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

二次打包 
通过工具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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.签名打包为DR_Test.apk,运行如此下图: 
这里写图片描述

2.使用apktool 反编译DR_Test.apk

这里写图片描述

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

这里写图片描述

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

这里写图片描述

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

这里写图片描述

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

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

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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

反编译的时候,直接将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,解压之后的路径

这里写图片描述

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

这里写图片描述

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

这里写图片描述

开始使用NDK

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

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

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

public class DR_JNITest {    static{        //引用自己so文件 名称和上一步gradle中的名称保证相同        System.loadLibrary("DRPrincess");    }    // 这个因为要引用c++文件,所以一定要加上native 关键字    public static  native String getSuccessKey(Object contextObject);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

这里写图片描述

  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 <jni.h>/* Header for class dr_dr_testappdemo_DR_JNITest */#ifndef _Included_dr_dr_testappdemo_DR_JNITest#define _Included_dr_dr_testappdemo_DR_JNITest#ifdef __cplusplusextern "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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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 <jni.h>#include <string.h>#include <stdio.h>#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<jstring>(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<jstring>(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");    }     */}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

6.Activity中调用c++方法

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

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

代码地址

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

参考博客

Android APK加固技术方案调研

Android studio 编译C生成.so文件

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


原创粉丝点击