Android中如何Hook住JNI方法

背景

最近在研究插件技术时遇到一个问题,用插件技术调起应用,应用里面的摄像头无法打开,我就查看了摄像头相关的源码,发现问题出在了android.hardware.Camera类的初始化里面。具体来说,当我们初始化一个Camera类对象时,在初始化过程中调用了一个native的方法:

private void final int native_setup(Object camera_this,int cameraId, int halVersion, String packageName);

这个方法在JNI层通过Binder通信请求Server端的ICameraService去初始化摄像头。在Server端ICameraService会检查Client端传过来的包名,然后去PackageManagerService那边请求该包名对应的应用是否声明了Camera相关的权限,如果有,则打开摄像头,如果没有在界面上就会提示去设置里面打开相应的权限。我们的问题就出在这里,当我们的插件调起初始化Camera类时,Camera类获取的包名是插件的包名,而插件没有安装,所以在Server端进行权限校验时就会失败,最终导致摄像头无法打开

解决问题的思路

其实这个问题我们也好解,只要我们在宿主应用里面申请Camera相关的权限,然后Hook住Camera类里面的native_setup方法在调用该方法时传入宿主的包名,这样权限校验就能通过,摄像头就能打开。我们解决问题的具体的思路如下:

  1. 对于Java里面的native方法,在JNI层肯定有一个对应的函数

  2. 我们在JNI层找到保存这个函数地址的指针,先把指针保存起来

  3. 我们自己写一个代理函数,然后替换上面的那个指针

  4. 在我们的代理函数里面对传进来的参数做修改(针对我们的这个Case只需要替换包名,把“com.baidu.browser.apps”替换成我们的宿主包名),然后再调用我们在上面保存的函数指针,传进我们修改后的参数完成相应的操作

    针对上面解决问题的思路,我们的目标就很清晰了,首先并且最重要的是:我们得在JNI层找到android.hardware.Camera.native_setup的对应的函数。

JNI中如何去调用Java方法

// 这是调用Java中的static方法
jmethodID  method = env->GetStaticMethodID(g_jclass,        JAVA_CALLBACK__ON_KILL_PROCESS, JAVA_CALLBACK__ON_KILL_PROCESS_SIGNATURE);
env->CallStaticVoidMethod(g_jclass, method, pid, sig);

// 这是调用Java中的非static方法
jmethodID  method = env->GetMethodID(g_jclass, JAVA_CALLBACK__ON_KILL_PROCESS, JAVA_CALLBACK__ON_KILL_PROCESS_SIGNATURE);
env->CallVoidMethod(g_jclass, method, pid, sig);

从上面的代码我们看到,不管是调用静态方法还是非静态方法,我们都得找到代表这个方法的jmethodID,然后通过这个jmethodID去调用JNIEnv的相应的CallXXXXMethod;

jmethodID到底是什么呢?从jni.h文件里面可以看到下面这段定义:

struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */

其实可以看到jmethodID其实就是一个结构体指针,这个结构体叫__jmethodID是如何定义的呢?其定义如下:

struct Method {

    ClassObject*    clazz;  // 代表这个方法所属的类

    u4  accessFlags;     // 访问标识符, 定义是public? native?synchronized?

    u2  methodIndex;   // index,在ClassObject中一个保存Method对象的数组的位置。

    u2  registersSize;  // 仅对native方法有用,保存方法的参数的个数。

    u2  outsSize;   // unknown, 不重要

    u2  insSize;    // unknown, 不重要

    const char*   name;   // 方法名

    DexProto  prototype;   // 我们可以理解这是一个代表该方法签名的字符串(包含返回值以及参数类型)

    const char*  shorty; // 跟上面的意义,方法的签名,like:(ILjava/lang/String)V

    const u2*  insns;  // 方法的指令代码

    int  jniArgInfo;  // jni参数信息,好像没啥用

    DalvikBridgeFunc nativeFunc;   // native函数指针,可能是真正的native函数,也可能是JNI桥接函数

    bool fastJni;   // unknown, 不重要

    bool noRef;     // unknown, 不重要

    bool shouldTrace;   // unknown, 不重要

    const RegisterMap* registerMap;   // unknown, 不重要

    bool   inProfile;   // unknown, 不重要
};

