【Android学习】壳的通识与发展

工具学习

Posted by Corax on May 15, 2024

image-20240526144047568

到activitythread才算进入到我们app的代码,来看会源码

这个activitythread里面这个

image-20240526154451009

先获取到仅有的这个单例的实例,,然后反射到这个mpackages的这个arraymap,然后通过当前的这个包名获得loadedapk,然后通过loadedapk获得当前的mclassloader

image-20240526154736809

image-20240526154847060

这个handlebindapplication才是第一次进入app代码世界,重点是第四步的makeappapplication

image-20240526154958079

image-20240526155026558

来,我们跟进去在 这个newapplication是关键

image-20240526155335171

newapplication调用其重载 执行attach函数

image-20240526155416620再跟 熟悉的attachbasecontext

image-20240526155639642

image-20240526155849559

经过上面的源码分析也可以看到attachbasecontext和oncreate是最先执行的,加载壳一般也在这里做文章。

image-20240526160014804

壳程序要解密dex,还要跟正确的application交付各种资源,不然的话类似于组件等资源加载是不正常。

何时进行dex解密

​ attachbasecontext和oncreate里面

何时解决动态加载的dex中类的生命周期问题

image-20240526203912010

通过反射进行替换

下面是一个例子

package com.example.kanxue122;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;

import java.lang.Object;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        Context appContext = this.getApplicationContext();
        startTestActivityFirstMethod(this,"/sdcard/4.dex");
    }
    public void testDexClassLoader(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());
        Class<?> clazz=null;
        try {
            clazz = dexClassLoader.loadClass("com.kanxue.test02.TestClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if(clazz!=null){
            try {
                Method testFuncMethod=clazz.getDeclaredMethod("testFunc");
                Object obj=clazz.newInstance();
                testFuncMethod.invoke(obj);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }


    }
    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.kanxue.test02.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();
        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();
        }
/*
* this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]
this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.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("kanxue","this:"+tmpClassloader+"--parent:"+parentClassloader);
            tmpClassloader=parentClassloader;
            parentClassloader=parentClassloader.getParent();
        }
        Log.i("kanxue","root:"+tmpClassloader);
        Class<?> clazz=null;
        try {
            clazz = dexClassLoader.loadClass("com.kanxue.test02.TestActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        context.startActivity(new Intent(context,clazz));
    }
}

大概是怎么样一个替换法

oncreate

startTestActivityFirstMethodstar

replaceClassloader 通过反射 替换了目标dex当中mclassloader

startTestActivityFirstMethodstar 完成对目标dex目标activity的调用

注意manifest还要改

注意是替换组件的classloader

第二种解决方法

image-20240526233843756

利用了这个双亲委派机制,当前pathclassloader无法加载的时候,通过调用我们加载的classloader来加载,这就需要对当前classloader的父节点做操作

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();
        }
/*

其实也是通过反射来实现插入的,

DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);

把插入的dexclassloader的parent变成现在classloader的parent

把现在的parent变成dexclassloader

经过log打印

* this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]
this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]--parent:java.lang.BootClassLoader@fd4323d
root:java.lang.BootClassLoader@fd4323d*/

可以看到很明显修改了classloader的双亲委派关系

说回加壳,有两种方式,本身没有application的声明,只要自己进行声明,然后壳程序实现解密,加载就可以

另外的一种要额外去调用原apk的application

8以上的inmemoryclassloader从内存中读,避免直接被找到文件

4左右的 opendexfile是文件形式 安全性低

动态加载

是dex加壳,插件化,热更新的一个基础

什么是热加载 就是用到的时候再去加载

动态加载的dex是不具有声明周期特征的,app当中的activity,service等组件无法正常工作。

因为组件的class loader和我们使用的dexclassloader 不具备双亲委派的特征,导致pathclassloader找不到相关的组件类

所以需要去修复这个classloader

解决方案一 就是

image-20240527000348285

第二种

image-20240527000358537

上面有做过

壳的知识

image-20240527000622689

一代壳也叫dex整体壳 内存当中的dex连续的 好脱一些

二代壳 首先对dex有个整体的加密 但是对特定的函数进行了抽取,只有被调用,或者被加载,才在内存有。这里有一个dex hunter 遍历dex所有类进行调用和初始化

image-20240527004442030

image-20240527004452058

发展 整体—函数为粒度的保护

image-20240527004540965

在so的加固方面

image-20240527004822267

vmp的核心在于自定义的解释器

dex2c 也可以理解成编译器 是把dex的代码编译成c的一个过程,c文件再经过ndk变成so文件

对一经过dex2c的apk进行反编译发现

image-20240527012742459

apkotool 反编译

语法分析 找到需要保护的代码 然后编译

image-20240527014341335

image-20240527015309479

加载activity的地址 vmp相同 dex2c不同

针对每一种加壳技术进行分析

image-20240527015951436

一代壳

文件加载和内存加载两种表现形式

image-20240527020057005

抽取解密的时机点 是一个重点

image-20240527020222819

vmp 解释器 找到映射关系

dex2c 语义转换很恶心

要关注JNI相关的调用