折騰了一段時間的Android加固,看了不少大牛的文章,收穫仍是蠻大的,不過好像大部分的文章都是直接寫在哪裏下斷點如何修復之類的,寫出如何找到這個脫殼點的文章仍是比較少,因此我準備整理一下這部分的知識,講講如何找到脫殼點,但願能和你們多交流javascript
用過JEB,IDA Pro,若是有跟着其它表哥本身脫過殼的那就更好了:),另外,既然都開始玩加固了,那麼解壓apk後的工程目錄,smali語法等這種基礎的東西就再也不提了php
由於我手上的加固樣本有限,不是每一個版本的加固樣本都有,因此綜合考慮了一下,選擇阿里的樣本,能比較容易造成一種按部就班學習的感受,樣本所有來自歷年阿里CTF和阿里移動安全挑戰賽java
最適合跟着其它表哥文章脫過殼,殊不知道爲何要那樣脫殼的同窗,由於接下來這幾篇文章講的就是如何經過一步步的分析,找到脫殼點linux
這個樣本是阿里14年出的,名字是jscrack.apk,咱們來載入JEB瞭解大概信息android
首先咱們來看箭頭指向的地方:nginx
1.fak.jar:從名字來看,這是一個jar文件,可是JEB識別出來是一個dex,這個信息提供的很關鍵,咱們能夠猜測,阿里加固的方法會不會將源dex文件隱藏在這個fak.jar裏面?算法
2.StupApplication:能夠看到入口變成了StupApplication,有過Android開發經驗的同窗們都知道,通常狀況下,咱們在開發APP的時候,若是有全局變量,數據初始化之類的操做,會寫一個StartApplication類繼承Application類,那麼顯然這裏是阿里加固本身添加的一個入口,用來執行一些初始化的操做,好比解密dex,反調試,檢測模擬器等等之類的,固然這只是咱們的猜想,不必定正確docker
3.mobisec.so:加載了一個so文件,這個so文件就是咱們的切入點shell
而後來看兩個紅色框框,兩個native方法:attachBaseContext()和onCreate(),通常狀況下,入口應該是onCreate(),可是attachBaseContext()更早於onCreate()執行express
剛剛咱們經過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{ @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"); } }
咱們來實現一下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 * * 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); } }
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; else pNewEntry->onLoadResult = kOnLoadFailed; pNewEntry->onLoadThreadId = 0; dvmLockMutex(&pNewEntry->onLoadLock); pthread_cond_broadcast(&pNewEntry->onLoadCond); dvmUnlockMutex(&pNewEntry->onLoadLock); return result; } }
看着有點複雜,詳細的來解釋一下,一段一段來看
先經過findSharedLibEntry()方法查找內存中所要加載的so的信息,若是曾經加載過,就返回一個指針,指向這個so在內存的信息,指針保存爲pEntry,若是這個指針不爲空,表示確實是加載過,那麼就會判斷當前傳進來的classloader和在內存中保存的so的classloader是否是同樣:若是不同,則返回失敗;若是同樣,則返回已加載,而後還有一個小判斷checkOnLoadResult()方法,很少講了
/*
* 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; }
上面是在內存中存在所要加載的so的狀況,在咱們如今討論的狀況下,它是沒有被加載過的,也就是下面的分支纔是咱們要重點關注的
使用dlopen()打開一個庫,這個方法有兩個參數,一個是pathName,還有一個是mode,這裏的mode是RTLD_LAZY,還有好幾種其它的mode,好比RTLD_NOW,主要是用於要不要馬上處理該庫裏的符號,而後返回一個句柄handle,若是handle爲空則返回失敗
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; }
若是正常獲取到了handle,就建立一個新的pNewEntry來描述改so的信息,這和咱們最開始那個判斷內存中是否已加載目標so的pEntry是一個意思
/* 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;
使用addSharedLibEntry()方法添加該pNewEntry的信息,返回一個pActualEntry
/* try to add it to the list */ SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
若是pNewEntry和pActualEntry不同,什麼意思呢?
由於是這樣的,當執行addSharedLibEntry()方法的時候,若是還有一個線程B同時在加載該so,而且B線程先執行到了這裏,那麼就說明該so的信息已經添加過了,咱們就不須要再執行添加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); }
若是成功添加pNewEntry的信息,則執行下面的代碼
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; }
剛剛咱們使用dlopen()方法打開so,而後返回了一個handle句柄,這個句柄在接下來的做用就是定位JNI_OnLoad()方法,還記得最開始咱們用IDA載入libmobisec.so時搜了JNI_OnLoad()方法嗎?
若是這個地址返回值爲空說明沒有JNI_OnLoad()方法
vonLoad = dlsym(handle, "JNI_OnLoad"); if (vonLoad == NULL) { ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader); result = true; }
若是JNI_OnLoad()方法的地址獲取正常,就將它保存在func中
OnLoadFunc func = (OnLoadFunc)vonLoad;
而後就是執行JNI_OnLoad()方法了,返回值賦值給version
version = (*func)(gDvmJni.jniVm, NULL);
那麼爲何要單獨挑出JNI_OnLoad()方法來執行呢?它有什麼特殊嗎?它的做用是什麼?
這裏就要講講在NDK開發中靜態註冊和動態註冊了
靜態註冊就像咱們最開始講的那個NDKDemo,先在Java層執行loadLibrary()方法,而後聲明一下native,而後在native層用完整類路徑等一系列的標誌組成一個方法名,直接在Java層進行調用便可
動態註冊一樣須要先在Java層執行loadLibrary()方法,而且聲明native,簡單的Demo以下
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; }
JNI_OnLoad()方法有兩個參數,一個是JavaVM,另外一個是保留參數,可爲空,這個vm就是程序當前使用的Dalvik虛擬機實例,vm是進程級別,而env屬於線程級別,雖然不是很準確,可是確實是這個意思
獲取env
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; }
註冊native方法
if (!registerNatives(env)) { return -1; }
registerNatives()方法的實現,調用了另外一個方法registerNativeMethods()來實現註冊
/* * 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; }
registerNativeMethods()方法有四個參數,第一個是env,第二個是要註冊的類,第三個是要註冊的方法表,第四個是方法數量
/* * 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; }
要註冊的類
#define JNIREG_CLASS "com/jni/JavaHello"
要註冊的方法表
/** * Table of methods associated with a single class. */ static JNINativeMethod gMethods[] = { { "hello", "()Ljava/lang/String;", (void*)native_hello }, };
註冊完後,當咱們調用Java層的hello()的時候,就會調用native層的native_hello()方法
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz) { printf("hello in c native code./n"); return (*env)->NewStringUTF(env, "hello world returned."); }
如今明白爲何要先搜一下JNI_OnLoad()方法了嗎?
.init section和.init_array section下次再講
那麼DNK開發的知識就講到這裏,咱們回到剛纔放在一邊的IDA
如今看JNI_OnLoad()方法是否是很熟悉或者說頗有感受了呢?
剛剛咱們已經知道JNI_OnLoad()方法第一個參數是JavaVM*類型,這裏沒有識別正確,咱們來修正一下參數類型,在第一個參數的int上面右擊,點擊Set lvar type,下次直接用Y快捷鍵
輸入JavaVM*
而後重命名一下參數a1爲vm,重命名能夠右鍵,也能夠快捷鍵N
能夠看到GetEnv函數已經識別出來了
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; }
在GetEnv()方法上面右擊,點擊Force call type
那麼如今就比較清楚了,GetEnv()方法一共三個參數,第一個是vm,第二個是env,第三個是JNIVERSION*,第三個參數沒必要在乎
JNIEnv* env = NULL;
jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; }
IDA中反編譯出來的代碼
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; }
知道了第二個參數是env,重命名一下,順便修改類型爲JNIEnv*
這一下看起來好看多了,該有的函數都已經識別出來
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; }
下面兩個(*env)->的函數也須要Force call type,就是下面這個樣子,首先執行FindClass()方法,而後執行RegisterNatives()動態註冊native方法,能夠看到最後那個參數是2,表明什麼還記得嗎?它表示要註冊的native方法數量
signed int __fastcall JNI_OnLoad(JavaVM *vm, int a2) { const char *v2; // r1@2 const char *v3; // r2@2 jclass v4; // r1@3 signed int result; // r0@5 bool v6; // 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; } v4 = (*env)->FindClass(env, "com/ali/mobisecenhance/StubApplication"); if ( !v4 ) { v2 = "debug"; v3 = "failed to get class reference"; goto LABEL_5; } v6 = (*env)->RegisterNatives(env, v4, (const JNINativeMethod *)off_54010, 2) == 0; result = 65542; if ( !v6 ) result = -1; return result; }
第三個參數是gMethods,咱們修改一下變量名
來看它的結構體定義
typedef struct { constchar*name; constchar* signature; void* fnPtr; } JNINativeMethod;
而後跳過去看看數據,從結構體定義能夠看出來,它是三個數據爲一組
第一個是Java層的方法名,第二個是簽名,第三個就是一個指針了,也就是native方法的地址
.data:00054010 gMethods DCD aAttachbasecont ; DATA XREF: JNI_OnLoad+44 .data:00054010 ; .text:off_24784 .data:00054010 ; "attachBaseContext" .data:00054014 DCD aLandroidCont_1 ; "(Landroid/content/Context;)V" .data:00054018 DCD sub_24D3C+1 .data:0005401C DCD aOncreate ; "onCreate" .data:00054020 DCD aV ; "()V" .data:00054024 DCD sub_24498+1
那麼能夠看出來,一共註冊了兩個方法,一個是attachBaseContext()方法,另外一個是onCreate()方法,這兩個方法恰好就對應咱們在JEB裏看到的那兩個native方法,對着Java層的名字修改一下方法名
而後跟入attachBaseContext()方法來分析一下代碼
int __fastcall attachBaseContext(ali *a1, int a2, int a3) { int v3; // r8@1 int v4; // r10@1 ali *v5; // r4@1 _JNIEnv *v6; // r1@1 int result; // r0@1 ali *v8; // r0@2 int v9; // r0@2 int v10; // r0@2 int v11; // r3@2 int v12; // r0@2 int v13; // r5@2 int v14; // r0@2 int v15; // r0@2 int v16; // r3@2 int v17; // r0@2 int v18; // r0@4 int v19; // r0@5 char *v20; // r0@9 int v21; // r0@6 int v22; // r3@13 int v23; // r0@13 int v24; // r3@15 int v25; // r0@15 int v26; // r3@15 int v27; // r8@15 const char *v28; // r0@16 const char *v29; // r5@16 size_t v30; // r0@16 int v31; // r5@17 int v32; // r8@17 int v33; // r0@17 int v34; // r0@17 int v35; // r5@17 const char *v36; // r1@18 const char *v37; // r2@18 int v38; // r0@19 ali *v39; // r0@20 int v40; // r4@22 unsigned __int64 v41; // r2@22 int v42; // [sp+8h] [bp-78h]@2 __int64 v43; // [sp+18h] [bp-68h]@17 char v44; // [sp+24h] [bp-5Ch]@6 char v45; // [sp+3Ch] [bp-44h]@2 char *v46; // [sp+4Ch] [bp-34h]@3 char *v47; // [sp+50h] [bp-30h]@3 int v48; // [sp+54h] [bp-2Ch]@1 v3 = a2; v4 = a3; v5 = a1; v48 = _stack_chk_guard; ((void (__fastcall *)(signed int, const char *, const char *))_android_log_print)(6, "debug", "in..."); result = ali::init_classes(v5, v6); if ( !result ) { v8 = (ali *)_JNIEnv::CallNonvirtualVoidMethod(v5, v3, ali::ContextWrapper, unk_54128); v42 = ali::NanoTime(v8); v9 = _JNIEnv::GetObjectClass(v5, v3); v10 = _JNIEnv::GetMethodID(v5, v9, "getFilesDir", "()Ljava/io/File;"); v12 = _JNIEnv::CallObjectMethod(v5, v3, v10, v11); v13 = v12; v14 = _JNIEnv::GetObjectClass(v5, v12); v15 = _JNIEnv::GetMethodID(v5, v14, "getAbsolutePath", "()Ljava/lang/String;"); v17 = _JNIEnv::CallObjectMethod(v5, v13, v15, v16); sub_247D8(&v45, v5, v17); if ( (_UNKNOWN *)&v45 != &ali::g_filePath ) std::string::_M_assign((std::string *)&ali::g_filePath, v47, v46); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v45); _android_log_print(3, "debug", "global files path is %s"); v18 = _JNIEnv::CallObjectMethod(v5, v3, unk_541A4, 0xFFFFFC78); if ( ali::sdk_int <= 8 ) { v21 = _JNIEnv::GetObjectField(v5, v18, unk_5416C); sub_247D8(&v44, v5, v21); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v45, &v44, "/lib"); if ( (_UNKNOWN *)&v45 != &ali::g_libPath ) std::string::_M_assign((std::string *)&ali::g_libPath, v47, v46); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v45); v20 = &v44; } else { v19 = _JNIEnv::GetObjectField(v5, v18, unk_54170); sub_247D8(&v45, v5, v19); if ( (_UNKNOWN *)&v45 != &ali::g_libPath ) std::string::_M_assign((std::string *)&ali::g_libPath, v47, v46); v20 = &v45; } std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v20); _android_log_print(3, "debug", "global native path is %s", unk_540D0, v4); v23 = _JNIEnv::CallObjectMethod(v5, v3, unk_541B0, v22); sub_247D8(&v45, v5, v23); if ( (_UNKNOWN *)&v45 != &ali::g_apkPath ) std::string::_M_assign((std::string *)&ali::g_apkPath, v47, v46); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v45); setenv("APKPATH", dword_540B8, 1); _android_log_print(3, "debug", "global apk path is %s", dword_540B8); sub_24A64(v5, v3); v25 = _JNIEnv::CallObjectMethod(v5, v4, unk_541A0, v24); v27 = v25; if ( v25 ) { v28 = (const char *)(*(int (__fastcall **)(ali *, int, _DWORD))(*(_DWORD *)v5 + 676))(v5, v25, 0); v29 = v28; v30 = strlen(v28); std::string::_M_assign((std::string *)&ali::g_pkgName, v29, &v29[v30]); (*(void (__fastcall **)(ali *, int, const char *))(*(_DWORD *)v5 + 680))(v5, v27, v29); } v43 = 0LL; v31 = _JNIEnv::CallObjectMethod(v5, v4, unk_541A8, v26); parse_dex((_JNIEnv *)v5, &v43); replace_classloader_cookie(v5, v31, v43, HIDWORD(v43)); _android_log_print(3, "debug", "enter new application"); v32 = unk_54120; v33 = _JNIEnv::NewStringUTF((_JNIEnv *)v5, "android.app.Application"); v34 = _JNIEnv::CallObjectMethod(v5, v31, v32, v33); v35 = v34; if ( v34 ) { v38 = _JNIEnv::GetMethodID(v5, v34, "<init>", "()V"); dword_540A0 = _JNIEnv::NewObject(v5, v35, v38); _JNIEnv::CallVoidMethod(v5, dword_540A0, unk_54134); _JNIEnv::DeleteLocalRef(v5, v35); v36 = "debug"; v37 = "exit new application"; } else { v36 = "debug"; v37 = "can't findClass realAppClass"; } v39 = (ali *)_android_log_print(3, v36, v37); if ( dword_540A0 ) { v39 = (ali *)(*(int (__fastcall **)(ali *))(*(_DWORD *)v5 + 84))(v5); dword_540A0 = (int)v39; } v40 = ali::NanoTime(v39); _android_log_print(3, "debug", "##### attachBaseContext spent:"); ali::PrettyDuration((ali *)(v40 - v42), v41); result = _android_log_print(3, "debug", "exit attachBaseContext"); } if ( v48 != _stack_chk_guard ) _stack_chk_fail(result); return result; }
結合前面咱們說的修正類型,修改變量名什麼的,先來搞一波
int __fastcall attachBaseContext(JNIEnv *a1, jobject *a2, jobject *a3) { jobject *jobj; // r8@1 jobject *context; // r10@1 JNIEnv *env; // r4@1 _JNIEnv *v6; // r1@1 int v7; // r2@1 int result; // r0@1 JNIEnv *v9; // r0@2 int v10; // r1@2 int v11; // r2@2 int v12; // r0@2 int v13; // r3@2 int v14; // r5@2 int v15; // r0@2 int v16; // r3@2 int v17; // r0@2 int v18; // r0@5 char *v19; // r0@9 int v20; // r0@6 int v21; // r3@13 int v22; // r0@13 int v23; // r3@15 void *v24; // r0@15 int v25; // r3@15 void *v26; // r8@15 const char *v27; // r0@16 const char *v28; // r5@16 size_t v29; // r0@16 int v30; // r5@17 int v31; // r8@17 int v32; // r0@17 int v33; // r5@17 const char *v34; // r1@18 const char *v35; // r2@18 int v36; // r0@19 JNIEnv *v37; // r0@20 int v38; // r2@20 int v39; // r1@20 int v40; // r4@22 unsigned __int64 v41; // r2@22 int v42; // [sp+8h] [bp-78h]@2 __int64 v43; // [sp+18h] [bp-68h]@17 char v44; // [sp+24h] [bp-5Ch]@6 char v45; // [sp+3Ch] [bp-44h]@2 char *v46; // [sp+4Ch] [bp-34h]@3 char *v47; // [sp+50h] [bp-30h]@3 int v48; // [sp+54h] [bp-2Ch]@1 jobj = a2; context = a3; env = a1; v48 = _stack_chk_guard; _android_log_print(6, "debug", "in..."); result = ali::init_classes((ali *)env, v6, v7); if ( !result ) { v9 = (JNIEnv *)_JNIEnv::CallNonvirtualVoidMethod(env, (int)jobj, ali::ContextWrapper, unk_54128); v42 = ali::NanoTime(v9, v10, v11); _JNIEnv::GetObjectClass(env); v12 = _JNIEnv::GetMethodID(env); v14 = _JNIEnv::CallObjectMethod(env, (int)jobj, v12, v13); _JNIEnv::GetObjectClass(env); v15 = _JNIEnv::GetMethodID(env); v17 = _JNIEnv::CallObjectMethod(env, v14, v15, v16); sub_247D8(&v45, env, v17); if ( (_UNKNOWN *)&v45 != &ali::g_filePath ) std::string::_M_assign((std::string *)&ali::g_filePath, v47, v46); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v45); _android_log_print(3, "debug", "global files path is %s", unk_540E8, context); _JNIEnv::CallObjectMethod(env, (int)jobj, unk_541A4, -904); if ( ali::sdk_int <= 8 ) { v20 = _JNIEnv::GetObjectField(env); sub_247D8(&v44, env, v20); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v45, &v44, "/lib"); if ( (_UNKNOWN *)&v45 != &ali::g_libPath ) std::string::_M_assign((std::string *)&ali::g_libPath, v47, v46); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v45); v19 = &v44; } else { v18 = _JNIEnv::GetObjectField(env); sub_247D8(&v45, env, v18); if ( (_UNKNOWN *)&v45 != &ali::g_libPath ) std::string::_M_assign((std::string *)&ali::g_libPath, v47, v46); v19 = &v45; } std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v19); _android_log_print(3, "debug", "global native path is %s", unk_540D0); v22 = _JNIEnv::CallObjectMethod(env, (int)jobj, unk_541B0, v21); sub_247D8(&v45, env, v22); if ( (_UNKNOWN *)&v45 != &ali::g_apkPath ) std::string::_M_assign((std::string *)&ali::g_apkPath, v47, v46); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v45); setenv("APKPATH", dword_540B8, 1); _android_log_print(3, "debug", "global apk path is %s", dword_540B8); sub_24A64(env, jobj); v24 = (void *)_JNIEnv::CallObjectMethod(env, (int)context, unk_541A0, v23); v26 = v24; if ( v24 ) { v27 = (*env)->GetStringUTFChars(env, v24, 0); v28 = v27; v29 = strlen(v27); std::string::_M_assign((std::string *)&ali::g_pkgName, v28, &v28[v29]); (*env)->ReleaseStringUTFChars(env, v26, v28); } v43 = 0LL; v30 = _JNIEnv::CallObjectMethod(env, (int)context, unk_541A8, v25); parse_dex((_JNIEnv *)env, &v43); replace_classloader_cookie(env, v30, v43, HIDWORD(v43)); _android_log_print(3, "debug", "enter new application"); v31 = unk_54120; v32 = _JNIEnv::NewStringUTF((_JNIEnv *)env, "android.app.Application"); v33 = _JNIEnv::CallObjectMethod(env, v30, v31, v32); if ( v33 ) { v36 = _JNIEnv::GetMethodID(env); dword_540A0 = _JNIEnv::NewObject(env, v33, v36); _JNIEnv::CallVoidMethod(env, dword_540A0, unk_54134); _JNIEnv::DeleteLocalRef(env, v33); v34 = "debug"; v35 = "exit new application"; } else { v34 = "debug"; v35 = "can't findClass realAppClass"; } v37 = (JNIEnv *)_android_log_print(3, v34, v35); v39 = dword_540A0; if ( dword_540A0 ) { v37 = (JNIEnv *)(*env)->NewGlobalRef(env, (jobject)dword_540A0); dword_540A0 = (int)v37; } v40 = ali::NanoTime(v37, v39, v38); _android_log_print(3, "debug", "##### attachBaseContext spent:"); ali::PrettyDuration((ali *)(v40 - v42), v41); result = _android_log_print(3, "debug", "exit attachBaseContext"); } if ( v48 != _stack_chk_guard ) _stack_chk_fail(result); return result; }
依舊是來讀代碼
獲取參數,而後執行init_classes()方法
jobj = a2;
context = a3;
env = a1;
v48 = _stack_chk_guard;
_android_log_print(6, "debug", "in..."); result = ali::init_classes((ali *)env, v6, v7);
觀察到init_classes()方法前兩個參數都是JNIEnv*類型,跟進去,接下去修正類型之類的就不講了
unsigned int __fastcall ali::init_classes(JNIEnv *this, _JNIEnv *a2, int a3)
{
JNIEnv *env1_1; // r4@1 const char *v4; // r1@2 const char *v5; // r2@2 _DWORD *v6; // r9@6 JNIEnv *v7; // r0@8 int v8; // r0@44 unsigned int v9; // r1@44 const char *v10; // r2@44 const char *v11; // r3@44 JNIEnv *v12; // r0@44 int v13; // r0@45 unsigned int v14; // r7@50 unsigned int v15; // r11@53 int v16; // r10@53 int v17; // r11@53 int v18; // r10@53 int v19; // r10@53 unsigned int v20; // r11@53 JNIEnv *v21; // ST00_4@53 int v22; // r10@53 int v23; // r9@53 unsigned int v24; // r9@53 int v25; // r8@53 int v26; // r9@53 int v27; // r8@53 JNIEnv *env1; // [sp+0h] [bp-30h]@1 _JNIEnv *env2; // [sp+4h] [bp-2Ch]@1 int v31; // [sp+8h] [bp-28h]@1 env1 = this; env2 = a2; v31 = a3; env1_1 = this; _android_log_print(3, "debug", "enter init classes"); if ( sub_26FDC(env1_1, (unsigned int *)&ali::VERSION, "android/os/Build$VERSION") ) { v4 = "debug"; v5 = "ERROR: class version"; } else { dword_541F4 = (*env1_1)->GetStaticFieldID(env1_1, ali::VERSION, "SDK_INT", "I"); ali::sdk_int = (*env1_1)->GetStaticIntField(env1_1, ali::VERSION, dword_541F4); if ( sub_26FDC(env1_1, &ali::ActivityThread, "android/app/ActivityThread") ) { v4 = "debug"; v5 = "ERROR; class ActivityThread"; } else { _android_log_print(3, "debug", "sdk_int is %d", ali::sdk_int, env1, env2, v31); if ( ali::sdk_int > 18 ) { unk_541DC = _JNIEnv::GetFieldID(env1_1, ali::ActivityThread, "mPackages", "Landroid/util/ArrayMap;"); v6 = &ali::ArrayMap; if ( sub_26FDC(env1_1, (unsigned int *)&ali::ArrayMap, "android/util/ArrayMap") ) { v4 = "debug"; v5 = "ERROR: ArrayMap"; goto LABEL_52; } v7 = env1_1; } else { unk_541DC = _JNIEnv::GetFieldID(env1_1, ali::ActivityThread, "mPackages", "Ljava/util/HashMap;"); v6 = &ali::HashMap; if ( sub_26FDC(env1_1, (unsigned int *)&ali::HashMap, "java/util/HashMap") ) { v4 = "debug"; v5 = "ERROR: HashMap"; goto LABEL_52; } v7 = env1_1; } v6[1] = _JNIEnv::GetMethodID(v7); unk_541E0 = _JNIEnv::GetFieldID( env1_1, ali::ActivityThread, "mBoundApplication", "Landroid/app/ActivityThread$AppBindData;"); unk_541E4 = _JNIEnv::GetFieldID(env1_1, ali::ActivityThread, "mInitialApplication", "Landroid/app/Application;"); unk_541E8 = _JNIEnv::GetFieldID(env1_1, ali::ActivityThread, "mAllApplications", "Ljava/util/ArrayList;"); unk_541EC = _JNIEnv::GetStaticMethodID( env1_1, ali::ActivityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); if ( sub_26FDC(env1_1, &ali::AppBindData, "android/app/ActivityThread$AppBindData") ) { v4 = "debug"; v5 = "ERROR: class AppBindData"; } else { unk_541C8 = _JNIEnv::GetFieldID(env1_1, ali::AppBindData, "info", "Landroid/app/LoadedApk;"); if ( sub_26FDC(env1_1, (unsigned int *)&ali::ArrayList, "java/util/ArrayList") ) { v4 = "debug"; v5 = "ERROR:class ArrayList"; } else { unk_541B8 = _JNIEnv::GetMethodID(env1_1); unk_541BC = _JNIEnv::GetMethodID(env1_1); unk_541C0 = _JNIEnv::GetMethodID(env1_1); if ( sub_26FDC(env1_1, (unsigned int *)&ali::Context, "android/content/Context") ) { v4 = "debug"; v5 = "ERROR: class Context"; } else { unk_541A0 = _JNIEnv::GetMethodID(env1_1); unk_541A4 = _JNIEnv::GetMethodID(env1_1); unk_541A8 = _JNIEnv::GetMethodID(env1_1); unk_541AC = _JNIEnv::GetMethodID(env1_1); unk_541B0 = _JNIEnv::GetMethodID(env1_1); if ( sub_26FDC(env1_1, (unsigned int *)&ali::WeakReference, "java/lang/ref/WeakReference") ) { v4 = "debug"; v5 = "ERROR: WeakReference"; } else { unk_54188 = _JNIEnv::GetMethodID(env1_1); if ( sub_26FDC(env1_1, &ali::LoadedApk, "android/app/LoadedApk") ) { v4 = "debug"; v5 = "ERROR: class LoadedApk"; } else { unk_5417C = _JNIEnv::GetFieldID(env1_1, ali::LoadedApk, "mClassLoader", "Ljava/lang/ClassLoader;"); unk_54180 = _JNIEnv::GetFieldID(env1_1, ali::LoadedApk, "mApplication", "Landroid/app/Application;"); if ( sub_26FDC(env1_1, &ali::ApplicationInfo, "android/content/pm/ApplicationInfo") ) { v4 = "debug"; v5 = "ERROR: class ApplicationInfo"; } else { unk_5416C = _JNIEnv::GetFieldID(env1_1, ali::ApplicationInfo, "dataDir", "Ljava/lang/String;"); unk_54170 = _JNIEnv::GetFieldID( env1_1, ali::ApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;"); unk_54174 = _JNIEnv::GetFieldID(env1_1, ali::ApplicationInfo, "sourceDir", "Ljava/lang/String;"); if ( sub_26FDC(env1_1, (unsigned int *)&ali::Application, "android/app/Application") ) { v4 = "debug"; v5 = "ERROR: class Application"; } else { unk_54130 = _JNIEnv::GetMethodID(env1_1); unk_54134 = _JNIEnv::GetMethodID(env1_1); if ( sub_26FDC(env1_1, (unsigned int *)&ali::ContextWrapper, "android/content/ContextWrapper") ) { v4 = "debug"; v5 = "ERROR: class ContextWrapper"; } else { unk_54128 = _JNIEnv::GetMethodID(env1_1); _android_log_print(3, "debug", "PathClassLoader start"); if ( sub_26FDC(env1_1, &ali::PathClassLoader, "dalvik/system/PathClassLoader") ) { v4 = "debug"; v5 = "ERROR: PathClassLoader"; } else { if ( ali::sdk_int > 13 ) { if ( sub_26FDC(env1_1, &ali::BaseDexClassLoader, "dalvik/system/BaseDexClassLoader") ) { v4 = "debug"; v5 = "ERROR: BaseDexClassLoader"; goto LABEL_52; } unk_5415C = _JNIEnv::GetFieldID( env1_1, ali::BaseDexClassLoader, "pathList", "Ldalvik/system/DexPathList;"); if ( sub_26FDC(env1_1, &ali::DexPathList, "dalvik/system/DexPathList") ) { v4 = "debug"; v5 = "ERROR: class DexPathList"; goto LABEL_52; } unk_54154 = _JNIEnv::GetFieldID( env1_1, ali::DexPathList, "dexElements", "[Ldalvik/system/DexPathList$Element;"); if ( sub_26FDC(env1_1, &ali::Element, "dalvik/system/DexPathList$Element") ) { v4 = "debug"; v5 = "ERROR: class Element"; goto LABEL_52; } unk_54148 = _JNIEnv::GetFieldID(env1_1, ali::Element, "dexFile", "Ldalvik/system/DexFile;"); unk_5414C = _JNIEnv::GetFieldID(env1_1, ali::Element, "file", "Ljava/io/File;"); } else { unk_54164 = _JNIEnv::GetFieldID( env1_1, ali::PathClassLoader, "mDexs", "[Ldalvik/system/DexFile;"); } if ( sub_26FDC(env1_1, (unsigned int *)&ali::JFile, "java/io/File") ) { v4 = "debug"; v5 = "ERROR: class File"; } else { unk_54118 = _JNIEnv::GetMethodID(env1_1); _android_log_print(3, "debug", "PathClassLoader end"); if ( sub_26FDC(env1_1, &ali::JDexFile, "dalvik/system/DexFile") ) { v4 =