针对上面的这些参数,我们只挑几个重要的下面可能会用到的说一下:

  1. accessFlags: 在Dalvik虚拟机中定义了一系列的值用来指示这个方法是public的还是private的,是static的还是非static的,是native 的还是非native的,有没有final、synchronized、absttact等等关键字。 我们可以修改这个值来修改一个方法的属性,比如说下面这段代码,可以讲一个非native的方法修改成native的方法:

     public static final int ACC_NATIVE = 0x0100;
    
     (method->accessFlags & ACC_NATIVE) != 0
    
  2. registersSize:这个保存方法的参数个数

  3. nativeFuc:这个最重要,它是一个函数指针,这个指针可能指向该方法对应的native函数,也可能指向一个桥接函数。简单的说就是,在Dalvik虚拟机下面,这个指向的是桥接函数;在Art虚拟机下,指向的是native函数。至于这个桥接函数,它的声明如下:

     typedef void (* DalvikBridgeFunc)(const u4* args, JValue* pResult,
         const Method* method, struct Thread* self);
    

这个桥接函数的参数我解释一下:

args: 存储的是调研native方法传过来的参数,对于非static方法,args[0]存储的是this对象,args[1]存储的是第一个参数值,args[2]存储的是第二个参数值,以此类推。对于static对象,args[0]存储的是第一个参数值,args[1]存储的是第二个参数值, 举个例子,比如说对于我自己定一个一个方法getSubString(String aString, int aStart, int aLength), 这是一个非static方法。 我现在调用该方法,getString("This is a sentence", 5, 6); 当方法走到我们的桥接函数中时,我们可以通过args[1]获取到字符串"This is a sentence", 通过args[2]获取到5,通过args[3]获取到6。

pResult: 存储函数的返回值

method: 存储与该桥接函数对应的method对象。

self: 抱歉,这个我也没搞明白。-_-

对于native函数,这个就简单了,看下面的代码就一目了然了:

    static JNINativeMethod gMethods[] = {
        {"hookNativeMethod", "(Ljava/lang/Object;Ljava/lang/String;Z)Z", (void *) native_hook}

    };
    
    void native_hook(JNIEnv *env, jclass clazz, jobject method, jstring pkg,            
        jboolean isArt)

    env->RegisterNatives(javaClass, gMethods, 1)

上面的"hookNativeMethod"对应的是java里面的声明为native的方法, native_hook代表的是native函数,我们通过JNIEnv 的RegisterNatives将这他们关联起来。 java层调用hookNativeMethod最终回转化成native层调用native_hook函数。在Art虚拟机下,Method结构体中存储的nativeFuc指针指向的就是这个native_hook函数。

上面已经解释清楚jmethodID代表啥之后,下面我们就来着手Hook住Camara的native_setup方法:我们分步来实现Hook:

第一步: 获取native_setup方法的jmethodID值:

我们直接看代码:

private static native boolean hookNativeMethod(Object method, String packageName);

public static void fixCamera() {
    if (!sNativeFixed) {
        try {
            Method native_setup;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                native_setup = Reflect.on(Camera.class).exactMethod("native_setup",
                        new Class[]{Object.class, int.class, int.class, String.class});
            } else {
                native_setup = Reflect.on(Camera.class).exactMethod("native_setup",
                        new Class[]{Object.class, int.class, String.class});
            }
            hookNativeMethod(native_setup, VirtualCore.getCore().getHostPkg());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        sNativeFixed = true;
    }
}

这是Java层代码,我们通过反射获取到Camera类里面native_setup方法对应的Method对象(注意,这是Java里面的Method类,不是我们上面说的Method结构体);然后我们通过一个native方法hookNativeMethod将这个Method传到JNI层。

下面是JNI层的代码:

jmethodID mtd_openDexNative =  env->FromReflectedMethod(javaMethod);

在JNI层,我们调用JNIEnv的FromReflectedMethod方法,传入java层传过来的代表Method对象的jobject对象,得到了native_setup在native层的jmethodID对象。

第二步:我们找到jmethodID之后,接下来要找到这个结构体里面的nativeFuc指针

