首先,动态加载与调用不是个新问题,方法也非常直接。关键就是使用DexClassLoader。

这个方法有一个副作用,那就是/proc/self/maps下会留下很多份dex加载记录,需要到jvm GC时才能释放,内存泄漏倒是没有,就是不太好看。

对于入参不是String,返回结果不是String的情况,做个Ser/De来搞定即可,接口的界面也能保持干净。

注意,如果要保证Dex文件的安全,这个方法是不行的。关键点在DexClassLoader的构造函数第二个参数cachedDir,所有的简单加壳都会走到这里,解密脱壳后的Dex文件写到这个cachedDir目录下。所以有些简单的脱壳机在这里hook就能解决战斗了。

复杂的加壳/脱壳的对抗需要去寻找jvm加载Dex的更底层的api了,比如这里介绍的这些api:

const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { 
  { "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I", 
  Dalvik_dalvik_system_DexFile_openDexFile }, 
  { "openDexFile", "([B)I", 
  Dalvik_dalvik_system_DexFile_openDexFile_bytearray }, 
  { "closeDexFile", "(I)V", 
  Dalvik_dalvik_system_DexFile_closeDexFile }, 
  { "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;", 
  Dalvik_dalvik_system_DexFile_defineClass }, 
  { "getClassNameList", "(I)[Ljava/lang/String;", 
  Dalvik_dalvik_system_DexFile_getClassNameList }, 
  { "isDexOptNeeded", "(Ljava/lang/String;)Z", 
  Dalvik_dalvik_system_DexFile_isDexOptNeeded }, 
  { NULL, NULL, NULL }, 
};

当然,Dex加壳/脱壳的对抗并没有止于此,寻找更底层的api,或者更本质的讲,寻找更底层的代码片段来Hook/Call就是更进一步的对抗方向了。但结论是明显的,无论如何,逆向工程最终会胜利。

附,加载过程示例代码(Java)如下:

btnLoadDexAndCall.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    Class libProviderClazz = null;
    try {
      libProviderClazz = Class.forName("me.xjump.utils.Test");
    }catch(ClassNotFoundException ex) {   // 每次都会到这里。不是Dex,而是jar包的情况可以自己探索一下。
      Log.e(TAG, ex.toString());
      try {
        final File dexPath = new File("/data/local/tmp/classes1.dex");
        Log.d(TAG, dexPath.getAbsolutePath());
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        DexClassLoader cl = new DexClassLoader(dexPath.getAbsolutePath(),
            ctx.getFilesDir().toString(), null, loader);
        libProviderClazz = cl.loadClass("me.xjump.utils.Test");
        Method method = libProviderClazz.getMethod("testme", String.class);
        Log.w(TAG, method.invoke(null, "message").toString());

      } catch (Exception exception) {
        Log.e(TAG, exception.toString());
        exception.printStackTrace();
      }
    }
  }
});

翻译成C++/jni的代码如下:

