本文主要对早期的阿里加固方式进行了一些梳理
最近一直在分析安卓加固的方案,对很多知识都不太懂,一个偶然的机会在论坛上看到寒号鸟二代师傅的一篇仿ali加固一个早期的加固方案(https://bbs.pediy.com/thread-215078.htm),于是花了一段时间来分析这个项目实现流程,遂写下这篇文章,希望对学习jni编程和安卓dex文件运行流程的师傅们有帮助,同时也欢迎各位师傅们提出建议。
完成分析之后,我使用AS3.5的环境下编写了一个加固项目,项目地址如下:https://github.com/lzh18972615051/ShellCode1,Readme.md里面含有使用方法,欢迎各位Star和Fork
java层仅有有一个StubApplication.java类,该类加载的onCreate方法和attachBaseContext方法均放在了native层
首先自然是对attachBaseContext和onCreate进行了动态注册
// 生成jni函数注册的jni函数table static JNINativeMethod method_table[] = { // 被注册的函数的名称(java函数名称)、被注册函数的签名(javah获取)、 // 被注册函数native实现的函数指针 { "attachBaseContext", "(Landroid/content/Context;)V", (void*)native_attachContextBaseContext}, { "onCreate","()V",(void*)native_onCreate}, }; static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == 0) { return JNI_FALSE; } LOGI("gMethods %s,%s,%p\n ",gMethods[0].name,gMethods[0].signature,gMethods[0].fnPtr); if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } int register_ndk_load(JNIEnv *env) { // 对类"com/example/unpack/StubApplication"的函数attachBaseContext和onCreate进行注册 return registerNativeMethods(env, JNIREG_CLASS, method_table, NELEM(method_table)); } JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = 0; jint result = -1; // LOGI("JNI_OnLoad is called"); if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return result; } int status=register_ndk_load(env); if(!status) { LOGI("register call failed"); } return JNI_VERSION_1_4; }
阅读安卓相关源码可以知道,Application的attachBaseContext方法是早于onCreate方法执行的:
// 获取指定名称类的全局引用 jclass myFindClass(JNIEnv *env,jclass* ptr,char* name) { jobject g_cls_string; jclass clazz = (*env)->FindClass(env,name); if(clazz) { //// 获取类的全局引用(class) g_cls_string = (*env)->NewGlobalRef(env,clazz); //将全局引用(class)存到ptr中 *ptr=g_cls_string;myFindClass return g_cls_string; } else { return 0; } }
init_class与myFindClass结合初始化所有可能用到的变量,并对安卓虚拟机运行模式进行判断:
// 获取类android.os.Build.VERSION的全局引用(class) if( !myFindClass(env,&Build_version,"android/os/Build$VERSION")) { LOGI("ERROR:Build$VERSION"); return; } //通过Build_VERSION获取当前SDK版本的ID,并通过ID调用获取当前的SDK版本 jfieldID fieldID= ((*env)->GetStaticFieldID)(env, Build_version, "SDK_INT", "I"); sdk_int=(*env)->GetStaticIntField(env,Build_version,fieldID); LOGI("sdk_int %d\n",sdk_int); if(sdk_int>18){ }
{ //获取类android.app.ActivityThread的非静态成员变量mPackages的ID mPackages=(*env)->GetFieldID(env, ActivityThread, "mPackages", "Landroid/util/ArrayMap;"); // 获取类android.util.ArrayMap的全局引用(class) if ( !myFindClass(env, &myArrayMap, "android/util/ArrayMap") ) { LOGI("ERROR:myArrayMap"); return; } //通过myArrayMap获取其get方法的ID ArrayMap_get=(*env)->GetMethodID(env,myArrayMap,"get","(Ljava/lang/Object;)Ljava/lang/Object;"); //通过ActivityThread获取mBoundApplication的ID mBoundApplication = (*env)->GetFieldID(env, ActivityThread, "mBoundApplication", "Landroid/app/ActivityThread$AppBindData;"); //通过ActivityThread获取mInitialApplication的ID mInitialApplication = (*env)->GetFieldID( env, ActivityThread, "mInitialApplication", "Landroid/app/Application;"); //通过ActivityThread获取mAllApplications的ID mAllApplications = (*env)->GetFieldID(env, ActivityThread, "mAllApplications", "Ljava/util/ArrayList;"); //通过ActivityThread获取currentActivityThread的ID currentActivityThread = (*env)->GetStaticMethodID( env, ActivityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); //LOGI("ActivityThread:%p,%p,%p,%p",mBoundApplication,mInitialApplication,mAllApplications,currentActivityThread); // 获取类android.app.ActivityThread$AppBindData的全局引用(内部class) if ( !myFindClass(env, &AppBindData, "android/app/ActivityThread$AppBindData") ) { LOGI("ERROR:AppBindData"); return; } //通过AppBindData获取info的ID AppBindData_info=(*env)->GetFieldID(env, AppBindData, "info", "Landroid/app/LoadedApk;"); // 获取类java.util.ArrayList的全局引用(class) if ( !myFindClass(env, &myArrayList, "java/util/ArrayList") ) { LOGI("ERROR:myArrayList"); return; } //通过ArrayList获取ArrayList的size、get、set方法的ID arraylist_size = (*env)->GetMethodID(env, myArrayList, "size", "()I"); arraylist_get = (*env)->GetMethodID(env, myArrayList, "get", "(I)Ljava/lang/Object;"); arraylist_set = (*env)->GetMethodID(env, myArrayList, "set", "(ILjava/lang/Object;)Ljava/lang/Object;"); // 获取类android.content.Context的全局引用(class) if ( !myFindClass(env, &myContext, "android/content/Context") ) { LOGI("ERROR:myContext"); return; } //通过Context获取Context的getPackName、getApplicationInfo、getClassLoader、getAssets、getPackageResourePath方法的ID context_getPackageName = (*env)->GetMethodID(env, myContext, "getPackageName", "()Ljava/lang/String;"); context_getApplicationInfo = (*env)->GetMethodID( env, myContext, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); context_getClassLoader = (*env)->GetMethodID( env, myContext, "getClassLoader", "()Ljava/lang/ClassLoader;"); context_getAssets = (*env)->GetMethodID( env, myContext, "getAssets", "()Landroid/content/res/AssetManager;"); context_getPackageResourePath = (*env)->GetMethodID( env, myContext, "getPackageResourcePath", "()Ljava/lang/String;"); // 获取类java.lang.rel.WeakReference的全局引用(class) if ( !myFindClass(env, &myWeakReference, "java/lang/ref/WeakReference") ) { LOGI("ERROR:myWeakReference"); return; } //通过WeakReference获取其的get方法的ID WeakReference_get = (*env)->GetMethodID(env, myWeakReference, "get", "()Ljava/lang/Object;"); // 获取类android.app.LoadedApk的全局引用(class) if ( !myFindClass(env, &myLoadedApk, "android/app/LoadedApk") ) { LOGI("ERROR:myLoadedApk"); return; } //通过LoadedApk获取其的mClassLoader、mApplication的ID LoadedApk_mClassLoader = (*env)->GetFieldID( env, myLoadedApk, "mClassLoader", "Ljava/lang/ClassLoader;"); LoadedApk_mApplication = (*env)->GetFieldID( env, myLoadedApk, "mApplication", "Landroid/app/Application;"); // 获取类android.content.pm.ApplicationInfo的全局引用(class) if ( !myFindClass(env, &myApplicationInfo, "android/content/pm/ApplicationInfo") ) { LOGI("ERROR:myApplicationInfo"); return; } //通过ApplicationInfo获取其dataDir、nativeLibraryDir、sourceDir的ID ApplicationInfo_dataDir = (*env)->GetFieldID( env, myApplicationInfo, "dataDir", "Ljava/lang/String;"); ApplicationInfo_nativeLibraryDir =(*env)->GetFieldID( env, myApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;"); ApplicationInfo_sourceDir = (*env)->GetFieldID( env, myApplicationInfo, "sourceDir", "Ljava/lang/String;"); // 获取类android.app.Application的全局引用(class) if ( !myFindClass(env, &myApplication, "android/app/Application") ) { LOGI("ERROR:myApplication"); return; } //通过Application类获取其onCreate、attach方法的ID Application_onCreate = (*env)->GetMethodID(env, myApplication, "onCreate", "()V"); Application_attach = (*env)->GetMethodID( env, myApplication, "attach", "(Landroid/content/Context;)V"); /* StubApplication-->atachContextBaseContext()-->super.attachBaseContext */ // 获取类android.content.ContextWrapper的全局引用(class) if ( !myFindClass(env, &myContextWrapper, "android/content/ContextWrapper") ) { LOGI("ERROR:myContextWrapper"); return; } //通过android.content.ContextWrapper类获取其attachBaseContext方法的ID ContextWrapper_attachBaseContext = (*env)->GetMethodID( env, myContextWrapper, "attachBaseContext", "(Landroid/content/Context;)V"); LOGI("PathClassLoader start"); // 获取类dalvik.system.PathClassLoader的全局引用(class) if ( !myFindClass(env, &myPathClassLoader, "dalvik/system/PathClassLoader") ) { LOGI("ERROR:myPathClassLoader"); return; } //判断sdk版本 if(sdk_int>13) { // 获取类dalvik.system.BaseDexClassLoader的全局引用(class) if ( !myFindClass(env, &myBaseDexClassLoader, "dalvik/system/BaseDexClassLoader") ) { LOGI("ERROR:myBaseDexClassLoader"); return; } //通过BaseDexClassLoader类获取其pathlist的ID BaseDexClassLoader_pathList = (*env)->GetFieldID( env, myBaseDexClassLoader, "pathList", "Ldalvik/system/DexPathList;"); // 获取类dalvik.system.DexPathList的全局引用(class) if ( !myFindClass(env, &myDexPathList, "dalvik/system/DexPathList") ) { LOGI("ERROR:myDexPathList"); return; } //通过DexPathList类获取其pathlist的ID DexPathList_dexElements = (*env)->GetFieldID( env, myDexPathList, "dexElements", "[Ldalvik/system/DexPathList$Element;"); // 获取类dalvik.system.DexPathList.Element的全局引用(内部class) if ( !myFindClass(env, &myElement, "dalvik/system/DexPathList$Element") ) { LOGI("ERROR:myElement"); return; } //通过Element类获取其dexFile的ID DexPathList_Element_dexFile = (*env)->GetFieldID( env, myElement, "dexFile", "Ldalvik/system/DexFile;"); //判断sdk版本,这里是基于安卓6.0的初始化 /* 由于6.0和其之前的版本存在差异,需要反射其他代码 */ if(sdk_int>22){//6.0 //通过Element类获取其dir的ID DexPathList_Element_file = (*env)->GetFieldID(env, myElement, "dir", "Ljava/io/File;"); }else{ //通过Element类获取其dir的ID DexPathList_Element_file = (*env)->GetFieldID(env, myElement, "file", "Ljava/io/File;"); } //获取类java.io.File的全局引用(class) if ( !myFindClass(env, &myFile, "java/io/File") ) { LOGI("ERROR:myFile"); return; } //通过myFile类获取其getAbsolutePath的ID myFile_getAbsolutePath = (*env)->GetMethodID( env, myFile, "getAbsolutePath", "()Ljava/lang/String;"); LOGI("PathClassLoader end"); //获取类dalvik.system.DexFile的全局引用(class) if ( !myFindClass(env, &myDexFile, "dalvik/system/DexFile") ) { LOGI("ERROR:myDexFile"); return; } //判断sdk版本(其实这里有点累赘了可以把代码放在前面的判断) if(sdk_int>22) {//通过DexFile类获取其mCookie的ID和静态方法openDexFile的ID mCookie = (*env)->GetFieldID(env, myDexFile, "mCookie", "Ljava/lang/Object;"); myOpenDexFile=(*env)->GetStaticMethodID(env, myDexFile, "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;"); } else if ( sdk_int > 19 )//5.0版本的mCookie和myOpenDexFile的签名和6.0存在差异 {//通过DexFile类获取其mCookie的ID和静态方法openDexFile的ID mCookie = (*env)->GetFieldID(env, myDexFile, "mCookie", "J"); myOpenDexFile=(*env)->GetStaticMethodID(env, myDexFile, "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)J"); } else { //5.0一下版本的mCookie和myOpenDexFile的签名和6.0存在差异 mCookie = (*env)->GetFieldID(env,myDexFile, "mCookie", "I"); myOpenDexFile=(*env)->GetStaticMethodID(env, myDexFile, "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I"); } if ( !myFindClass(env, &myClassLoader, "java/lang/ClassLoader") ) { LOGI("ERROR:myClassLoader"); return; } //android 5+以上无法用findClass找到android.app.Application类 classLoader_findClass = (*env)->GetMethodID( env, myClassLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); LOGI("System start"); //获取类java.lang.System的全局引用(class) if ( !myFindClass(env, &mySystem, "java/lang/System") ) { LOGI("ERROR:myClassLoader"); return; } //通过System类获取getProperty静态方法的ID system_getProperty = (*env)->GetStaticMethodID( env, mySystem, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); LOGI("SystemProperties start"); status= myFindClass(env, &mySystemProperties, "android/os/SystemProperties"); if(status) { /* SystemProperties_get = (*env)->GetStaticMethodID( env, mySystemProperties, "get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");*/ SystemProperties_get = (*env)->GetStaticMethodID( env, mySystemProperties, "get", "(Ljava/lang/String;)Ljava/lang/String;"); //获取jstring类型的变量vmname,存放"java.vm.name"字符串 jstring vmname = (*env)->NewStringUTF(env, "java.vm.name"); //通过获取的getProperty方法的ID,调用有参方法getProperty("java.vm.name"),获取当前安卓虚拟机的名字 jobject tmp= (*env)->CallStaticObjectMethod(env, mySystem, system_getProperty, vmname); // 将tmp转换成C语言字符串 const char* v22 = (*env)->GetStringUTFChars(env, tmp, 0); LOGI("------- vmNameStr:%s", v22);//将转换成c语言字符的虚拟机名字打印 (*env)->ReleaseStringUTFChars(env, tmp, v22); // persist.sys.dalvik.vm.lib // persist.sys.dalvik.vm.lib.2 jstring vmlibstr = (*env)->NewStringUTF(env, "persist.sys.dalvik.vm.lib.2"); //这里反编译出错 jobject runtime = (*env)->CallStaticObjectMethod(env, mySystemProperties, SystemProperties_get, vmlibstr ); const char* v28 = (*env)->GetStringUTFChars(env, runtime, 0); //释放内存空间 (*env)->ReleaseStringUTFChars(env, runtime, v28); //获取jstring类型的变量vm_version,存放"java.vm.version"字符串 jstring vm_version = (*env)->NewStringUTF(env, "java.vm.version"); //通过获取的getProperty方法的ID,调用有参方法getProperty("java.vm.name"),获取当前安卓虚拟机的版本 jobject v32 = (*env)->CallStaticObjectMethod(env, mySystem, system_getProperty, vm_version); // 将v32转换成C语言字符串 const char* runtime_version = (*env)->GetStringUTFChars(env, v32, 0); LOGI("---- vmVersionStr:%s", runtime_version); //将虚拟机版本字符串转化成双精度浮点型(double) double d=atof(runtime_version); //根据逻辑,如果d>2则不是Dalvik虚拟机 if(d>2) isDalvik=0; else isDalvik=1; //释放内存 (*env)->ReleaseStringUTFChars(env, v32, runtime_version); return ; } } }
通过native层的反射,生成相关的文件夹,存放源apk所含有的所有文件,同时获取必要的apk包名等等,同时构建临时内存空间的字符串
/** *通过init_class生成的ContextWrapper和获取的attachBaseContext *调用父类的方法android.content.ContextWrapper.attachBaseContext(ctx) */ (*env)->CallNonvirtualVoidMethod(env, application_obj, myContextWrapper, ContextWrapper_attachBaseContext,ctx); /** *首先通过obj和env获取StubApplication类,再通过其获取到它的getFilesDir()方法 *然后调用getFileDir()方法获取能得到文件路径的File实例对象 *再调用File对象获取File类的getAbsolutePath()方法,获取到文件路径的对象,在将其转化成C语言字符串 *StubApplication-->getFilesDir()-->File(obj)-->File(class)-->getAbsolutePath()-->AbsolutePath(obj)-->AbsolutePath(jstring) , */ jclass v12 = (*env)->GetObjectClass(env, application_obj);// 获取StubApplication类 jmethodID v13 =(*env)->GetMethodID(env, v12, "getFilesDir", "()Ljava/io/File;");//获取getFilesDir()方法 // 调用File.getFilesDir()方法,该方法返回/data/data/<packagename>/files的File实例对象 jobject file_obj = (*env)->CallObjectMethod(env,obj , v13); //file_obj(obj)-->file_classz(class),通过File对象获取File类 jclass file_classz=(*env)->GetObjectClass(env,file_obj); //获取file类的getAbsolutePath方法ID jmethodID v18 = (*env)->GetMethodID(env, file_classz, "getAbsolutePath", "()Ljava/lang/String;"); //调用File.getAbsolutePath()获取文件路径/data/data/<packagename>/files jobject mAbsolutePath = (*env)->CallObjectMethod(env, file_obj, v18); //6.0下为/data/user/0/packagename/files/目录 //将文件路径对象转化成字符串 mAbsolutePath_str=(*env)->GetStringUTFChars(env,mAbsolutePath,0); LOGI("global files path is %s",mAbsolutePath_str); //return ApplicationInfo /** *调用一些初始化的方法获取外壳apk的so文件路径, *首先根据当前application_obj调用context的getApplicationInfo方法获取ApplicationInfo实例对象 *然后Application对象的nativeLibraryDir方法获取so文件路径的对象 *最后将so文件路径对象转化成C语言字符串,存放到mNativeLibraryDir对象中 *context.getApplicationInfo()-->ApplicationInfo(obj)-->ApplicationInfo.nativeLibraryDir()-->v24(obj)-->mNati*veLibraryDir(const char) */ //调用getApplicationInfo方法获取当前ApplicationInfo jobject ApplicationInfo = (*env)->CallObjectMethod(env, application_obj, context_getApplicationInfo); //获取so文件路径对象 jobject v24 = (*env)->GetObjectField(env, ApplicationInfo, ApplicationInfo_nativeLibraryDir); //获取外壳apk的so文件路径 const char* mNativeLibraryDir=(*env)->GetStringUTFChars(env,v24,0); //LOGI("mNativeLibraryDir is %s",mNativeLibraryDir); /** *调用一些初始化的方法ID获取apk的资源文件路径,然后指定外壳apk的进程APKAPATH作为资源存放路径 *调用StubApplication的getPackageResourcePath()获取资源文件路径对象,然后将其转化成char,在把它设置为APKPATH *的值设置为apk的资源文件存放路径 *StubApplication.getPackageResourcePath()-->v32(obj)-->mPackageResourePath(char*)->setenv("APKPATH",char*,1) */ //调用StuApplication的getPackageResourcePath()获取资源文件路径对象 jobject v32 = (*env)->CallObjectMethod(env, application_obj, context_getPackageResourePath); //将资源文件路径对象转化成char* const char* mPackageResourePath=(*env)->GetStringUTFChars(env,v32,0); //将环境变量"APKPATH"的值设置为apk的资源文件存放路径 setenv("APKPATH", mPackageResourePath, 1); //LOGI("APK Path is %s",mPackageResourePath); /** *调用Context的getPackageName方法获取APK包名 *mPackageName接受包名字符串 * */ //调用Context的getPackageName()方法,获取APK包名对象 jobject v36 = (*env)->CallObjectMethod(env, ctx, context_getPackageName); //获取APK包名字符串(jstring) mPackageName=(*env)->GetStringUTFChars(env,v36,0); LOGI("mPackageName:%s",mPackageName); /** *调用Context的getClassLoader()方法获取APK当前加载的ClassLoader *以便替换mCookie * */ // public ClassLoader getClassLoader() //调用context的getClassLoader()方法,获取当前apk加载使用的ClassLoader实例 jobject classLoader =(*env)->CallObjectMethod(env, ctx, context_getClassLoader); LOGI("classLoader:%p",classLoader); /** *调用获取到的mPackgeName,从/data/data/<packagename>/files目录下释放出dump.dex */ char szPath[260]={0}; // sprintf(szPath,"%s/dump.dex",mAbsolutePath_str); //使用获取/data/data/<packagename>/files/dump.dex的完整路径 sprintf(szPath,"/data/data/%s/files/dump.dex",mPackageName); LOGI("szPath:%s",szPath); /** *将/data/data/<packagename>/assets/dump.dex文件释放到内存中,在写入到 * data/data/<pacakgename>/files/dump.dex文件中 */
该函数调用资源管理器对象,打开apk资源文件目录下的源dex文件,将其进行解密操作,并把解密后的dex文件存放到临时的目录中去
myExtractFile(env,application_obj,szPath);
void myExtractFile(JNIEnv* env,jobject application_obj,const char* szPath) { //szPath=/data/data/%s/files/dump.dex AAssetManager* mgr;//声明资源管理器对象mgr //判断szPath路径下是否已经存在dump.dex文件 if(access(szPath,R_OK)) { //通过application对象获取application类 jclass v19 = (*env)->GetObjectClass(env, application_obj); //通过application类获取getAssets()方法的ID jmethodID v20 = (*env)->GetMethodID(env, v19, "getAssets", "()Landroid/content/res/AssetManager;"); //调用getAssets()方法返回值给v22(obj) jobject v22 = (*env)->CallObjectMethod(env, application_obj, v20); //给定一个Dalvik AssetManager对象,获取相应的本地AAssetManager对象。 mgr= AAssetManager_fromJava(env, v22); if(mgr==NULL) { //获取失败 LOGI(" %s","AAssetManager==NULL"); return ; } //打开apk资源文件夹下的dump.dex //即data/data/<packagename>/assets/dump.dex AAsset* asset = AAssetManager_open(mgr, "dump.dex",AASSET_MODE_STREAMING); FILE* file; void* buffer; int numBytesRead; if(asset) { //以szPath的字符串路径创建新的file(dump.dex)位于/data/data/<packagename>/files/目录下 file=fopen(szPath,"w"); // int bufferSize = AAsset_getLength(asset); // LOGI("buffersize is %d",bufferSize); //申请1024临时空间 buffer=malloc(1024); while(1) { //将资源文件目录下的dump.dex文件读取出来放到申请的临时空间buffer中 numBytesRead=AAsset_read(asset, buffer, 1024); //读取完成,结束while循环 if(numBytesRead<=0) break; //将存放到/data/data/<packagename>/files/file 文件中 fwrite(buffer,numBytesRead,1,file); } free(buffer); fclose(file); AAsset_close(asset); } else { LOGI("Error AAssetManager_open"); return; } } else { LOGI("dump.dex existed"); } }
加载模式一共有两种art模式和dvm模式,详细原理将会在下面说明
if(isDalvik) { myLoadDex_dvm(env,szPath); } else { myLoadDex_art(env,szPath); }
void myLoadDex_dvm(JNIEnv* env,char* szPath) { //加载动态库libdvm.so void *ldvm = (void*) dlopen("libdvm.so", 1); //获取动态库"libdvm.so"的导出结构体dvm_dalcik_system_DexFile,该结构体如下,里面存着各种方法: /** // jni函数注册的结构体DalvikNativeMethod const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { { "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I", Dalvik_dalvik_system_DexFile_openDexFileNative }, { "openDexFile", "([B)I", Dalvik_dalvik_system_DexFile_openDexFile_bytearray }, { "closeDexFile", "(I)V", Dalvik_dalvik_system_DexFile_closeDexFile }, { "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;", Dalvik_dalvik_system_DexFile_defineClassNative }, { "getClassNameList", "(I)[Ljava/lang/String;", Dalvik_dalvik_system_DexFile_getClassNameList }, { "isDexOptNeeded", "(Ljava/lang/String;)Z", Dalvik_dalvik_system_DexFile_isDexOptNeeded }, { NULL, NULL, NULL }, }; **/ JNINativeMethod* dvm_dalvik_system_DexFile = (JNINativeMethod*) dlsym(ldvm,"dvm_dalvik_system_DexFile"); //找到dvm_dalvik_system_DexFile结构体的openDexFile方法 if(0 == lookup(dvm_dalvik_system_DexFile, "openDexFile", "([B)I", &openDexFile)) { openDexFile = NULL; LOGI("method does not found ");//如果没有找到,就返回 return ; } else { LOGI("openDexFile method found ! HAVE_BIG_ENDIAN"); } int handle; struct stat buf={0}; //打开szPath文件下的dex文件/data/data/<pacakgename>/files/dump.dex handle=open(szPath,0); LOGI("handle:%X\n",handle); if(!handle) {//打开失败 LOGI("open dump.dex failed"); return; } //获取dex文件的大小 int status=fstat(handle,&buf); if(status) { LOGI("fstat failed"); return; } //获取dex文件长度 int dexLen=buf.st_size; LOGI("dexLen:%d,st_blksize:%d",dexLen,(int)buf.st_blksize); //#define PROT_READ 0x1 /* Page can be read. */ //#define PROT_WRITE 0x2 /* Page can be written. */ //#define PROT_EXEC 0x4 /* Page can be executed. */ //#define PROT_NONE 0x0 /* Page can not be accessed. */ //#define MAP_SHARED 0x01 /* Share changes. */ //#define MAP_PRIVATE 0x02 /* Changes are private. */ //将dex映射进入内存mmap函数 //映射起始地址0,映射内存区域大小dexLen,期望的内存保护标志3,映射对象类型1,文件描述符handle(open函数返 //回值),被映射对象从哪里开始对应0 char* dexbase = (char*)mmap(0, dexLen, 3, 2, handle, 0); LOGI("dex magic %c %c %c %c %c %c %c", *dexbase, *(dexbase + 1), *(dexbase + 2), *(dexbase + 3), *(dexbase + 4), *(dexbase + 5), *(dexbase + 6)); char* arr; //申请dexLen+16大小的空间Dalvik构建Dalvik_dalvik_system_DexFile_openDexFile_bytearray函数第一个传入参数args arr=(char*)malloc(16+dexLen); ArrayObject *ao=(ArrayObject*)arr; LOGI("sizeof ArrayObject:%d",sizeof(ArrayObject)); //ao获取dexLen ao->length=dexLen; //为什么获取的空间大小必须为dexLen+16呢? memcpy(arr+16,dexbase,dexLen); munmap(dexbase, dexLen); u4 args[] = { (u4) ao }; union JValue pResult; jint result; if(openDexFile != NULL) { //调用Dalvik_dalvik_system_DexFile_openDexFile_bytearray加载dex文件,返回mCookie openDexFile(args,&pResult); result = (jint) pResult.l; dvm_Cookie=result; LOGI("Dalvik Cookie :%X" , result); } }
void myLoadDex_art(JNIEnv* env,char* szPath) { jstring inPath=(*env)->NewStringUTF(env,szPath); if(sdk_int>22)//6.0以上 { art_MarCookie=(*env)->CallStaticObjectMethod(env, myDexFile, myOpenDexFile, inPath,0,0); LOGI("----MarCookie:%p",art_MarCookie); } else { art_Cookie=(*env)->CallStaticLongMethod(env, myDexFile, ,, inPath,0,0); LOGI("----artCookie:%llx",art_Cookie); } void* dlart=dlopen("libart.so",1); // 获取动态库文件libart.so的导出函数art::DexFile::FindClassDef的调用地址 pArtFun pArtDexFileFindClassDef=(pArtFun)dlsym(dlart,"_ZNK3art7DexFile12FindClassDefEt"); LOGI("pArtDexFileFindClassDef:%p",pArtDexFileFindClassDef); }
void replace_classloader_cookie(JNIEnv *env,jobject classLoader) { if(sdk_int>13) { // "java/lang/ClassLoader"-->pathList对象 jobject v28 = (*env)->GetObjectField(env, classLoader, BaseDexClassLoader_pathList); //获取Elements数组对象 //pathList-->Element数组对象 jobject v15 = (*env)->GetObjectField(env, v28, DexPathList_dexElements); int count = (*env)->GetArrayLength(env, v15);//获取Element数组长度 //由于只有一个dex文件,count一直是1 LOGI("element count: %d", count); int i=0; while(i<count) { //获取Element数组的第i个元素 jobject Element = (*env)->GetObjectArrayElement(env, v15, i); //获取dDexPathList&Element示例对象的非静态成员变量dir/file,这些好像有点多余的感觉 jobject fileclazz= (*env)->GetObjectField(env, Element, DexPathList_Element_file);// 获取file类 jobject v20 = (*env)->CallObjectMethod(env, fileclazz, myFile_getAbsolutePath);// file.getAbsolutePath() const char* str = (*env)->GetStringUTFChars(env, v20, 0); //android 6.0下str为:/ LOGI("element is %s", str); // /*int length = ((*env)->GetStringUTFLength)(env, v20); int cmpstatus = strncasecmp("apk", (length - 3 + str), 3); ((*env)->ReleaseStringUTFChars)(env, v20, str);*/ //通过Element获取DexFile对象 jobject DexFile = (*env)->GetObjectField(env, Element, DexPathList_Element_dexFile);// 获取DexFile类 //判断sdk版本,并替换对应的mCookie if(sdk_int<=19) { LOGI("SetIntField"); LOGI("---dvm_Cookie:%X",dvm_Cookie); (*env)->SetIntField(env, DexFile, mCookie, dvm_Cookie); } else if(sdk_int>22) { LOGI("SetObjectField"); LOGI("---art_MarCookie:%X",art_MarCookie); (*env)->SetObjectField(env, DexFile, mCookie, art_MarCookie); } else { LOGI("SetLongField"); LOGI("----artCookie:%llx",art_Cookie); (*env)->SetLongField(env, DexFile, mCookie, art_Cookie); } /*if(!cmpstatus) { break; }*/ i++; } LOGI("exit replace_classloader_cookie"); } }
在介绍完之后,我们在对attachBaseContext执行的流程和原理进行一次剖析
显然在加固的过程中由于attachBaseContext的加载早于各个组件的创建过程,因此我们会选择attachBaseContext作为加载源dex地方
首先为了能够顺利的加载源apk程序,我们需要创建apk文件对应的各种文件夹,在将相关的文件放入其中,然后分配一个临时的内存空间/data/data/<包名>/files/dump.dex,并将待加载的dex(已加密,存放在资源文件下)解密后,写入到该临时空间中
将dex文件解密之后,我们需要读取临时空间,将该空间中的dex文件映射到内存中进行加载,根据不同的安卓虚拟机,我们会采取不同的加载方式,例如dvm模式下,采用Dalvik_dalvik_system_DexFile_openDexFile_bytearray
函数,与其对应的还有一个函数Dalvik_dalvik_system_DexFile_openDexFileNative
二者同样都能实现dex文件的加载,然而我们却选择了前者,一个很重要的原因就是,Dalvik_dalvik_system_DexFile_openDexFile_bytearray
相较于后者不会暴露待加载dex文件所在的位置,通过其源码比较:
openDexFileNative:
bytearray:
二者最大的区别在于bytearray函数进行dex加载时,是对已经映射到内存中dex文件的字节数组进行的加载,而openDexFileNative会要求使用到dex文件的路径,这种操作对于早期的壳来说,时间十分致命的事情,破解者可以轻松的找到被解密的dex存放的路径,然后轻松的将其dump出来,大大的降低了软件壳的安全性,因此我们采用的是bytearray函数
而art模式下,由于安卓源码没有提供较好的加载方法,在加上贾荣祥等考虑,我们采用了其openDexFile函数来实现dex文件的加载,该函数在前面的初始化中已经获取了其ID值,现在只需要进行调用即可
static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); return app; }
final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
一旦通过LoadedApk的马克Application方法创建Application对象Application app=(Application)clazz.newInstance();
,就一定会调用到Application方法中的attach()方法,进而就会调用到attachBaseContext()方法,因此只要在壳程序的Application类中重写这一个方法,并在此处解密源dex文件,再用DexClassLoader进行动态加载,然后替换跳LoadedApk中默认的类加载器为自定义的类加载器,那么就能使壳apk加载源dex了,而要想替换掉当前的类加载器,我们只需要替换掉其mCookie成员变量即可,
那么如何替换掉mCookie呢,我们再深挖安卓源码发现每一个Android应用启动后,都会有一个对应的ActivityThread对象实例来管理,dex文件都是以ClassLoader对象的实例被ActivityThread对象进行管理的,然后我们再剖析DexClassLoader源码发现,每一个DexClassLoader对象的实例都是通过DexPathList列表存储dex文件相关信息的,而其中一个含有一个重要的结构体DexFile,DexFile的成员变量之一就是mCookie,分析到这里,我们就找到了mCookie所在的地方
DexClassLoader-->BaseDexClassLoader-->DexPathList-->DexPathList-->DexFile-->mCookie
分析安卓源码发现在替换掉类加载器后,还需要替换掉一些对象,例如需要将待加载dex的application对象替换掉壳application对象
if (mApplication != null) { return mApplication; } mActivityThread.mAllApplications.add(app); mApplication = app;
除了mApplication,我们还需要修改4处Application的实例对象的成员变量
为了修改该参数,我们通过分析源码找到了一个变量mPackages
mPackages是ActivityThread的一个非静态成员变量,在函数中的定义如下
ArrayMap<String,WeakReference<LoadedApk>>
我们通过获取该成员变量就能获取LoadedApk对象,进而就能获取到LoadedApk的非静态成员变量mApplication,最后将其替换成源dex文件的Application实例对象
/** *(1)获取壳apk的ActivityThread实例对象的的非静态成员变量mPackages *mPackages: ArrayMap<String,WeakReference<LoadedApk>> mPackages *再修改对应的LoadedApk实例对象的非静态成员变量mApplication为被源dex文件的类Application *java代码: android.util.ArrayMap arrayMap=activityThread.mPackages; * LoadedApk objLoadedApk=arrayMap.get("thePackagename"); * */ //获取android.util.ArrayMap的成员变量mPackages的对象 jobject arraymap_mPackages =(*env)->GetObjectField(env, theCurrentActivityThread, mPackages); //获取壳apk包名字符串 jstring thePackagename=(*env)->NewStringUTF(env,mPackageName); LOGI("mPackageName %s",mPackageName); // 调用arrayMap_get函数 获取WeakReference<LoadedApk>类 jobject v9 = (*env)->CallObjectMethod(env, arraymap_mPackages, ArrayMap_get, thePackagename); //调用WeakReference_get获取LoadedApk类 jobject v15 = (*env)->CallObjectMethod(env, v9, WeakReference_get); //修改LoadedApk中的非静态成员变量mApplication为源dex的Application (*env)->SetObjectField(env, v15, LoadedApk_mApplication, onCreateObj);
/** *(2)修改壳apk应用的ActivityThread实例对象的成员变量mInitialApplication *为源dex文件的Application对象实例 * * */ (*env)->SetObjectField(env, theCurrentActivityThread, mInitialApplication,onCreateObj);
修改壳apk应用的ActivityThread实例对象的成员变量mBoundApplication中的LoadApk非静态成员变量info实例对象的实例成员变量mApplication为源dex的Application对象
/** *(3)修改壳apk应用的ActivityThread实例对象的成员变量mBoundApplication中的LoadApk非静态成员变量info实例对象的 *实例成员变量mApplication为源dex的Application对象 *AppBindData mBoundApplication; *AppBindData{LoadedApk info;} *LoadedApk{Application mApplication} */ // AppBindData mBoundApplication; jobject v16 = (*env)->GetObjectField(env, theCurrentActivityThread, mBoundApplication); //AppBindData_info=(*env)->GetFieldID(env, AppBindData, "info", "Landroid/app/LoadedApk;"); jobject v17 =(*env)->GetObjectField(env, v16, AppBindData_info); (*env)->SetObjectField(env, v17, LoadedApk_mApplication, onCreateObj);
/** *(4)遍历壳apk应用的ActivityThread实例对象的非静态成员变量mAllApplications中的类Application实例对象 *将壳apk的StubApplication替换为源dexapplication实例对象 *StubApplication-->Application *ArrayList<Application> mAllApplications */ //获取allApplications jobject v11 = (*env)->GetObjectField(env, theCurrentActivityThread, mAllApplications); //获取ArrayList的大小 int count = (*env)->CallIntMethod(env, v11, arraylist_size); LOGI("array_size:%d",count); int index = 0; while ( index < count ) { //调用mAllApplication(ArrayList)的get方法获取每一个元素ArrayList.get(index) jobject v12 = (*env)->CallObjectMethod(env, v11, arraylist_get, index); LOGI("compare: i=%d item=%p", index, v12); //在类对象mApplication实例中查找壳apk应用的StubApplication类对象 if ( ((*env)->IsSameObject)(env, obj, v12) == 1 )//是否等于StubApplication类的对象 { LOGI("replace: find same replace"); //调用ArrayList的set方法替换源Application的实例对象 (*env)->CallObjectMethod(env, v11, arraylist_set, index,onCreateObj); } // _JNIEnv::DeleteLocalRef(env, v12); ++index; } //IDA反编译这里出错,多了一个参数 (*env)->CallVoidMethod(env, onCreateObj, Application_onCreate); (*env)->DeleteGlobalRef(env, onCreateObj); }