发新帖

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

[复制链接]
22532 10
1.准备工作

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



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


2.开始分析

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




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



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




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





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


3.测试



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




可以看到直接测试通过。

4.思考



虽然写这个判断需要很多行代码,但是过验证的方法,最终是都可以归结到一个判断上面的。
然后我们这里补充一下在java层的常用方法:
这个是从网上摘抄的:
大家也可以跳转过去看下原文章。
前人栽树,后人乘凉,在技术研究的路上,大家都是站在前辈们的肩膀上的。
Java层:

[JavaFX] 纯文本查看 复制代码
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层:

[C++] 纯文本查看 复制代码
#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;
}



5.总结

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
已有2人评分 NB 荣获致谢 理由
少不更事 + 1 + 1 很好的文章,最近刚好在弄签名验证.
不依 + 1 + 1 很给力!

查看全部评分 总评分: NB +2  荣获致谢 +2 

举报 使用道具

回复

精彩评论9

莫沉    发表于 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 | 显示全部楼层
哈,鬼哥,你的马赛克打的不好,还是不小心透漏出来了这是神马软件了

举报 使用道具

回复 支持 反对
huluxia    发表于 2015-12-10 21:03:59 来自手机  | 显示全部楼层
前排支持

举报 使用道具

回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表