std::string callDexStaticMethod(const std::string &dexFile, const std::string &dexCacheDir, const std::string &clz, const std::string &staticMethod, const std::string& args) 
{
  std::string ret = "";

  int status = -1;
  std::string className = "";

  JNIEnv *env = NULL;
  jstring jDexPath = NULL;
  jstring jDestDexPath = NULL;
  jstring jClz = NULL;
  jstring jArgs = NULL;

  jclass classloaderClass = NULL;
  jmethodID getSysLoaderMethod = NULL;
  jobject sysLoader = NULL;
  jclass dexLoaderClass = NULL;
  jmethodID initDexLoaderMethod = NULL;
  jobject dexLoader = NULL;
  jmethodID loadClassMethod = NULL;
  jclass javaClientClass = NULL;
  jmethodID javaClientStaticFuncMethod = NULL;
  jstring result = NULL;

  do {
    if (g_JVM == nullptr) {
      LOGE("[-] g_JVM is null.");
      break;
    }
    status = ((JavaVM *) g_JVM)->AttachCurrentThread(&env, NULL);
    if (status != 0 || env == NULL) {
      LOGE("[-] init: failed to attach current thread %d", status);
      break;
    }

    LOGI("[*] dex=%s,dexDest=%s,clz=%s,method=%s", dexFile.c_str(), dexCacheDir.c_str(),
       clz.c_str(), staticMethod.c_str());

    jDexPath = CppUtils::stdString2jstring(env, dexFile.c_str());
    jDestDexPath = CppUtils::stdString2jstring(env, dexCacheDir.c_str());
    jClz = CppUtils::stdString2jstring(env, clz.c_str());
    jArgs = CppUtils::stdString2jstring(env, args.c_str());
    if(jDexPath==NULL || jDestDexPath==NULL || jClz==NULL ||jArgs==NULL) {
      LOGE("[-] jDexPath/jDestDexPath/jClz/jArgs is null.");
      break;
    }
    className = "java/lang/ClassLoader";
    classloaderClass = env->FindClass(className.c_str());
    if (env->ExceptionCheck()) {
      LOGE("[-] find %s cls exception.", className.c_str());
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (classloaderClass == NULL) {
      LOGE("[-] class(%s) not find.", className.c_str());
      break;
    }

    getSysLoaderMethod = env->GetStaticMethodID(classloaderClass,
                          "getSystemClassLoader",
                          "()Ljava/lang/ClassLoader;");
    if (env->ExceptionCheck()) {
      LOGE("[-] getSysLoaderMethod exception.");
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (getSysLoaderMethod == NULL) {
      LOGE("[-] getSysLoaderMethod not find.");
      break;
    }

    sysLoader = env->CallStaticObjectMethod(classloaderClass, getSysLoaderMethod);
    if (env->ExceptionCheck()) {
      LOGE("[-] sysLoader exception.");
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (sysLoader == NULL) {
      LOGE("[-] sysLoader not find.");
      break;
    }

    dexLoaderClass = env->FindClass("dalvik/system/DexClassLoader");
    if (env->ExceptionCheck()) {
      LOGE("[-] dexLoaderClass exception.");
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (dexLoaderClass == NULL) {
      LOGE("[-] dexLoaderClass not find.");
      break;
    }
    initDexLoaderMethod = env->GetMethodID(dexLoaderClass, "<init>",
                         "(Ljava/lang/String;Ljava/lang/String;"
                         "Ljava/lang/String;Ljava/lang/ClassLoader;)V");
    if (env->ExceptionCheck()) {
      LOGE("[-] initDexLoaderMethod exception.");
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (initDexLoaderMethod == NULL) {
      LOGE("[-] initDexLoaderMethod not find.");
      break;
    }

    dexLoader = env->NewObject(dexLoaderClass, initDexLoaderMethod,
                   jDexPath, jDestDexPath, NULL, sysLoader);
    if (env->ExceptionCheck()) {
      LOGE("[-] dexLoader exception.");
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (dexLoader == NULL) {
      LOGE("[-] dexLoader not find.");
      break;
    }

    loadClassMethod = env->GetMethodID(classloaderClass, "loadClass",
                       "(Ljava/lang/String;)Ljava/lang/Class;");
    if (env->ExceptionCheck()) {
      LOGE("[-] loadClassMethod exception.");
      //env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (loadClassMethod == NULL) {
      LOGE("[-] loadClassMethod not find.");
      break;
    }

    javaClientClass = (jclass) env->CallObjectMethod(dexLoader, loadClassMethod, jClz);
    if (env->ExceptionCheck()) {
      LOGE("[-] javaClientClass exception.");
      env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (javaClientClass == NULL) {
      LOGE("[-] javaClientClass not find.");
      break;
    }
    javaClientStaticFuncMethod = env->GetStaticMethodID(javaClientClass,
                              staticMethod.c_str(),
                              "(Ljava/lang/String;)Ljava/lang/String;");
    if (env->ExceptionCheck()) {
      LOGE("[-] javaClientStaticFuncMethod exception.");
      env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (javaClientStaticFuncMethod == NULL) {
      LOGE("[-] javaClientStaticFuncMethod not find.");
      break;
    }
    result = (jstring) env->CallStaticObjectMethod(javaClientClass,
        javaClientStaticFuncMethod, jArgs);

    if (env->ExceptionCheck()) {
      LOGE("[-] result exception.");
      env->ExceptionDescribe();
      env->ExceptionClear();
      break;
    }
    if (result == NULL) {
      LOGE("[-] result not find.");
      break;
    }
    ret = CppUtils::jstring2StdString(env, result);
    LOGD("[+] call ok, ret=%s", ret.c_str());


  } while (0);

  if (env != NULL && result != NULL) {
    env->DeleteLocalRef(result);
  }

  /*
   if(env!=NULL && javaClientStaticFuncMethod!=NULL) {
     env->DeleteLocalRef(javaClientStaticFuncMethod);
   }
  */
  if (env != NULL && javaClientClass != NULL) {
    env->DeleteLocalRef(javaClientClass);
  }
  /*
    if(env!=NULL && loadClassMethod!=NULL) {
      env->DeleteLocalRef(loadClassMethod);
    }
    */
  if (env != NULL && dexLoader != NULL) {
    env->DeleteLocalRef(dexLoader);
  }
  /*
  if(env!=NULL && initDexLoaderMethod!=NULL) {
    env->DeleteLocalRef(initDexLoaderMethod);
  }
  */
  if (env != NULL && dexLoaderClass != NULL) {
    env->DeleteLocalRef(dexLoaderClass);
  }

  if (env != NULL && sysLoader != NULL) {
    env->DeleteLocalRef(sysLoader);
  }

  /*
  if(env!=NULL && getSysLoaderMethod!=NULL) {
    env->DeleteLocalRef(getSysLoaderMethod);
  }
  */

  if (env != NULL && classloaderClass != NULL) {
    env->DeleteLocalRef(classloaderClass);
  }

  if (env != NULL && jDexPath != NULL) {
    env->DeleteLocalRef(jDexPath);
  }
  if (env != NULL && jDestDexPath != NULL) {
    env->DeleteLocalRef(jDestDexPath);
  }
  if (env != NULL && jClz != NULL) {
    env->DeleteLocalRef(jClz);
  }
  if (env != NULL && jArgs != NULL) {
    env->DeleteLocalRef(jArgs);
  }

  if (env != NULL && status != 0 && g_JVM != nullptr) {
    g_JVM->DetachCurrentThread();
  }

  return ret;
}