正如我们上面所说,这个jmethodID其实就是上面的我们解释的Method结构体了,我们现在就是要找到这个结构体里面的nativeFunc指针,然后把这个指针改成我们自己定义的一个函数,这样就完成了Hook。那么我们怎么去找到这个nativeFuc指针呢,一种比较简单的方法就是将jmethodID强制转换成结构题Method的指针,但是我们目前没这么做,原因很简单,上面的Method结构体里面还包含了另外一些结构题,如DexProto、RegisterMap等等,这两个结构题里面可能还包含了其他的结构体,我要么得把这些结构体都拷过来,要么我就得引用定义这些结构体的头文件,这个比较麻烦。我们换用另一种方法:

我们在java类里面定义一个mark()的native方法,在JNI里面也定义一个nativeMark()函数,然后通过JNIEnv的RegisterNatives函数将他们两个关联起来。然后我们通过JNIEnv的GetMethodId函数获取到mark方法的jmethodID,jmethodID是Method结构体的指针(其实就是这个结构体的内存起始地址),我们知道这个指针之后,下面要分两种情况来分析了:

  • Art虚拟机下
    我们上面说了Art虚拟机下面结构体Method里面的nativeFunc其实指向的就是native函数,在我们这个案例中指向的就是nativeMark函数。那下面就好办了:我们用一个while循环,从jmethodID所代表的内存地址开始,4个字节4个字节的往后寻址,然后通过“*”取出所指地址中存储的值,用这个值跟nativeHook指针比较,当相等时,我们就找到了结构体Method里面的nativeFuc的偏移量。

  • Dalvik虚拟机下
    还是上面说的,在Dalvik虚拟机下结构体Method里面的nativeFunc指向的是一个桥接函数,那么我们如何去找到保存这个桥接函数的指针呢?可以通过下面这段代码找到:

char vmSoName[15] = {0};
__system_property_get("persist.sys.dalvik.vm.lib", vmSoName);
LOGD("Find the so name : %s.", strlen(vmSoName) == 0 ? "<EMPTY>" : vmSoName);

void *vmHandle = dlopen(vmSoName, 0);
if (!vmHandle) {
    LOGE("Unable to open the %s.", vmSoName);
    vmHandle = RTLD_DEFAULT;
}

gOffset.art_work_around_app_jni_bugs = dlsym(vmHandle, "art_work_around_app_jni_bugs");

上面这段代码可以找到桥接函数的指针,同样,我们从jmethodID代表的内存地址开始,4个字节4个字节的往后寻址,然后通过“*”取出所指地址中存储的值,用这个值跟我们刚刚找到的桥接函数的指针比较,当相等时,我们就找到了结构体Method里面的nativeFuc的偏移量。
下面这段代码就是寻址和比较的代码:

void mark() {
     // Do nothing
};

void searchJniOffset(JNIEnv *env, bool isArt) {

    jclass g_class = env->FindClass(JAVA_CLASS);
    jmethodID mtd_nativeHook = env->GetStaticMethodID(g_class, "mark", "()V");

    size_t startAddress = (size_t) mtd_nativeHook;
    size_t targetAddress = (size_t) nativeMark;
    if (isArt && gOffset.art_work_around_app_jni_bugs) {
        targetAddress = (size_t) gOffset.art_work_around_app_jni_bugs;
    }

    int offset = 0;
    bool found = false;
    while (true) {
        if (*((size_t *) (startAddress + offset)) == targetAddress) {
            found = true;
            break;
        }
        
        offset += 4;
        if (offset >= 100) {
            LOGE("Ops: Unable to find the jni function.");
            break;
        }
    }
    
    if (found) {
        gOffset.nativeOffset = offset;
        if (!isArt) {
            gOffset.nativeOffset += (sizeof(int) + sizeof(void *));
        }
        LOGD("Hoho, Get the offset : %d.", gOffset.nativeOffset);
    }
}

上面我们找到nativeFunc偏移量之后,我们前面又找到了Camera类的native_setup对应的jmethodID,jmethodID加上这个偏移量就是native_setup对应的Method结构体里的nativeFuc地址,这个地址里面存储了一个C函数指针,在Dalvik虚拟机下,这个是桥接函数的指针,在Art虚拟机下这个指针就是native函数的指针。

第三步:找到C函数指针之后,把指针替换掉,换成我们定义的Hook函数的函数指针

这一步就很简单了,直接看代码:

