分析一些东西时,想实现监控Android JNI函数的调用,网上找了下,发现这个库https://github.com/w296488320/JnitraceForCpp实现了这样的效果,但是实际使用时,我自己遇到了一些问题,比如hook打印jobject数据时,陷入了死循环,然后调用FindClass解析时,有时也会闪退等,最终自己处理问题后的版本如下https://github.com/carrys17/Study-Notes/tree/master/JniHelper
主要改动:
1、在getJObjectInfo等方法中,会调用Jni方法去解析jobject数据,例如调用CallObjectMethod时,因为前边hook了CallObjectMethodV导致死循环。处理方式就是判断定义一个CallObjectMethod_SAFE方法,如果判断到CallObjectMethodV_f不为空(即CallObjectMethodV函数已经被hook了),则调用它而不是CallObjectMethod方法。
jobject CallObjectMethod_SAFE(JNIEnv *env, jobject jobj, jmethodID jmethodId,...){
// WLOGD("enter CallObjectMethod_SAFE method, CallObjectMethodV_f = %p",CallObjectMethodV_f);
if (CallObjectMethodV_f){
va_list args;
jobject result;
va_start(args,jmethodId);
result = CallObjectMethodV_f(env, jobj,jmethodId,args);
va_end(args);
return result;
}else{
return env->CallObjectMethod(jobj,jmethodId);
}
}
// 直接通过toString获取jobject 信息
const char *getJObjectToString(JNIEnv *env, jobject obj) {
if(!g_method_id_Object_toString){
g_method_id_Object_toString =
GetMethodID_SAFE(env,FindClass_SAFE(env,ENCRYPT("java/lang/Object")), ENCRYPT("toString"),
ENCRYPT("()Ljava/lang/String;"));
}
jobject jobj = CallObjectMethod_SAFE(env, obj, g_method_id_Object_toString);
return GetStringUTFChars_SAFE(env,(jstring) (jobj), nullptr);
}
2、FindClass方法在部分JNI方法hook了之后再去调用时,会触发闪退,从日志的行为看是一直打印了CallObjectMethod方法然后闪退,这里之所以会去调用FindClass,其实是为了最终拿到Object_toString的method_id,所以在init初始化的时候保存一个Object_toString的method_id
void init(JNIEnv *env, bool isForbidMode, const std::list<std::string> &forbid_list, const std::list<std::string> &filter_list){
WLOGD("enter init method");
g_method_id_Class_getName =
env->GetMethodID(env->FindClass(ENCRYPT("java/lang/Class")), ENCRYPT("getName"),
ENCRYPT("()Ljava/lang/String;"));
g_method_id_Object_toString =
env->GetMethodID(env->FindClass(ENCRYPT("java/lang/Object")), ENCRYPT("toString"),
ENCRYPT("()Ljava/lang/String;"));
gForbidSoList = std::list<std::string>(forbid_list);
gFilterSoList = std::list<std::string>(filter_list);
gIsForbidMode = isForbidMode;
WLOGD("init success!");
}
其他的一些就是避免我们主动调用的JNI方法解析数据时,过滤掉不让干扰我们分析目标的JNI方法打印。
3、因为我的场景是注入so到目标里边,所以我们打印堆栈过滤一些so时,需要调用dladdr((void *) __builtin_return_address(1), &info);
而不是__builtin_return_address(0)
,因为__builtin_return_address(0)
获取到的永远是我自己注入的so名称