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
  • 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 __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

 
 
  • 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本地验证 获取应用签名

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序邦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值