inline void replaceImplementation(JNIEnv *env, jobject javaMethod, jboolean isArt) {

    size_t mtd_openDexNative = (size_t) env->FromReflectedMethod(javaMethod);
    int nativeFuncOffset = gOffset.nativeOffset;
    void **jniFuncPtr = (void **) (mtd_openDexNative + nativeFuncOffset);

    if (!isArt) {
        LOGD("The vm is dalvik");
        gOffset.orig_DalvikBridgeFunc = (Bridge_DalvikBridgeFunc) (*jniFuncPtr);
        *jniFuncPtr = (void *) new_bridge_nativeSetUpFunc;
    } else {
        char vmSoName[4] = {0};
        __system_property_get("ro.build.version.sdk", vmSoName);
        int sdk;
        sscanf(vmSoName, "%d", &sdk);
        LOGD("The vm is art and the sdk int is %d", sdk);

        if (sdk < 21) {
            gOffset.orig_native_openDexNativeDalvikFunc = (Native_nativeSetUpDalvikFunc) (*jniFuncPtr);
            *jniFuncPtr = (void *) new_nativeSetUpDalvikFunc;
        } else {
            gOffset.orig_native_openDexNativeFunc = (Native_nativeSetUpFunc) (*jniFuncPtr);
            *jniFuncPtr = (void *) new_nativeSetUpFunc;
        }

    }

}

这里我们解释一下,我们区分了Dalvik虚拟机和Art虚拟机两种情形,Art虚拟机下直接替换的就是native函数,而Dalvik虚拟机下我们替换的是桥接函数。其实更通俗的讲就是,Art虚拟机下面我们Hook的是真正的native函数,在Dalvik虚拟机下面我们要Hook的是桥接函数。

第四步: 定义Hook函数,在第三步中会用我们定义的Hook函数替换掉原来的函数

直接看代码,分Dalvik虚拟机和Art虚拟机:

// 这个函数是Art虚拟机下的hook函数
static jobject new_nativeSetUpArtFunc(JNIEnv *env, jclass jclazz,
                                     jobject weak_this, jint halVersion,
                                     jstring clientPackageName) {
    jstring newPkg = env->NewStringUTF(newPkgName);

    return gOffset.orig_native_openArtNativeDalvikFunc(env, jclazz, weak_this, halVersion, newPkg);
}

// 这个是Dalvik虚拟机下面的hook函数,用该函数去Hook桥接函数
static void new_bridge_nativeSetUpFunc(const void **args, void *pResult, const void *method, void *self) {
    JNIEnv *env = NULL;
    g_vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    g_vm->AttachCurrentThread(&env, NULL);

    typedef char* (*GetCstrFromString)(void *);
    typedef void* (*GetStringFromCstr)(const char*);

    const char* origPkg3 = args[3] == NULL ? NULL : gOffset.GetCstrFromString((void*) args[3]);
    LOGD("The original package3 is: %s", origPkg3);

    args[3] = gOffset.GetStringFromCstr(newPkgName);
    gOffset.orig_DalvikBridgeFunc(args, pResult, method, self);
}

至此,我们分四步就已经完成了Camera类的native_setup的Hook。总结起来就是:

  1. 找到Camera.native_setup方法在jni层的jmethodID
  2. 在jmethodID中找到nativeFuc,将这个值改成我们自己定义的Hook函数的地址
  3. 自己写Hook函数,要分Dalvik虚拟机和Art虚拟机

进阶(如果你想,你就能)

其实上面的代码可以实现所有Java方法的Hook;
对于native的方法上面的代码稍微把方法名,方法签名修改一下,实现一下对应的Hook函数就可以了。
对于非native的方法,我们可以把Method结构体中的accessFlags改一下,就可以把它变成native方法,然后将nativeFuc赋值成我们写的Hook函数,然后再改一下registersSize和insSize的值,这就是保存参数个数的地方,最后再该一下jniArgsInfo值,具体的改法可以参照git上的代码AllHookInOne

下面是我写的完整版的代码

这是Java层的代码

<!-- lang:java -->
public class CameraFixer {
    
    private static native boolean hookNativeMethod(
            Object method, String packageName, boolean isArt);
    
    private static native void hook();
}

这是C代码:

camera_hook.h

#ifndef VIRTUALAPP_CAMERA_HOOK_H
#define VIRTUALAPP_CAMERA_HOOK_H

#include <jni.h>
#include <stdlib.h>
#include <android/log.h>
#include <dlfcn.h>
#include <stddef.h>
#include <fcntl.h>
#include <sys/system_properties.h>
#include <stdio.h>

