美文网首页
Android 一二三代壳加固原理分析

Android 一二三代壳加固原理分析

作者: 曾经灬 | 来源:发表于2022-01-12 19:15 被阅读0次

    简介

    • 所有的加固代码 都需要通过Classloader加载然后才可以执行

    classloader介绍

    • 双亲委派机制

      双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
      如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器
      如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
      即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这个就是双亲委派。

    • Android中的classloader

      ClassLoader为抽象类;
      BootClassLoader预加载常用类,单例模式。
      BaseDexClassLoader是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类,类加载的主要逻辑都是在BaseDexClassLoader完成的。
      SecureClassLoader继承了抽象类ClassLoader,拓展了ClassLoader类加入了权限方面的功能,加强了安全性,其子类URLClassLoader是用URL路径从jar文件中加载类和资源。
      其中重点关注的是PathClassLoader和DexClassLoader。
      PathClassLoader是Android默认使用的类加载器,一个apk中的Activity等类便是在其中加载。
      DexClassLoader可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现插件化、热修复以及dex加壳的重点。
      Android8.0新引入InMemoryDexClassLoader,从名字便可看出是用于直接从内存中加载dex。

    596742716226444.png

    APP 的启动流程

    534413116246610.png

    APP类加载过程

    • BootClassLoader加载系统核心库
    • PathClassLoader加载APP自身dex
    • 进入APP自身组件开始执行,调用声明Application的attachBaseContext,onCreate
      • AppThread中有LoadedApk对象。
      • AppThread handledBindApplication(应该是AMS 回调的ApplicationThread)中,第一次进入到App自身的代码中去。
      • Application attach -> oncreate最先被执行,如果加壳的话,此时的classloader只有壳的代码 没有自身代码
      • 如果不处理classloader的话,只有壳代码,是无法启动注册的Activity Service等的。
        *<mark> 所以壳代码需要在这里加载真正的代码</mark>

    组件生命周期的处理

    DexClassLoader加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对APK的动态加载完成了对组件类的加载,当系统启动该组件时,依然会出现加载类失败的异常。
    需要替换系统组件的classloader才可以。加固厂商必然饶不过去
    两种解决方案:
    1、替换系统组件类加载器为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器;

    //classloader 已经加载了 自身代码 并且parent是原始的classloader
    public void replaceClassloader(ClassLoader classloader){
            try {
                Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
                Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
                currentActivityThreadMethod.setAccessible(true);
                Object activityThreadObj=currentActivityThreadMethod.invoke(null);
                //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
                Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
                mPackagesField.setAccessible(true);
                ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
                WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
                Object loadedApkObj=wr.get();
    
                Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
                //private ClassLoader mClassLoader;
                Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
                mClassLoaderField.setAccessible(true);
                mClassLoaderField.set(loadedApkObj,classloader);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    2、打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader即可;

    public void startTestActivitySecondMethod(Context context,String dexfilepath){
    
            File optfile=context.getDir("opt_dex",0);
            File libfile=context.getDir("lib_path",0);
            ClassLoader pathClassloader=MainActivity.class.getClassLoader();
            ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
            DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
            try {
                Field parentField=ClassLoader.class.getDeclaredField("parent");
                parentField.setAccessible(true);
                parentField.set(pathClassloader,dexClassLoader);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    

    dex整体加密型加固

    • 将Dex整体加密,在需要的时候解密,通过Classloader加入内存中。

    关键代码

    public class TestActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //com.example.myapplication.TestClass.testFunc
            Context appContext=this.getApplicationContext();
            //testDexClassLoader(appContext,"/sdcard/3.dex");
            //startTestActivity(this,"/sdcard/4.dex");
            startTestActivitySecondMethod(this,"/sdcard/6.dex");
        }
    
        //替换LoadedApk中的mClassLoader 加载自身代码
        public void replaceClassloader(ClassLoader classloader){
            try {
                Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
                Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
                currentActivityThreadMethod.setAccessible(true);
                Object activityThreadObj=currentActivityThreadMethod.invoke(null);
                //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
                Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
                mPackagesField.setAccessible(true);
                ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
                WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
                Object loadedApkObj=wr.get();
    
                Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
                //private ClassLoader mClassLoader;
                Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
                mClassLoaderField.setAccessible(true);
                mClassLoaderField.set(loadedApkObj,classloader);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
    
    
        }
    
        public void startTestActivityFirstMethod(Context context,String dexfilepath){
    
            File optfile=context.getDir("opt_dex",0);
            File libfile=context.getDir("lib_path",0);
            ClassLoader parentClassloader=MainActivity.class.getClassLoader();
            ClassLoader tmpClassLoader=context.getClassLoader();
            DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
    
            replaceClassloader(dexClassLoader);
    
            Class<?> clazz=null;
            try {
                clazz = dexClassLoader.loadClass("com.example.myapplication.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            context.startActivity(new Intent(context,clazz));
        }
        public void startTestActivitySecondMethod(Context context,String dexfilepath){
    
            File optfile=context.getDir("opt_dex",0);
            File libfile=context.getDir("lib_path",0);
            ClassLoader pathClassloader=MainActivity.class.getClassLoader();
            ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
            //加载dex 这里直接加载了未加密的,可以解密后 再加载,然后删除。也可以替换为memoryclassloader加载 ,实现不落地加载
            DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
            try {
                //插入我们自己的dexClassLoader 加入生命周期的处理
                Field parentField=ClassLoader.class.getDeclaredField("parent");
                parentField.setAccessible(true);
                parentField.set(pathClassloader,dexClassLoader);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    /*
    * this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.xyh.loaddex-8_fCxispeBuExjw1ryrRZg==/base.apk"],nativeLibraryDirectories=[/data/app/com.xyh.loaddex-8_fCxispeBuExjw1ryrRZg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.xyh.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]
    this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.xyh.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]--parent:java.lang.BootClassLoader@fd4323d
    root:java.lang.BootClassLoader@fd4323d*/
            ClassLoader tmpClassloader=pathClassloader;
            ClassLoader parentClassloader=pathClassloader.getParent();
            while(parentClassloader!=null){
                Log.i("xyh","this:"+tmpClassloader+"--parent:"+parentClassloader);
                tmpClassloader=parentClassloader;
                parentClassloader=parentClassloader.getParent();
            }
            Log.i("xyh","root:"+tmpClassloader);
            Class<?> clazz=null;
            try {
                clazz = dexClassLoader.loadClass("com.example.myapplication.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            context.startActivity(new Intent(context,clazz));
        }
    }
    

    加固类型-函数抽取

    壳与脱壳之二代壳函数抽取/#源码分析

    Dalvik指令抽取原理介绍

    Android中实现「类方法指令抽取方式」加固方案原理解析
    Android免Root权限通过Hook系统函数修改程序运行时内存指令逻辑

    • 在函数粒度上保护代码
    • 将函数指令全部替换为Nop或无效指令,在执行时还原代码。
    • 主要原理是将原指令置为nop,然后hook,dexFindClass,找到原始的方法指令指针,DexCode->insns,替换insns。
    207665618235835.png

    Art 抽取壳介绍

    Art下实现难点:dex2oat编译流程,dex2oat是可以进行脱壳,dex2oat完成了对抽取的dex进行编译生成了oat文件,后续的函数运行中,从oat中取出函数编译生成的二进制代码来执行,因此函数对dex填充后,如果时机不对,时机在dex2oat后,自然从dex2oat后那么我们动态修改的dex中的smali指令流就不会生效,因为后面app运行调用的真正的代码就会从dex2oat编译生成的oat文件,和以前的dex无关了。因此如果希望填充回去smali指令生效要么禁用dex2oat实现阻止编译,这样对加载到内存中的dex文件进行填充始终会保持生效,要么保持dex2oat编译,但是还原代码时机要早于dex2oat就ok了,保证dex2oat再次对dex编译的时候,dex已经是一个完整dex,不会影响我们填充的代码,但是肯定dex文件存在完整的时候,可以利用dex2oat编译的流程进行脱壳,一般加壳厂商都是牺牲掉app一部分的运行效率,干掉dex2oat的过程,因为google本身提倡dex2oat就是为了提升app运行效率。
    <mark>ART模式下抽取壳 要么阻止dex2oat 要么在dex2oat之前保证加载的dex是一个完整的dex</mark>
    如果选择第一种方案 , 在 dex2oat 之前进行恢复 , 这没有任何意义 , dex2oat 编译后 , 生成的 oat 文件是完整的 , 此时 可以 完整的将 oat 文件 dump 到 SD 卡中 , 基本等于没有加固 , 还是一个一代壳 ;
    因此 , 大部分加固厂商 , 选择 禁用 dex2oat 机制 ; 这样处于安全考虑 , 牺牲了应用的运行效率 ;

    145113619231589.png
    • classloader加载dex的时候 会进行dex2oat的优化,将dex的解释执行,变成直接执行,提升运行效率。
    • 7.0之后会根据函数使用频率进行dex2oat的优化,并不会直接全部dex2oat

    https://source.android.google.cn/devices/tech/dalvik/configure
    ART 使用预先 (AOT) 编译,并且从 Android 7.0(代号 Nougat,简称 N)开始结合使用 AOT、即时 (JIT) 编译和配置文件引导型编译。所有这些编译模式的组合均可配置,我们将在本部分中对此进行介绍。例如,Pixel 设备配置了以下编译流程:
    7.0之后最初安装应用时不进行任何 AOT 编译。应用前几次运行时,系统会对其进行解译,并对经常执行的方法进行 JIT 编译。
    当设备闲置和充电时,编译守护程序会运行,以便根据在应用前几次运行期间生成的配置文件对常用代码进行 AOT 编译。
    下一次重新启动应用时将会使用配置文件引导型代码,并避免在运行时对已经过编译的方法进行 JIT 编译。在应用后续运行期间经过 JIT 编译的方法将会添加到配置文件中,然后编译守护程序将会对这些方法进行 AOT 编译。
    ART 的运作方式 在 Android O 版本中,将会生成以下文件:
    .vdex:其中包含 APK 的未压缩 DEX 代码,以及一些旨在加快验证速度的元数据。
    使用010editer就可以找到dex
    .odex:其中包含 APK 中已经过 AOT 编译的方法代码。
    oatdump 可以反编译odex文件,可以dump出函数的smali指令
    .art (optional):其中包含 APK 中列出的某些字符串和类的 ART 内部表示,用于加快应用启动速度。
    全部进行oat优化的话,非常耗时,所以7.0之后选取了保守的优化策略
    odex 优化后,oatdump还是可以dump出原始的smali指令,只是优化后的函数 在CODE段,会有优化后的二进制汇编指令,在执行的时候会直接进入quick模式,执行汇编指令,加快运行速度。具有CODE段后,将不再进入解释执行模式。

    • art抽取壳必须在dex2oat之前还原,否则dex2oat之后,就无法还原。要么阻止dex2oat 要么提前还原
    • 首先要干掉Dex2oat的过程。可以参考github TurboDex(快速加载dex 阻止dex2oat) dex2oat 非常耗时;不进行oat的话 会影响dex的运行效率。
    • 阻止了dex2oat后 所有代码都是解释执行了。那么必然会去内存中寻找ArtMethod的code_item去执行
    • 那么在art类加载的时候还原code_item就可以正常去执行了
    • 剩下的和dalvik基本一致
      • 1、先通过010edite将一个函数全部抽取为nop
      • 2、hook art loadmethod的过程
      • 3、将原始的指令还原到artmethod中

    ART的类加载执行流程

    强烈建议阅读此文章
    ART 在 Android 安全攻防中的应用

    关键代码

    #include <jni.h>
    #include <string>
    #include <unistd.h>
    #include <android/log.h>
    #include <fcntl.h>
    #include <asm/fcntl.h>
    #include <sys/mman.h>
    #include <dlfcn.h>
    //import c header
    extern "C" {
    #include "hook/dlfcn/dlfcn_compat.h"
    #include "hook/include/inlineHook.h"
    }
    typedef unsigned char byte;
    #define TAG "SecondShell"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
    struct DexFile {
        // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
        // The class we are a part of.
        uint32_t declaring_class_;
        // Access flags; low 16 bits are defined by spec.
        void *begin;
        /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
        // Offset to the CodeItem.
        uint32_t size;
    };
    struct ArtMethod {
        // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
        // The class we are a part of.
        uint32_t declaring_class_;
        // Access flags; low 16 bits are defined by spec.
        uint32_t access_flags_;
        /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
        // Offset to the CodeItem.
        uint32_t dex_code_item_offset_;
        // Index into method_ids of the dex file associated with this method.
        uint32_t dex_method_index_;
    };
    
    void* *(*oriexecve)(const char *__file, char *const *__argv, char *const *__envp);
    
    void* *myexecve(const char *__file, char *const *__argv, char *const *__envp) {
        LOGD("process:%d,enter execve:%s", getpid(), __file);
        if (strstr(__file, "dex2oat")) {
            return NULL;
        } else {
            return oriexecve(__file, __argv, __envp);
        }
    
    
    }
    
    //void ClassLinker::LoadMethod(Thread* self, const DexFile& dex_file, const ClassDataItemIterator& it,Handle<mirror::Class> klass, ArtMethod* dst)
    void *(*oriloadmethod)(void *, void *, void *, void *, void *);
    
    void *myloadmethod(void *a, void *b, void *c, void *d, void *e) {
        LOGD("process:%d,before run loadmethod:", getpid());
        struct ArtMethod *artmethod = (struct ArtMethod *) e;
        struct DexFile *dexfile = (struct DexFile *) b;
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d", getpid(), dexfile->begin,
             dexfile->size);//0,57344
        char dexfilepath[100] = {0};
        sprintf(dexfilepath, "/sdcard/%d_%d.dex", dexfile->size, getpid());
        int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
        if (fd > 0) {
            write(fd, dexfile->begin, dexfile->size);
            close(fd);
        }
    
        void *result = oriloadmethod(a, b, c, d, e);
        LOGD("process:%d,enter loadmethod:code_offset:%d,idx:%d", getpid(),
             artmethod->dex_code_item_offset_, artmethod->dex_method_index_);
    
        byte *code_item_addr = static_cast<byte *>(dexfile->begin) + artmethod->dex_code_item_offset_;
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,beforedumpcodeitem:%p", getpid(),
             dexfile->begin, dexfile->size, code_item_addr);
    
    
        if (artmethod->dex_method_index_ == 15203) {//TestClass.testFunc->methodidx
            LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,start repire method", getpid(),
                 dexfile->begin, dexfile->size);
            byte *code_item_addr = (byte *) dexfile->begin + artmethod->dex_code_item_offset_;
            LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,beforedumpcodeitem:%p", getpid(),
                 dexfile->begin, dexfile->size, code_item_addr);
    
            int result = mprotect(dexfile->begin, dexfile->size, PROT_WRITE);
            byte *code_item_start = static_cast<byte *>(code_item_addr) + 16;
            LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,code_item_start:%p", getpid(),
                 dexfile->begin, dexfile->size, code_item_start);
            byte inst[16] = {0x1a, 0x00, 0xed, 0x34, 0x1a, 0x01, 0x43, 0x32, 0x71, 0x20, 0x91, 0x05,
                             0x10, 0x00, 0x0e, 0x00};
            for (int i = 0; i < sizeof(inst); i++) {
                code_item_start[i] = inst[i];
            }
            //2343->i am from com.kanxue.test02.TestClass.testFunc
            code_item_start[2] = 0x43;//34ed->kanxue
            code_item_start[3] = 0x23;
            memset(dexfilepath, 0, 100);
            sprintf(dexfilepath, "/sdcard/%d_%d.dex_15203_2", dexfile->size, getpid());
            fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
            if (fd > 0) {
                write(fd, dexfile->begin, dexfile->size);
                close(fd);
            }
        }
        LOGD("process:%d,after loadmethod:code_offset:%d,idx:%d", getpid(),
             artmethod->dex_code_item_offset_, artmethod->dex_method_index_);//0,57344
        return result;
    
    }
    
    void hooklibc() {
        LOGD("go into hooklibc");
        //7.0 命名空间限制
        void *libc_addr = dlopen_compat("libc.so", RTLD_NOW);
        void *execve_addr = dlsym_compat(libc_addr, "execve");
        if (execve_addr != NULL) {
            if (ELE7EN_OK == registerInlineHook((uint32_t) execve_addr, (uint32_t) myexecve,
                                                (uint32_t **) &oriexecve)) {
                if (ELE7EN_OK == inlineHook((uint32_t) execve_addr)) {
                    LOGD("inlineHook execve success");
                } else {
                    LOGD("inlineHook execve failure");
                }
            }
        }
    }
    
    void hookART() {
        LOGD("go into hookART");
        void *libart_addr = dlopen_compat("/system/lib/libart.so", RTLD_NOW);
        if (libart_addr != NULL) {
            void *loadmethod_addr = dlsym_compat(libart_addr,
                                                 "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE");
            if (loadmethod_addr != NULL) {
                if (ELE7EN_OK == registerInlineHook((uint32_t) loadmethod_addr, (uint32_t) myloadmethod,
                                                    (uint32_t **) &oriloadmethod)) {
                    if (ELE7EN_OK == inlineHook((uint32_t) loadmethod_addr)) {
                        LOGD("inlineHook loadmethod success");
                    } else {
                        LOGD("inlineHook loadmethod failure");
                    }
                }
            }
        }
    
    
    }
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_kanxue_secondshell_180_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "ART SecondShell";
        return env->NewStringUTF(hello.c_str());
    }
    extern "C" JNIEXPORT void JNICALL
    Java_com_kanxue_secondshell_180_MainActivity_SecondShell(
            JNIEnv *env,
            jobject /* this */) {
        hooklibc();//hook execve 禁止执行dex2oat
        hookART();//hook LoadMethod 修复被抽取的artmethod,并在修复前修复后dumpdex
        return;
    }
    
    

    加固类型-VMP,Dex2C

    • VMP 自定义解释器,解释执行自定义的指令。
    • Dex2c 在编译时,将java代码编译成c代码 增加破解难度
    • 未来加固的主要方向->VMP,Dex2C
      • VMP 会把java函数native化,原理:VMP是做虚拟机解释器
        • VMP的多个native函数 很可能是一个地址。多个native函数 都是调用的一个地方
        • 破解需要找到解释器,找到smaili的映射关系即可恢复
        • github 搜索 ADVMP 可以参考 没具体研究
      • Dex2c 开源项目 dcc,原理:对java语义分析,重新生成native函数。
        • python dcc.py dcc.apk -o dcc_out.apk 使用DCC增加保护,java函数会变成native函数。 只建议部分函数增加保护,否则会严重影响性能
        • dcc可以将APK的java代码 转化为native代码,并且可以增加ollvm的混淆
        • dcc的native注册函数 基本是一一对应
        • 基本不太好破解。。

    相关文章

      网友评论

          本文标题:Android 一二三代壳加固原理分析

          本文链接:https://www.haomeiwen.com/subject/dozwcrtx.html