2017年5月14日星期日

Android 逆向学习笔记 (九)- 从 Android 源码看 SO 的加载

分析完好久, 再不写估计要忘了 ....

做安卓逆向的时候常常会碰到写在 Shared Object(即 [*.so] 文件,下文 SO)的加固逻辑,碰到 SO 的时候惯性分析方法是:静态找 JNI_Onload / .init / .init_array 这些在加载之初会被调用的代码段或者是指向被调用代码段的指针段。

然而现在的加固基本上都会用各种方式抹掉函数关系、SHT 等导致直接静态分析 SO 时既不能从函数表里找到 JNI_Onload 也不能从 SECTION 里找到 .init,部分逆向人员在这种情况下走了弯路。

#1. Source Code

Android 作为开源的系统,既然可以拿到源码,那么就可以尝试分析加载 SO 时的流程,进而找到对应正确的下断位置来确定 JNI_Onload / .init 的偏移量。下文中所有源码均为 android-4.4.2_r1 版本,各个版本在细节上的实现可能存在差异。

[java.lang.Runtime -> load] 为例子来说明,对应的 Android 源码在 [java/lang/Runtime.java],从 320 行开始。

/**
 * 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 absolute (platform dependent) path to the library to load.
 * @throws UnsatisfiedLinkError
 *             if the library can not be loaded.
 */
public void load(String pathName) {
    load(pathName, VMStack.getCallingClassLoader());
}
/*
 * Loads and links the given library without security checks.
 */
void load(String pathName, ClassLoader loader) {
    if (pathName == null) {
        throw new NullPointerException("pathName == null");
    }
    String error = doLoad(pathName, loader);
    if (error != null) {
        throw new UnsatisfiedLinkError(error);
    }
}
可以看到 load(String pathName) 实际上是调用了 load(String pathName, ClassLoader loader),而后者又调用了 doLoad(pathName, loader),这里的函数调用没有什么实际的意义(仅指对逆向者没有实质的意义,下同)一直在传值。

下面是 doLoad(pathName, loader) 的定义,源码还是在上面的 Runtime.java 里。
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);
    }
}

主要是检测 loader 的正确性,并带上 LD_LIBRARY_PATH 一起进入 nativeLoad(name, loader, ldLibraryPath),这里开始进入 native 层,nativeLoad 的定义在 vm/native/java_lang_Runtime.cpp # 64 行,如下。

