首先,动态加载与调用不是个新问题,方法也非常直接。关键就是使用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;
}