閒聊阿里加固(一)

閒聊阿里加固(一)

0x00 閒扯

1.爲何要寫這些?

折騰了一段時間的Android加固,看了不少大牛的文章,收穫仍是蠻大的,不過好像大部分的文章都是直接寫在哪裏下斷點如何修復之類的,寫出如何找到這個脫殼點的文章仍是比較少,因此我準備整理一下這部分的知識,講講如何找到脫殼點,但願能和你們多交流javascript

2.須要什麼樣的基礎?

用過JEB,IDA Pro,若是有跟着其它表哥本身脫過殼的那就更好了:),另外,既然都開始玩加固了,那麼解壓apk後的工程目錄,smali語法等這種基礎的東西就再也不提了php

3.爲何選擇阿里加固?

由於我手上的加固樣本有限,不是每一個版本的加固樣本都有,因此綜合考慮了一下,選擇阿里的樣本,能比較容易造成一種按部就班學習的感受,樣本所有來自歷年阿里CTF和阿里移動安全挑戰賽java

4.適合對象?

最適合跟着其它表哥文章脫過殼,殊不知道爲何要那樣脫殼的同窗,由於接下來這幾篇文章講的就是如何經過一步步的分析,找到脫殼點linux

0x01 樣本初分析---classes.dex

這個樣本是阿里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

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{ @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 = "debug"; v5 = "ERROR: class DexFile"; } else { if ( ali::sdk_int > 19 ) { v13 = _JNIEnv::GetFieldID(env1_1, ali::JDexFile, "mCookie", "J"); v9 = ali::JDexFile; v10 = "openDexFile"; v11 = "(Ljava/lang/String;Ljava/lang/String;I)J"; unk_5413C = v13; v12 = env1_1; } else { v8 = _JNIEnv::GetFieldID(env1_1, ali::JDexFile, "mCookie", "I"); v9 = ali::JDexFile; v10 = "openDexFile"; v11 = "(Ljava/lang/String;Ljava/lang/String;I)I"; unk_5413C = v8; v12 = env1_1; } unk_54140 = _JNIEnv::GetStaticMethodID(v12, v9, v10, v11); if ( sub_26FDC(env1_1, (unsigned int *)&ali::ClassLoader, "java/lang/ClassLoader") ) { v4 = "debug"; v5 = "ERROR: class ClassLoader"; } else { unk_54120 = _JNIEnv::GetMethodID(env1_1); _android_log_print(3, "debug", "System start"); if ( sub_26FDC(env1_1, &ali::JSystem, "java/lang/System") ) { v4 = "debug"; v5 = "ERROR: class System"; } else { unk_541D4 = _JNIEnv::GetStaticMethodID( env1_1, ali::JSystem, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); _android_log_print(3, "debug", "exit init classes normal"); _android_log_print(3, "debug", "SystemProperties start"); v14 = sub_26FDC(env1_1, &ali::SystemProperties, "android/os/SystemProperties"); if ( !v14 ) { unk_54110 = _JNIEnv::GetStaticMethodID( env1_1, ali::SystemProperties, "get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); _android_log_print(3, "debug", "exit init class normal"); v15 = ali::JSystem; v16 = unk_541D4; _JNIEnv::NewStringUTF((_JNIEnv *)env1_1, "java.vm.name"); v17 = _JNIEnv::CallStaticObjectMethod(env1_1, v15, v16); v18 = _JNIEnv::GetStringUTFChars(env1_1, v17, 0); _android_log_print(6, "debug", "------- vmNameStr:%s", v18); _JNIEnv::ReleaseStringUTFChars(env1_1, v17, v18); v19 = unk_54110; v20 = ali::SystemProperties; _JNIEnv::NewStringUTF((_JNIEnv *)env1_1, "persist.sys.dalvik.vm.lib"); v21 = (JNIEnv *)_JNIEnv::NewStringUTF((_JNIEnv *)env1_1, "Dalvik"); v22 = _JNIEnv::CallStaticObjectMethod(env1_1, v20, v19); v23 = _JNIEnv::GetStringUTFChars(env1_1, v22, 0); _android_log_print(6, "debug", "------- runtimeLig:%s", v23, v21); _JNIEnv::ReleaseStringUTFChars(env1_1, v22, v23); v24 = ali::JSystem; v25 = unk_541D4; _JNIEnv::NewStringUTF((_JNIEnv *)env1_1, "java.vm.version"); v26 = _JNIEnv::CallStaticObjectMethod(env1_1, v24, v25); v27 = _JNIEnv::GetStringUTFChars(env1_1, v26, 0); _android_log_print(6, "debug", "---- vmVersionStr:%s", v27); _JNIEnv::ReleaseStringUTFChars(env1_1, v26, v27); ali::isDalvik = 1; ali::isArt = 0; return v14; } v4 = "debug"; v5 = "ERROR: class SystemProperties"; } } } } } } } } } } } } } } } LABEL_52: _android_log_print(3, v4, v5); v14 = -1; _android_log_print(3, "debug", "exit init classes error"); return v14; }

代碼也是比較長,這一下就333行,不過沒有關係,咱們耐心一點,分析一下

首先咱們能夠看到sub_26FDC這個方法貫穿着每個if,若是返回值不爲空則會顯示出錯

好比第一個if-else塊

_android_log_print(3, "debug", "enter init classes"); if ( sub_26FDC(env1_2, (unsigned int *)&ali::VERSION, "android/os/Build$VERSION") ) { v4 = "debug"; v5 = "ERROR: class version"; } else {} LABEL_52: _android_log_print(3, v4, v5); v14 = -1; _android_log_print(3, "debug", "exit init classes error"); return v14; }

進入方法sub_26FDC,能夠看到執行的代碼很簡單,先執行FindClass()方法,而後執行NewGlobalRef()方法

unsigned int __fastcall sub_26FDC(JNIEnv *env1_1, unsigned int *a2, const char *a3) { unsigned int *v3; // r5@1 JNIEnv *env; // r4@1 jclass clazz; // r1@1 jobject v6; // r0@2 unsigned int result; // r0@2 v3 = a2; env = env1_1; clazz = (*env1_1)->FindClass(env1_1, a3); if ( clazz ) { v6 = (*env)->NewGlobalRef(env, clazz); *v3 = (unsigned int)v6; result = -(__clz((unsigned int)v6) >> 5); } else { result = -1; } return result; }

若是不明白這段代碼具體的做用也沒有關係,這和咱們分析關係不大,若是縱觀整個init_classes()方法,能夠看到它實際上是在執行初始化的操做

那麼接下去有比較多的這種寫法的代碼,若是你沒有修正參數的類型可能和我顯示的不同,這個就須要各位同窗本身去修正了

_JNIEnv::GetObjectClass(env, jobj);

若是不清楚這種寫法的意思能夠跟進去,其實就是一個調用

jclass __fastcall _JNIEnv::GetObjectClass(JNIEnv *env, jobject *a2) { return (*env)->GetObjectClass(env, a2); }

若是不清楚它們的參數是什麼類型,能夠參考JNI.h文件,以下:

jclass (*GetObjectClass)(JNIEnv*, jobject);

下面還有一種寫法的代碼

std::string::_M_assign((std::string *)&ali::g_filePath, v47, v46);

一樣的跟進去就能夠知道是什麼東西了,不過這些也不是重點

std::string *__fastcall std::string::_M_assign(std::string *this, const char *a2, const char *a3) { std::string *v3; // r4@1 int v4; // r3@1 size_t v5; // r5@1 void *v6; // r0@1 const char *v7; // r6@1 const char *v8; // r7@1 _BYTE *v9; // r6@2 _BYTE *v10; // r5@2 v3 = this; v4 = *(this + 4); v5 = a3 - a2; v6 = *(this + 5); v7 = a3; v8 = a2; if ( a3 - a2 > (v4 - v6) ) { std::__char_traits_base<char,int>::move(v6, a2, v4 - v6); std::string::_M_append(v3, &v8[*(v3 + 4) - *(v3 + 5)], v7); } else { std::__char_traits_base<char,int>::move(v6, a2, v5); v9 = *(v3 + 4); v10 = (v5 + *(v3 + 5)); if ( v10 != v9 ) { std::__char_traits_base<char,int>::move(v10, *(v3 + 4), 1u); *(v3 + 4) -= v9 - v10; } } return v3; }

可能咱們有些代碼段很是難理解,直接讀的話仍是很不容易的

可是不知道你們發現了沒有,代碼中間穿插着不少log

我這裏整理出了正常邏輯下的log

int __fastcall attachBaseContext(JNIEnv *a1, jobject *a2, jobject *a3) { _android_log_print(6, "debug", "in..."); if ( !result ) { _android_log_print(3, "debug", "global files path is %s", unk_540E8, context); _android_log_print(3, "debug", "global native path is %s", unk_540D0); _android_log_print(3, "debug", "global apk path is %s", dword_540B8); _android_log_print(3, "debug", "enter new application"); if ( v33 ) { v34 = "debug"; v35 = "exit new application"; } else { v34 = "debug"; v35 = "can't findClass realAppClass"; } v37 = (JNIEnv *)_android_log_print(3, v34, v35); _android_log_print(3, "debug", "##### attachBaseContext spent:"); } }

從這些log代碼中能夠發現:其實咱們能夠不用特別仔細的去讀反編譯出來的代碼,直接看每一段後面的log就行

中間有一個log比較顯眼,這是在說進入新的app,什麼新的app?難道是解密後的app?

_android_log_print(3, "debug", "enter new application");

帶着這個疑問,咱們來找一下這個log

在兩個log中間的代碼以下

sub_24A64(env, jobj); v24 = _JNIEnv::CallObjectMethod(env, 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 = (jobject *)_JNIEnv::CallObjectMethod(env, 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");

原本想從第一行開始看起,可是忽然發現下面有一行

parse_dex((_JNIEnv *)env, &v43);

解析dex?

那這個估計是重點,跟進去

signed int __fastcall parse_dex(_JNIEnv *a1, __int64 *a2) { int v2; // r7@3 char *v3; // r0@4 const char *v4; // r1@4 char *v5; // r0@6 char *v6; // r9@16 _BYTE *v7; // r3@17 int v8; // r2@17 int v9; // t1@19 int fd; // ST14_4@23 unsigned int v11; // r8@24 int v12; // r7@24 int v13; // r5@26 int v14; // r5@25 int v15; // r7@25 int v16; // r1@25 int v17; // r5@25 int v18; // ST04_4@27 int (__fastcall *v19)(int, signed int); // r5@27 int v20; // r5@27 unsigned __int8 *v21; // r8@27 const char *v22; // r3@27 char *v23; // r0@29 char *v24; // r0@28 char *v25; // r6@30 ali::EncFile *v26; // r7@30 int v27; // r0@31 int *v28; // r0@34 char *v29; // r0@34 int v30; // r10@35 void *v31; // r7@36 int (__fastcall *v32)(unsigned __int8 *, int, signed int *); // r9@36 int (__fastcall *v33)(_DWORD); // r7@36 const char *v34; // r1@37 const char *v35; // r2@37 int v36; // r9@38 signed int v37; // r7@38 _DWORD *v38; // r9@41 _BYTE *v39; // r5@41 unsigned __int8 *v40; // r3@41 void *v41; // r0@42 JNINativeMethod *v42; // r0@42 unsigned __int8 *v43; // r3@42 signed int v44; // r3@42 signed int result; // r0@44 _JNIEnv *env; // [sp+8h] [bp-2A0h]@1 __int64 *v47; // [sp+10h] [bp-298h]@1 int v48; // [sp+24h] [bp-284h]@30 unsigned __int8 *v49; // [sp+28h] [bp-280h]@30 unsigned __int8 *v50; // [sp+2Ch] [bp-27Ch]@30 void (__cdecl *v51)(const unsigned int *, jvalue *); // [sp+30h] [bp-278h]@42 char v52; // [sp+34h] [bp-274h]@3 signed int v53[2]; // [sp+38h] [bp-270h]@17 char s; // [sp+40h] [bp-268h]@23 char v55; // [sp+54h] [bp-254h]@2 int v56; // [sp+64h] [bp-244h]@23 int v57; // [sp+68h] [bp-240h]@23 char v58; // [sp+6Ch] [bp-23Ch]@2 const char *v59; // [sp+80h] [bp-228h]@2 char v60; // [sp+84h] [bp-224h]@2 const char *v61; // [sp+98h] [bp-210h]@25 char v62; // [sp+9Ch] [bp-20Ch]@27 unsigned int v63; // [sp+B0h] [bp-1F8h]@27 char v64; // [sp+B4h] [bp-1F4h]@3 char v65; // [sp+CCh] [bp-1DCh]@6 int v66; // [sp+E0h] [bp-1C8h]@23 char v67; // [sp+E4h] [bp-1C4h]@23 char v68; // [sp+FCh] [bp-1ACh]@23 const char *v69; // [sp+110h] [bp-198h]@23 char v70; // [sp+114h] [bp-194h]@23 char v71; // [sp+12Ch] [bp-17Ch]@23 const char *v72; // [sp+140h] [bp-168h]@23 char v73; // [sp+144h] [bp-164h]@23 char *v74; // [sp+154h] [bp-154h]@23 char *v75; // [sp+158h] [bp-150h]@23 char v76; // [sp+15Ch] [bp-14Ch]@23 char v77; // [sp+174h] [bp-134h]@23 char v78; // [sp+18Ch] [bp-11Ch]@23 char v79; // [sp+1A4h] [bp-104h]@23 char v80; // [sp+1BCh] [bp-ECh]@23 char v81; // [sp+1D4h] [bp-D4h]@23 char v82; // [sp+1ECh] [bp-BCh]@23 char v83; // [sp+204h] [bp-A4h]@23 char v84; // [sp+21Ch] [bp-8Ch]@23 char v85; // [sp+234h] [bp-74h]@17 int v86; // [sp+244h] [bp-64h]@17 _BYTE *v87; // [sp+248h] [bp-60h]@17 char v88; // [sp+24Ch] [bp-5Ch]@22 char v89; // [sp+264h] [bp-44h]@22 char *v90; // [sp+274h] [bp-34h]@22 char *v91; // [sp+278h] [bp-30h]@22 int v92; // [sp+27Ch] [bp-2Ch]@1 env = a1; v47 = a2; v92 = _stack_chk_guard; _android_log_print(3, "debug", "enter parse_dex"); if ( ali::isDalvik ) { v48 = 0; std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v89, &ali::g_filePath, "/cls.jar"); v25 = v91; v26 = operator new(0xCu); ali::EncFile::EncFile(v26, v25); v49 = 0; v50 = 0; if ( ali::sdk_int > 13 ) { v27 = ali::EncFile::openWithHeader(v26, &v49, &v48, 0x10u); v50 = v49 + 16; } else { v27 = ali::EncFile::open(v26, &v50, &v48); } if ( v50 == -1 ) { v28 = _errno(v27); v29 = strerror(*v28); _android_log_print(3, "debug", "mmap dex file :%s", v29); LABEL_45: v24 = &v89; goto LABEL_46; } v30 = *(v50 + 8); if ( ali::sdk_int > 13 ) { v41 = dlopen("libdvm.so", 1); v42 = dlsym(v41, "dvm_dalvik_system_DexFile"); v51 = 0; lookup(v42, "openDexFile", "([B)I", &v51); v43 = v49; *(v49 + 2) = v48; *&v52 = v43; (v51)(); v44 = v53[0]; *v47 = v53[0]; *(*(*(v44 + 8) + 4) + 32) = *(v44 + 16); *(*(*(v44 + 8) + 4) + 36) = v48; ali::EncFile::~EncFile(v26); operator delete(v26); } else { v31 = dlopen("libdvm.so", 1); v32 = dlsym(v31, "dvmDexFileOpenPartial"); v33 = dlsym(v31, "dexCreateClassLookup"); v53[0] = 0; if ( v32(v50, v30, v53) == -1 ) { v34 = "debug"; v35 = "dvmDexFileOpenPartial error"; LABEL_40: _android_log_print(3, v34, v35); goto LABEL_45; } v36 = *v53[0]; *(v36 + 36) = v33(*v53[0]); v37 = v53[0]; if ( !*(*v53[0] + 36) ) { v34 = "debug"; v35 = "dexCreateClassLookup error"; goto LABEL_40; } v38 = malloc(0x2Cu); v39 = malloc(0x14u); strdup(&unk_4CEE9); v39[4] = 0; v39[5] = 0; *(v39 + 2) = 0; v40 = v50; *v39 = v39; *(v39 + 3) = v38; v38[10] = v37; *(v37 + 32) = v40; *(v37 + 36) = v48; *v47 = v39; } v23 = &v89; goto LABEL_44; } std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v55, &ali::g_filePath, "/cls.jar"); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v58, &ali::g_filePath, "/cls.dex"); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v60, &ali::g_filePath, "/fak.jar"); _android_log_print(3, "debug", "before oat gen"); if ( !access(v59, 0) ) goto LABEL_24; v2 = android_getCpuFamily(); std::string::string(&v64, "arm", &v52); if ( v2 == 1 ) { v3 = &v64; v4 = "arm"; LABEL_5: std::string::operator=(v3, v4); goto LABEL_6; } if ( v2 == 2 ) { v3 = &v64; v4 = "x86"; goto LABEL_5; } if ( v2 == 3 || v2 == 6 ) { v3 = &v64; v4 = "mips"; goto LABEL_5; } if ( v2 == 4 ) { v3 = &v64; v4 = "arm64"; goto LABEL_5; } if ( v2 == 5 ) { v3 = &v64; v4 = "x86_64"; goto LABEL_5; } LABEL_6: std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v65, &ali::g_libPath, "/libhack.so"); v5 = getenv("LD_PRELOAD"); v6 = v5; if ( v5 ) { _android_log_print(3, "debug", "the system already define LD_PRELOAD=%s", v5); std::string::string(&v85, v6, v53); v7 = v87; v8 = v86; while ( v7 != v8 ) { v9 = *v7++; if ( v9 == 32 ) *(v7 - 1) = 58; } std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v88, &v85, ":"); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v89, &v88, &v65); std::string::_M_assign(&v65, v91, v90); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v89); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v88); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v85); } _android_log_print(3, "debug", "the new LD_PRELOAD is %s", v66); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v67, &ali::g_filePath, "/juice.data"); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v68, &ali::g_filePath, "/fak.jar"); fd = open(v69, 0); memset(&s, 0, 0x14u); sprintf(&s, "%d", fd); std::string::string(&v70, &s, v53); v74 = &v73; v75 = &v73; std::priv::_String_base<char,std::allocator<char>>::_M_allocate_block(&v73, v56 - v57 + 10); *v74 = 0; std::string::_M_appendT<char const*>(&v73); std::string::append(&v73, &v55); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v76, &v73, " JUICE_FILE="); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v77, &v76, &v67); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v78, &v77, " LD_PRELOAD="); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v79, &v78, &v65); std::operator+<char,std::char_traits<char>,std::allocator<char>>( &v80, &v79, " /system/bin/dex2oat \t\t\t\t \t --runtime-arg -Xms64m \t\t\t\t\t --runtime-arg -Xmx64m \t\t\t\t" "\t --boot-image=/system/framework/boot.art --zip-fd="); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v81, &v80, &v70); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v82, &v81, "\t\t\t\t\t --zip-location="); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v83, &v82, &v68); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v84, &v83, "\t\t\t\t\t --oat-file="); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v85, &v84, &v58); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v71, &v85, " "); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v85); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v84); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v83); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v82); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v81); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v80); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v79); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v78); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v77); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v76); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v73); _android_log_print(3, "debug", "cmd is %s", v72); system(v72); close(fd); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v71); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v70); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v68); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v67); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v65); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v64); LABEL_24: _android_log_print(3, "debug", "after oat gen"); v11 = ali::JDexFile; v12 = unk_54140; if ( ali::sdk_int <= 19 ) { v13 = _JNIEnv::NewStringUTF(env, v61); _JNIEnv::NewStringUTF(env, v59); v15 = _JNIEnv::CallStaticIntMethod(env, v11, v12, v13); v17 = 0; } else { v14 = _JNIEnv::NewStringUTF(env, v61); _JNIEnv::NewStringUTF(env, v59); v15 = _JNIEnv::CallStaticLongMethod(env, v11, v12, v14); v17 = v16; } v18 = v17; _android_log_print(3, "debug", "cookie is %llx"); *v47 = v15; *(v47 + 1) = v17; v19 = dlsym(0xFFFFFFFF, "_ZNK3art7DexFile12FindClassDefEt"); _android_log_print(3, "debug", "DexFile::FindClassDefFn is %p", v19, v15, v18); v20 = v19(v15, 1); _android_log_print(3, "debug", "call FindClassDefFn(%p,%d) => %p", v15, 1, v20); _android_log_print(3, "debug", "dex position is %p", v20 - 572); _android_log_print(3, "debug", "dex head is %08x %08x", *(v20 - 572), *(v20 - 568)); v21 = *(v20 - 540); _android_log_print(3, "debug", "dex size is %d", v21); MemEnableWrite((v20 - 572), &v21[v20 - 572]); std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v62, &ali::g_filePath, "/juice.data"); if ( !ali::dex_juicer_patch((v20 - 572), v21, v63, v22) ) { std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v62); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v60); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v58); v23 = &v55; LABEL_44: std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v23); _android_log_print(3, "debug", "exit parse_dex"); result = 0; goto LABEL_47; } _android_log_print(6, "debug", "fail to patch dex"); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v62); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v60); std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v58); v24 = &v55; LABEL_46: std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v24); _android_log_print(3, "debug", "exit parse_dex error"); result = -1; LABEL_47: if ( v92 != _stack_chk_guard ) _stack_chk_fail(result); return result; }

驚喜的發現還有好多log啊(*^__^*)

整理一下看看

signed int __fastcall parse_dex(_JNIEnv *a1, __int64 *a2) { _android_log_print(3, "debug", "enter parse_dex"); if ( ali::isDalvik ) { if ( v50 == -1 ) { _android_log_print(3, "debug", "mmap dex file :%s", v29); LABEL_45: goto LABEL_46; } if ( ali::sdk_int > 13 ) {} else { if ( v32(v50, v30, v53) == -1 ) { v34 = "debug"; v35 = "dvmDexFileOpenPartial error"; LABEL_40: _android_log_print(3, v34, v35); goto LABEL_45; } if ( !*(*v53[0] + 36) ) { v34 = "debug"; v35 = "dexCreateClassLookup error"; goto LABEL_40; } } goto LABEL_44; } _android_log_print(3, "debug", "before oat gen"); LABEL_6: if ( v5 ) { _android_log_print(3, "debug", "the system already define LD_PRELOAD=%s", v5); } _android_log_print(3, "debug", "the new LD_PRELOAD is %s", v66); _android_log_print(3, "debug", "cmd is %s", v72); LABEL_24: _android_log_print(3, "debug", "after oat gen"); _android_log_print(3, "debug", "cookie is %llx"); _android_log_print(3, "debug", "DexFile::FindClassDefFn is %p", v19, v15, v18); _android_log_print(3, "debug", "call FindClassDefFn(%p,%d) => %p", v15, 1, v20); _android_log_print(3, "debug", "dex position is %p", v20 - 572); _android_log_print(3, "debug", "dex head is %08x %08x", *(v20 - 572), *(v20 - 568)); _android_log_print(3, "debug", "dex size is %d", v21); if ( !ali::dex_juicer_patch((v20 - 572), v21, v63, v22) ) { LABEL_44: _android_log_print(3, "debug", "exit parse_dex"); goto LABEL_47; } _android_log_print(6, "debug", "fail to patch dex"); LABEL_46: _android_log_print(3, "debug", "exit parse_dex error"); LABEL_47: }

稍微的保留了一下if-else邏輯,第一個log很明確的顯示了開始解析dex

入口處判斷了是不是Dalvik模式,這個變量在init_classes()方法裏有進行賦值操做

ali::isDalvik = 1; ali::isArt = 0;

咱們這裏使用Dalvik模式來分析,包括接下來動態調試脫殼也同樣是使用Dalvik模式

根據邏輯整理一下Dalvik模式下的執行流程

signed int __fastcall parse_dex(_JNIEnv *a1, __int64 *a2) { env = a1; v47 = a2; v92 = _stack_chk_guard; _android_log_print(3, "debug", "enter parse_dex"); if ( ali::isDalvik ) { v48 = 0; std::operator+<char, std::char_traits<char>, std::allocator<char>>(&v89, &ali::g_filePath, "/cls.jar"); v25 = v91; v26 = operator new('\f'); ali::EncFile::EncFile(v26, v25); v49 = 0; v50 = 0; if ( ali::sdk_int > 13 ) { v27 = ali::EncFile::openWithHeader(v26, &v49, &v48, 0x10u); v50 = v49 + 16; } else { v27 = ali::EncFile::open(v26, &v50, &v48); } v30 = *(v50 + 8); if ( ali::sdk_int > 13 ) { v41 = dlopen("libdvm.so", 1); v42 = dlsym(v41, "dvm_dalvik_system_DexFile"); v51 = 0; lookup(v42, "openDexFile", "([B)I", &v51); v43 = v49; *(v49 + 2) = v48; *&v52 = v43; (v51)(); v44 = v53[0]; *v47 = v53[0]; *(*(*(v44 + 8) + 4) + 32) = *(v44 + 16); *(*(*(v44 + 8) + 4) + 36) = v48; ali::EncFile::~EncFile(v26); operator delete(v26); } v23 = &v89; goto LABEL_44; } LABEL_44: std::priv::_String_base<char, std::allocator<char>>::_M_deallocate_block(v23); _android_log_print(3, "debug", "exit parse_dex"); result = 0; goto LABEL_47; LABEL_47: if ( v92 != _stack_chk_guard ) _stack_chk_fail(result); return result; }

若是當前手機系統是Dalvik模式且sdk_int大於13,執行

v27 = ali::EncFile::openWithHeader(v26, &v49, &v48, 0x10u);

若是sdk_int小於13,執行

v27 = ali::EncFile::open(v26, &v50, &v48);

sdk_int等於13那得安卓3.x了,你們應該都是至少安卓4.x,因此這裏進入openWithHeader()方法

int __fastcall ali::EncFile::openWithHeader(ali::EncFile *this, unsigned __int8 **a2, unsigned int *a3, unsigned int a4) { ali::EncFile *v4; // r5@1 unsigned __int8 **v5; // r11@1 unsigned int *v6; // r6@1 unsigned int v7; // r7@1 const char *v8; // r1@2 const char *v9; // r2@2 int v10; // r8@3 int v11; // r10@3 __blksize_t v13; // r3@6 ali *v14; // r4@6 JNIEnv *v15; // r0@6 int v16; // r1@6 int v17; // r2@6 signed __int64 v18; // ST18_8@6 unsigned int *v19; // r3@6 JNIEnv *v20; // r0@6 int v21; // r1@6 int v22; // r2@6 signed __int64 v23; // r0@6 int v24; // r8@6 int v25; // r3@6 char v26; // r2@7 unsigned __int64 v27; // r0@7 JNIEnv *v28; // r0@8 int v29; // r9@8 JNIEnv *v30; // ST24_4@8 int v31; // r1@8 int v32; // r2@8 signed __int64 v33; // r0@8 signed __int64 v34; // ST18_8@8 JNIEnv *v35; // r0@8 int v36; // r1@8 int v37; // r2@8 signed __int64 v38; // r0@8 size_t v39; // [sp+2Ch] [bp-9Ch]@8 int v40; // [sp+30h] [bp-98h]@8 struct stat buf; // [sp+38h] [bp-90h]@3 v4 = this; v5 = a2; v6 = a3; v7 = a4; if ( !*(this + 2) ) { v8 = "debug"; v9 = "file path is null"; LABEL_5: _android_log_print(6, v8, v9); return 0; } v10 = open(*(this + 2), 0); v11 = fstat(v10, &buf); if ( v11 ) { v8 = "debug"; v9 = "fstat failed"; goto LABEL_5; } v13 = buf.st_blksize; *v6 = buf.st_blksize; *v4 = v13; v14 = mmap(0, *v6, 3, 2, v10, 0); *(v4 + 1) = v14; close(v10); v15 = _android_log_print( 3, "debug", "dex magic %c %c %c %c %c %c %c", *v14, *(v14 + 1), *(v14 + 2), *(v14 + 3), *(v14 + 4), *(v14 + 5), *(v14 + 6)); v18 = ali::NanoTime(v15, v16, v17); ali::decryptRc4(v14, v14, v6, v19); v23 = ali::NanoTime(v20, v21, v22); ali::PrettyDuration((v23 - v18), v23 - v18); _android_log_print(3, "debug", "decrypted len:%u", *v6); v24 = 0; _android_log_print( 3, "debug", "after decrypt dex magic %c %c %c %c %c %c %c", *v14, *(v14 + 1), *(v14 + 2), *(v14 + 3), *(v14 + 4), *(v14 + 5), *(v14 + 6)); v25 = v14 + 4; do { v26 = 8 * v11++; v27 = *(v25++ + 1) << v26; v24 += v27; } while ( v11 != 8 ); _android_log_print(3, "debug", "unpackSize: %u", v24); *v4 = v7 + v24; v28 = mmap(0, v7 + v24, 3, 34, -1, 0); *(v4 + 1) = v28; v29 = v28 + v7; v30 = v28; v33 = ali::NanoTime(v28, v31, v32); v39 = *v6; v34 = v33; v40 = v24; v35 = LzmaDecode(v29, &v40, v14 + 13, &v39); v38 = ali::NanoTime(v35, v36, v37); ali::PrettyDuration((v38 - v34), v38 - v34); munmap(v14, buf.st_blksize); _android_log_print( 3, "debug", "after uncompressed dex magic %c %c %c %c %c %c %c", *(v30 + v7), *(v29 + 1), *(v29 + 2), *(v29 + 3), *(v29 + 4), *(v29 + 5), *(v29 + 6)); *v6 = v24; if ( v5 ) *v5 = *(v4 + 1); return *(v4 + 1); }

簡單上下瀏覽一下,仍是比較簡單的,能夠看到兩個比較關鍵的字眼ali::decryptRc4()LzmaDecode()

RC4不用說,Lzma是一個壓縮解壓相關的算法

第一想法是等這兩個方法運行完後,看一下處理事後的數據區域是什麼樣的,這是一個關鍵的地方

並且這代碼裏有兩個log,第一個是解密後的dexmagic,第二個是解壓事後的dexmagic,因此這兩處的log能夠看到很是關鍵的信息

或者說,在openWithHeader()這個方法結束後,查看返回值,能夠看到一些比較有用的數據

回到parse_dex()方法,接下來仍是經過判斷sdk_int是否大於13來決定程序執行流程

由於咱們是Dalvik模式,因此進入if,這個分支執行的就是解析完dex以後的代碼

這部分執行完就退出了parse_dex()函數

再根據attachBaseContext()函數的log信息,能夠看到這裏差很少就是解析完成dex了

那麼這些都是咱們在靜態分析時的猜測,確定有對的,有錯的,都是我的思路

至於哪些是對的,哪些是錯的,咱們接下來進行動態調試驗證一下

0x03 動態調試libmobisec.so

準備一個Root事後的Android系統手機,系統是4.x

先將IDA的dbgsrv文件夾裏的android_server複製到手機的/data/local

C:\Users\wangz\Desktop>adb push android_server /data/local

打開/data/local

C:\Users\wangz\Desktop>adb shell root@jflte:/ #cd data/local

而後加權限

root@jflte:/data/local #chmod 777 android_server

安裝jscrack.apk

C:\Users\wangz\Desktop>adb install jscrack.apk
[100%] /data/local/tmp/jscrack.apk WARNING: linker: app_process has text relocations. This is wasting memory and is a security risk. Please fix. WARNING: linker: app_process has text relocations. This is wasting memory and is a security risk. Please fix. pkg: /data/local/tmp/jscrack.apk Success

開啓android_server服務

root@jflte:/data/local # ./android_server IDA Android 32-bit remote debug server(ST) v1.19. Hex-Rays (c) 2004-2015 Listening on port #23946...

這個cmd窗口能夠放一邊了,接下來說講調試Android應用的小Tip

咱們要想調試Android應用,就要在AndroidManifest.xml插入

android:debuggable="true"

這種辦法有些狀況不太好用,想修改AndroidManifest.xml,就避免不了反編譯和重打包,可是有些殼作了對抗apktool這類工具的保護,好比鵝廠的樂固,雖然ShaKaapktool能夠解決這些問題可是常常會有各式各樣的錯誤出現

因此這裏介紹一種我的以爲仍是很好用的工具mprop,這個工具搜一下就能夠搜到

Android系統在跑起來後,會將default.prop文件裏的配置信息寫進內存

C:\Users\wangz>adb shell root@jflte:/ # cat default.prop # # ADDITIONAL_DEFAULT_PROPERTIES # ro.secure=0 ro.allow.mock.location=0 ro.debuggable=0 ro.adb.secure=0 persist.sys.usb.config=mtp,adb persist.radio.add_power_save=1 androidboot.selinux=0

這個文件裏ro.debuggable字段的值爲0,它的做用就是用來標記是否可調試,當這個字段爲1的時候,說明全部應用均可以調試

咱們來看正常狀況下內存裏這個字段的信息

root@jflte:/data # getprop ro.debuggable 0

咱們來執行這個文件,新開cmd窗口

root@jflte:/data # ./mprop ro.debuggable 1 ...... 0001ffa0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0001ffb0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0001ffc0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0001ffd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0001ffe0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0001fff0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ patching it at:0xb6e48290[0xb6f8d288], value:[0x00000031 -> 0x00000031] patching it at:0xb6e4828c[0xb6f8d284], value:[0x01000000 -> 0x01000000] patched, reread: [0xb6f8d288] => 0x00000031 patched, reread: [0xb6f8d284] => 0x01000000

再看內存裏的信息

root@jflte:/data # getprop ro.debuggable 1

要說明一下的是,這個只是對內存中的值作一個patch,並不會修改default.prop文件的信息,因此在系統重啓後內存裏的值依舊會變回來

打開Android Device Monitor

新開cmd,開啓端口轉發

C:\Users\wangz>adb forward tcp:23946 tcp:23946

調試模式打開應用

C:\Users\wangz>adb shell am start -D -n com.ali.tg.testapp/com.ali.tg.testapp.MainActivity WARNING: linker: app_process has text relocations. This is wasting memory and is a security risk. Pl ease fix. WARNING: linker: app_process has text relocations. This is wasting memory and is a security risk. Pl ease fix. Starting: Intent { cmp=com.ali.tg.testapp/.MainActivity }

此時手機上應該出現Waiting For Debugger

新開IDA,attach進程

如圖勾選三個選項

勾上紅框裏三個選項

在接下來出現的框裏選擇本次要調試的app

跑一下子會斷下

jdb掛上

C:\Users\wangz>jdb -connect com.sun.jdi.SocketAttach:port=8700,hostname=localhost

掛上後點擊IDA左上角的三角形運行程序再跑起來,又斷下,來設置斷點

查看Segments

能夠看到libmobisec.so在內存裏的加載地址是0x776E9000

回到最開始咱們靜態分析的IDA界面,咱們在這裏下個斷點,偏移是0x00026A84,那麼加上在內存裏的加載基地址就是0x7770FA84

text:00026A84 01 F0 B4 FE BL _ZN3ali7EncFile14openWithHeaderEPPhPjj ; ali::EncFile::openWithHeader(uchar **,uint *,uint)

使用快捷鍵g跳轉到這個地址,按一下C就能夠看到代碼了,下好斷點

F9跑起來,斷下,F7跟進去,按p建立函數

這裏有個小技巧,若是想直觀的看函數調用圖的話,能夠在函數入口第一句下斷點,這樣斷下後,直接按p就能夠建立函數,按一下空格就能夠看到整個比較完整的調用圖了

F8跑起來,中間各類log輸出,使用tag:debug便可

在解壓函數執行完後咱們驚喜的發現

dex的magicnumber出現了,那麼很大狀況已經dex已經被釋放了,咱們執行完這個函數,返回

查看R0所指向的內存區域,發現確實已經釋放了dex

這時候咱們能夠高高興興的寫個腳本dump這個dex了

0x04 Dump源dex文件

dump腳本,dex長度在偏移0x20的位置

auto fp, begin, end, dexbyte; fp = fopen("E:\\dump.dex", "wb"); begin = 0x77841010; end = begin + 0x000941FC; for ( dexbyte = begin; dexbyte < end; dexbyte ++ ) fputc(Byte(dexbyte), fp);

執行

跑完腳本後咱們能夠在目錄下看到一個dump.dex,使用JEB查看

能夠看到效果不錯

那麼到這裏,其實能夠先告一段落了,東扯西扯講了一大堆,最終仍是把真實dex脫下來了,可是咱們來思考一下,網上也有很多關於阿里加固這個版本的脫殼文章,他們並不是在此處下斷點,而是在libdvm.so裏的dvmDexFileOpenPartial()函數下斷

咱們來對比一下這兩種脫殼方法的不一樣:首先是斷點下的不同,咱們須要在libmobisec.so加載起來後,經過計算相關函數偏移下斷點,而dvmDexFileOpenPartial()是在libdvm.so中,只須要在attach上進程後直接在加載的Module list裏找到libdvm.so,而後找到dvmDexFileOpenPartial()函數下斷點,跑起來斷下便可脫殼,整體來講仍是第二種方法比較簡單

源碼路徑

/dalvik/vm/DvmDex.cpp

第一個參數是內存裏dex的起始地址,第二個參數是dex長度,因此有的脫殼腳本直接就是用R0和R1的值來執行,由於R0就是起始地址,R1就是dex長度

前面調用dexFileParse()函數解析程序的dex文件,後面還有allocateAuxStructures()函數用於設置輔助的數據字段等等,這一系列的解析優化和驗證過程仍是比較複雜的,咱們暫時不須要這部分知識,就不過多的講解,感興趣的同窗能夠先去閱讀Android的相關源碼,之後咱們會詳細的來說解這部分的內容

/*
 * Create a DexFile structure for a "partial" DEX. This is one that is in * the process of being optimized. The optimization header isn't finished * and we won't have any of the auxillary data tables, so we have to do * the initialization slightly differently. * * Returns nonzero on error. */ int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex) { DvmDex* pDvmDex; DexFile* pDexFile; int parseFlags = kDexParseDefault; int result = -1; /* -- file is incomplete, new checksum has not yet been calculated if (gDvm.verifyDexChecksum) parseFlags |= kDexParseVerifyChecksum; */ pDexFile = dexFileParse((u1*)addr, len, parseFlags); if (pDexFile == NULL) { ALOGE("DEX parse failed"); goto bail; } pDvmDex = allocateAuxStructures(pDexFile); if (pDvmDex == NULL) { dexFileFree(pDexFile); goto bail; } pDvmDex->isMappedReadOnly = false; *ppDvmDex = pDvmDex; result = 0; bail: return result; }

0x05 修復Application

使用AndroidKiller反編譯加固後的apk,找到AndroidManifest.xml,刪除Application的android:name

android:name="com.ali.mobisecenhance.StubApplication" 

回編譯後,找到生成的apk,壓縮軟件打開,替換咱們dump出來的classes.dex,同時刪除assets文件夾,至於lib文件夾,由於這個樣本自身帶了so,因此留着不去動好了

修改完後從新簽名,安裝運行,妥妥的

0x06 小結

這種文章寫起來確實是很不容易,由於不少東西都要寫的很詳細,並且都須要結合源碼來說解,因此本文貼了比較多的源碼,但願你們在看到源碼的時候不要跳過去,稍微看一下源碼是怎麼寫的,不少東西或者說有至關一部分東西很難講解清楚可是看源碼就會有一個很直觀的理解,有些同窗看完這篇文章可能會有疑問,我在這來先回答一部分:有些殼確實是會檢測android_server,因此須要改一下名字,好比as,而後端口檢測,數字殼會檢測23946端口,這個端口是android_server在監聽,因此咱們須要在執行android_server的時候加上參數-p1995,這樣就能夠繞過端口檢測,有的so會進行混淆,或者對so加固,還有一些反調試這個樣本並無使用到,因此我就沒有說起,脫殼點不止這一處,後面還有,脫殼方法須要根據樣本實際狀況來,這些知識點我是但願在之後的樣本里慢慢的寫,因此想學習脫殼又不知道怎麼學習的同窗把這篇文章好好的理解一下,必定是可以學習到很多東西的,總體dump如今已經不存在了,之因此第一篇講這些,是但願能有一種按部就班學習的感受,若是第一篇就講阿里近期的加固,一來很費勁,二來大夥看着也比較暈

相關文章
相關標籤/搜索