/*
 * 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);
}

还是传值 + 检查,然后执行 [bool success = dvmLoadNativeCode(fileName, classLoader, &reason);] ,看下 dvmLoadNativeCode(...) 的代码,位于 vm/Native.cpp # 301 行

/*
 * Load native code from the specified absolute pathname.  Per the spec,
 * if we've already loaded a library with the specified pathname, we
 * return without doing anything.
 *
 * TODO? for better results we should absolutify the pathname.  For fully
 * correct results we should stat to get the inode and compare that.  The
 * existing implementation is fine so long as everybody is using
 * System.loadLibrary.
 *
 * The library will be associated with the specified class loader.  The JNI
 * spec says we can't load the same library into more than one class loader.
 *
 * Returns "true" on success. On failure, sets *detail to a
 * human-readable description of the error or NULL if no detail is
 * available; ownership of the string is transferred to the caller.
 */
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;
    /*
     * 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;
    }
    /*
     * Open the shared library.  Because we're using a full path, the system
     * doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
     * resolve this library's dependencies though.)
     *
     * Failures here are expected when java.library.path has several entries
     * and we have to hunt for the lib.
     *
     * The current version of the dynamic linker prints detailed information
     * about dlopen() failures.  Some things to check if the message is
     * cryptic:
     *   - make sure the library exists on the device
     *   - verify that the right path is being opened (the debug log message
     *     above can help with that)
     *   - check to see if the library is valid (e.g. not zero bytes long)
     *   - check config/prelink-linux-arm.map to ensure that the library
     *     is listed and is not being overrun by the previous entry (if
     *     loading suddenly stops working on a prelinked library, this is
     *     a good one to check)
     *   - write a trivial app that calls sleep() then dlopen(), attach
     *     to it with "strace -p <pid>" while it sleeps, and watch for
     *     attempts to open nonexistent dependent shared libs
     *
     * This can execute slowly for a large library on a busy system, so we
     * want to switch from RUNNING to VMWAIT while it executes.  This allows
     * the GC to ignore us.
     */
    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 {
            /*
             * 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;
    }
}

做了一些常规的检查,不赘述了,可以看到 [version = (*func)(gDvmJni.jniVm, NULL);] 这里调用了 JNI_OnLoad,上一行是 [ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);],记录一下方便逆向时确定位置。

根据逆向经验 .init(_array) 段定义的内容是在 JNI_OnLoad 之前执行的,而 dlopen 是加载 SO 的函数可能会在这里执行 .init,看一下 dlopen 函数,它的定义在 linker/dlfcn.cpp # 63 行

void* dlopen(const char* filename, int flags) {
  ScopedPthreadMutexLocker locker(&gDlMutex);
  soinfo* result = do_dlopen(filename, flags);
  if (result == NULL) {
    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
    return NULL;
  }
  return result;
}

其实还是调用了 do_dlopen,do_dlopen 的定义在 linker/linker.cpp # 823 行,代码如下。

soinfo* do_dlopen(const char* name, int flags) {
  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {
    DL_ERR("invalid flags to dlopen: %x", flags);
    return NULL;
  }
  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
  soinfo* si = find_library(name);
  if (si != NULL) {
    si->CallConstructors();
  }
  set_soinfo_pool_protection(PROT_READ);
  return si;
}

做了一些检查,*是否符合调用 dlopen 的格式、*是否属于已经加在过的 SO,如果属于之前没有加在过的 SO 就执行 [si->CallConstructors();],看一下 CallConstructors() 的定义。

void soinfo::CallConstructors() {
  if (constructors_called) {
    return;
  }
  // We set constructors_called before actually calling the constructors, otherwise it doesn't
  // protect against recursive constructor calls. One simple example of constructor recursion
  // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
  // 1. The program depends on libc, so libc's constructor is called here.
  // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
  // 3. dlopen() calls the constructors on the newly created
  //    soinfo for libc_malloc_debug_leak.so.
  // 4. The debug .so depends on libc, so CallConstructors is
  //    called again with the libc soinfo. If it doesn't trigger the early-
  //    out above, the libc constructor will be called again (recursively!).
  constructors_called = true;
  if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
    // The GNU dynamic linker silently ignores these, but we warn the developer.
    PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
          name, preinit_array_count);
  }
  if (dynamic != NULL) {
    for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
      if (d->d_tag == DT_NEEDED) {
        const char* library_name = strtab + d->d_un.d_val;
        TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
        find_loaded_library(library_name)->CallConstructors();
      }
    }
  }
  TRACE("\"%s\": calling constructors", name);
  // DT_INIT should be called before DT_INIT_ARRAY if both are present.
  CallFunction("DT_INIT", init_func);
  CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}

重点是最后这的 [CallFunction("DT_INIT", init_func);][CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);],很明显是执行 .init(_array) 定义的内容,这里不贴 CallArray 的代码了,其实还是循环调用了 CallFunction,下面看看 CallFunction 的代码,linker/linker.cpp # 1172 行

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
  if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
    return;
  }
  TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
  function();
  TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);
  // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
  // are still writable. This happens with our debug malloc (see http://b/7941716).
  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
}

看到这行代码 [function();],所以可以确定 .init(_array) 定义的内容最终在这执行。同样记录一下 [TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);] 方便逆向时确定位置。

#2. Reverse Offset

找到具体调用的位置之后下面开始确定这两个位置在 ELF 中的便宜,以便动态调试时下断点,根据上面的分析:
1. JNI_OnLoad 的调用在 vm/Native.cpp 里,对应 /system/lib/libdvm.so。
2. .init(_array) 的调用在 linker/linker.cpp 里,对应 /system/bin/linker。

下面拿 JNI_OnLoad 做例子说明,adb pull 取出手机里的二进制文件 /system/lib/libdvm.so,拉入 IDA,[Shift + F12] 显示所有的字符串,直接找 "[Calling JNI_OnLoad for \"%s\"]" ,找到后如图所示。
跳到 DATA XREF 指向的位置 [dvmLoadNativeCode(char const*,Object *,char **)+1C4 ],汇编码如下,可以看到符合源代码中的调用情况,下图选中的 [BLX R8] 就是调用 JNI_OnLoad 的位置。
可以看到偏移为 [libdvm.so + 0x53A08],所以当载入一个 SO 时只需要在这个偏移量对应的位置下断即可,.inti(_array) 的处理方法也相同,不重复说明了。

#3. 结论

在本次分析的版本 4.4.2_r1 中,只需要在 [/system/bin/linker + 0x274C]
 [/system/lib/libdvm.so + 0x53A08] 这两个位置下断,即可成功找到 .inti / .init_array / JNI_OnLoad 并实现断点。具体效果可以参考 ANDROID 逆向实例(八)- 乐固加固脱壳(2017.01)

#4. Reference

Read More

2017年5月11日星期四

ANDROID 逆向实例(八)- 乐固加固脱壳(2017.01)

虽然乐固做了一些反调试的东西,不过感觉比 Ali 更好分析一点,很多情况并不是反调试越厉害加固就越好。感觉 Ali 在加固方面做的比乐固有意思,Ali 利用 SP 来储存返回地址和参数所以整个流程很混乱,基本看上去就是在各种 JUMP,而乐固仍然是常规规的函数调用和返回方式,流程清晰很多。

另外想说一下,之前把对 Ali 壳的分析发到看雪了,可能因为我只把大概的流程放上去了所以有评论觉得这是一个很没技术的秒脱壳子,至于究竟这个壳子怎么样,好不好脱,完全没有自己尝试过。个人感觉 Ali 的壳子对于初学者来说有很深的学习意义,有不少有意思的细节,建议分析。

* 为了阅读这篇文章时更好理解文中所说内容,下面大多形如 [sub_] [unk_xxx] [loc_xxx] 的函数都经过了重命名以帮助理解。

下面言归正传,下图是反编译 apk 后的 smali 差异,另外还新增了一个 lib 文件夹。
lib 文件夹的格式如下所示。

bugly 相关的可以不用管了,是腾讯的一个类似上报 bug 之类的玩意和加固没有关系。smali 层面只做了一些简单的混淆,不做过多分析了(其实是时间太久忘了 ....),直接进入 Native 层。

直接动态 IDA 挂上,然后在 Linker 和 libdvm.so 对应调用 .init / .init_array / JNI_Onload 的偏移地址下好断点,这样下断点的好处是可以无视 .so 的 ELF 头被修改的情况直达想要的位置,也上个图简单示意一下。

触发断点之后说明运行的到了 .init / .init_array / JNI_Onload 的出发点,然后 F7 即可进入对应 .so ELF 内执行的代码位置。

.init Segment

根据上面说的来到 .init 在 .so 里的位置,偏移量是 [+ 14D4],IDA 静态 + F5 之后得到伪代码,下面是完善函数名之后的伪代码。

int func_init()
{
  unsigned int v0; // ST44_4@11
  unsigned int v1; // ST40_4@11
  unsigned __int64 v2; // kr00_8@13
  _BYTE v3; // ST27_1@15
  _DWORD j; // [sp+14h] [bp-44h]@13
  int v6; // [sp+18h] [bp-40h]@13
  _DWORD v7; // [sp+1Ch] [bp-3Ch]@13
  char v8; // [sp+2Eh] [bp-2Ah]@13
  char v9; // [sp+2Fh] [bp-29h]@13
  char v10; // [sp+30h] [bp-28h]@13
  char v11; // [sp+33h] [bp-25h]@13
  unsigned int v12; // [sp+34h] [bp-24h]@4
  int v13; // [sp+38h] [bp-20h]@4
  unsigned int v14; // [sp+48h] [bp-10h]@4
  unsigned int i; // [sp+4Ch] [bp-Ch]@1

  for ( i = (unsigned int)func_init & 0xFFFFF000; *(_DWORD *)i != 1179403647; i -= 4096 )
    ;
  v14 = 0;
  v13 = i + *(_DWORD *)(i + 28);
  v12 = 0;
  while ( v12 < *(_WORD *)(i + 44) )
  {
    if ( *(_DWORD *)v13 != 1 || *(_DWORD *)(v13 + 24) != 5 )
    {
      if ( *(_DWORD *)v13 == 1 && *(_DWORD *)(v13 + 24) == 6 )
      {
        v0 = *(_DWORD *)(v13 + 8) & 0xFFFFF000;
        v1 = (*(_DWORD *)(v13 + 8) + *(_DWORD *)(v13 + 16) + 4095) & 0xFFFFF000;
        break;
      }
    }
    else
    {
      v14 = (*(_DWORD *)(v13 + 8) + *(_DWORD *)(v13 + 16) + 4095) & 0xFFFFF000;
    }
    ++v12;
    v13 += 32;
  }
  v11 = 43;
  v10 = -103;
  v9 = 32;
  v8 = 21;
  v2 = (unsigned __int64)word_A010[0] << 16;
  v7 = LOWORD(word_A010[0]);
  v6 = LOWORD(word_A010[0]) - (word_A010[0] >> 16);
  mmprotect(
    (void *)(i + HIDWORD(v2)),
    (void *)((v6 + 4095) & 0xFFFFF000),
    &bad_offset_80003911[2],
    (void *)(i + HIDWORD(v2)));
  for ( j = HIDWORD(v2); j <= v7; ++j )
  {
    v3 = *(_BYTE *)(i + j);
    *(_BYTE *)(i + j) ^= (unsigned __int8)((j ^ (v10 - v9)) + v8) ^ v11;
    *(_BYTE *)(i + j) += v10 ^ v9 & v8;
    v11 += v3 & (v10 + v9 - v8) & j;
    v10 += v3 ^ (v11 + j);
    v9 ^= (unsigned __int8)(v3 - v11) ^ (unsigned __int8)j;
    v8 -= v3 + v11 - j;
  }
  mmprotect(
    (void *)(i + HIDWORD(v2)),
    (void *)((v6 + 4095) & 0xFFFFF000),
    (char *)&off_4 + 1,
    (void *)(i + HIDWORD(v2)));
  cacheflush_syscall(i + HIDWORD(v2), v6);
  word_A010[0] = i;
  dword_A380 = i;
  unk_A384 = v14;
  return sub_8630();
}
略过很多字节变换,略过重命名后的 mmprotect() 和 cacheflush_syscall(),整段其实就执行了一个函数 [sub_8630],跟进之后发现伪代码如下。

int sub_8630()
{
  int result; // r0@1
  int v1; // [sp+Ch] [bp-4h]@1

  v1 = 0;
  dword_A378 = 0;
  result = pthread_create(&v1, 0, anti_init, dword_A380);
  unk_A37C = 1;
  return result;
}
可以看出是 pthread_create 新开了一个线程执行 anti_init 函数,进入 F5 查看 anti_init 函数伪代码。


int __fastcall anti_init(int a1)
{
  int v2; // r0@3
  int v3; // r0@3
  int v4; // r1@3
  int v5; // r2@7
  int v6; // r0@10
  int *v7; // r3@14
  int *v8; // r1@15
  int *v9; // r3@15
  int v10; // r2@15
  unsigned int v11; // r0@15
  int *v12; // r3@16
  int v13; // [sp+0h] [bp-480h]@3
  int v14; // [sp+4h] [bp-47Ch]@16
  int v15; // [sp+8h] [bp-478h]@16
  int v16; // [sp+Ch] [bp-474h]@15
  int v17; // [sp+10h] [bp-470h]@14
  int v18; // [sp+14h] [bp-46Ch]@14
  int v19; // [sp+18h] [bp-468h]@12
  int v20; // [sp+1Ch] [bp-464h]@12
  int v21; // [sp+20h] [bp-460h]@9
  int v22; // [sp+24h] [bp-45Ch]@7
  int v23; // [sp+28h] [bp-458h]@7
  int v24; // [sp+2Ch] [bp-454h]@3
  int v25; // [sp+30h] [bp-450h]@3
  int fd_1; // [sp+34h] [bp-44Ch]@3
  int v27; // [sp+38h] [bp-448h]@3
  int v28; // [sp+3Ch] [bp-444h]@1
  void *v29; // [sp+40h] [bp-440h]@1
  char *v30; // [sp+44h] [bp-43Ch]@1
  char *v31; // [sp+48h] [bp-438h]@1
  char v32; // [sp+4Ch] [bp-434h]@1
  char v33; // [sp+4Dh] [bp-433h]@1
  char v34; // [sp+4Eh] [bp-432h]@1
  char v35; // [sp+4Fh] [bp-431h]@1
  char v36; // [sp+50h] [bp-430h]@1
  char v37; // [sp+51h] [bp-42Fh]@1
  char v38; // [sp+52h] [bp-42Eh]@1
  char v39; // [sp+53h] [bp-42Dh]@1
  char v40; // [sp+54h] [bp-42Ch]@1
  char v41; // [sp+55h] [bp-42Bh]@1
  char v42; // [sp+56h] [bp-42Ah]@1
  char v43; // [sp+57h] [bp-429h]@1
  char v44; // [sp+58h] [bp-428h]@1
  char v45; // [sp+59h] [bp-427h]@1
  char v46; // [sp+5Ah] [bp-426h]@1
  char v47; // [sp+5Bh] [bp-425h]@1
  int fd; // [sp+5Ch] [bp-424h]@3
  char v49; // [sp+60h] [bp-420h]@7
  int v50; // [sp+460h] [bp-20h]@4
  int v51; // [sp+464h] [bp-1Ch]@1
  int v52; // [sp+468h] [bp-18h]@1
  int v53; // [sp+46Ch] [bp-14h]@1

  v53 = a1;
  ++dword_A378;
  v31 = (char *)&GLOBAL_OFFSET_TABLE_;
  v30 = &v32;
  v29 = &GLOBAL_OFFSET_TABLE_;
  unk_A388 = malloc(1024);
  v28 = sub_6DEC();
  unk_A38C = sub_6EE0(0, v53, unk_A384);
  v52 = getppid();
  v38 = unk_A2B7 ^ 0x96;
  v37 = unk_A2B6 ^ 0xF7;
  v46 = unk_A2BF ^ 0xE1;
  v39 = unk_A2B8 ^ 0x9E;
  v34 = unk_A2B3 ^ 0x85;
  v41 = unk_A2BA ^ 0x98;
  v47 = unk_A2C0;
  v32 = unk_A2B1 ^ 0xE9;
  v35 = unk_A2B4 ^ 0x95;
  v44 = unk_A2BD ^ 0xCA;
  v42 = unk_A2BB ^ 0xEE;
  v36 = unk_A2B5 ^ 0xEA;
  v33 = unk_A2B2 ^ 0x97;
  v45 = unk_A2BE ^ 0xD3;
  v43 = unk_A2BC ^ 0xA5;
  v40 = unk_A2B9 ^ 0xFB;
  v51 = opendir(&v32);
  if ( v51 )
  {
    v27 = 0xFFF;
    fd = inotify_init();
    fd_1 = fd;
    v2 = fcntl(fd, 3, 0);
    v3 = fcntl(fd_1, 4, v2 | 0x800);
    v4 = fd;
    *((_BYTE *)&v13 - 4) = byte_A2CD ^ 0x8D;
    *((_BYTE *)&v13 - 16) = unk_A2C1 ^ 0x91;
    *((_BYTE *)&v13 - 7) = byte_A2CA ^ 0xC5;
    *((_BYTE *)&v13 - 5) = byte_A2CC ^ 0x80;
    *((_BYTE *)&v13 - 13) = byte_A2C4 ^ 0xD3;
    *((_BYTE *)&v13 - 11) = byte_A2C6 ^ 0xE1;
    *((_BYTE *)&v13 - 9) = byte_A2C8 ^ 0xCA;
    *((_BYTE *)&v13 - 8) = byte_A2C9 ^ 0xE1;
    *((_BYTE *)&v13 - 2) = byte_A2CF;
    *((_BYTE *)&v13 - 3) = byte_A2CE ^ 0xC8;
    *((_BYTE *)&v13 - 15) = byte_A2C2 ^ 0x87;
    *((_BYTE *)&v13 - 14) = byte_A2C3 ^ 0x96;
    *((_BYTE *)&v13 - 6) = byte_A2CB ^ 0xDA;
    *((_BYTE *)&v13 - 12) = byte_A2C5 ^ 0xD7;
    *((_BYTE *)&v13 - 10) = byte_A2C7 ^ 0xE2;
    v25 = v3;
    v24 = inotify_add_watch(v4, (int)(&v13 - 4), v27);// "/proc/self/mem"
    while ( 1 )
    {
      v50 = readdir(v51);
      if ( !v50 )
        break;
      if ( *(_BYTE *)(v50 + 18) & 4 && 46 != *(_BYTE *)(v50 + 19) )
      {
        v5 = v50 + 19;
        *((_BYTE *)&v13 - 11) = byte_A2DD ^ 0x8D;
        *((_BYTE *)&v13 - 22) = byte_A2D2 ^ 0x9E;
        *((_BYTE *)&v13 - 12) = byte_A2DC ^ 0xAD;
        *((_BYTE *)&v13 - 5) = byte_A2E3 ^ 0x86;
        *((_BYTE *)&v13 - 14) = byte_A2DA ^ 0xE1;
        *((_BYTE *)&v13 - 8) = byte_A2E0 ^ 0xB1;
        *((_BYTE *)&v13 - 10) = byte_A2DE ^ 0xCF;
        *((_BYTE *)&v13 - 15) = byte_A2D9 ^ 0xB1;
        *((_BYTE *)&v13 - 24) = unk_A2D0 ^ 0xA7;
        *((_BYTE *)&v13 - 13) = byte_A2DB ^ 0xE0;
        *((_BYTE *)&v13 - 7) = byte_A2E1 ^ 0xA2;
        *((_BYTE *)&v13 - 3) = byte_A2E5 ^ 0xAB;
        *((_BYTE *)&v13 - 17) = byte_A2D7 ^ 0x8B;
        *((_BYTE *)&v13 - 2) = byte_A2E6;
        *((_BYTE *)&v13 - 23) = byte_A2D1 ^ 0xBD;
        *((_BYTE *)&v13 - 19) = byte_A2D5 ^ 0xCC;
        *((_BYTE *)&v13 - 9) = byte_A2DF ^ 0xA7;
        *((_BYTE *)&v13 - 6) = byte_A2E2 ^ 0xE6;
        *((_BYTE *)&v13 - 16) = byte_A2D8 ^ 0xA0;
        *((_BYTE *)&v13 - 18) = byte_A2D6 ^ 0x83;
        *((_BYTE *)&v13 - 21) = byte_A2D3 ^ 0x98;
        *((_BYTE *)&v13 - 20) = byte_A2D4 ^ 0xD2;
        *((_BYTE *)&v13 - 4) = byte_A2E4 ^ 0xC2;
        v23 = sprintf(&v49, &v13 - 6, v5);
        v22 = inotify_add_watch(fd, (int)&v49, 0xFFF);// "/proc/self/task/{pid}/mem"
      }
    }
    v21 = closedir(v51);
    while ( 1 )
    {
      *((_BYTE *)&v13 - 12) = byte_A2FC ^ 0x8D;
      *((_BYTE *)&v13 - 7) = byte_A301;
      *((_BYTE *)&v13 - 10) = byte_A2FE ^ 0xD9;
      *((_BYTE *)&v13 - 14) = byte_A2FA ^ 0xD2;
      *((_BYTE *)&v13 - 17) = byte_A2F7 ^ 0xD5;
      *((_BYTE *)&v13 - 19) = byte_A2F5 ^ 0x81;
      *((_BYTE *)&v13 - 22) = byte_A2F2 ^ 0xB8;
      *((_BYTE *)&v13 - 13) = byte_A2FB ^ 0xC7;
      *((_BYTE *)&v13 - 15) = byte_A2F9 ^ 0xB2;
      *((_BYTE *)&v13 - 18) = byte_A2F6 ^ 0x81;
      *((_BYTE *)&v13 - 24) = unk_A2F0 ^ 0x9C;
      *((_BYTE *)&v13 - 9) = byte_A2FF ^ 0x95;
      *((_BYTE *)&v13 - 20) = byte_A2F4 ^ 0x82;
      *((_BYTE *)&v13 - 23) = byte_A2F1 ^ 0x81;
      *((_BYTE *)&v13 - 16) = byte_A2F8 ^ 0x88;
      *((_BYTE *)&v13 - 8) = byte_A300 ^ 0xC;
      *((_BYTE *)&v13 - 11) = byte_A2FD ^ 0xC8;
      *((_BYTE *)&v13 - 21) = byte_A2F3 ^ 0x8B;
      v6 = chk_status_trace((int)(&v13 - 6));
      if ( v6 != v52 )
      {
        *((_BYTE *)&v13 - 16) = byte_A2F8 ^ 0x88;
        *((_BYTE *)&v13 - 11) = byte_A2FD ^ 0xC8;
        *((_BYTE *)&v13 - 24) = unk_A2F0 ^ 0x9C;
        *((_BYTE *)&v13 - 9) = byte_A2FF ^ 0x95;
        *((_BYTE *)&v13 - 8) = byte_A300 ^ 0xC;
        *((_BYTE *)&v13 - 10) = byte_A2FE ^ 0xD9;
        *((_BYTE *)&v13 - 20) = byte_A2F4 ^ 0x82;
        *((_BYTE *)&v13 - 18) = byte_A2F6 ^ 0x81;
        *((_BYTE *)&v13 - 7) = byte_A301;
        *((_BYTE *)&v13 - 23) = byte_A2F1 ^ 0x81;
        *((_BYTE *)&v13 - 19) = byte_A2F5 ^ 0x81;
        *((_BYTE *)&v13 - 14) = byte_A2FA ^ 0xD2;
        *((_BYTE *)&v13 - 21) = byte_A2F3 ^ 0x8B;
        *((_BYTE *)&v13 - 12) = byte_A2FC ^ 0x8D;
        *((_BYTE *)&v13 - 13) = byte_A2FB ^ 0xC7;
        *((_BYTE *)&v13 - 22) = byte_A2F2 ^ 0xB8;
        *((_BYTE *)&v13 - 15) = byte_A2F9 ^ 0xB2;
        *((_BYTE *)&v13 - 17) = byte_A2F7 ^ 0xD5;
        if ( chk_status_trace((int)(&v13 - 6)) )
        {
          *((_BYTE *)&v13 - 6) = byte_A304 ^ 0xAF;
          *((_BYTE *)&v13 - 8) = unk_A302 ^ 0x8B;
          *((_BYTE *)&v13 - 5) = byte_A305 ^ 0xF3;
          *((_BYTE *)&v13 - 7) = byte_A303 ^ 0xCB;
          *((_BYTE *)&v13 - 4) = byte_A306;
          *((_BYTE *)&v13 - 13) = byte_A30A ^ 0xA7;
          *((_BYTE *)&v13 - 5) = byte_A312 ^ 0xDF;
          *((_BYTE *)&v13 - 9) = byte_A30E ^ 0xB5;
          *((_BYTE *)&v13 - 14) = byte_A309 ^ 0xAC;
          *((_BYTE *)&v13 - 4) = byte_A313 ^ 0x80;
          *((_BYTE *)&v13 - 8) = byte_A30F ^ 0xFD;
          *((_BYTE *)&v13 - 12) = byte_A30B ^ 0xC4;
          *((_BYTE *)&v13 - 7) = byte_A310 ^ 0xE7;
          *((_BYTE *)&v13 - 15) = byte_A308 ^ 0xC5;
          *((_BYTE *)&v13 - 16) = unk_A307 ^ 0xA9;
          *((_BYTE *)&v13 - 3) = byte_A314;
          *((_BYTE *)&v13 - 6) = byte_A311 ^ 0x9E;
          *((_BYTE *)&v13 - 10) = byte_A30D ^ 0x93;
          *((_BYTE *)&v13 - 11) = byte_A30C ^ 0xE3;
          v20 = log(6, (int)(&v13 - 2), (int)(&v13 - 4), (int)(&v13 - 4));// log("anti", "tracer antied")
          v19 = j_raise(0x11);
        }
      }
      if ( read(fd, &v49, dword_400) > 0 )
      {
        *((_BYTE *)&v13 - 5) = byte_A305 ^ 0xF3;
        *((_BYTE *)&v13 - 4) = byte_A306;
        *((_BYTE *)&v13 - 6) = byte_A304 ^ 0xAF;
        *((_BYTE *)&v13 - 8) = unk_A302 ^ 0x8B;
        *((_BYTE *)&v13 - 7) = byte_A303 ^ 0xCB;
        v7 = &v13 - 4;
        *(_BYTE *)v7 = unk_A315 ^ 0xB2;
        *((_BYTE *)&v13 - 14) = unk_A317 ^ 0xA6;
        *((_BYTE *)&v13 - 11) = unk_A31A ^ 0xBF;
        *((_BYTE *)&v13 - 13) = unk_A318 ^ 0xBA;
        *((_BYTE *)&v13 - 15) = unk_A316 ^ 0xAF;
        *((_BYTE *)&v13 - 10) = unk_A31B ^ 0x92;
        *((_BYTE *)&v13 - 7) = unk_A31E ^ 0xE7;
        *((_BYTE *)&v13 - 9) = unk_A31C ^ 0x9E;
        *((_BYTE *)&v13 - 8) = unk_A31D ^ 0xB3;
        *((_BYTE *)&v13 - 12) = unk_A319 ^ 0xDD;
        *((_BYTE *)v7 + 10) = unk_A31F;
        v18 = log(6, (int)(&v13 - 2), (int)(&v13 - 4), (int)(&v13 - 4));// log("anti", "mem antied")
        v17 = j_raise(17);
      }
      v8 = (int *)&v31[(_DWORD)&dword_438[2]];
      v9 = (int *)&v31[(_DWORD)&dword_438[4]];
      ++*((_DWORD *)v31 + 269);
      v10 = *v8;
      v16 = *v9;
      v11 = sub_6EE0(0, v53, v10);
      if ( v16 != v11 )
      {
        *((_BYTE *)&v13 - 5) = byte_A305 ^ 0xF3;
        *((_BYTE *)&v13 - 4) = byte_A306;
        *((_BYTE *)&v13 - 7) = byte_A303 ^ 0xCB;
        *((_BYTE *)&v13 - 8) = unk_A302 ^ 0x8B;
        *((_BYTE *)&v13 - 6) = byte_A304 ^ 0xAF;
        v12 = &v13 - 2;
        *(_BYTE *)v12 = unk_A320 ^ 0xCD;
        *((_BYTE *)&v13 - 6) = unk_A322 ^ 0x87;
        *((_BYTE *)&v13 - 7) = unk_A321 ^ 0xDF;
        *((_BYTE *)&v13 - 3) = unk_A325 ^ 0xF5;
        *((_BYTE *)&v13 - 5) = unk_A323 ^ 0xFC;
        *((_BYTE *)&v13 - 2) = unk_A326;
        *((_BYTE *)v12 + 4) = unk_A324 ^ 0x1A;
        v15 = log(6, (int)(&v13 - 2), (int)(&v13 - 2), (int)(&v13 - 2));// log("anti", "antied")
        v14 = j_raise(17);
      }
      v13 = sleep(1);
    }
  }
  return 0;
}
一个 while(1) 的循环里面根据不同的条件执行了 3 个 raise() 函数,不用管具体是根据什么样的 3 个条件执行的 raise() 操作,直接把 [BL  j_raise] 对应 arm 十六进制码修改成 [Mov R1, R1] 就可以达到 Bypass 反调试的目的,这个地方殊途同归的 Bypass 方法很多。

JNI_Onload

还是同样的方法来到 JNI_Onload 在 .so 里的位置,偏移量是 [+ 65A8],IDA 静态查看,F5 之后得到对应的伪代码,动态 + 静态结合调试可以还原出大部分函数的真实目的。

void *__fastcall JNI_Onload(JavaVM *J_vm_1, JNIEnv *J_env)
{
  char v2; // r0@5
  int v3; // r0@9
  int v4; // lr@9
  int v5; // r3@9
  char *v6; // r12@9
  int v8; // [sp+0h] [bp-450h]@8
  int (__fastcall *J_dlsym_1)(_DWORD, _DWORD); // [sp+4h] [bp-44Ch]@10
  int v10; // [sp+8h] [bp-448h]@9
  int v11; // [sp+Ch] [bp-444h]@9
  int v12; // [sp+10h] [bp-440h]@9
  int *v13; // [sp+14h] [bp-43Ch]@9
  int a1a; // [sp+18h] [bp-438h]@9
  int v15; // [sp+1Ch] [bp-434h]@8
  int v16; // [sp+20h] [bp-430h]@4
  char *v17; // [sp+24h] [bp-42Ch]@1
  int v18; // [sp+28h] [bp-428h]@1
  int (__fastcall *J_dlsym)(_DWORD, _DWORD); // [sp+2Ch] [bp-424h]@9
  int v20; // [sp+30h] [bp-420h]@9
  int v21; // [sp+34h] [bp-41Ch]@2
  JNIEnv *J_env_1; // [sp+434h] [bp-1Ch]@1
  JavaVM *J_vm; // [sp+438h] [bp-18h]@1
  void *v24; // [sp+43Ch] [bp-14h]@10

  J_vm = J_vm_1;
  J_env_1 = J_env;
  v18 = 0;
  v17 = (char *)&GLOBAL_OFFSET_TABLE_;
  while ( sub_69D4((int)&v21) )
    ;
  if ( !*(_DWORD *)&v17[(_DWORD)dword_438] )
    v16 = j_raise(9);
  while ( 1 )
  {
    v2 = 0;
    if ( *((_DWORD *)v17 + 269) )
      v2 = 1;
    if ( !(((unsigned __int8)v2 ^ 1) & 1) )
      break;
    *((_BYTE *)&v8 - 7) = byte_A025 ^ 0xB5;
    *((_BYTE *)&v8 - 8) = unk_A024 ^ 0x81;
    *((_BYTE *)&v8 - 4) = byte_A028 ^ 0xEC;
    *((_BYTE *)&v8 - 6) = byte_A026 ^ 0xD4;
    *((_BYTE *)&v8 - 5) = byte_A027 ^ 0xF5;
    *((_BYTE *)&v8 - 3) = byte_A029;
    *((_BYTE *)&v8 - 13) = byte_A207 ^ 0xBA;
    *((_BYTE *)&v8 - 6) = byte_A20E;
    *((_BYTE *)&v8 - 14) = byte_A206 ^ 0x88;
    *((_BYTE *)&v8 - 8) = byte_A20C ^ 0xD3;
    *((_BYTE *)&v8 - 9) = byte_A20B ^ 0xC9;
    *((_BYTE *)&v8 - 12) = byte_A208 ^ 0xF7;
    *((_BYTE *)&v8 - 11) = byte_A209 ^ 0xED;
    *((_BYTE *)&v8 - 16) = unk_A204 ^ 0xD1;
    *((_BYTE *)&v8 - 10) = byte_A20A ^ 0xEB;
    *((_BYTE *)&v8 - 15) = byte_A205 ^ 0xCE;
    *((_BYTE *)&v8 - 7) = byte_A20D ^ 0x91;
    v15 = log(6, (int)(&v8 - 2), (int)(&v8 - 4), (int)(&v8 - 4));
  }
  a1a = 6;
  v13 = &v21;
  v12 = (int)&GLOBAL_OFFSET_TABLE_;
  v11 = J_unzip((int)&v21, dword_A020);
  v3 = linker_method_dlopen((int)v13, 0);
  v20 = v3;
  v4 = v12 + 715;
  *((_BYTE *)&v8 - 6) = *(_BYTE *)(v12 + 725);
  *((_BYTE *)&v8 - 14) = *(_BYTE *)(v4 + 2) ^ 0x8B;
  *((_BYTE *)&v8 - 7) = *(_BYTE *)(v4 + 9) ^ 0xB0;
  *((_BYTE *)&v8 - 8) = *(_BYTE *)(v4 + 8) ^ 0x92;
  *((_BYTE *)&v8 - 9) = *(_BYTE *)(v4 + 7) ^ 0xCD;
  *((_BYTE *)&v8 - 11) = *(_BYTE *)(v4 + 5) ^ 0x84;
  *((_BYTE *)&v8 - 16) = *(_BYTE *)v4 ^ 0xC3;
  *((_BYTE *)&v8 - 12) = *(_BYTE *)(v4 + 4) ^ 0xA4;
  *((_BYTE *)&v8 - 13) = *(_BYTE *)(v4 + 3) ^ 0xB1;
  *((_BYTE *)&v8 - 10) = *(_BYTE *)(v4 + 6) ^ 0x9E;
  *((_BYTE *)&v8 - 15) = *(_BYTE *)(v4 + 1) ^ 0xDB;
  J_dlsym = (int (__fastcall *)(_DWORD, _DWORD))linker_method_dlsym(v3, &v8 - 4);
  v5 = v12;
  v6 = &byte_E0[v12];
  *((_BYTE *)&v8 - 7) = byte_E0[v12 + 1] ^ 0xB5;
  *((_BYTE *)&v8 - 4) = v6[4] ^ 0xEC;
  *((_BYTE *)&v8 - 3) = v6[5];
  *((_BYTE *)&v8 - 8) = *v6 ^ 0x81;
  *((_BYTE *)&v8 - 5) = v6[3] ^ 0xF5;
  *((_BYTE *)&v8 - 6) = v6[2] ^ 0xD4;
  *((_BYTE *)&v8 - 13) = *(_BYTE *)(v5 + 729) ^ 0xAA;
  *((_BYTE *)&v8 - 12) = *(_BYTE *)(v5 + 730) ^ 0xBE;
  *((_BYTE *)&v8 - 16) = *(_BYTE *)(v5 + 726) ^ 0x84;
  *((_BYTE *)&v8 - 15) = *(_BYTE *)(v5 + 727) ^ 0xC9;
  *((_BYTE *)&v8 - 6) = *(_BYTE *)(v5 + 736);
  *((_BYTE *)&v8 - 7) = *(_BYTE *)(v5 + 735) ^ 0xC7;
  *((_BYTE *)&v8 - 14) = *(_BYTE *)(v5 + 728) ^ 0xA9;
  *((_BYTE *)&v8 - 11) = *(_BYTE *)(v5 + 731) ^ 0x95;
  *((_BYTE *)&v8 - 9) = *(_BYTE *)(v5 + 733) ^ 0x9D;
  *((_BYTE *)&v8 - 10) = *(_BYTE *)(v5 + 732) ^ 0xDC;
  *((_BYTE *)&v8 - 8) = *(_BYTE *)(v5 + 734) ^ 0xC2;
  v10 = log(a1a, (int)(&v8 - 2), (int)(&v8 - 4), v5);// log("txtag", "load done!")
  if ( J_dlsym )
  {
    J_dlsym_1 = J_dlsym;
    v24 = (void *)J_dlsym(J_vm, J_env_1);
  }
  else
  {
    v24 = &unk_10004;
  }
  return v24;
}

这里需要重点关注的就是 dlopen 和 dlsym 这两个函数了,其他都是一些字符串操作的内容,对脱壳没有什么太大的帮助。其实 dlopen 都不需要关注了,直接在最后 J_dlsym 这里下断,F7 。

跳到 .so 外的一段内存,很明显是程序 mmap 进来的内容,至于怎么 mmap mmprotect 之类的不需要做太多关注,看看这里指向代码的功能。

debug150:7860E800 ; signed int __fastcall sub_7860E800(int a1, int)
debug150:7860E800 sub_7860E800
debug150:7860E800
debug150:7860E800 var_14= -0x14
debug150:7860E800
debug150:7860E800 PUSH            {R0,R1,R4-R6,LR}
debug150:7860E802 ADD             R5, SP, #0x18+var_14
debug150:7860E804 MOVS            R4, #0
debug150:7860E806 MOVS            R1, R5
debug150:7860E808 LDR             R2, =0x10006
debug150:7860E80A MOVS            R6, R0
debug150:7860E80C STR             R4, [SP,#0x18+var_14]
debug150:7860E80E BL              J_getEnv
debug150:7860E812 CMP             R0, R4
debug150:7860E814 BEQ             loc_7860E844
debug150:7860E816 MOVS            R0, R6
debug150:7860E818 MOVS            R1, R5
debug150:7860E81A LDR             R2, =0x10004
debug150:7860E81C BL              J_getEnv
debug150:7860E820 CMP             R0, R4
debug150:7860E822 BEQ             loc_7860E848
debug150:7860E824 MOVS            R0, R6
debug150:7860E826 MOVS            R1, R5
debug150:7860E828 LDR             R2, =0x10002
debug150:7860E82A BL              J_getEnv
debug150:7860E82E CMP             R0, R4
debug150:7860E830 BEQ             loc_7860E84C
debug150:7860E832 MOVS            R0, R6
debug150:7860E834 MOVS            R1, R5
debug150:7860E836 LDR             R2, =0x10001
debug150:7860E838 BL              J_getEnv
debug150:7860E83C CMP             R0, R4
debug150:7860E83E BNE             loc_7860E85E
debug150:7860E840 LDR             R4, =0x10001
debug150:7860E842 B               loc_7860E84E
debug150:7860E844 ; ---------------------------------------------------------------------------
debug150:7860E844
debug150:7860E844 loc_7860E844                            ; CODE XREF: sub_7860E800+14 j
debug150:7860E844 LDR             R4, =0x10006
debug150:7860E846 B               loc_7860E84E
debug150:7860E848 ; ---------------------------------------------------------------------------
debug150:7860E848
debug150:7860E848 loc_7860E848                            ; CODE XREF: sub_7860E800+22 j
debug150:7860E848 LDR             R4, =0x10004
debug150:7860E84A B               loc_7860E84E
debug150:7860E84C ; ---------------------------------------------------------------------------
debug150:7860E84C
debug150:7860E84C loc_7860E84C                            ; CODE XREF: sub_7860E800+30 j
debug150:7860E84C LDR             R4, =0x10002
debug150:7860E84E
debug150:7860E84E loc_7860E84E                            ; CODE XREF: sub_7860E800+42 j
debug150:7860E84E                                         ; sub_7860E800+46 j ...
debug150:7860E84E LDR             R3, [SP,#0x18+var_14]
debug150:7860E850 CMP             R3, #0
debug150:7860E852 BEQ             loc_7860E85E
debug150:7860E854 BL              sub_7860EFB8
debug150:7860E858 LDR             R0, [SP,#0x18+var_14]
debug150:7860E85A BL              sub_7860E7C4
debug150:7860E85E
debug150:7860E85E loc_7860E85E                            ; CODE XREF: sub_7860E800+3E j
debug150:7860E85E                                         ; sub_7860E800+52 j
debug150:7860E85E MOVS            R0, R4
debug150:7860E860 POP             {R1,R2,R4-R6,PC}
debug150:7860E860 ; End of function sub_7860E800

还是一堆的赋值操作略过不表,看到执行了 [sub_7860EFB8] [sub_7860E7C4] 两个函数,分别看下这两个函数的作用。

signed int sub_7860EFB8()
{
  signed int result; // r0@1

  dword_78626068 = (int)"java/lang/String";
  dword_7862606C = (int)"getBytes";
  dword_78626070 = (int)"()[B";
  dword_78626074 = (int)"android/os/Build$VERSION";
  dword_78626078 = (int)"SDK_INT";
  dword_7862607C = (int)"I";
  .......
}

[sub_7860EFB8] 全是赋值操作,跳过。下面是 F5 之后的 [sub_7860E7C4],主要执行了 [sub_7860E780] 然后打了个 registerNatives 相关的 log,相信作安卓逆向的同学看到 registerNative 这个都会比较激动吧,跟进 [sub_7860E780]。

int __fastcall sub_7860E7C4(int a1)
{
  int v1; // r4@1

  v1 = sub_7860E780(a1, (int)"com/tencent/StubShell/TxAppEntry", (int)&dword_78626004, 5);
  if ( v1 )
    v1 = 1;
  else
    log(3, (int)"SecShell", (int)"registerNatives Fail");
  return v1;
}
F5 后的伪代码如下,亮眼的 0x35C 刚好是 registerNatives 相对于 JNINativeInterface 的偏移量,在这下断。

int __fastcall sub_7860E780(int a1, int a2, int a3, int a4)
{
  int v4; // r5@1
  int v5; // r4@1
  int v6; // r6@1
  int J_env; // r1@1
  int v8; // r0@2

  v4 = a4;
  v5 = a1;
  v6 = a3;
  J_env = (*(int (**)(void))(*(_DWORD *)a1 + 0x18))();
  if ( J_env )
  {
    v8 = (*(int (__fastcall **)(int, int, int, int))(*(_DWORD *)v5 + 0x35C))(v5, J_env, v6, v4);// registerNatives
    J_env = 1;
    if ( v8 < 0 )
    {
      ((void (__fastcall *)(signed int, const char *, const char *))log)(3, "SecShell", "register nativers error");
      J_env = 0;
    }
  }
  return J_env;
}
触发断点之后查看 R2 寄存器的值。

可以看到这里的 RegisterNative 是用来注册 native load(Landroid/content/Context;)V ,函数指针则是 [0x78615839],至于为什么是这样可以参考我 *这一篇文章

接着跳入 [0x78615839],这里我直接贴 F5 + 重命名函数之后的伪代码了,如图。
可以看到先检查了 * sdk 版本,* dalvik Or art,* 是否存在 zjdroid,然后在分别进行了两个操作,因为这里我用的是 dalvik,所以跟进 [sub_78614CD0]。

_DWORD *__fastcall sub_78614CD0(int a1, int a2)
{
  int v2; // r6@1
  int v3; // r0@2
  int v4; // r4@2
  int v5; // r0@2
  int v6; // r0@2
  int v7; // r0@2
  int v8; // r7@2
  int v9; // ST00_4@3
  int v10; // ST00_4@3
  int v11; // r0@5
  int v12; // ST00_4@5
  const char *v13; // r1@6
  const char *v14; // r2@6
  int v15; // r0@16
  int v16; // r5@16
  _DWORD *v17; // r3@16
  int v18; // ST00_4@17
  int v19; // ST00_4@18
  const char *v20; // r1@20
  const char *v21; // r2@20
  int v22; // r4@21
  int v23; // r1@23
  const char *v24; // r1@24
  const char *v25; // r2@24
  int v26; // r4@33
  int v27; // ST14_4@37
  int v28; // r0@37
  int v29; // r1@38
  int v30; // r1@41
  int v31; // r5@44
  int v32; // ST00_4@44
  int v33; // r0@45
  int v34; // ST00_4@46
  int v35; // r5@46
  int v36; // r4@50
  int v37; // r4@54
  int v38; // r2@58
  int v39; // r3@59
  int v40; // r3@61
  int v41; // r3@62
  int v42; // r3@63
  int v43; // r3@64
  int v44; // r5@64
  int v45; // r2@65
  int v46; // r5@77
  int v47; // r2@77
  int v48; // r0@79
  int v49; // r0@80
  int v50; // r4@82
  int v51; // r0@84
  int v52; // r5@84
  int m; // r4@84
  int v54; // r0@86
  int v55; // r2@87
  const char *v56; // r3@87
  int v57; // ST00_4@87
  int v58; // r1@87
  int v59; // r0@87
  int v60; // r0@76
  int i; // r4@76
  int v62; // r0@90
  int v63; // r5@92
  int v64; // r0@92
  int j; // r4@92
  int v66; // r0@94
  int v67; // r5@95
  int v68; // r4@95
  int v69; // r0@95
  int k; // r4@95
  int v71; // r0@97
  int v72; // r5@98
  int v73; // r4@98
  int v74; // r7@98
  int v75; // r0@98
  int v76; // r0@98
  int l; // r4@98
  int v78; // r0@100
  int v79; // r4@101
  int v80; // r0@101
  int v81; // ST00_4@102
  int v82; // ST00_4@103
  _DWORD *result; // r0@105
  int v84; // [sp+10h] [bp-120h]@3
  int v85; // [sp+10h] [bp-120h]@76
  int v86; // [sp+14h] [bp-11Ch]@5
  signed int v87; // [sp+14h] [bp-11Ch]@37
  int v88; // [sp+14h] [bp-11Ch]@95
  int v89; // [sp+18h] [bp-118h]@3
  int v90; // [sp+18h] [bp-118h]@13
  int v91; // [sp+1Ch] [bp-114h]@1
  signed int v92; // [sp+20h] [bp-110h]@33
  int v93; // [sp+20h] [bp-110h]@51
  int v94; // [sp+24h] [bp-10Ch]@3
  int v95; // [sp+24h] [bp-10Ch]@95
  int v96; // [sp+28h] [bp-108h]@2
  signed int v97; // [sp+28h] [bp-108h]@51
  int v98; // [sp+2Ch] [bp-104h]@3
  int v99; // [sp+30h] [bp-100h]@13
  int v100; // [sp+34h] [bp-FCh]@13
  int v101; // [sp+38h] [bp-F8h]@2
  int v102; // [sp+3Ch] [bp-F4h]@13
  int v103; // [sp+40h] [bp-F0h]@37
  int v104; // [sp+44h] [bp-ECh]@3
  int v105; // [sp+4Ch] [bp-E4h]@92
  int v106; // [sp+54h] [bp-DCh]@2
  _DWORD *v107; // [sp+5Ch] [bp-D4h]@1
  int v108; // [sp+60h] [bp-D0h]@34
  char v109; // [sp+64h] [bp-CCh]@37
  char v110; // [sp+74h] [bp-BCh]@50
  int v111; // [sp+88h] [bp-A8h]@50
  char v112; // [sp+8Ch] [bp-A4h]@50
  int v113; // [sp+A0h] [bp-90h]@50
  char v114; // [sp+A4h] [bp-8Ch]@34
  int v115; // [sp+B8h] [bp-78h]@34
  signed int v116; // [sp+C4h] [bp-6Ch]@37
  int v117; // [sp+114h] [bp-1Ch]@1

  v2 = a1;
  v117 = *(_DWORD *)dword_78625DA4;
  v107 = (_DWORD *)dword_78625DA4;
  v91 = J_GC_object_method(
          a1,
          a2,
          (int)"android/content/Context",
          (int)"getClassLoader",
          (int)"()Ljava/lang/ClassLoader;");
  if ( !v91 )
    goto LABEL_105;
  v3 = getEnv(v2, "com/tencent/StubShell/TxAppEntry");
  v4 = v3;
  v5 = ((int (__fastcall *)(int, int, const char *, const char *))unk_78610864)(
         v2,
         v3,
         "mSrcPath",
         "Ljava/lang/String;");
  v101 = ((int (__fastcall *)(int, int, int))unk_78610872)(v2, v4, v5);
  v96 = J_copy_array_bytes(v2, v101);
  v6 = ((int (__fastcall *)(int, int, const char *, const char *))unk_78610864)(v2, v4, "mPKName", "Ljava/lang/String;");
  v7 = ((int (__fastcall *)(int, int, int))unk_78610872)(v2, v4, v6);
  v106 = J_copy_array_bytes(v2, v7);
  v8 = unk_78625DA8;
  if ( J_sdk_int > 10 )
  {
    v9 = *(_DWORD *)(unk_78625DA8 + 248);
    v104 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EE40)(
             v2,
             v91,
             *(_DWORD *)(unk_78625DA8 + 240),
             *(_DWORD *)(unk_78625DA8 + 244));
    v10 = *(_DWORD *)(v8 + 220);
    v98 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EE40)(
            v2,
            v104,
            *(_DWORD *)(v8 + 212),
            *(_DWORD *)(v8 + 216));
    v94 = J_getArrayLength(v2, v98);
    v89 = 0;
    v84 = 0;
    while ( 1 )
    {
      if ( v89 >= v94 )
      {
        v90 = 0;
        v102 = 0;
        v100 = 0;
        v99 = 0;
        goto LABEL_33;
      }
      v11 = ((int (__fastcall *)(int, int, int))unk_7860E8C8)(v2, v98, v89);
      v12 = *(_DWORD *)(v8 + 232);
      v86 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860EE40)(
              v2,
              v11,
              *(_DWORD *)(v8 + 224),
              *(_DWORD *)(v8 + 228));
      if ( !v86 )
        break;
      v84 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F770)(
              v2,
              v86,
              *(_DWORD *)(v8 + 236),
              *(_DWORD *)(v8 + 424));
      if ( !v84 )
      {
        v84 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F950)(
                v2,
                v86,
                *(_DWORD *)(v8 + 236),
                *(_DWORD *)(v8 + 424));
        if ( !v84 )
        {
          v13 = "SecShell";
          v14 = "SetDexClassLoaderCookie GetIntField fail";
          goto LABEL_10;
        }
      }
      if ( *(_DWORD *)v84 && !((int (__cdecl *)(_DWORD))unk_7861EC2C)(*(_DWORD *)v84) )
      {
        v90 = 0;
        v102 = 0;
        v100 = 0;
        v99 = 0;
        goto LABEL_33;
      }
LABEL_14:
      ++v89;
    }
    v13 = "SecShell";
    v14 = "SetDexClassLoaderCookie GetObjectField fail:pDexPathListElementsClassName";
LABEL_10:
    log(3, (int)v13, (int)v14);
    goto LABEL_14;
  }
  v15 = getEnv(v2, *(_DWORD *)(unk_78625DA8 + 456));
  v16 = ((int (__fastcall *)(int, int, int))unk_7861080E)(v2, v91, v15);
  v17 = (_DWORD *)(v8 + 204);
  if ( v16 )
  {
    v18 = *(_DWORD *)(v8 + 208);
    v99 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860EE40)(v2, v91, *(_DWORD *)(v8 + 456), *v17);
    v100 = ((int (__fastcall *)(int, int, _DWORD, const char *))unk_7860EE40)(v2, v91, *(_DWORD *)(v8 + 456), "mPaths");
    v102 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
             v2,
             v91,
             *(_DWORD *)(v8 + 456),
             "mFiles");
    v90 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
            v2,
            v91,
            *(_DWORD *)(v8 + 456),
            "mZips");
  }
  else
  {
    v19 = *(_DWORD *)(v8 + 208);
    v99 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860EE40)(v2, v91, *(_DWORD *)(v8 + 200), *v17);
    v102 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
             v2,
             v91,
             *(_DWORD *)(v8 + 200),
             "mFiles");
    v100 = 0;
    v90 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
            v2,
            v91,
            *(_DWORD *)(v8 + 200),
            "mZips");
  }
  if ( !v99 )
  {
    v20 = "SecShell";
    v21 = "SetDexClassLoaderCookie GetObjectField fail:pmDexs";
    goto LABEL_48;
  }
  v22 = 0;
  v94 = J_getArrayLength(v2, v99);
  v84 = 0;
  while ( 1 )
  {
    if ( v22 >= v94 )
    {
      v98 = 0;
      v104 = 0;
      goto LABEL_33;
    }
    v23 = ((int (__fastcall *)(int, int, int))unk_7860E8C8)(v2, v99, v22);
    if ( !v23 )
    {
      v24 = "SecShell";
      v25 = "SetDexClassLoaderCookie GetObjectArrayElement fail";
LABEL_27:
      log(3, (int)v24, (int)v25);
      goto LABEL_30;
    }
    v84 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F770)(
            v2,
            v23,
            *(_DWORD *)(v8 + 236),
            *(_DWORD *)(v8 + 424));
    if ( !v84 )
    {
      v24 = "SecShell";
      v25 = "SetDexClassLoaderCookie GetIntField fail";
      goto LABEL_27;
    }
    if ( *(_DWORD *)v84 && !((int (__cdecl *)(_DWORD))unk_7861EC2C)(*(_DWORD *)v84) )
      break;
LABEL_30:
    ++v22;
  }
  v98 = 0;
  v104 = 0;
LABEL_33:
  v26 = sub_786123A4(v106, (int)"classes.dex", 0);
  v92 = 0;
  if ( !v26 )
  {
    ((void (__fastcall *)(char *, int, int *))unk_786124E8)(&v114, v96, &v108);
    log(3, (int)"SecShell", (int)"strSrcPath:%s");
    ((void (__fastcall *)(char *))unk_78612A54)(&v114);
    log(3, (int)"SecShell", (int)"strSrcPath:%s");
    v26 = sub_786123A4(v115, (int)"classes.dex", 0);
    ((void (__fastcall *)(char *))unk_786124A4)(&v114);
    if ( v26 )
    {
      v92 = 0;
    }
    else
    {
      v26 = ((int (__fastcall *)(int))unk_786117A4)(v84) - 40;
      v92 = 1;
    }
  }
  v27 = ((int (__fastcall *)(int))unk_786108CA)(v26 + 40);
  log(3, (int)"SecShell", (int)"orgDexOffset:%d");
  j_j_memset_0((int)&v114, 0, 112);
  memcpy((int)&v114, v26 + v27 + 40);
  j_j_memset_0((int)&v109, 0, 16);
  ((void (__fastcall *)(char *, char *, signed int, signed int))unk_78617E4C)(&v109, &v114, 112, 32);
  v103 = v26 + v27 + 40;
  v87 = v116;
  v28 = log(3, (int)"SecShell", (int)"fileSize:%d");
  if ( v92 )
  {
    v29 = v87;
    if ( v87 & 0xFFF )
      v29 = (v87 / 4096 + 1) << 12;
    v28 = ((int (__fastcall *)(int, int, signed int))unk_7861EC7C)(v26, v29, 3);
    if ( v28 )
    {
      v30 = v87;
      if ( v87 & 0xFFF )
        v30 = (v87 / 4096 + 1) << 12;
      v28 = ((int (__fastcall *)(int, int, signed int))unk_7861EC7C)(v26, v30, 5);
    }
  }
  v31 = ((int (__fastcall *)(int))unk_78610CD4)(v28);
  v32 = *(_DWORD *)errno();
  log(3, (int)"SecShell", (int)"mRes:%d error:%d");
  if ( v31 != -1
    || (v33 = log(3, (int)"SecShell", (int)"wrong code1"), ((int (__fastcall *)(int))unk_78610D24)(v33) != -1) )
  {
LABEL_50:
    ((void (__fastcall *)(char *, int, signed int, signed int))unk_78617E4C)(&v109, v103, 112, 32);
    ((void (__fastcall *)(char *, const char *, int *))unk_786124E8)(&v110, "/data/data/", &v108);
    ((void (__fastcall *)(char *, int))loc_7861261A)(&v110, v106);
    ((void (__fastcall *)(char *, char *, const char *))unk_786127D4)(&v112, &v110, "/mix.so");
    ((void (__fastcall *)(char *, const char *))loc_7861261A)(&v110, "/mix.dex");
    v36 = sub_7860ECA4(v2, v111);
    sub_7860ECA4(v2, v113);
    if ( ((int (__fastcall *)(int, int, int))unk_786112A0)(v2, v101, v111)
      && (v93 = J_GC_static_method(
                  v2,
                  (int)"dalvik/system/DexFile",
                  (int)"loadDex",
                  (int)"(Ljava/lang/String;Ljava/lang/String;I)Ldalvik/system/DexFile;")) != 0 )
    {
      v101 = v36;
      v97 = 0;
    }
    else
    {
      log(3, (int)"SecShell", (int)"load mix.dex failed");
      ((void (__fastcall *)(char *, int))unk_78612738)(&v110, v96);
      v93 = J_GC_static_method(
              v2,
              (int)"dalvik/system/DexFile",
              (int)"loadDex",
              (int)"(Ljava/lang/String;Ljava/lang/String;I)Ldalvik/system/DexFile;");
      log(3, (int)"SecShell", (int)"load org.dex end");
      v97 = 1;
    }
    v37 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860F770)(
            v2,
            v93,
            *(_DWORD *)(v8 + 236),
            *(_DWORD *)(v8 + 424));
    if ( !v37 )
    {
      v37 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F950)(
              v2,
              v93,
              *(_DWORD *)(v8 + 236),
              *(_DWORD *)(v8 + 424));
      if ( !v37 )
        log(3, (int)"SecShell", (int)"testCookie is null");
    }
    if ( v97 )
    {
      v38 = J_sdk_int;
    }
    else
    {
      v38 = J_sdk_int;
      if ( J_sdk_int > 10 )
      {
        v39 = *(_DWORD *)(*(_DWORD *)(v37 + 8) + 4);
        goto LABEL_64;
      }
    }
    v40 = *(_DWORD *)(v37 + 12);
    if ( v38 == 8 )
      v41 = *(_DWORD *)(v40 + 36);
    else
      v42 = *(_DWORD *)(v40 + 40);
LABEL_64:
    v108 = 0;
    ((void (__fastcall *)(int, int, signed int, int *))unk_78611690)(v2, v103, v87, &v108);
    v43 = v108;
    v44 = *(_DWORD *)(v108 + 4);
    if ( v97 )
    {
      *(_DWORD *)(v37 + 8) = v108;
      *(_BYTE *)(v37 + 4) = 1;
      if ( J_sdk_int == 10 )
        *(_DWORD *)(v84 + 16) = v103;
    }
    else
    {
      v45 = J_sdk_int;
      if ( J_sdk_int > 10 )
      {
        if ( J_sdk_int <= 18 && chk_yunos() )
          ((void (__fastcall *)(_DWORD, int, int))unk_78615CF0)(*(_DWORD *)(*(_DWORD *)(v37 + 8) + 4), v44, J_sdk_int);
        else
          ((void (__fastcall *)(_DWORD, int, int))unk_78615D04)(*(_DWORD *)(*(_DWORD *)(v37 + 8) + 4), v44, J_sdk_int);
LABEL_75:
        if ( J_sdk_int <= 10 )
        {
          v60 = getEnv(v2, "dalvik/system/DexFile");
          v85 = ((int (__fastcall *)(int, int, int, _DWORD))unk_78610880)(v2, v94 + 1, v60, 0);
          ((void (__fastcall *)(int, int, _DWORD, int))unk_7860E8D6)(v2, v85, 0, v93);
          for ( i = 0; i < v94; ((void (__fastcall *)(int, int, int, int))unk_7860E8D6)(v2, v85, i, v62) )
            v62 = ((int (__fastcall *)(int, int, int))unk_7860E8C8)(v2, v99, i++);
          if ( v100 )
          {
            v63 = J_getArrayLength(v2, v100);
            v64 = getEnv(v2, "java/lang/String");
            v105 = ((int (__fastcall *)(int, int, int, _DWORD))unk_78610880)(v2, v63 + 1, v64, 0);
            ((void (__fastcall *)(int, int, _DWORD, int))unk_7860E8D6)(v2, v105, 0, v101);
            for ( j = 0; j < v63; ((void (__fastcall *)(_DWORD, _DWORD, int, _DWORD))unk_7860E8D6)(v2, v105, j, v66) )
              v66 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v100, j++);
          }
          v67 = J_getArrayLength(v2, v102);
          v68 = getEnv(v2, "java/io/File");
          v88 = ((int (__fastcall *)(_DWORD, int, _DWORD, _DWORD))unk_78610880)(v2, v67 + 1, v68, 0);
          v69 = J_getObjectClass(v2, v68, "", "(Ljava/lang/String;)V");
          v95 = J_NewObjectV(v2, v68, v69, v101);
          ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v88, 0, v95);
          for ( k = 0; k < v67; ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v88, k, v71) )
            v71 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v102, k++);
          v72 = J_getArrayLength(v2, v90);
          v73 = getEnv(v2, "java/util/zip/ZipFile");
          v74 = ((int (__fastcall *)(_DWORD, int, _DWORD, _DWORD))unk_78610880)(v2, v72 + 1, v73, 0);
          v75 = J_getObjectClass(v2, v73, "", "(Ljava/io/File;)V");
          v76 = J_NewObjectV(v2, v73, v75, v95);
          ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v74, 0, v76);
          for ( l = 0; l < v72; ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v74, l, v78) )
            v78 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v90, l++);
          v79 = unk_78625DA8;
          v80 = getEnv(v2, *(_DWORD *)(unk_78625DA8 + 456));
          if ( ((int (__fastcall *)(int, int, int))unk_7861080E)(v2, v91, v80) )
          {
            v81 = *(_DWORD *)(v79 + 208);
            ((void (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 456),
              *(_DWORD *)(v79 + 204));
            ((void (__fastcall *)(int, int, _DWORD, const char *))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 456),
              "mPaths");
            ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 456),
              "mFiles");
            v55 = *(_DWORD *)(v79 + 456);
            v59 = v2;
            v58 = v91;
            v56 = "mZips";
          }
          else
          {
            v82 = *(_DWORD *)(v79 + 208);
            ((void (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 200),
              *(_DWORD *)(v79 + 204));
            ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 200),
              "mFiles");
            v55 = *(_DWORD *)(v79 + 200);
            v59 = v2;
            v58 = v91;
            v56 = "mZips";
          }
          goto LABEL_88;
        }
        v46 = getEnv(v2, "dalvik/system/DexPathList$Element");
        v47 = J_getObjectClass(v2, v46, "", "(Ljava/io/File;Ljava/util/zip/ZipFile;Ldalvik/system/DexFile;)V");
        if ( v47
          || (((void (__fastcall *)(int))unk_78610804)(v2),
              (v47 = J_getObjectClass(v2, v46, "", "(Ljava/io/File;Ljava/io/File;Ldalvik/system/DexFile;)V")) != 0) )
        {
          v48 = J_NewObjectV(v2, v46, v47, 0);
        }
        else
        {
          ((void (__fastcall *)(int))unk_78610804)(v2);
          v49 = J_getObjectClass(v2, v46, "", "(Ljava/io/File;ZLjava/io/File;Ldalvik/system/DexFile;)V");
          if ( !v49 )
          {
            v50 = 0;
LABEL_84:
            v51 = getEnv(v2, "dalvik/system/DexPathList$Element");
            v52 = ((int (__fastcall *)(int, int, int, _DWORD))unk_78610880)(v2, v94 + 1, v51, 0);
            ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v52, 0, v50);
            for ( m = 0; m < v94; ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v52, m, v54) )
              v54 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v98, m++);
            v55 = *(_DWORD *)(v8 + 212);
            v56 = *(const char **)(v8 + 216);
            v57 = *(_DWORD *)(v8 + 220);
            v58 = v104;
            v59 = v2;
LABEL_88:
            ((void (__fastcall *)(int, int, int, const char *))unk_7860EEFC)(v59, v58, v55, v56);
            dword_78626714 = (*(int (__fastcall **)(int, int))(*(_DWORD *)v2 + 84))(v2, v93);
            log(3, (int)"SecShell", (int)&unk_78622BD8);
            ((void (__fastcall *)(char *))unk_786124A4)(&v112);
            ((void (__fastcall *)(char *))unk_786124A4)(&v110);
            goto LABEL_105;
          }
          v48 = J_NewObjectV(v2, v46, v49, 0);
        }
        v50 = v48;
        goto LABEL_84;
      }
      *(_BYTE *)(v37 + 4) = 1;
      *(_DWORD *)(v37 + 8) = v43;
      if ( v45 == 10 )
        *(_DWORD *)(v84 + 16) = v103;
    }
    *(_DWORD *)(v37 + 12) = 0;
    goto LABEL_75;
  }
  log(3, (int)"SecShell", (int)"wrong code");
  v34 = ((int (__fastcall *)(const char *, signed int))unk_7861EDBC)("/dev/zero", 2);
  v35 = ((int (__cdecl *)(_DWORD, signed int))unk_7861EEEC)(0, v87);
  ((void (__fastcall *)(int))unk_7861EE2C)(v34);
  if ( v35 )
  {
    memmove();
    v103 = v35;
    goto LABEL_50;
  }
  v20 = "SecShell";
  v21 = "mmap fail";
LABEL_48:
  log(3, (int)v20, (int)v21);
LABEL_105:
  result = v107;
  if ( v117 != *v107 )
    _stack_chk_fail(v107);
  return result;
}
*ClassLoader*loadDex(), *multidex 这些关键字是不是想到了什么 ?

Surprise

其实 QEver 的脚本可以直接 dump 出加载后正确完整的 dex,不知道为啥乐固没想到这一点。
Read More