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