听鬼哥说故事 发表于 2015-11-23 22:29:51

通过某软件学习简单本地签名检测方法

1.准备工作

帮一个朋友看一个软件的检测,说对软件的Application里面也加入了一些调试打印的方法,但是一直都走不了,软件就直接停止运行。这是他的截图:


推辞不过,就打开电脑看一下了。

2.开始分析
打开先看AndroidManifest.xml配置清单文件,去了解我们需要知道的信息。



软件包名,是否有Application字段,启动Activity都是我们需要了解的内容。结合朋友说的测试方法,以及看到软件的Application字段,我们先打开看一下这个类里面的流程。Application类是优先于MainActivity的,去做一些初始化的操作。我们打开Application类:


构造方法这里没什么内容,我们继续往下看:



结合朋友给我发的那张图他自己加入的打印方法,可以发现第一个方法vr.h(this)上面他没加打印,所以我们打开看一下这个方法.




看到这个,既有Signature的获取方法,又有killProcess方法,我们就可以直接确定这里是验证的地方了。好吧,都不用调试就直接判断了,对朋友的分析也是佩服了,就差这一行代码舍不得看,把下面的几个方法都加入调试信息了。

3.测试



我们直接将那个h(context)方法返回void.



可以看到直接测试通过。
4.思考



虽然写这个判断需要很多行代码,但是过验证的方法,最终是都可以归结到一个判断上面的。然后我们这里补充一下在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;
         
      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))) {
      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;
}


5.总结

从本篇文章从头看到尾,我们可以通过自己的逆向能力了解到自己可以做到哪一点,是只能做到java层的验证,还是可以做好native层的验证。上面的都是简单常用的一些验证,想一想如果是在java层加上反射,将那些函数中的字符串如signature全部反射调用,把方法名字符串全部用一个算法来生成,如反转字符串或者一些其他算法来组合呢?Native层的,我们也可以使用上面的方法,还可以将方法多次分割几次,然后每次分割的短方法都做一下验证,虽然最终有个判断,但是如果将判断单独提到一个方法,然后再做几个方法检测这个方法是否是被人修改过的,都是可以加深下逆向难度的~

莫沉 发表于 2015-11-23 22:40:45

鬼哥威武,学习了

花墨 发表于 2015-11-23 22:57:07

鬼哥威武!学习下!

sndncel 发表于 2015-11-24 05:49:43

支持鬼哥。。。。。。顶顶。。。。

越狱 发表于 2015-11-24 16:23:19

鬼哥威武!学习下!

lxfan 发表于 2015-11-25 16:53:43

鬼哥威武!

chyz2015 发表于 2015-11-25 19:06:36

鬼哥,String类型的前俩排该选什么,就是第三排自定义数字的那里

xly1208 发表于 2015-11-30 10:56:31

鬼哥都亲自写帖子了,必须细细瞅瞅

xly1208 发表于 2015-11-30 11:00:00

哈,鬼哥,你的马赛克打的不好,还是不小心透漏出来了这是神马软件了{:5_117:}

huluxia 发表于 2015-12-10 21:03:59

前排支持
页: [1] 2
查看完整版本: 通过某软件学习简单本地签名检测方法