admin 管理员组文章数量: 887006
闲聊阿里加固(一)
0x00 闲扯
1.为什么要写这些?
折腾了一段时间的Android加固,看了很多大牛的文章,收获还是蛮大的,不过好像大部分的文章都是直接写在哪里下断点如何修复之类的,写出如何找到这个脱壳点的文章还是比较少,所以我准备整理一下这部分的知识,讲讲如何找到脱壳点,希望能和大家多交流
2.需要什么样的基础?
用过JEB,IDA Pro,如果有跟着其它表哥自己脱过壳的那就更好了:),另外,既然都开始玩加固了,那么解压apk后的工程目录,smali语法等这种基础的东西就不再提了
3.为什么选择阿里加固?
因为我手上的加固样本有限,不是每个版本的加固样本都有,所以综合考虑了一下,选择阿里的样本,能比较容易形成一种循序渐进学习的感觉,样本全部来自历年阿里CTF和阿里移动安全挑战赛
4.适合对象?
最适合跟着其它表哥文章脱过壳,却不知道为什么要那样脱壳的同学,因为接下来这几篇文章讲的就是如何通过一步步的分析,找到脱壳点
0x01 样本初分析—classes.dex
这个样本是阿里14年出的,名字是jscrack.apk,我们来载入JEB了解大概信息
首先我们来看箭头指向的地方:
1.fak.jar:从名字来看,这是一个jar文件,但是JEB识别出来是一个dex,这个信息提供的很关键,我们可以猜想,阿里加固的方法会不会将源dex文件隐藏在这个fak.jar里面?
2.StupApplication:可以看到入口变成了StupApplication,有过Android开发经验的同学们都知道,一般情况下,我们在开发APP的时候,如果有全局变量,数据初始化之类的操作,会写一个StartApplication类继承Application类,那么显然这里是阿里加固自己添加的一个入口,用来执行一些初始化的操作,比如解密dex,反调试,检测模拟器等等之类的,当然这只是我们的猜测,不一定正确
3.mobisec.so:加载了一个so文件,这个so文件就是我们的切入点
然后来看两个红色框框,两个native方法:attachBaseContext()和onCreate(),一般情况下,入口应该是onCreate(),但是attachBaseContext()更早于onCreate()执行
0x02 样本初分析—libmobisec.so
刚刚我们通过JEB简单的分析了加固后的样本,发现关键信息就是libmoisec.so和两个native方法attachBaseContext()和onCreate(),那么我们现在就来分析一下libmobisec.so
使用IDA Pro载入libmobisec.so
加载起来还是很顺利的,并没有遇到”Binary Data is incorrect”之类的报错
在左边搜一下JNI_OnLoad,至于为什么搜这个?纯粹只是感觉,如果找不到就搜其它的嘛,一步一步来
运气不错,搜到了,双击进入,F5看伪代码,毕竟F5大法好
有一些结构没有识别出来,我们来导入JNI.h来手动修正一下
File -> Load file -> Parse C header file
导入成功后会出现”Compilation successful”的MessageBox,点击OK就行
然后切换到Structures界面,如果没有的话可以使用快捷键”Shift+F9”
菜单栏也可以打开
View -> Open subviews -> Structures
打开后我们按insert键,添加结构体,点击箭头那个按钮
分两次添加下面两个结构体
添加完后回到刚才F5反编译过后的窗口,放着先
我们来学习一下NDK开发中的一些概念知识,虽然大家搞的都是脱壳,但是不一定每个同学都搞过NDK开发,所以我们来补一补这部分的知识,如果已经很清楚的同学就当复习吧,这部分的知识相当重要,Very Important
JNI:Java Native Interface,类似一种标准,提供了很多的API,使Java可以和C/C++进行通信
NDK:Native Development Kit,这是一套工具或者说是一套组件,实现用C/C++来开发Android Application
这是一个简单的NDKDemo,也许这个Demo你觉得眼熟但是又好像不一样,没错就是你想到的那个,我稍微改了一下代码
public class HelloJni extends Activity{@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);TextView tv = new TextView(this);tv.setText(stringFromJNI());setContentView(tv);}public native String stringFromJNI();static {System.loadLibrary("hello-jni");}
}
我们来实现一下native层的代码,在NDK开发中,有C和C++两种写法,显然它们在开发中是有差别的,那么结合这里的例子来看一下差别在哪里
#include <string.h>
#include <jni.h>
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
{return (*env)->NewStringUTF(env, "Hello Castiel");
}
首先大概看一下代码,这是C语言写的,头文件的引入很好理解没有问题,然后是定义原生方法,来看原生方法的命名:
Java_com_example_hellojni_HelloJni_stringFromJNI
Java_:前缀
com_example_hellojni_HelloJni:完整的类路径
stringFromJNI:Java层中定义的方法名
完整的定义方式:
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
我们记得在Java层中,并没有传递参数进来,只是纯粹的调用了这个原生方法,但是这里有两个参数,好了,这里就是很重要的一处关于C和C++在NDK开发中不一样的地方,第一个参数是env,如果使用C开发,这里的env其实是一个二级指针,最终指向JNINativeInterface的结构,有疑惑对吧,来看JNI.h中对这个结构的定义
typedef const struct JNINativeInterface* JNIEnv;
所以结合上面的原生方法定义形式,相当于
const struct JNINativeInterface** env;
顺便补充看一下这个结构体的定义,方法非常多,后面省略了
struct JNINativeInterface {void* reserved0;void* reserved1;void* reserved2;void* reserved3;jint (*GetVersion)(JNIEnv *);jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,jsize);jclass (*FindClass)(JNIEnv*, const char*);jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);jfieldID (*FromReflectedField)(JNIEnv*, jobject);/* spec doesn't show jboolean parameter */jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);jclass (*GetSuperclass)(JNIEnv*, jclass);jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass);......
};
如果使用C++来开发的话,同样,先来看定义
typedef _JNIEnv JNIEnv;
那么这时的env就是一个一级指针了,定义相当于
struct _JNIEnv* env;
在JNI.h中的定义,省略了一点
struct _JNIEnv {/* do not rename this; it does not seem to be entirely opaque */const struct JNINativeInterface* functions;#if defined(__cplusplus)jint GetVersion(){ return functions->GetVersion(this); }jclass DefineClass(const char *name, jobject loader, const jbyte* buf,jsize bufLen){ return functions->DefineClass(this, name, loader, buf, bufLen); }jclass FindClass(const char* name){ return functions->FindClass(this, name); }......
#endif /*__cplusplus*/
};
那么在对比完两种语言开发下的env的差别后,大家对它应该是有一个大概的认识了,同时我们可以注意一下_JNIEnv结构体,里面有一句
const struct JNINativeInterface* functions;
再结合结构体里的代码可以看出来这个结构体里的方法实现也是通过functions指针对JNINativeInterface结构体里的方法进行调用,也就是说无论是C还是C++,最后都调用了JNINativeInterface结构体里的方法,如果不考虑详细调用形式的话,那么大概就是上面这个情况
再来对比一下具体的代码:
return (*env)->NewStringUTF(env, "Hello Castiel"); //C
return env->NewStringUTF("Hello Castiel"); //C++
第一个参数就讲到这里,然后来看第二个参数,在Java中,有实例方法和静态方法,两种都可以在Java层通过添加native关键字来声明
Java层:
public native String stringFromJNI(); //实例方法
public static native String stringFromJNI(); //静态方法
native层:
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz) //实例方法
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jclass clazz) //静态方法
可以看出来实例方法和静态方法的第二个参数不一样,实例方法是jobject类型,而静态方法是jclass类型,是这样的,如果是实例方法,那么必然是通过获取实例进行引用,而静态方法则没有实例,只能通过类引用
回到开头,还记不记得我们说在调用stringFromJNI()的时候,并没有进行参数传递,但是在native里却有两个参数env和thiz这个问题,这个点非常重要,因为在IDA反编译so的时候,并不会识别的非常准确,需要我们去修复,靠的就是这些小Tips
接下来看数据类型,还是在JNI.h里面找的
还是很好理解的,简单看一下就好
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
然后是数组类型,区分了C和C++
#ifdef __cplusplus
/** Reference types, in C++*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;#else /* not __cplusplus *//** Reference types, in C.*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;#endif /* not __cplusplus */
既然讲了JNIEnv,那么不得不提一下JavaVM,因为这个在JNI.h中是和JNIEnv放在一起定义的
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
这两个结构体代码比较短,放在一起来看
/** JNI invocation interface.*/
struct JNIInvokeInterface {void* reserved0;void* reserved1;void* reserved2;jint (*DestroyJavaVM)(JavaVM*);jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);jint (*DetachCurrentThread)(JavaVM*);jint (*GetEnv)(JavaVM*, void**, jint);jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};/** C++ version.*/
struct _JavaVM {const struct JNIInvokeInterface* functions;#if defined(__cplusplus)jint DestroyJavaVM(){ return functions->DestroyJavaVM(this); }jint AttachCurrentThread(JNIEnv** p_env, void* thr_args){ return functions->AttachCurrentThread(this, p_env, thr_args); }jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); }jint GetEnv(void** env, jint version){ return functions->GetEnv(this, env, version); }jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args){ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
那么可以在代码里看到,如果是使用C++开发,一样是通过一个functions指针来实现对结构体JNIInvokeInterface里方法的调用
讲一下so的加载
当我们在加载so的时候,有两种加载方式,一个是直接load,还有一个是loadLibrary,看源码
/*** Loads and links the dynamic library that is identified through the* specified path. This method is similar to {@link #loadLibrary(String)},* but it accepts a full path specification whereas {@code loadLibrary} just* accepts the name of the library to load.** @param pathName* the path of the file to be loaded.*/
public static void load(String pathName) {Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
}
/*** Loads and links the library with the specified name. The mapping of the* specified library name to the full path for loading the library is* implementation-dependent.** @param libName* the name of the library to load.* @throws UnsatisfiedLinkError* if the library could not be loaded.*/
public static void loadLibrary(String libName) {Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
可以看到无论是哪种方式,都会先获取ClassLoader,然后再调用相应的方法,那么明显的这里需要切到Runtime.java
/*** Loads and links the library with the specified name. The mapping of the* specified library name to the full path for loading the library is* implementation-dependent.** @param libName* the name of the library to load.* @throws UnsatisfiedLinkError* if the library can not be loaded.*/
public void loadLibrary(String libName) {loadLibrary(libName, VMStack.getCallingClassLoader());
}/** Searches for a library, then loads and links it without security checks.*/
void loadLibrary(String libraryName, ClassLoader loader) {if (loader != null) {String filename = loader.findLibrary(libraryName);if (filename == null) {throw new UnsatisfiedLinkError("Couldn't load " + libraryName +" from loader " + loader +": findLibrary returned null");}String error = doLoad(filename, loader);if (error != null) {throw new UnsatisfiedLinkError(error);}return;}String filename = System.mapLibraryName(libraryName);List<String> candidates = new ArrayList<String>();String lastError = null;for (String directory : mLibPaths) {String candidate = directory + filename;candidates.add(candidate);if (IoUtils.canOpenReadOnly(candidate)) {String error = doLoad(candidate, loader);if (error == null) {return; // We successfully loaded the library. Job done.}lastError = error;}}if (lastError != null) {throw new UnsatisfiedLinkError(lastError);}throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
这里我们主要来分析一下loadLibrary()方法分支,load()方法的分支就在边上,有兴趣的同学翻一翻源码就可以看到了
当传进来的loader不为空,则会调用findLibrary()方法,然后执行doLoad()方法,如果loader为空,则会执行另一个流程,但是后面也会执行doLoad()方法
不过这里有个地方不是很好理解,关于findLibrary()方法,返回null???
protected String findLibrary(String libName) {return null;
}
其实不是这样的,当运行程序的时候,真正ClassLoade的实现在PathClassLoader.java里,仅仅是做了一个继承而已,那么实现的代码想必是在BaseDexClassLoader.java里了
/** Copyright (C) 2007 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** .0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package dalvik.system;/*** Provides a simple {@link ClassLoader} implementation that operates on a list* of files and directories in the local file system, but does not attempt to* load classes from the network. Android uses this class for its system class* loader and for its application class loader(s).*/
public class PathClassLoader extends BaseDexClassLoader {/*** Creates a {@code PathClassLoader} that operates on a given list of files* and directories. This method is equivalent to calling* {@link #PathClassLoader(String, String, ClassLoader)} with a* {@code null} value for the second argument (see description there).** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param parent the parent class loader*/public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ul>** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param libraryPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);}
}
findLibrary()方法在BaseDexClassLoader.java里的实现如下
@Override
public String findLibrary(String name) {return pathList.findLibrary(name);
}
继续doLoad方法的代码,
private String doLoad(String name, ClassLoader loader) {// Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,// which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.// The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load// libraries with no dependencies just fine, but an app that has multiple libraries that// depend on each other needed to load them in most-dependent-first order.// We added API to Android's dynamic linker so we can update the library path used for// the currently-running process. We pull the desired path out of the ClassLoader here// and pass it to nativeLoad so that it can call the private dynamic linker API.// We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the// beginning because multiple apks can run in the same process and third party code can// use its own BaseDexClassLoader.// We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any// dlopen(3) calls made from a .so's JNI_OnLoad to work too.// So, find out what the native library search path is for the ClassLoader in question...String ldLibraryPath = null;if (loader != null && loader instanceof BaseDexClassLoader) {ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();}// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized// internal natives.synchronized (this) {return nativeLoad(name, loader, ldLibraryPath);}
}
ldLibraryPath获取这部分不是很重要,来看下面的nativeLoad()方法,这个方法的定义如下
// TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);
它是一个native方法,方法实现在java_lang_Runtime.cpp中
/** static String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath)** Load the specified full path as a dynamic library filled with* JNI-compatible methods. Returns null on success, or a failure* message on failure.*/
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,JValue* pResult)
{StringObject* fileNameObj = (StringObject*) args[0];Object* classLoader = (Object*) args[1];StringObject* ldLibraryPathObj = (StringObject*) args[2];assert(fileNameObj != NULL);char* fileName = dvmCreateCstrFromString(fileNameObj);if (ldLibraryPathObj != NULL) {char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");if (sym != NULL) {typedef void (*Fn)(const char*);Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);(*android_update_LD_LIBRARY_PATH)(ldLibraryPath);} else {ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");}free(ldLibraryPath);}StringObject* result = NULL;char* reason = NULL;bool success = dvmLoadNativeCode(fileName, classLoader, &reason);if (!success) {const char* msg = (reason != NULL) ? reason : "unknown failure";result = dvmCreateStringFromCstr(msg);dvmReleaseTrackedAlloc((Object*) result, NULL);}free(reason);free(fileName);RETURN_PTR(result);
}
先获取一下传进来的参数,然后将Java的字符串转换为native层的字符串,接着ldLibraryPath和ldLibraryPathObj这个if代码块可以略过,对我们这部分的知识并不是很重要,如果有同学手里的Android源码是4.2或者更早的,可能和我这里不一样,你可能没有第三个参数,也就是没有这个if代码块
然后这一句比较关键
bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
它的实现在Native.cpp
bool dvmLoadNativeCode(const char* pathName, Object* classLoader, char** detail)
{SharedLib* pEntry;void* handle;bool verbose;/* reduce noise by not chattering about system libraries */verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);if (verbose)ALOGD("Trying to load lib %s %p", pathName, classLoader);*detail = NULL;pEntry = findSharedLibEntry(pathName);if (pEntry != NULL) {if (pEntry->classLoader != classLoader) {ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p", pathName, pEntry->classLoader, classLoader);return false;}if (verbose) {ALOGD("Shared lib '%s' already loaded in same CL %p", pathName, classLoader);}if (!checkOnLoadResult(pEntry))return false;return true;}Thread* self = dvmThreadSelf();ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);handle = dlopen(pathName, RTLD_LAZY);dvmChangeStatus(self, oldStatus);if (handle == NULL) {*detail = strdup(dlerror());ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);return false;}/* create a new entry */SharedLib* pNewEntry;pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));pNewEntry->pathName = strdup(pathName);pNewEntry->handle = handle;pNewEntry->classLoader = classLoader;dvmInitMutex(&pNewEntry->onLoadLock);pthread_cond_init(&pNewEntry->onLoadCond, NULL);pNewEntry->onLoadThreadId = self->threadId;/* try to add it to the list */SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);if (pNewEntry != pActualEntry) {ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)", pathName, classLoader);freeSharedLibEntry(pNewEntry);return checkOnLoadResult(pActualEntry);} else {if (verbose)ALOGD("Added shared lib %s %p", pathName, classLoader);bool result = false;void* vonLoad;int version;vonLoad = dlsym(handle, "JNI_OnLoad");if (vonLoad == NULL) {ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);result = true;} else {OnLoadFunc func = (OnLoadFunc)vonLoad;Object* prevOverride = self->classLoaderOverride;self->classLoaderOverride = classLoader;oldStatus = dvmChangeStatus(self, THREAD_NATIVE);if (gDvm.verboseJni) {ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);}version = (*func)(gDvmJni.jniVm, NULL);dvmChangeStatus(self, oldStatus);self->classLoaderOverride = prevOverride;if (version == JNI_ERR) {*detail = strdup(StringPrintf("JNI_ERR returned from JNI_OnLoad in \"%s\"", pathName).c_str());} else if (dvmIsBadJniVersion(version)) {*detail = strdup(StringPrintf("Bad JNI version returned from JNI_OnLoad in \"%s\": %d",pathName, version).c_str());} else {result = true;}if (gDvm.verboseJni) {ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]", (result ? "successfully" : "failure"), pathName);}}if (result)pNewEntry->onLoadResult = kOnLoadOkay;elsepNewEntry->onLoadResult = kOnLoadFailed;pNewEntry->onLoadThreadId = 0;dvmLockMutex(&pNewEntry->onLoadLock);pthread_cond_broadcast(&pNewEntry-
本文标签: 闲聊阿里加固(一)
版权声明:本文标题:闲聊阿里加固(一) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1732357432h1534838.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论