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来手动修正一下
[code]File -> Load file -> Parse C header file[/code]
导入成功后会出现"Compilation successful"的MessageBox,点击OK就行
然后切换到Structures界面,如果没有的话可以使用快捷键"Shift+F9"
菜单栏也可以打开
[code]View -> Open subviews -> Structures[/code]
打开后我们按insert键,添加结构体,点击箭头那个按钮
分两次添加下面两个结构体
添加完后回到刚才F5反编译过后的窗口,放着先
我们来学习一下NDK开发中的一些概念知识,虽然大家搞的都是脱壳,但是不一定每个同学都搞过NDK开发,所以我们来补一补这部分的知识,如果已经很清楚的同学就当复习吧,这部分的知识相当重要,Very Important
JNI:Java Native Interface,类似一种标准,提供了很多的API,使Java可以和C/C++进行通信
NDK:Native Development Kit,这是一套工具或者说是一套组件,实现用C/C++来开发Android Application
这是一个简单的NDKDemo,也许这个Demo你觉得眼熟但是又好像不一样,没错就是你想到的那个,我稍微改了一下代码
[code]public class HelloJni extends Activity{
@Override
public 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");
}
}[/code]
我们来实现一下native层的代码,在NDK开发中,有C和C++两种写法,显然它们在开发中是有差别的,那么结合这里的例子来看一下差别在哪里
[code]#include <string.h>
include <jni.h>
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv env, jobject thiz)
{
return (env)->NewStringUTF(env, "Hello Castiel");
}[/code]
首先大概看一下代码,这是C语言写的,头文件的引入很好理解没有问题,然后是定义原生方法,来看原生方法的命名:
[code]Java_com_example_hellojni_HelloJnistringFromJNI[/code]
Java:前缀com_example_hellojni_HelloJni:完整的类路径stringFromJNI:Java层中定义的方法名
完整的定义方式:
[code]jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)[/code]
我们记得在Java层中,并没有传递参数进来,只是纯粹的调用了这个原生方法,但是这里有两个参数,好了,这里就是很重要的一处关于C和C++在NDK开发中不一样的地方,第一个参数是env,如果使用C开发,这里的env其实是一个二级指针,最终指向JNINativeInterface的结构,有疑惑对吧,来看JNI.h中对这个结构的定义
[code]typedef const struct JNINativeInterface* JNIEnv;[/code]
所以结合上面的原生方法定义形式,相当于
[code]const struct JNINativeInterface** env;[/code]
顺便补充看一下这个结构体的定义,方法非常多,后面省略了
[code]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);
......
};[/code]
如果使用C++来开发的话,同样,先来看定义
[code]typedef _JNIEnv JNIEnv;[/code]
那么这时的env就是一个一级指针了,定义相当于
[code]struct _JNIEnv* env;[/code]
在JNI.h中的定义,省略了一点
[code]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/
};[/code]
那么在对比完两种语言开发下的env的差别后,大家对它应该是有一个大概的认识了,同时我们可以注意一下_JNIEnv结构体,里面有一句
[code]const struct JNINativeInterface* functions;[/code]
再结合结构体里的代码可以看出来这个结构体里的方法实现也是通过functions指针对JNINativeInterface结构体里的方法进行调用,也就是说无论是C还是C++,最后都调用了JNINativeInterface结构体里的方法,如果不考虑详细调用形式的话,那么大概就是上面这个情况
再来对比一下具体的代码:
[code]return (*env)->NewStringUTF(env, "Hello Castiel"); //C
return env->NewStringUTF("Hello Castiel"); //C++[/code]
第一个参数就讲到这里,然后来看第二个参数,在Java中,有实例方法和静态方法,两种都可以在Java层通过添加native关键字来声明
Java层:
[code]public native String stringFromJNI(); //实例方法
public static native String stringFromJNI(); //静态方法[/code]
native层:
[code]jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv env, jobject thiz) //实例方法
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv env, jclass clazz) //静态方法[/code]
可以看出来实例方法和静态方法的第二个参数不一样,实例方法是jobject类型,而静态方法是jclass类型,是这样的,如果是实例方法,那么必然是通过获取实例进行引用,而静态方法则没有实例,只能通过类引用
回到开头,还记不记得我们说在调用stringFromJNI()的时候,并没有进行参数传递,但是在native里却有两个参数env和thiz这个问题,这个点非常重要,因为在IDA反编译so的时候,并不会识别的非常准确,需要我们去修复,靠的就是这些小Tips
接下来看数据类型,还是在JNI.h里面找的
还是很好理解的,简单看一下就好
[code]#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[/code]
然后是数组类型,区分了C和C++
[code]#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 /[/code]
既然讲了JNIEnv,那么不得不提一下JavaVM,因为这个在JNI.h中是和JNIEnv放在一起定义的
[code]#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
else
typedef const struct JNINativeInterface JNIEnv;
typedef const struct JNIInvokeInterface JavaVM;
endif[/code]
这两个结构体代码比较短,放在一起来看
[code]/*
- 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/
};[/code]
那么可以在代码里看到,如果是使用C++开发,一样是通过一个functions指针来实现对结构体JNIInvokeInterface里方法的调用
讲一下so的加载
当我们在加载so的时候,有两种加载方式,一个是直接load,还有一个是loadLibrary,看源码
[code]/**
- 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());
}[/code]
可以看到无论是哪种方式,都会先获取ClassLoader,然后再调用相应的方法,那么明显的这里需要切到Runtime.java
[code]/**
- 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);
}[/code]
这里我们主要来分析一下loadLibrary()方法分支,load()方法的分支就在边上,有兴趣的同学翻一翻源码就可以看到了
当传进来的loader不为空,则会调用findLibrary()方法,然后执行doLoad()方法,如果loader为空,则会执行另一个流程,但是后面也会执行doLoad()方法
不过这里有个地方不是很好理解,关于findLibrary()方法,返回null???
[code]protected String findLibrary(String libName) {
return null;
}[/code]
其实不是这样的,当运行程序的时候,真正ClassLoade的实现在PathClassLoader.java里,仅仅是做了一个继承而已,那么实现的代码想必是在BaseDexClassLoader.java里了
[code]/*
- 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
http://www.apache.org/licenses/LICENSE-2.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);
}
}[/code]
findLibrary()方法在BaseDexClassLoader.java里的实现如下
[code]@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}[/code]
继续doLoad方法的代码,
[code]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);
}
}[/code]
ldLibraryPath获取这部分不是很重要,来看下面的nativeLoad()方法,这个方法的定义如下
[code]// TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);[/code]
它是一个native方法,方法实现在java_lang_Runtime.cpp中
[code]/*
- 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);
}[/code]
先获取一下传进来的参数,然后将Java的字符串转换为native层的字符串,接着ldLibraryPath和ldLibraryPathObj这个if代码块可以略过,对我们这部分的知识并不是很重要,如果有同学手里的Android源码是4.2或者更早的,可能和我这里不一样,你可能没有第三个参数,也就是没有这个if代码块
然后这一句比较关键
[code]bool success = dvmLoadNativeCode(fileName, classLoader, &reason);[/code]
它的实现在Native.cpp
[code]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;
else
pNewEntry->onLoadResult = kOnLoadFailed;
pNewEntry->onLoadThreadId = 0;
dvmLockMutex(&pNewEntry->onLoadLock);
pthread_cond_broadcast(&pNewEntry->onLoadCond);
dvmUnlockMutex(&pNewEntry->onLoadLock);
return result;
}
}[/code]
看着有点复杂,详细的来解释一下,一段一段来看
先通过findSharedLibEntry()方法查找内存中所要加载的so的信息,如果曾经加载过,就返回一个指针,指向这个so在内存的信息,指针保存为pEntry,如果这个指针不为空,表示确实是加载过,那么就会判断当前传进来的classloader和在内存中保存的so的classloader是不是一样:如果不一样,则返回失败;如果一样,则返回已加载,然后还有一个小判断checkOnLoadResult()方法,不多讲了
[code]/*
- See if we've already loaded it. If we have, and the class loader
- matches, return successfully without doing anything.
*/
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;
}[/code]
上面是在内存中存在所要加载的so的情况,在我们现在讨论的情况下,它是没有被加载过的,也就是下面的分支才是我们要重点关注的
使用dlopen()打开一个库,这个方法有两个参数,一个是pathName,还有一个是mode,这里的mode是RTLD_LAZY,还有好几种其它的mode,比如RTLD_NOW,主要是用于要不要立刻处理该库里的符号,然后返回一个句柄handle,如果handle为空则返回失败
[code]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;
}[/code]
如果正常获取到了handle,就创建一个新的pNewEntry来描述改so的信息,这和我们最开始那个判断内存中是否已加载目标so的pEntry是一个意思
[code]/ 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;[/code]
使用addSharedLibEntry()方法添加该pNewEntry的信息,返回一个pActualEntry
[code]/ try to add it to the list /
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);[/code]
如果pNewEntry和pActualEntry不一样,什么意思呢?
因为是这样的,当执行addSharedLibEntry()方法的时候,如果还有一个线程B同时在加载该so,并且B线程先执行到了这里,那么就说明该so的信息已经添加过了,我们就不需要再执行添加pNewEntry的操作
[code]if (pNewEntry != pActualEntry) {
ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)", pathName, classLoader);
freeSharedLibEntry(pNewEntry);
return checkOnLoadResult(pActualEntry);
}[/code]
如果成功添加pNewEntry的信息,则执行下面的代码
[code]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 {
/
Call JNI_OnLoad. We have to override the current class
loader, which will always be "null" since the stuff at the
top of the stack is around Runtime.loadLibrary(). (See
the comments in the JNI FindClass function.)
/
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());
/
It's unwise to call dlclose() here, but we can mark it
as bad and ensure that future load attempts will fail.
We don't know how far JNI_OnLoad got, so there could
be some partially-initialized stuff accessible through
newly-registered native method calls. We could try to
unregister them, but that doesn't seem worthwhile.
*/
} else {
result = true;
}
if (gDvm.verboseJni) {
ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]",
(result ? "successfully" : "failure"), pathName);
}
}
if (result)
pNewEntry->onLoadResult = kOnLoadOkay;
else
pNewEntry->onLoadResult = kOnLoadFailed;
pNewEntry->onLoadThreadId = 0;
/
Broadcast a wakeup to anybody sleeping on the condition variable.
*/
dvmLockMutex(&pNewEntry->onLoadLock);
pthread_cond_broadcast(&pNewEntry->onLoadCond);
dvmUnlockMutex(&pNewEntry->onLoadLock);
return result;
}[/code]
刚刚我们使用dlopen()方法打开so,然后返回了一个handle句柄,这个句柄在接下来的作用就是定位JNI_OnLoad()方法,还记得最开始我们用IDA载入libmobisec.so时搜了JNI_OnLoad()方法吗?
如果这个地址返回值为空说明没有JNI_OnLoad()方法
[code]vonLoad = dlsym(handle, "JNI_OnLoad");
if (vonLoad == NULL) {
ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);
result = true;
}[/code]
如果JNI_OnLoad()方法的地址获取正常,就将它保存在func中
[code]OnLoadFunc func = (OnLoadFunc)vonLoad;[/code]
然后就是执行JNI_OnLoad()方法了,返回值赋值给version
[code]version = (*func)(gDvmJni.jniVm, NULL);[/code]
那么为什么要单独挑出JNI_OnLoad()方法来执行呢?它有什么特殊吗?它的作用是什么?
这里就要讲讲在NDK开发中静态注册和动态注册了
静态注册就像我们最开始讲的那个NDKDemo,先在Java层执行loadLibrary()方法,然后声明一下native,然后在native层用完整类路径等一系列的标志组成一个方法名,直接在Java层进行调用即可
动态注册同样需要先在Java层执行loadLibrary()方法,并且声明native,简单的Demo如下
[code]JNIEXPORT jstring JNICALL native_hello(JNIEnv env, jclass clazz)
{
printf("hello in c native code./n");
return (env)->NewStringUTF(env, "hello world returned.");
}
define JNIREG_CLASS "com/jni/JavaHello"
/**
- Table of methods associated with a single class.
/
static JNINativeMethod gMethods[] = {
{ "hello", "()Ljava/lang/String;", (void)native_hello },
};
/*
- Register several native methods for one class.
/
static int registerNativeMethods(JNIEnv env, const char className,
JNINativeMethod gMethods, int numMethods)
{
jclass clazz;
clazz = (env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
- Register native methods for all classes we know about.
/
static int registerNatives(JNIEnv env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
/*
- Set some test stuff up.
* - Returns the JNI version on success, -1 on failure.
/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM vm, void reserved)
{
JNIEnv env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {
return -1;
}
/ success -- return valid version number /
result = JNI_VERSION_1_4;
return result;
}[/code]
JNI_OnLoad()方法有两个参数,一个是JavaVM,另一个是保留参数,可为空,这个vm就是程序当前使用的Dalvik虚拟机实例,vm是进程级别,而env属于线程级别,虽然不是很准确,但是确实是这个意思
获取env
[code]if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}[/code]
注册native方法
[code]if (!registerNatives(env)) {
return -1;
}[/code]
registerNatives()方法的实现,调用了另一个方法registerNativeMethods()来实现注册
[code]/*
- Register native methods for all classes we know about.
/
static int registerNatives(JNIEnv env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}[/code]
registerNativeMethods()方法有四个参数,第一个是env,第二个是要注册的类,第三个是要注册的方法表,第四个是方法数量
[code]/*
- Register several native methods for one class.
/
static int registerNativeMethods(JNIEnv env, const char className,
JNINativeMethod gMethods, int numMethods)
{
jclass clazz;
clazz = (env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}[/code]
要注册的类
[code]#define JNIREG_CLASS "com/jni/JavaHello"[/code]
要注册的方法表
[code]/**
- Table of methods associated with a single class.
/
static JNINativeMethod gMethods[] = {
{ "hello", "()Ljava/lang/String;", (void)native_hello },
};[/code]
注册完后,当我们调用Java层的hello()的时候,就会调用native层的native_hello()方法
[code]JNIEXPORT jstring JNICALL native_hello(JNIEnv env, jclass clazz)
{
printf("hello in c native code./n");
return (env)->NewStringUTF(env, "hello world returned.");
}[/code]
现在明白为什么要先搜一下JNI_OnLoad()方法了吗?
.init section和.init_array section下次再讲
那么DNK开发的知识就讲到这里,我们回到刚才放在一边的IDA
现在看JNI_OnLoad()方法是不是很熟悉或者说很有感觉了呢?
刚刚我们已经知道JNI_OnLoad()方法第一个参数是JavaVM*类型,这里没有识别正确,我们来修正一下参数类型,在第一个参数的int上面右击,点击Set lvar type,下次直接用Y快捷键
输入JavaVM*
然后重命名一下参数a1为vm,重命名可以右键,也可以快捷键N
可以看到GetEnv函数已经识别出来了
[code]signed int __fastcall JNI_OnLoad(JavaVM vm, int a2)
{
const char v2; // r1@2
const char *v3; // r2@2
signed int result; // r0@5
bool v5; // zf@6
int v6; // [sp+4h] [bp-Ch]@1
v6 = a2;
if ( ((int (cdecl )(JavaVM , int ))(vm)->GetEnv)(vm, &v6) )
{
v2 = "debug";
v3 = "Failed to get the environment";
LABEL_5:
_android_log_print(6, v2, v3);
return -1;
}
if ( !(*(int (cdecl )(int))((_DWORD )v6 + 24))(v6) )
{
v2 = "debug";
v3 = "failed to get class reference";
goto LABEL_5;
}
v5 = (*(int (__cdecl )(int))((_DWORD )v6 + 860))(v6) == 0;
result = 65542;
if ( !v5 )
result = -1;
return result;
}[/code]
在GetEnv()方法上面右击,点击Force call type
那么现在就比较清楚了,GetEnv()方法一共三个参数,第一个是vm,第二个是env,第三个是JNIVERSION*,第三个参数不必在意
[code]JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}[/code]
IDA中反编译出来的代码
[code]if ( (*vm)->GetEnv(vm, (void **)&v6, 65542) )
{
v2 = "debug";
v3 = "Failed to get the environment";
LABEL_5:
_android_log_print(6, v2, v3);
return -1;
}[/code]
知道了第二个参数是env,重命名一下,顺便修改类型为JNIEnv*
这一下看起来好看多了,该有的函数都已经识别出来
[code]signed int __fastcall JNI_OnLoad(JavaVM vm, int a2)
{
const char v2; // r1@2
const char v3; // r2@2
signed int result; // r0@5
bool v5; // zf@6
JNIEnv env; // [sp+4h] [bp-Ch]@1
env = (JNIEnv )a2;
if ( (vm)->GetEnv(vm, (void *)&env, 65542) )
{
v2 = "debug";
v3 = "Failed to get the environment";
LABEL_5:
_android_log_print(6, v2, v3);
return -1;
}
if ( !((int (__cdecl )(JNIEnv ))(env)->FindClass)(env) )
{
v2 = "debug";
v3 = "failed to get class reference";
goto LABEL_5;
}
v5 = ((int (__cdecl )(JNIEnv ))(*env)->RegisterNatives)(env) == 0;
result = 65542;
if ( !v5 )
result = -1;
return result;
}[/code]