如果你没有对你的应用做任何的安全保障措施,那么你的应用就非常的危险
首先了解一下什么是二次打包:
二次打包
通过工具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();
}
});
2.使用apktool 反编译DR_Test.apk
3.进入smail文件,修改字符串的内容 “快看,博主真的好美啊” 改为 “快看,天啊,是垃圾广告”,
4.改完之后,然后再将修改后的文件,打包成DR_Dirty.apk
5.使用Auto_sign工具再次签名DR_Dirty.apk(再次签名肯定和应用的本身签名不同),得到DR_Dirty_signed然后运行
这就改变一个显示文本的值,是不是超级简单,也超级危险,所以我们一定要防范这种的二次打包.
防范的思路是:
进入程序就检查签名是不是我们自己的合法的签名,如果不是,就提示盗版信息,或者退出程序
/**
* 验证是否是合法的签名
* @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啊,是吧
1.下载NDK:NDK Downloads(需要科学上网)
2.Setting -->Project Structure,添加NDK路径,就是上一部下载的NDK,解压之后的路径
添加之后你会发现 local.properties多了NDK的引用路径
3.gradle.properties添加一句android.useDeprecatedNdk=true
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
在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本地验证 获取应用签名