帮一个朋友看一个软件的检测,说对软件的Application里面也加入了一些调试打印的方法,但是一直都走不了,软件就直接停止运行。
这是他的截图:
推辞不过,就打开电脑看一下了。
打开先看AndroidManifest.xml配置清单文件,去了解我们需要知道的信息。
软件包名,是否有Application字段,启动Activity都是我们需要了解的内容。
结合朋友说的测试方法,以及看到软件的Application字段,我们先打开看一下这个类里面的流程。Application类是优先于MainActivity的,去做一些初始化的操作。
我们打开Application类:
构造方法这里没什么内容,我们继续往下看:
结合朋友给我发的那张图他自己加入的打印方法,可以发现第一个方法vr.h(this)上面他没加打印,所以我们打开看一下这个方法.
看到这个,既有Signature的获取方法,又有killProcess方法,我们就可以直接确定这里是验证的地方了。
好吧,都不用调试就直接判断了,对朋友的分析也是佩服了,就差这一行代码舍不得看,把下面的几个方法都加入调试信息了。
我们直接将那个h(context)方法返回void.
可以看到直接测试通过。
虽然写这个判断需要很多行代码,但是过验证的方法,最终是都可以归结到一个判断上面的。
然后我们这里补充一下在java层的常用方法:
这个是从网上摘抄的:
http://www.oschina.net/code/snippet_196085_37562
大家也可以跳转过去看下原文章。
前人栽树,后人乘凉,在技术研究的路上,大家都是站在前辈们的肩膀上的。
Java层:
int checkAPP(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(),
PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[0];
int hashcode = sign.hashCode();
Log.i("test", "hashCode : " + hashcode);
return hashcode == -82892576 ? 1 : 0;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
一般常用的是签名的hashCode()数值的验证。
而放到native层:
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include<ALog.h>
jvalue JNU_CallMethodByName(JNIEnv *env, jboolean *hasException, jobject obj,
const char *name, const char *descriptor, ...) {
va_list args;
jclass clazz;
jmethodID mid;
jvalue result;
if ((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
clazz = (*env)->GetObjectClass(env, obj);
mid = (*env)->GetMethodID(env, clazz, name, descriptor);
if (mid) {
const char *p = descriptor;
/* skip over argument types to find out the
· return type */
while (*p != ')')
p++;
/* skip ')' */
p++;
va_start(args, descriptor);
switch (*p) {
case 'V':
(*env)->CallVoidMethodV(env, obj, mid, args);
break;
case '[':
case 'L':
result.l = (*env)->CallObjectMethodV(env, obj, mid, args);
break;
case 'Z':
result.z = (*env)->CallBooleanMethodV(env, obj, mid, args);
break;
case 'B':
result.b = (*env)->CallByteMethodV(env, obj, mid, args);
break;
case 'C':
result.c = (*env)->CallCharMethodV(env, obj, mid, args);
break;
case 'S':
result.s = (*env)->CallShortMethodV(env, obj, mid, args);
break;
case 'I':
result.i = (*env)->CallIntMethodV(env, obj, mid, args);
break;
case 'J':
result.j = (*env)->CallLongMethodV(env, obj, mid, args);
break;
case 'F':
result.f = (*env)->CallFloatMethodV(env, obj, mid, args);
break;
case 'D':
result.d = (*env)->CallDoubleMethodV(env, obj, mid, args);
break;
default:
(*env)->FatalError(env, "illegaldescriptor");
}
va_end(args);
}
(*env)->DeleteLocalRef(env, clazz);
}
if (hasException) {
*hasException = (*env)->ExceptionCheck(env);
}
return result;
}
//合法的APP包名
const char *global_app_packageName = "com.example.abc";
//合法的hashcode
const int global_app_signature_hash_code = -82892576;
//合法标记
int legitimate = 0;
jint Java_com_example_abc_MainActivity_jniCheckAPP(JNIEnv* env, jobject context,
jobject thiz) {
LOGI("start jniCheckAPP");
// 获得 Context 类
jboolean hasException;
//获取包名
jstring jstr_packageName = (jstring) JNU_CallMethodByName(env,
&hasException, thiz, "getPackageName", "()Ljava/lang/String;").l;
if ((*env)->ExceptionCheck(env) || jstr_packageName == NULL) {
LOGI("can't get jstr of getPackageName");
return -1;
}
//获取包名的字符串
const char* loc_str_app_packageName = (*env)->GetStringUTFChars(env,
jstr_packageName, NULL);
if (loc_str_app_packageName == NULL) {
LOGI("can't get packagename from jstring");
return -2;
}
//当前应用包名与合法包名对比
if (strcmp(loc_str_app_packageName, global_app_packageName) != 0) {
LOGI("this app is illegal");
return -3;
}
//释放loc_str_app_packageName
(*env)->ReleaseStringUTFChars(env, jstr_packageName,
loc_str_app_packageName);
// 获得应用包的管理器
jobject package_manager = JNU_CallMethodByName(env, &hasException, thiz,
"getPackageManager", "()Landroid/content/pm/PackageManager;").l;
if ((*env)->ExceptionCheck(env) || package_manager == NULL) {
LOGI("can't get obj of getPackageManager");
return -4;
}
// 获得应用包的信息
jobject package_info = JNU_CallMethodByName(env, &hasException,
package_manager, "getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;",
(*env)->NewStringUTF(env, global_app_packageName), 64).l;
if ((*env)->ExceptionCheck(env) || package_info == NULL) {
(*env)->ExceptionClear(env);
LOGI("can't get obj of package_info");
return -5;
}
// 获得 PackageInfo 类
jclass pi_clazz = (*env)->GetObjectClass(env, package_info);
// 获得签名数组属性的 ID
jfieldID fieldID_signatures = (*env)->GetFieldID(env, pi_clazz,
"signatures", "[Landroid/content/pm/Signature;");
(*env)->DeleteLocalRef(env, pi_clazz);
// 得到签名数组,待修改
jobjectArray signatures = (*env)->GetObjectField(env, package_info,
fieldID_signatures);
if ((*env)->ExceptionCheck(env) || signatures == NULL) {
LOGI("can't get jobjectArray of signatures");
return -6;
}
// 得到签名
jobject signature = (*env)->GetObjectArrayElement(env, signatures, 0);
if ((*env)->ExceptionCheck(env) || signature == NULL) {
LOGI("can't get obj of signature");
return -7;
}
//获取当前应用hashcode
int hash_code = JNU_CallMethodByName(env, &hasException, signature,
"hashCode", "()I").i;
if ((*env)->ExceptionCheck(env) || package_manager == NULL) {
LOGI("can't get hash_code of signature");
return -8;
}
LOGI("this app hash_code of signature is %d", hash_code);
//合法返回1,否则返回0,并改变legitimate的值
return legitimate = (hash_code == global_app_signature_hash_code);
}
//=====================================================================
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
static JNINativeMethod methods[] = { { "jniCheckAPP",
"(Landroid/content/Context;)I",
(void*) Java_com_example_abc_MainActivity_jniCheckAPP } };
static const char *classPathName = "com/example/abc/MainActivity";
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed");
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env) {
if (!registerNativeMethods(env, classPathName, methods,
sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
if ((*vm)->GetEnv(vm, &uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
goto bail;
}
result = JNI_VERSION_1_4;
bail: LOGI("JNI_ONload result '%d' ", result);
return result;
}
从本篇文章从头看到尾,我们可以通过自己的逆向能力了解到自己可以做到哪一点,是只能做到java层的验证,还是可以做好native层的验证。
上面的都是简单常用的一些验证,想一想如果是在java层加上反射,将那些函数中的字符串如signature全部反射调用,把方法名字符串全部用一个算法来生成,如反转字符串或者一些其他算法来组合呢?
Native层的,我们也可以使用上面的方法,还可以将方法多次分割几次,然后每次分割的短方法都做一下验证,虽然最终有个判断,但是如果将判断单独提到一个方法,然后再做几个方法检测这个方法是否是被人修改过的,都是可以加深下逆向难度的~