#define TAG "HOOKCAMERA"
#define JAVA_CLASS "com/baidu/freeinstall/client/ContextFixer"


#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,  TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,  TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,  TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

__BEGIN_DECLS
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
    JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);
__END_DECLS

#endif //VIRTUALAPP_CAMERA_HOOK_H

camera_hook.c

#include "camera_hook.h"

JavaVM *g_vm;

typedef void (*Bridge_DalvikBridgeFunc)(const void **, void *, const void *,        void *);

typedef jobject (*Native_nativeSetUpFunc)(JNIEnv *, jclass, jobject weak_this,      jint cameraId, jint halVersion, jstring clientPackageName);

typedef jobject (*Native_nativeSetUpDalvikFunc)(JNIEnv *, jclass, 
    jobject weak_this,  jint halVersion, jstring clientPackageName);


void mark() {
    // Do nothing
};


static struct {
    bool isArt;
    int nativeOffset;
    void *art_work_around_app_jni_bugs;
    char *(*GetCstrFromString)(void *);
    void *(*GetStringFromCstr)(const char *);
    Bridge_DalvikBridgeFunc orig_DalvikBridgeFunc;
    Native_nativeSetUpFunc orig_native_openDexNativeFunc;
    Native_nativeSetUpDalvikFunc orig_native_openDexNativeDalvikFunc;
} gOffset;

char *newPkgName;
static jobject new_nativeSetUpFunc(JNIEnv *env, jclass jclazz,
     jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName) {
    
    jstring newPkg = env->NewStringUTF(newPkgName);
    return gOffset.orig_native_openDexNativeFunc(env, jclazz, weak_this, cameraId, halVersion, newPkg);

}

static jobject new_nativeSetUpDalvikFunc(JNIEnv *env, jclass jclazz,
    jobject weak_this, jint halVersion, jstring clientPackageName) {

    jstring newPkg = env->NewStringUTF(newPkgName);

    return gOffset.orig_native_openDexNativeDalvikFunc(env, jclazz, weak_this, halVersion, newPkg);
    
}

static void new_bridge_nativeSetUpFunc(const void **args, void *pResult, const      void *method,  void *self) {

    JNIEnv *env = NULL;
    g_vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    g_vm->AttachCurrentThread(&env, NULL);

    typedef char* (*GetCstrFromString)(void *);
    typedef void* (*GetStringFromCstr)(const char*);

    const char* origPkg0 = args[0] == NULL ? NULL : gOffset.GetCstrFromString((void*) args[0]);
    LOGD("The original package0 is: %s", origPkg0);

    const char* origPkg = args[2] == NULL ? NULL : gOffset.GetCstrFromString((void*) args[2]);
    LOGD("The original package2 is: %s", origPkg);

    const char* origPkg3 = args[3] == NULL ? NULL : gOffset.GetCstrFromString((void*) args[3]);
    LOGD("The original package3 is: %s", origPkg3);

    args[3] = gOffset.GetStringFromCstr(newPkgName);
    gOffset.orig_DalvikBridgeFunc(args, pResult, method, self);
}


void searchJniOffset(JNIEnv *env, bool isArt) {

    jclass g_class = env->FindClass(JAVA_CLASS);
    jmethodID mtd_nativeHook = env->GetStaticMethodID(g_class, "nativeMark", "()V");

    size_t startAddress = (size_t) mtd_nativeHook;
    size_t targetAddress = (size_t) mark;
    if (isArt && gOffset.art_work_around_app_jni_bugs) {
        targetAddress = (size_t) gOffset.art_work_around_app_jni_bugs;
    }

    int offset = 0;
    bool found = false;
    while (true) {
        if (*((size_t *) (startAddress + offset)) == targetAddress) {
            found = true;
            break;
        }
        offset += 4;
        if (offset >= 100) {
            LOGE("Ops: Unable to find the jni function.");
            break;
        }
    }
    
    if (found) {
        gOffset.nativeOffset = offset;
        if (!isArt) {
            gOffset.nativeOffset += (sizeof(int) + sizeof(void *));
        }
        LOGD("Hoho, Get the offset : %d.", gOffset.nativeOffset);
    }
}

