jni动态注册/轮询traceid/反调试学习笔记

huaweiP9 会给主动给app加一个trace进程

通过学习四哥的

Android安全防护之旅—Android应用”反调试”操作的几种方案解析

我们可以知道通过

第一、自己附加进程,先占坑,ptrace(PTRACE_TRACEME, 0, 0, 0)!

第二、签名校验不可或缺的一个选择,本地校验和服务端校验双管齐下!

第三、借助系统api判断应用调试状态和调试属性,最基础的防护!

第四、轮训检查android_server调试端口信息和进程信息,防护IDA的一种有效方式!

第五、轮训检查自身status中的TracerPid字段值,防止被其他进程附加调试的一种有效方式!

的方式来进行反调试

四哥也放出了源码地址
https://github.com/fourbrother/android_anti_debug

但是该源码在编译时报过时异常,其实就是AndroidMake方式过时了,要求用Cmake。
该篇笔记用Cmake方式进行操作学习(源码地址在文末)
本质就是把c代码实现改为c++实现。

准备工作

1.基本的jni开发知识
可以参考
android ndk开发索引
对ndk开发有一个基本了解,比如返回值类型,jni如何调用java方法等

2.so库加载的基本概念
可以参考
Android逆向新手答疑解惑篇——JNI与动态注册

JNI解析以及在Android中的实际应用
了解加载流程。

正式动工

首先当做你已经明白了so加载为什么会走jni_onLoad函数,
实在不懂就当作 重写了onCreate方法

1.自己提前附加一个进程
很简单,一行代码

ptrace(PTRACE_TRACEME, 0, 0, 0);

2.校验签名
纯java方法的

public static String getSignature() {
        Context ctx = MyApplication.getContext();
        try {
            //通过包管理器获得指定包名包含签名的包信息
            PackageInfo packageInfo = ctx.
                    getPackageManager()
                    .getPackageInfo(ctx.getPackageName(),
                            PackageManager.GET_SIGNATURES);
            // 通过返回的包信息获得签名数组
            Signature[] signatures = packageInfo.signatures;
            // 循环遍历签名数组拼接应用签名
            StringBuilder builder = new StringBuilder();
            for (Signature signature : signatures) {
                builder.append(signature.toCharsString());
            }
            // 得到应用签名
            return builder.toString();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return "";
    }

jni的(其实就是jni调java方法)

int check_signature(JNIEnv *env) {
    //调用Java层的Utils中的获取签名的方法
    jclass javaUtilClass = env->FindClass("com/example/lahm/ctest/Utils");
    if (javaUtilClass == NULL) {
        LOGD("not find class");
        return JNI_FALSE;
    }

    LOGD("class name:%p", javaUtilClass);

    jmethodID method = (env)->GetStaticMethodID(javaUtilClass, "getSignature",
                                                "()Ljava/lang/String;");
    if (method == NULL) {
        LOGD("not find method '%s'", method);
        return JNI_FALSE;
    }
    jstring obj = (jstring) (env)->CallStaticObjectMethod(javaUtilClass, method);
    if (obj == NULL) {
       LOGD("method invoke error:%p", obj);
        return JNI_FALSE;
    }

    const char *strAry = (env)->GetStringUTFChars(obj, 0);
    int cmpVal = strcmp(strAry, app_signature);
    (env)->ReleaseStringUTFChars(obj, strAry);

    if (cmpVal == 0)
        return JNI_TRUE;
    else
        return JNI_FALSE;
}

3.检查debug状态
类似与第2点,更加简单,java方法就一行。

public static boolean checkIsDebugB() {
        return android.os.Debug.isDebuggerConnected();
    }

还可以去拿ApplicationInfo里的字段,这里不展开了。

4和5.查看调试端口&查看traceid

现在很多的加固方法,门槛就是这个,通过开启一个子线程,读取
/proc/[pid]/status文件,拿到traceid,如果不为0,则认为该app已经被调试了,此时杀死进程。

那么要点在
开启子线程,轮询文件

void create_thread_check_traceid() {
    pthread_t t_id;
    int err = pthread_create(&t_id, NULL, thread_function, NULL);
    if (err != 0) {
        LOGD("create thread fail: %s\n", strerror(err));
    }
}

pthread_create的用法我是参考的
http://blog.csdn.net/liangxanhai/article/details/7767430

轮询文件

void *thread_function(void *argv) {
    int pid = getpid();
    char file_name[20] = {'\0'};
    sprintf(file_name, "/proc/%d/status", pid);
    char linestr[256];
    int i = 0, traceid;
    FILE *fp;
    while (1) {
        i = 0;
        fp = fopen(file_name, "r");
        if (fp == NULL) {
            break;
        }
        while (!feof(fp)) {
            fgets(linestr, 256, fp);
            if (i == 5) {
                traceid = get_number_for_str(linestr);
                LOGD("traceId:%d", traceid);
                if (traceid > 0) {
                    LOGD("I was be traced...trace pid:%d", traceid);
                    exit(0);
                }
                break;
            }
            i++;
        }
        fclose(fp);
        sleep(5);
    }
    return ((void *) 0);
}

如果你已经使用了1方法,自己附加了一个进程,那就不要用轮询了traceid了,改用端口监听吧。

动态注册

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
...
}

