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

2017年4月25日星期二

ANDROID 逆向实例(七)- Ali 加固(2017.01)

也算是第一次独立完成壳子的分析大概花了两周的时间,通过这个壳子学到了很多,感觉学习价值还不错 ~

先 apktool 看一下加固前后的目录结构,对比图如下,看出来主要修改的地方有两个,lib 下的 libdemolish.so 和 libdemolishdata.so,另一处是 com.ali.fixHelper 类。
这里还有一个需要留意的地方,原来的 Java 类都没有消失,目录结构也没有变化,先看一下加固后的 AndroidMainfest.xml。这里不贴图了,比较之后发现源 Mainfest 和加固后的 Mainfest 没有区别,入口还是 com.example.helloworld.MainActivity 类,看一下加固后的这个类长什么样。
乍一看和之前的类很像,只不过多了一个 [static constructor <clinit>()V] 方法, 而其他原函数都变成了 native 函数,可以看到这里比较关键的是调用了 [Lcom/ali/fixHelper;->fixfunc([I)V],不贴具体的 fixHelper 类代码了没什么实质性的内容,主要操作 load 了 libdemo.so 然后执行了 [public static native soInit()V] 方法。

不在 Java 层做过多的停留了,直接跳到 Native 层,IDA 打开 .so 看看。果然,无法正确打开,尝试了一下动态 dump 出来,用工具修复再静态加载到 IDA 还是报错,而且载入之后 IDA 的符号表为空,不过好在可以载入,虽然数据比较混乱。算了,不要函数表了,从零开始。

因为这里 dump 的没有函数表,所以动态调试的时候就算载入 libdemo.so 之后也没有办法通过 IDA 常规办法的 [Module -> Name] 来找到 JNI_Onload 函数下断点。[greadelf -a dump.so],可以看到这里是有 .init_array 的,不过这里的 .init_array 并没有什么用,不赘述了。
既然常规简单的办法不行,还可以通过逆向 libdvm.so 找到调用 JNI_Onload 的位置,我这 libdvm.so 调用 JNI_Onload 的位置在 +53A08,在这下个断点,然后动态调试进去看看。
在 +53A08 下好断点之后可以看到调用 JNI_Onload 时确实停下来了,F7,又回到了 libdemolish.so。这里因为没有函数表,先简单过一遍把 BL、BLX、PC 操作都列出来,可以 C 的地方都 make code,这里比较繁琐,需要一点耐心具体的过程不一个个说了,下面是我当时简单列的一个操作表。

+ 4214  # SDK_INT

+ 6920 # android.app.ActivityThread
+ 6B1C # currentActivityThread

+ 6B36 -> +8AB0 -> +8AD8 # new_obj_ActivityThread -> +8728
+ 6B48 -> +8AB0 -> +8AD8 # new_obj_CurrentActivityThread -> +8728

+ 6AFC -> +663E (IMP) -> JNI 操作
+ 6AAC -> +673A (IMP)-> JNI 操作

+ 6DD8 ->  +8BA4 # getString # "/data/app/com.example.helloworld-1.apk"
+ 6BC2 ->  检查是否存在 mobiledata.so
+ 70A6 # open("/data/app-lib/com.example.helloworld-1/libdemolishdata.so")
+ 6EEA # stat("mobiledata") -> +7028 # close()
+ 46E0 # malloc -> memset
+ 477C # malloc -> memset -> android.os.Binder
+ 4812 # malloc -> memset
+ 883e # 字符变换
+ 6FE8 # log("Ali", "parse data success")

+ 4298 # 检查 yunos
+ 3F86 # BLX R3 -> JNI 调用
+ 4a14 # check dvm OR art
+ 3418 # log("sdk_version: %d, dalvik, yunos: %d")

+ 3570 clock()
+ 3468 log("init success, total (%.2f ms)")

+ 8678 memset()
+ 8682 memcpy(0xBEA2B06C, ptr_dex, 0x70) # 上面的 dex 片段

while(times < 3){
+ 73EA 失败触发 -> log("find codeoffset by dexfileid error") # find_dex_codeoff

+ 74DC # strlen("Lcom/example/helloworld/MainActivity;") -> + 752C # calloc(len + 2)
+ 753E # strcpy("740A40E8", "Lcom/example/helloworld/MainActivity;")

+ 7668 # strlen("") -> + 760A # calloc(len + 2) -> memcpy(740A4E40, "", 6)
+ 7B2E # strlen("V") -> + 7ACC # strlen("V") -> memcpy(740A4E52, "V",) -> "()V"
+ 71FE 失败触发 -> log("") # chk_find_class_method
}

虽然比较乱,不过从上面的表可以看到整个流程已经很清楚了,先做了一系列初始化操作,检查 libdemolishdata.so 是否存在、检查是否 yunos、检查 dvm Or art,等等。初始化不赘述了,然后可以看到执行了三遍 [+ 73EA # find_dex_codeoff](+ 73EA 对应的是 libdemolish.so 的偏移量,find_dex_codeoff 是我方便理解取的名字下同),F5 + 还原后的伪代码如图所示。
如果执行失败则 [log("find codeoffset by dexfileid error")],看到 codeoff 其实 ali 的加固流程已经很明显了,通过 static 方法最先执行的特性 load 一个 libdemolish.so 然后再动态修复 odex 执行时对应 methodcodeoff。

也正是这个原因,所以之前看到的加固后的类结构并没有发生变化只是原有的方法变成了 native 方法,其实这里这个 native 也只不过是吓唬人的纸老虎罢了。

然后跟进 [+ 71FE # chk_find_class_method],F5 + 修复之后如下所示,检查是否有对应的类和方法,如果没有的话 log 失败日志。
而之所以 find_dex_codeoff chk_find_class_method 在 while 里执行了三遍,是因为加固前的 MainActivity 类里一共有三个方法[init, onCreate, onCreateOptions] 在加固后的 codeoff 都被修改了。

那么现在思路已经很清楚了,动态调试时在 [chk_find_class_method] 执行结束的位置下断点,然后看后续的操作。
我这里在对 [MainActivity->onCreate] 的 [chk_find_class_method] 结束后下断电,然后一路单步执行到这里,看到这个 R3 很可疑,因为它指向的是内存 libdemolishdata.so 片段,这里用的是 [R3 + #0x10] 位置的内容,即 0x73861068

0x73861068 指向的是 [6f 20 1c 00 21 00 15 00 03 7f 6e 20 b6 13 01 00 0e 00] 这么一串内容,而这一串恰巧是原 onCreate dalvik opcode!下图是通过 010Editor 看到加固前 onCreate insns
通过 dalvik opcode 对查表可以还原出 onCreatesmali 代码。

使用相同的方法可以对应恢复其他 ClassMethod,这里不一一说明了,Crack 加固成功。
Read More

2017年4月9日星期日

OS X 逆向实例(二)- BetterZip 3.1.2

一直在用 BetterZip,之前是找了一个网上的 License 最近突然不能用了,感觉可能是 BetterZip 升级到 3.x 之后之前的 License 过期 ✊。过期的 BetterZip 有几个不方便的地方:

1. 不可以单个文件拖入/拖出压缩文件夹
2. 不可以保存修改过的压缩文件夹
3. 每次打开会弹框,提示需要更新

之前一直在用 Hopper 静态分析 MachO 感觉不爽的地方很多 😥 也不知道是不是用的有问题,最后还是操起 IDA,MachO 拖进去,静态分析的时候有一个需要注意的问题,因为 IDA 还原出的函数很多很多,需要确定一下哪些函数是 Crack Registration 需要注意的。

直接点开过期的 BetterZip 有一个这样的框,一堆提示,直接到 IDA 里面找上面截取的部分字符串,我这里选的是 "trial period"。这个东西吧这玩意平常看到真的是很烦,不过倒是挺好用的来做入口的 ~
搜到了,然后通过 DATA XREF 找到定义字符串的位置 cfstr_YourTrialPeriod,然后通过 cfstr_YourTrialPeriod 再找到函数段的 DATA XREF ~ 比较重要的是选中高亮的部分。
从 DATA XREF :-[RegistrationController testCode]:loc_100039429 可以看到调用位置,跳到 testCode,另外这个时候注意一下 testCode 的 class 为 RegistrationController 这个类名很有诱惑力,如果现在跟进的 testCode 没有收获的话接下来看看这个 class。

下面是 testCode 还原成 C 为代码之后的样子,可以从我打的注释看出来 testCode 大多是一些根据当前注册状态修改提示界面和信息的代码,没有实质性的 Registration 逻辑代码,所以我也没有往下看了,有兴趣的话可以结合注释往下看看。

// RegistrationController - (char)testCode
char __cdecl -[RegistrationController testCode](struct RegistrationController *self, SEL a2)
{
  struct RegistrationController *self_1; // r15@1
  void (*objc_msgSend)(void *, const char *, ...); // r12@1
  void *v4; // rax@1
  __int64 tfCode; // rax@1
  __int64 tfCode_1; // rbx@1
  __int64 NoticeChkReg_1; // rax@1
  struct NSButton *btnContinue; // r14@1
  void *v9; // rax@1
  void *mainBundle; // rax@1
  void *mainBundle_1; // r13@1
  void *v12; // rax@1
  __int64 laterNotice; // rax@1
  __int64 laterNotice_1; // rbx@1
  __int64 laterNotice_2; // rdi@1
  void (__fastcall *objc_release_1)(void *); // rbx@1
  struct NSButton *btnBuy; // r14@2
  void *v18; // rax@2
  void *v19; // rax@2
  void *v20; // r13@2
  void *v21; // rax@2
  __int64 RegistrationNotice; // rax@2
  __int64 RegistrationNotice_1; // rbx@2
  void (__fastcall *objc_release_2)(void *); // r14@2
  struct NSTextField *tfHead; // r13@2
  void *v26; // rax@2
  void (*objc_msgSend_1)(void *, const char *, ...); // rbx@2
  void *mainBundle_2; // rax@2
  void *mainBundle_3; // r12@2
  void (*v30)(void *, const char *, ...); // r15@3
  __int64 v31; // rax@3
  void *v32; // rax@3
  void *v33; // rbx@3
  void (*objc_msgSend_3)(void *, const char *, ...); // r13@3
  void (*objc_msgSend_4)(void *, const char *, ...); // rbx@4
  __int64 NoticeChkReg_len; // rax@4
  __int64 NoticeChkReg_len_1; // ST20_8@4
  struct NSButton *btnBuy_1; // r12@4
  __int64 v39; // rax@4
  __int64 mainBundle_6; // rax@4
  __int64 mainBundle_7; // r13@4
  __int64 v42; // rax@4
  __int64 buyNowNotice; // rax@4
  __int64 buyNowNotice_1; // r14@4
  void (*objc_msgSend_5)(void *, const char *, ...); // r12@4
  __int64 buyNowNotice_2; // rdi@4
  void (__fastcall *objc_release_5)(_QWORD); // r14@4
  char v48; // r14@5
  __int64 v49; // r12@5
  void (*objc_msgSend_2)(void *, const char *, ...); // r15@6
  __int64 v51; // rax@7
  __int64 upgradeNotice; // rax@7
  __int64 upgradeNotice_1; // rbx@7
  struct NSTextField *tfHead_1; // rdi@7
  __int64 v55; // rdi@7
  void (__fastcall *objc_release_3)(void *); // rbx@7
  struct NSTextField *tfHead_2; // r14@8
  __int64 v58; // rax@8
  __int64 v59; // rax@8
  __int64 registeredNotice; // rbx@8
  void (__fastcall *objc_release_4)(void *); // r15@8
  struct NSButton *btnContinue_1; // ST20_8@8
  __int64 v63; // rax@8
  void *mainBundle_4; // rax@8
  void *mainBundle_5; // r12@8
  __int64 v66; // rax@8
  void *continueNotice_1; // rax@8
  void *continueNotice; // rbx@8
  struct RegistrationController *self_3; // r14@10
  int installedDays_1; // ebx@10
  int trialDaysLeft; // er15@10
  __int64 v72; // rax@11
  __int64 v73; // rax@11
  __int64 v74; // ST20_8@11
  __int64 v75; // rax@11
  __int64 Notice_1; // rax@11
  __int64 Notice; // rbx@11
  __int64 v78; // rax@11
  __int64 Notice_2; // rax@11
  __int64 Notice_3; // r12@11
  __int64 Notice_4; // rdi@11
  void (__fastcall *objc_release_6)(_QWORD); // rbx@11
  __int64 v83; // rax@13
  __int64 mainBundle_8; // rax@13
  __int64 mainBundle_9; // rbx@13
  __int64 v86; // rax@13
  __int64 v87; // rax@13
  void *v88; // rax@14
  void *v89; // rax@14
  void *v90; // r13@14
  void *v91; // rax@15
  __int64 v92; // rax@15
  __int64 v93; // rbx@15
  void *v94; // rax@16
  __int64 v95; // rax@16
  __int64 v96; // rax@18
  __int64 tfCode_2; // rax@18
  __int64 tfCode_3; // rbx@18
  __int64 v99; // rax@18
  __int64 v100; // r12@18
  struct NSTextField *v101; // r14@18
  __int64 v102; // rax@19
  __int64 v103; // rax@19
  __int64 v104; // r15@19
  __int64 v105; // rax@19
  __int64 v106; // rax@19
  __int64 v107; // rbx@19
  __int64 v108; // rdi@19
  void (__fastcall *v109)(_QWORD); // rbx@19
  __int64 v110; // rdi@19
  __int64 v111; // rax@20
  __int64 v112; // rax@20
  __int64 v113; // ST08_8@20
  __int64 v114; // rax@20
  __int64 v115; // rax@20
  __int64 v116; // r12@20
  __int64 v117; // rax@20
  __int64 v118; // rax@20
  __int64 v119; // rbx@20
  __int64 v120; // rdi@20
  __int64 v121; // rax@22
  __int64 v122; // rax@22
  __int64 v123; // rbx@22
  __int64 v124; // rdi@22
  void (__fastcall *v125)(_QWORD); // rbx@22
  struct RegistrationController *self_2; // [sp+10h] [bp-50h]@2
  struct RegistrationController *self_2a; // [sp+10h] [bp-50h]@18
  __int64 NoticeChkReg_2; // [sp+18h] [bp-48h]@1
  int registerFlag; // [sp+20h] [bp-40h]@1
  __int64 registerFlaga; // [sp+20h] [bp-40h]@18
  __int64 NoticeChkReg; // [sp+28h] [bp-38h]@1
  int v133; // [sp+34h] [bp-2Ch]@1

  self_1 = self;
  v133 = 0;
  objc_msgSend = *objc_msgSend_ptr;
  v4 = objc_msgSend_ptr(self->tfCode, selRef_stringValue);
  LODWORD(tfCode) = objc_retainAutoreleasedReturnValue(v4);
  tfCode_1 = tfCode;
  NoticeChkReg = 0LL;
  registerFlag = checkRegistration(tfCode, &NoticeChkReg, &v133);// 检查用户 tfCode 是否符合序列号标准,把返回的信息保存到 NoticeChkReg 里, 是否注册保存到 registerFlag
  LODWORD(NoticeChkReg_1) = objc_retain_ptr(NoticeChkReg, &NoticeChkReg);
  NoticeChkReg_2 = NoticeChkReg_1;
  objc_release(tfCode_1);
  objc_msgSend(self->btnBuy, selRef_setHidden_, 0LL);
  objc_msgSend(self->btnLost, selRef_setHidden_, 0LL);
  objc_msgSend(self->btnBuy, selRef_setKeyEquivalent_, &cfstr_Enter);// btnBuy 和回车键绑定
  objc_msgSend(self->btnBuy, selRef_setTag_, 1LL);
  objc_msgSend(self->btnContinue, selRef_setKeyEquivalent_, &cfstr_ESC);// btnContinue 和 ESC 绑定
  btnContinue = self->btnContinue;
  v9 = (objc_msgSend)(classRef_NSBundle, selRef_mainBundle);
  LODWORD(mainBundle) = objc_retainAutoreleasedReturnValue(v9);
  mainBundle_1 = mainBundle;
  v12 = (objc_msgSend)(
          mainBundle,
          selRef_localizedStringForKey_value_table_,
          &cfstr_Later,
          &cfstr_nil,
          &cfstr_Registration); // Registration.string 里 Later 对应的字符串
  LODWORD(laterNotice) = objc_retainAutoreleasedReturnValue(v12);
  laterNotice_1 = laterNotice;
  objc_msgSend(btnContinue, selRef_setTitle_, laterNotice);
  laterNotice_2 = laterNotice_1;
  objc_release_1 = objc_release;
  objc_release(laterNotice_2);
  objc_release_1(mainBundle_1);
  if ( registerFlag )
  {
    btnBuy = self_1->btnBuy;
    v18 = (objc_msgSend)(classRef_NSBundle, selRef_mainBundle);
    LODWORD(v19) = objc_retainAutoreleasedReturnValue(v18);
    v20 = v19;
    v21 = (objc_msgSend)(
            v19,
            selRef_localizedStringForKey_value_table_,// localizedStringForKey:value:table
            &cfstr_UpgradeNow,                  // key="Upgrade Now"
            &cfstr_nil,                         // value=""
            &cfstr_Registration);               // table="Registration"
    LODWORD(RegistrationNotice) = objc_retainAutoreleasedReturnValue(v21);
    RegistrationNotice_1 = RegistrationNotice;
    objc_msgSend(btnBuy, selRef_setTitle_, RegistrationNotice);
    objc_release_2 = objc_release;
    objc_release(RegistrationNotice_1);
    objc_release_2(v20);
    objc_msgSend(self_1->btnBuy, selRef_setTag_, 2LL);
    tfHead = self_1->tfHead;
    self_2 = self_1;
    v26 = (objc_msgSend)(classRef_NSBundle, selRef_mainBundle);
    objc_msgSend_1 = objc_msgSend;
    LODWORD(mainBundle_2) = objc_retainAutoreleasedReturnValue(v26);
    mainBundle_3 = mainBundle_2;
    if ( registerFlag == 9999998 )
    {                                           // License for 2
      v30 = objc_msgSend_1;
      LODWORD(v31) = (objc_msgSend_1)(
                       mainBundle_2,
                       selRef_localizedStringForKey_value_table_,
                       &cfstr_ThisIsALicenseFor2,
                       &cfstr_nil,
                       &cfstr_Registration);
      LODWORD(v32) = objc_retainAutoreleasedReturnValue(v31);
      v33 = v32;
      (v30)(tfHead, selRef_setStringValue_, v32);
      objc_release_2(v33);
      objc_release_2(mainBundle_3);
      objc_msgSend_3 = v30;
      self_1 = self_2;
    }
    else
    {
      objc_msgSend_2 = objc_msgSend_1;
      if ( registerFlag == 9999999 )
      {                                         // License for 2 from App Store
        LODWORD(v51) = (objc_msgSend_1)(
                         mainBundle_2,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_ThisIsALicenseFor2_0,
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(upgradeNotice) = objc_retainAutoreleasedReturnValue(v51);
        upgradeNotice_1 = upgradeNotice;
        tfHead_1 = tfHead;
        objc_msgSend_3 = objc_msgSend_2;
        (objc_msgSend_2)(tfHead_1, selRef_setStringValue_, upgradeNotice);
        v55 = upgradeNotice_1;
        objc_release_3 = objc_release;
        objc_release(v55);
        objc_release_3(mainBundle_3);
        self_1 = self_2;
      }
      else
      {
        tfHead_2 = tfHead;
        objc_msgSend_3 = objc_msgSend_1;
        LODWORD(v58) = (objc_msgSend_1)(
                         mainBundle_2,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_YourCopyOfBett, // "\nYour copy of BetterZip is registered. Thank you!"
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(v59) = objc_retainAutoreleasedReturnValue(v58);
        registeredNotice = v59;
        (objc_msgSend_2)(tfHead_2, selRef_setStringValue_, v59);
        objc_release_4 = objc_release;
        objc_release(registeredNotice);
        objc_release_4(mainBundle_3);
        btnContinue_1 = self_2->btnContinue;
        LODWORD(v63) = (objc_msgSend_3)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(mainBundle_4) = objc_retainAutoreleasedReturnValue(v63);
        mainBundle_5 = mainBundle_4;
        LODWORD(v66) = (objc_msgSend_3)(
                         mainBundle_4,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_Continue,
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(continueNotice_1) = objc_retainAutoreleasedReturnValue(v66);
        continueNotice = continueNotice_1;
        (objc_msgSend_3)(btnContinue_1, selRef_setTitle_, continueNotice_1);
        objc_release_4(continueNotice);
        objc_release_4(mainBundle_5);
        self_1 = self_2;
        (objc_msgSend_3)(self_2->btnContinue, selRef_setTag_, 0LL);
        (objc_msgSend_3)(self_2->btnContinue, selRef_setKeyEquivalent_, &cfstr_Enter);
        (objc_msgSend_3)(self_2->btnBuy, selRef_setKeyEquivalent_, &cfstr_nil);
        (objc_msgSend_3)(self_2->btnBuy, selRef_setHidden_, 1LL);
        (objc_msgSend_3)(self_2->btnLost, selRef_setHidden_, 1LL);
      }
    }
    v49 = NoticeChkReg_2;
    objc_msgSend_ptr(self_1->tfText, selRef_setStringValue_, NoticeChkReg_2);
    v48 = 1;
  }
  else
  {                                             // Licensed
    objc_msgSend_4 = objc_msgSend;
    LODWORD(NoticeChkReg_len) = (objc_msgSend)(NoticeChkReg_2, selRef_length);
    NoticeChkReg_len_1 = NoticeChkReg_len;
    btnBuy_1 = self_1->btnBuy;
    LODWORD(v39) = (objc_msgSend_4)(classRef_NSBundle, selRef_mainBundle);
    LODWORD(mainBundle_6) = objc_retainAutoreleasedReturnValue(v39);
    mainBundle_7 = mainBundle_6;
    LODWORD(v42) = (objc_msgSend_4)(
                     mainBundle_6,
                     selRef_localizedStringForKey_value_table_,
                     &cfstr_BuyNow,
                     &cfstr_nil,
                     &cfstr_Registration);
    LODWORD(buyNowNotice) = objc_retainAutoreleasedReturnValue(v42);
    buyNowNotice_1 = buyNowNotice;
    (objc_msgSend_4)(btnBuy_1, selRef_setTitle_, buyNowNotice);
    objc_msgSend_5 = objc_msgSend_4;
    buyNowNotice_2 = buyNowNotice_1;
    objc_release_5 = objc_release;
    objc_release(buyNowNotice_2);
    objc_release_5(mainBundle_7);
    if ( NoticeChkReg_len_1 )
    {                                           // Registered
      objc_msgSend_ptr(self_1->tfHead, selRef_setStringValue_, NoticeChkReg_2);
      v48 = 0;
      objc_msgSend_3 = objc_msgSend_4;
      v49 = NoticeChkReg_2;
    }
    else
    {                                           // Trial
      self_3 = self_1;
      installedDays_1 = installedDays;
      trialDaysLeft = 30 - installedDays;
      if ( 30 - installedDays < 2 )
      {
        if ( trialDaysLeft == 1 )
        {
          objc_msgSend_3 = objc_msgSend_5;
          LODWORD(v83) = (objc_msgSend_5)(classRef_NSBundle, selRef_mainBundle);
          LODWORD(mainBundle_8) = objc_retainAutoreleasedReturnValue(v83);
          mainBundle_9 = mainBundle_8;
          LODWORD(v86) = (objc_msgSend_5)(
                           mainBundle_8,
                           selRef_localizedStringForKey_value_table_,
                           &cfstr_BetterzipIsN_2,// "BetterZip is not yet registered. Your trial period will end tomorrow
                           &cfstr_nil,
                           &cfstr_Registration);
          LODWORD(v87) = objc_retainAutoreleasedReturnValue(v86);
          Notice_3 = v87;
          objc_release(mainBundle_9);
          trialDaysLeft = 1;
        }
        else
        {
          v88 = objc_msgSend_ptr(classRef_NSBundle, selRef_mainBundle);
          LODWORD(v89) = objc_retainAutoreleasedReturnValue(v88);
          v90 = v89;
          if ( installedDays_1 == 30 )
          {
            v91 = objc_msgSend_ptr(
                    v89,
                    selRef_localizedStringForKey_value_table_,
                    &cfstr_BetterzipIsN_0,      // BetterZip is not yet registered. Your trial period ends today
                    &cfstr_nil,
                    &cfstr_Registration);
            LODWORD(v92) = objc_retainAutoreleasedReturnValue(v91);
            v93 = v92;
            objc_release(v90);
          }
          else
          {
            v94 = objc_msgSend_ptr(
                    v89,
                    selRef_localizedStringForKey_value_table_,
                    &cfstr_YourTrialPerio,      // "Your trial period is over.
                    &cfstr_nil,
                    &cfstr_Registration);
            LODWORD(v95) = objc_retainAutoreleasedReturnValue(v94);
            v93 = v95;
            objc_release(v90);
            trialDaysLeft = 0;
          }
          objc_msgSend_3 = objc_msgSend_5;
          Notice_3 = v93;
        }
      }
      else
      {
        LODWORD(v72) = (objc_msgSend_5)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(v73) = objc_retainAutoreleasedReturnValue(v72);
        v74 = v73;
        LODWORD(v75) = (objc_msgSend_5)(
                         v73,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_BetterzipIsNot, // "BetterZip is not yet registered. Your trial period will end in %d days.
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(Notice_1) = objc_retainAutoreleasedReturnValue(v75);
        Notice = Notice_1;
        objc_msgSend_3 = objc_msgSend_5;
        LODWORD(v78) = (objc_msgSend_5)(classRef_NSString, selRef_stringWithFormat_, Notice_1, trialDaysLeft);
        LODWORD(Notice_2) = objc_retainAutoreleasedReturnValue(v78);
        Notice_3 = Notice_2;
        Notice_4 = Notice;
        objc_release_6 = objc_release;
        objc_release(Notice_4);
        objc_release_6(v74);
      }
      registerFlaga = Notice_3;
      self_2a = self_3;
      (objc_msgSend_3)(self_3->tfHead, selRef_setStringValue_, Notice_3);
      LODWORD(v96) = (objc_msgSend_3)(self_3->tfCode, selRef_stringValue);
      LODWORD(tfCode_2) = objc_retainAutoreleasedReturnValue(v96);
      tfCode_3 = tfCode_2;
      LODWORD(v99) = (objc_msgSend_3)(tfCode_2, selRef_length);
      v100 = v99;
      objc_release(tfCode_3);
      v101 = self_3->tfText;
      if ( v100 )
      {                                         // self.tfCode is not nil
        LODWORD(v102) = (objc_msgSend_3)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(v103) = objc_retainAutoreleasedReturnValue(v102);
        v104 = v103;
        LODWORD(v105) = (objc_msgSend_3)(
                          v103,
                          selRef_localizedStringForKey_value_table_,
                          &cfstr_TheCodeIsInval,// "The code is invalid
                          &cfstr_nil,
                          &cfstr_Registration);
        LODWORD(v106) = objc_retainAutoreleasedReturnValue(v105);
        v107 = v106;
        (objc_msgSend_3)(v101, selRef_setStringValue_, v106);
        v108 = v107;
        v109 = objc_release;
        objc_release(v108);
        v110 = v104;
      }
      else
      {
        LODWORD(v111) = (objc_msgSend_3)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(v112) = objc_retainAutoreleasedReturnValue(v111);
        v113 = v112;
        LODWORD(v114) = (objc_msgSend_3)(
                          v112,
                          selRef_localizedStringForKey_value_table_,
                          &cfstr_TrialUserDDays,// Trial user\n%d days left"
                          &cfstr_nil,
                          &cfstr_Registration);
        LODWORD(v115) = objc_retainAutoreleasedReturnValue(v114);
        v116 = v115;
        LODWORD(v117) = (objc_msgSend_3)(classRef_NSString, selRef_stringWithFormat_, v115, trialDaysLeft);
        LODWORD(v118) = objc_retainAutoreleasedReturnValue(v117);
        v119 = v118;
        (objc_msgSend_3)(v101, selRef_setStringValue_, v118);
        v120 = v119;
        v109 = objc_release;
        objc_release(v120);
        v109(v116);
        v110 = v113;
      }
      v109(v110);
      v49 = NoticeChkReg_2;
      objc_release(registerFlaga);
      v48 = 0;
      self_1 = self_2a;
    }
  }
  LODWORD(v121) = (objc_msgSend_3)(classRef_NSNotificationCenter, selRef_defaultCenter);
  LODWORD(v122) = objc_retainAutoreleasedReturnValue(v121);
  v123 = v122;
  (objc_msgSend_3)(v122, selRef_postNotificationName_object_, MIBRegDidChangeNotification, self_1);
  v124 = v123;
  v125 = objc_release;
  objc_release(v124);
  v125(v49);
  return v48;
}
虽然说粗看一遍不关注上面显示提示信息的代码了,不过发现了另外一个比较关键的函数 checkRegistration 点进去看看,可以看到是验证 tfCode 的函数,如下。

__int64 __fastcall checkRegistration(__int64 tfCode, _QWORD *Notice_2, _DWORD *a3)
{
  _DWORD *v3; // r12@1
  void *tfCode_1; // rax@1
  void *tfCode_2; // r15@1
  int result_mid; // er13@1
  BIO *mem_buf_1; // r14@1
  void *(*objc_msgSend)(void *, const char *, ...); // r14@2
  char *codeLength; // rax@2
  void *v10; // rax@2
  void *lastCode; // rax@2
  void *lastCode_1; // rbx@2
  void *v13; // rax@3
  void *tfCodeEndsWithEuqlSign_1; // rax@3
  void *tfCodeEndsWithEuqlSign; // rbx@3
  BIO *mem_buf; // rax@4
  void *v17; // rax@6
  char *v18; // rax@6
  int (__fastcall *objc_msgSend_1)(void *, char *, signed __int64, signed __int64); // r13@8
  void *v20; // rax@8
  void *decrypt_UTF8; // rax@8
  void *decrypt_UTF8_1; // rbx@8
  unsigned __int8 prefixFlag2; // al@8
  int (__fastcall *objc_msgSend_2)(void *, char *, signed __int64, signed __int64); // rbx@8
  __int64 v25; // rax@9
  __int64 decrypt_UTF8_index16; // rax@9
  __int64 decrypt_UTF8_index16_1; // r13@9
  int decrypt_UTF8_index16_intValue; // ebx@9
  int *v29; // rax@9
  unsigned int v30; // ecx@9
  void *(*objc_msgSend_3)(void *, const char *, ...); // r13@13
  void *v32; // rax@13
  void *decrypt_UTF8_index24_1; // rax@13
  void *decrypt_UTF8_index24; // rbx@13
  void *DateFormatter; // rax@13
  void *v36; // rax@13
  void *v37; // r12@13
  void *v38; // rax@13
  void *decrypt_UTF8_index24_dateFormat; // rax@13
  void *v40; // rax@13
  void *dateFormat_2014_05_1; // rax@13
  void *decrypt_UTF8_index41; // rax@13
  __int64 v46; // rax@13
  void *v45; // rax@13
  void *decrypt_UTF8_index35_1; // rax@13
  void *decrypt_UTF8_index35; // rbx@13
  void *v44; // rax@13
  void *mainBundle; // rax@13
  void *v49; // rax@14
  __int64 v50; // rax@14
  __int64 v51; // rbx@14
  __int64 v52; // r9@14
  void *v53; // rax@14
  void *v54; // rax@16
  __int64 v55; // rax@16
  __int64 Notice_1; // rax@17
  __int64 Notice; // rax@17
  void (__fastcall *objc_release_1)(void *); // r12@17
  void (__fastcall *objc_release_2)(void *); // rbx@20
  __int64 result; // rax@26
  void *NSDate_init; // [sp+8h] [bp-1A8h]@13
  void *v62; // [sp+10h] [bp-1A0h]@14
  void *dateFormat_2014_05_2; // [sp+20h] [bp-190h]@13
  void *decrypt_UTF8_index24_dateFormat_1; // [sp+28h] [bp-188h]@13
  __int64 decrypt_UTF8_index41_2; // [sp+30h] [bp-180h]@13
  void *decrypt_UTF8_index24_2; // [sp+38h] [bp-178h]@13
  unsigned __int8 prefixFlag1; // [sp+43h] [bp-16Dh]@8
  int decrypt_UTF8_index16_intValue_1; // [sp+44h] [bp-16Ch]@13
  _DWORD *decrypt_UTF8_index35_intValue; // [sp+48h] [bp-168h]@7
  void *decrypt_UTF8_2; // [sp+50h] [bp-160h]@8
  int len[2]; // [sp+60h] [bp-150h]@4
  RSA *x; // [sp+68h] [bp-148h]@1
  unsigned __int8 *from; // [sp+70h] [bp-140h]@1
  void *base64Buf; // [sp+78h] [bp-138h]@1
  unsigned __int8 decrypt[16]; // [sp+80h] [bp-130h]@7
  __int128 v76; // [sp+90h] [bp-120h]@7
  __int128 v77; // [sp+A0h] [bp-110h]@7
  __int128 v78; // [sp+B0h] [bp-100h]@7
  __int128 v79; // [sp+C0h] [bp-F0h]@7
  __int128 v80; // [sp+D0h] [bp-E0h]@7
  __int128 v81; // [sp+E0h] [bp-D0h]@7
  __int128 v82; // [sp+F0h] [bp-C0h]@7
  __int128 v83; // [sp+100h] [bp-B0h]@7
  __int128 v84; // [sp+110h] [bp-A0h]@7
  __int128 v85; // [sp+120h] [bp-90h]@7
  __int128 v86; // [sp+130h] [bp-80h]@7
  __int128 v87; // [sp+140h] [bp-70h]@7
  __int128 v88; // [sp+150h] [bp-60h]@7
  __int128 v89; // [sp+160h] [bp-50h]@7
  __int128 v90; // [sp+170h] [bp-40h]@7
  __int64 stack_chk_guard; // [sp+180h] [bp-30h]@1

  v3 = a3;
  stack_chk_guard = *__stack_chk_guard_ptr;
  LODWORD(tfCode_1) = objc_retain_ptr(tfCode, Notice_2);
  tfCode_2 = tfCode_1;
  base64Buf = 0LL;
  from = 0LL;
  x = 0LL;
  *v3 = 0;
  objc_retainAutorelease(&cfstr_nil);
  *Notice_2 = &cfstr_nil;
  result_mid = 0;
  mem_buf_1 = 0LL;
  if ( objc_msgSend_ptr(tfCode_2, selRef_length) >= 0x80 )
  {
    objc_msgSend = *objc_msgSend_ptr;
    codeLength = objc_msgSend_ptr(tfCode_2, selRef_length);
    v10 = objc_msgSend(tfCode_2, selRef_substringFromIndex_, codeLength - 1);// tfCode.substring(length - 1)
    LODWORD(lastCode) = objc_retainAutoreleasedReturnValue(v10);
    lastCode_1 = lastCode;
    LOBYTE(objc_msgSend) = objc_msgSend(lastCode, selRef_isEqualTo_, &cfstr_equalSign);
    objc_release(lastCode_1);
    if ( !objc_msgSend )                        // lastCode != "="
    {
      v13 = objc_msgSend_ptr(tfCode_2, selRef_stringByAppendingString_, &cfstr_equalSign);
      LODWORD(tfCodeEndsWithEuqlSign_1) = objc_retainAutoreleasedReturnValue(v13);
      tfCodeEndsWithEuqlSign = tfCodeEndsWithEuqlSign_1;
      objc_release(tfCode_2);
      tfCode_2 = tfCodeEndsWithEuqlSign;
    }
    decodeBase64(b64_key, &base64Buf, len);
    mem_buf = BIO_new_mem_buf(base64Buf, len[0]);
    mem_buf_1 = mem_buf;
    result_mid = 0;
    if ( mem_buf )
    {                                           // RSA Decrypt
      result_mid = 0;
      if ( PEM_read_bio_RSA_PUBKEY(mem_buf, &x, 0LL, 0LL) )
      {
        LODWORD(v17) = objc_retainAutorelease(tfCode_2);
        tfCode_2 = v17;
        v18 = objc_msgSend_ptr(v17, selRef_UTF8String);
        decodeBase64(v18, &from, len);
        if ( *len == 128LL )
        {
          decrypt_UTF8_index35_intValue = v3;
          v90 = 0LL;
          v89 = 0LL;
          v88 = 0LL;
          v87 = 0LL;
          v86 = 0LL;
          v85 = 0LL;
          v84 = 0LL;
          v83 = 0LL;
          v82 = 0LL;
          v81 = 0LL;
          v80 = 0LL;
          v79 = 0LL;
          v78 = 0LL;
          v77 = 0LL;
          v76 = 0LL;
          *decrypt = 0LL;
          if ( RSA_public_decrypt(128, from, decrypt, x, 1) != -1 )
          {
            objc_msgSend_1 = *objc_msgSend_ptr;
            v20 = objc_msgSend_ptr(classRef_NSString, selRef_stringWithUTF8String_, decrypt);
            LODWORD(decrypt_UTF8) = objc_retainAutoreleasedReturnValue(v20);
            decrypt_UTF8_1 = decrypt_UTF8;
            decrypt_UTF8_2 = decrypt_UTF8;
            prefixFlag1 = (objc_msgSend_1)(decrypt_UTF8, selRef_hasPrefix_, &cfstr___mas_betterzp);// __MAS_BetterZp2_
            prefixFlag2 = (objc_msgSend_1)(decrypt_UTF8_1, selRef_hasPrefix_, &cfstr___mib_betterzi);// __MIB_BetterZip_
            objc_msgSend_2 = objc_msgSend_1;
            result_mid = 0;
            if ( prefixFlag1 | prefixFlag2 )
            {
              LODWORD(v25) = objc_msgSend_2(decrypt_UTF8_2, selRef_substringWithRange_, 16LL, 7LL);// range(16, 16+7)
              LODWORD(decrypt_UTF8_index16) = objc_retainAutoreleasedReturnValue(v25);
              decrypt_UTF8_index16_1 = decrypt_UTF8_index16;
              decrypt_UTF8_index16_intValue = (objc_msgSend_2)(decrypt_UTF8_index16, selRef_intValue);
              objc_release(decrypt_UTF8_index16_1);
              v29 = &unk_1000AEE20;
              v30 = 0;
              do
              {
                if ( *v29 > decrypt_UTF8_index16_intValue )
                  break;
                result_mid = 0;
                if ( *v29 == decrypt_UTF8_index16_intValue )
                  goto LABEL_21;
                ++v29;
                ++v30;
              }
              while ( v30 <= 0x2F );
              decrypt_UTF8_index16_intValue_1 = decrypt_UTF8_index16_intValue;
              objc_msgSend_3 = *objc_msgSend_ptr;
              v32 = objc_msgSend_ptr(decrypt_UTF8_2, selRef_substringWithRange_, 24LL, 10LL);
              LODWORD(decrypt_UTF8_index24_1) = objc_retainAutoreleasedReturnValue(v32);
              decrypt_UTF8_index24 = decrypt_UTF8_index24_1;
              decrypt_UTF8_index24_2 = decrypt_UTF8_index24_1;
              DateFormatter = objc_msgSend_3(classRef_NSDateFormatter, selRef_alloc);
              v36 = objc_msgSend_3(DateFormatter, selRef_init);
              v37 = v36;
              NSDate_init = v36;
              objc_msgSend_3(v36, selRef_setDateFormat_, &cfstr_YyyyMmDd);
              v38 = objc_msgSend_3(v37, selRef_dateFromString_, decrypt_UTF8_index24);
              LODWORD(decrypt_UTF8_index24_dateFormat) = objc_retainAutoreleasedReturnValue(v38);
              decrypt_UTF8_index24_dateFormat_1 = decrypt_UTF8_index24_dateFormat;
              v40 = objc_msgSend_3(v37, selRef_dateFromString_, &cfstr_20140501);// "2014-05-01"
              LODWORD(dateFormat_2014_05_1) = objc_retainAutoreleasedReturnValue(v40);
              dateFormat_2014_05_2 = dateFormat_2014_05_1;
              decrypt_UTF8_index41 = objc_msgSend_3(decrypt_UTF8_2, selRef_substringFromIndex_, 41LL);
              LODWORD(v46) = objc_retainAutoreleasedReturnValue(decrypt_UTF8_index41);
              decrypt_UTF8_index41_2 = v46;
              v45 = objc_msgSend_3(decrypt_UTF8_2, selRef_substringWithRange_, 35LL, 4LL);
              LODWORD(decrypt_UTF8_index35_1) = objc_retainAutoreleasedReturnValue(v45);
              decrypt_UTF8_index35 = decrypt_UTF8_index35_1;
              *decrypt_UTF8_index35_intValue = objc_msgSend_3(decrypt_UTF8_index35_1, selRef_intValue);
              objc_release(decrypt_UTF8_index35);
              LODWORD(decrypt_UTF8_index35) = *decrypt_UTF8_index35_intValue;
              v44 = objc_msgSend_3(classRef_NSBundle, selRef_mainBundle);
              LODWORD(mainBundle) = objc_retainAutoreleasedReturnValue(v44);
              if ( decrypt_UTF8_index35 < 2 )
              {
                v62 = mainBundle;
                v54 = objc_msgSend_3(
                        mainBundle,
                        selRef_localizedStringForKey_value_table_,
                        &cfstr_Registration_2,  // "Registration: # %d on %@\nLicensee: %@"
                        &cfstr_nil,
                        &cfstr_Registration);
                LODWORD(v55) = objc_retainAutoreleasedReturnValue(v54);
                v51 = v55;
                v53 = objc_msgSend_3(
                        classRef_NSString,
                        selRef_stringWithFormat_,
                        v55,
                        decrypt_UTF8_index16_intValue_1,
                        decrypt_UTF8_index24_2);
              }
              else
              {
                v62 = mainBundle;
                v49 = objc_msgSend_3(
                        mainBundle,
                        selRef_localizedStringForKey_value_table_,
                        &cfstr_RegistrationDO,  // "Registration: # %d on %@ for %d licenses\nLicensee: %@"
                        &cfstr_nil,
                        &cfstr_Registration);
                LODWORD(v50) = objc_retainAutoreleasedReturnValue(v49);
                v51 = v50;
                v52 = *decrypt_UTF8_index35_intValue;
                v53 = objc_msgSend_3(
                        classRef_NSString,
                        selRef_stringWithFormat_,
                        v50,
                        decrypt_UTF8_index16_intValue_1,
                        decrypt_UTF8_index24_2,
                        decrypt_UTF8_index41_2,
                        NSDate_init);
              }
              LODWORD(Notice_1) = objc_retainAutoreleasedReturnValue(v53);
              LODWORD(Notice) = objc_autorelease(Notice_1);
              *Notice_2 = Notice;
              objc_release_1 = objc_release;
              objc_release(v51);
              objc_release_1(v62);
              if ( objc_msgSend_ptr(decrypt_UTF8_index24_dateFormat_1, selRef_compare_, dateFormat_2014_05_2) == -1
                || prefixFlag1 )
              {
                decrypt_UTF8_index16_intValue_1 = (prefixFlag1 != 0) | 0x98967E;
              }
              objc_release_2 = objc_release;
              objc_release(decrypt_UTF8_index41_2);
              objc_release_2(dateFormat_2014_05_2);
              objc_release_2(decrypt_UTF8_index24_dateFormat_1);
              objc_release_2(NSDate_init);
              objc_release_2(decrypt_UTF8_index24_2);
              result_mid = decrypt_UTF8_index16_intValue_1;
            }
LABEL_21:
            objc_release(decrypt_UTF8_2);
          }
        }
      }
    }
    else
    {
      mem_buf_1 = 0LL;
    }
  }
  if ( x )
    RSA_free(x);
  if ( mem_buf_1 )
    BIO_free(mem_buf_1);
  free(from);
  free(base64Buf);
  objc_release(tfCode_2);
  result = *__stack_chk_guard_ptr;
  if ( *__stack_chk_guard_ptr == stack_chk_guard )
    result = result_mid;
  return result;
}
也打了一些注释,可以看到先对 tfCode 的格式做了判断,对应补全后做了 base64 + RSA 的解码/解密,还有一些后续对特定字符位置比较的操作,如果要写一个注册机的话从这里入手是个很好的路子

上面提到了好几次 tfCode,这个在 IDA 反编译出来的代码中显示的比较不直观,需要在 Structures(SHIFT + F9)里找到对应类查看,这里推荐使用 class-dump 可以比较方便得导出 oc 运行时的声明文件,也就包括类的定义文件。看上去很舒服,比 IDA 直接查看更加直观。

➜ test ❯❯❯ class-dump -C RegistrationController BetterZip
....
@interface RegistrationController : NSWindowController 
{
    NSTabView *tabView;
    NSTextField *tfEmail;
    NSButton *btnConvert;
    NSTextField *tfHead;
    NSTextField *tfText;
    NSTextField *tfCode;
    NSTextField *tfLabel;
    NSButton *btnBuy;
    NSButton *btnLost;
    NSButton *btnContinue;
    RendezvousController *rendezvous;
    int usersFound;
    NSString *ownName;
    NSTimer *timer;
    int timerCountDown;
    BOOL isModalSheet;
    NSString *hashString;
}

+ (BOOL)isRegistered;
+ (int)trialDaysLeft;
+ (BOOL)trialIsOver;
- (void).cxx_destruct;
- (BOOL)testCode;
- (id)regCode;
- (void)lostKey:(id)arg1;
- (void)laterContinue:(id)arg1;
- (void)buy:(id)arg1;
- (int)registrationStatus;
- (id)createNameArrayFromDiscoveredServices:(id)arg1;
- (void)discoveredServicesDidChange;
- (void)controlTextDidChange:(id)arg1;
- (void)registerWithCode:(id)arg1;
- (void)convert:(id)arg1;
- (void)convertMASReceiptToCode;
- (void)windowDidLoad;
- (id)initWithParams:(id)arg1;

// Remaining properties
@property(readonly, copy) NSString *debugDescription;
@property(readonly, copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;

@end

如果要根据 checkRegistration 写注册机显然比较麻烦,看看 checkRegistration 的调用位置,DATA XREF 找到 registrationStatus ,是一个操作 self.is_registered 的函数,根据不同的情况保存不同的值到 is_registered 看到这个已经离终点很近了。

因为 checkRegistration 是把当前注册状态写到 is_registered 里的函数,那么肯定就有另外一个读函数,根据 is_registered 的 DATA XREF 找到读函数 isRegistered。

// RegistrationController + (char)isRegistered
char __cdecl +[RegistrationController isRegistered](struct RegistrationController_meta *self, SEL a2)
{
  return is_registered ^ 1;
}
可以看到这个函数其实是在开始很显眼的 RegistrationController 里,逆向中,类名往往可以透漏很多信息。接下来这里的操作只需要把 isRegistered 修改成永远返回 1 的函数就行了,如果要用 IDA 修改的话只能找到对应 x86 汇编指令的 16 进制机器码然后在 HEX-View 里修改比较麻烦。

这里推荐使用 Hopper 来修改,直接 alt + A 就可以修改汇编指令了,很简单 ~ 图如下
改好了之后把 MachO 拖入 BetterZip.app 里面打开,👏👏👏,已经解决之前提出的 1.2 点问题了,现在只剩下问题 3 —— 反复弹框。

按理来说此时 BetterZip 已经认为是注册了的状态,不应该再弹框的,可现在还在弹框,那么有一种很可能的情况,先谈一个窗然后再根据注册状态修改/关闭弹窗。巧了,再回到之前看过的 testCode 当时有这么一句总结:“testCode 大多是一些根据当前注册状态修改提示界面和信息的代码”,刚好证实了我的这个猜想。

然后再看看 -[RegistrationController initWithParams:] 确实有调用相关代码 [[self super] initWithWindowNibName:@"Register"] , 直接把相关代码 ret 掉,大功告成 ~
Read More