背景
最近在研究插件技术时遇到一个问题,用插件技术调起应用,应用里面的摄像头无法打开,我就查看了摄像头相关的源码,发现问题出在了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方法在调用该方法时传入宿主的包名,这样权限校验就能通过,摄像头就能打开。我们解决问题的具体的思路如下:
对于Java里面的native方法,在JNI层肯定有一个对应的函数
我们在JNI层找到保存这个函数地址的指针,先把指针保存起来
我们自己写一个代理函数,然后替换上面的那个指针
-
在我们的代理函数里面对传进来的参数做修改(针对我们的这个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, 不重要
};
针对上面的这些参数,我们只挑几个重要的下面可能会用到的说一下:
-
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
registersSize:这个保存方法的参数个数
-
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。总结起来就是:
- 找到Camera.native_setup方法在jni层的jmethodID
- 在jmethodID中找到nativeFuc,将这个值改成我们自己定义的Hook函数的地址
- 自己写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;
}