inline void replaceImplementation(JNIEnv *env, jobject javaMethod, jboolean isArt) {

    size_t mtd_openDexNative = (size_t) env->FromReflectedMethod(javaMethod);
    int nativeFuncOffset = gOffset.nativeOffset;
    void **jniFuncPtr = (void **) (mtd_openDexNative + nativeFuncOffset);

    if (!isArt) {
        LOGD("The vm is dalvik");
        gOffset.orig_DalvikBridgeFunc = (Bridge_DalvikBridgeFunc) (*jniFuncPtr);
        *jniFuncPtr = (void *) new_bridge_nativeSetUpFunc;
    } else {
        char vmSoName[4] = {0};
        __system_property_get("ro.build.version.sdk", vmSoName);
        int sdk;
        sscanf(vmSoName, "%d", &sdk);
        LOGD("The vm is art and the sdk int is %d", sdk);

        if (sdk < 21) {
            gOffset.orig_native_openDexNativeDalvikFunc = (Native_nativeSetUpDalvikFunc) (*jniFuncPtr);
            *jniFuncPtr = (void *) new_nativeSetUpDalvikFunc;
        } else {
            gOffset.orig_native_openDexNativeFunc = (Native_nativeSetUpFunc) (*jniFuncPtr);
            *jniFuncPtr = (void *) new_nativeSetUpFunc;
        }

    }

}

static JNINativeMethod gMarkMethods[] = {
        {"nativeMark", "()V", (void *) mark}
};

void native_hook(JNIEnv *env, jclass clazz, jobject method, jstring pkg, jboolean isArt) {

    newPkgName = (char *) env->GetStringUTFChars(pkg, NULL);
    g_vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    g_vm->AttachCurrentThread(&env, NULL);

    jclass g_class = env->FindClass(JAVA_CLASS);
    if (env->RegisterNatives(g_class, gMarkMethods, 1) < 0) {
        return;
    }

    gOffset.isArt = isArt;

    char vmSoName[15] = {0};
    __system_property_get("persist.sys.dalvik.vm.lib", vmSoName);
    LOGD("Find the so name : %s.", strlen(vmSoName) == 0 ? "<EMPTY>" : vmSoName);

    void *vmHandle = dlopen(vmSoName, 0);
    if (!vmHandle) {
        LOGE("Unable to open the %s.", vmSoName);
        vmHandle = RTLD_DEFAULT;
    }


    if (isArt) {
        gOffset.art_work_around_app_jni_bugs = dlsym(vmHandle, "art_work_around_app_jni_bugs");
    } else {
        gOffset.GetCstrFromString = (char *(*)(void *)) dlsym(vmHandle,                                                 "_Z23dvmCreateCstrFromStringPK12StringObject");
        if (!gOffset.GetCstrFromString) {
            gOffset.GetCstrFromString = (char *(*)(void *)) dlsym(vmHandle,
                     "dvmCreateCstrFromString");
        }
        
        gOffset.GetStringFromCstr = (void *(*)(const char *)) dlsym(vmHandle,
                "_Z23dvmCreateStringFromCstrPKc");
        if (!gOffset.GetStringFromCstr) {
            gOffset.GetStringFromCstr = (void *(*)(const char *)) dlsym(vmHandle,  "dvmCreateStringFromCstr");
        }
    }
    
    searchJniOffset(env, isArt);
    replaceImplementation(env, method, isArt);
}


static JNINativeMethod gMethods[] = {
    { "hookNativeMethod", "(Ljava/lang/Object;Ljava/lang/String;Z)Z",
         (void *) native_hook }
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    g_vm = vm;
    JNIEnv *env;

    LOGE("JNI_Onload start");
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("GetEnv() FAILED!!!");
        return JNI_ERR;
    }
    
    jclass javaClass = env->FindClass(JAVA_CLASS);
    LOGE("we have found the class: %s", JAVA_CLASS);
    if (javaClass == NULL) {
        LOGE("unable to find class: %s", JAVA_CLASS);
        return JNI_ERR;
    }
    
    env->UnregisterNatives(javaClass);
    if (env->RegisterNatives(javaClass, gMethods, 1) < 0) {
        LOGE("register methods FAILED!!!");
        return JNI_ERR;
    }

    env->DeleteLocalRef(javaClass);
    LOGI("JavaVM::GetEnv() SUCCESS!");
    return JNI_VERSION_1_6;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容