jni_onLoad的方法只给了JavaVM环境
需要先获得jni环境

if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
    LOGD("ERROR: GetEnv failed\n");
        return JNI_ERR;
    }

然后再调用RegisterNatives注册,传的参数
clazz 就是native函数所在的类,可提前调用env->FindClass获取,methods是一个数组,其中包含注册信息,nMethods是数量。


JNINativeMethod methods[] = {
            {"isEquals", "(Ljava/lang/String;)Z", (void *) isEquals},
    };
    jclass clazz = env->FindClass("com/example/lahm/ctest/MyApplication");
    if (clazz == NULL) {
        LOGD("Native registration unable to find class '%s'", className);
        return JNI_ERR;
    }
    int methodsLength;
    //建立方法隐射关系
    //取得方法长度
    methodsLength = sizeof(methods) / sizeof(methods[0]);
    if (env->RegisterNatives(clazz, methods, methodsLength) != 0) {
        LOGD("RegisterNatives failed for '%s'", className);
        return JNI_ERR;
    }

完成动态注册,记得要在申明该native方法的类里先写上方法名,
cpp文件里写上同名方法(就不是自动生成的jni方法名),不然找不到。

liblog的调用

因为四哥的代码写了很多的log,所以他写了个宏

#include 

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ceshi", __VA_ARGS__)

但是这样写,无法通过编译
需要在CmakeLists文件里链接上去(这个是官方指导)

find_library( # 设置外部引用库.这个库支持你在c/c++中打印log,具体请见  android/log.h
             log-lib

             # 外部引用库的名称
             log )

target_link_libraries( # 指定被链接的库.
             ctest

             # 链接log-lib到ctest
             ${log-lib} )

#将不同的源文件编译成多份so包
add_library( ccheck SHARED src/main/cpp/ccheck.cpp )
target_link_libraries(ccheck ${log-lib})

至此,完成学习。
完整代码地址
https://github.com/lamster2018/learnNDK

至于以上方法的反制方式
四哥也写了
Android逆向之旅—应用的”反调试”方案解析(附加修改IDA调试端口和修改内核信息

另附上
看雪出品-企业壳反调试及hook检测分析

Android so加固
https://mp.weixin.qq.com/s/BhGxnJrRnrYAEWJ7zDZKmQ

----后续更--
还有一种类似检查签名的方法--检查dex的完整性防二次打包

Android代码保护(签名校验、classes.dex文件完整性校验)

private void checkCRC() {
        String orginalCrc = getString(R.string.str_code);
        ZipFile zf;
        try {
            zf = new ZipFile(getApplicationContext().getPackageCodePath());
            ZipEntry ze = zf.getEntry("classes.dex");
            String strCrc = String.valueOf(ze.getCrc());
            String MD5Crc = MD5Util.GetMD5Code(strCrc);
            Log.e("checkcrc", MD5Crc);
            if (!orginalCrc.equals(MD5Crc)) {
                //ActivityManagerUtil.getScreenManager().removeAllActivity();
                Process.killProcess(Process.myPid());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

文中也提到

"DEX文件打包在APK文件中,针对APK代码的篡改攻击就是针对该文件,比如使用apktool工具反编译文件,修改smali代码。以下就是通过检查classes.dex文件的CRC32值来判断文件是否被篡改。
其中R.string.str_code是存放在本地的crc加密后的值,string.xml文件是不在dex文件中的,所以修改string.xml文件是不会造成crc值的变化(亲测),其实也可以把这个值存放在后台,通过请求网络来获取。"

本质和检查签名是一样的,不过是提供另一种思路。

你可能感兴趣的:(jni动态注册/轮询traceid/反调试学习笔记)