文章目录

  • Android APK加固-安全人员角度
  • 关于类加载器
  • 类加载器
  • 类加载器的种类和个数
  • 创建类加载实例
  • 类加载器DexClassLoader和PathClassLoader
  • 使用类加载器动态加载dex文件
  • 制作dex文件
  • 动态加载dex文件
  • 完整步骤回顾


Android APK加固-安全人员角度

Android安全人员对APK加固采取的角度大概分为以下几个方面:

  1. 将可执行代码dex文件加密,能够动态解密并执行
  2. 能够检测当前状态是被调试,想尽一切办法反调试

那如果继续再细分一下,实现一个初级的APK加固:

  1. 使用类加载器动态加载dex文件
  2. 设计傀儡dex替换原dex,动态加载dex
  3. 在傀儡dex中的各种地方增加反调试代码

接下来就是从类加载器开始一步步完成APK加固

关于类加载器

类加载器

在JAVA开发中动态加载的技术主要用于加载jar包,使软件具备动态添加插件的功能,Java代码都是写在Class里的,程序运行在虚拟机上时,虚拟机需要把需要的Class加载进来才能创建实例对象并工作,而完成这一加载工作的角色就是ClassLoader。

Android的Dalvik/ART虚拟机如同标准JAVA的JVM虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。因此我们可以利用这一点,在程序运行时手动加载Class,从而达到代码动态加载可执行文件的目的。

Android的Dalvik/ART虚拟机虽然与标准Java的JVM虚拟机不一样,ClassLoader具体的加载细节不一样,但是工作机制是类似的,也就是说在Android中同样可以采取类似的动态加载jar或者dex的功能,只是在Android应用中动态加载一个jar或者dex的工作要比Eclipse加载一个插件复杂许多

类加载器的种类和个数

动态加载的基础是ClassLoader,从名字可以看出ClassLoader就是专门用来处理类加载工作的,所以这个类也叫类加载器,而且一个运行中的APP不仅有一个类加载器。

其实在Android系统启动的时候会创建一个Boot类型的ClassLoader实例,用于加载一些系统Framework层级需要的类,我们的Android应用里也需要用到一些系统的类,所以APP启动的时候也会把Boot类型的ClassLoader传进来。此外,APP也有自己的类。

这些类保存在APK的dex文件里面,所以APP启动的时候,也会创建一个自己的ClassLoader实例,用于加载自己dex文件中的类。下面我们在项目里验证看看。

public void enmuClassLoader()
    {
        int count=0;
        //获取当前包的ClassLoader
        ClassLoader classLoader=getClassLoader();
        if (classLoader!=null)
        {
            Log.d("GuiShou","[onCreate] classLoader"+count++ +":"+classLoader.toString());
        }

        while (classLoader.getParent()!=null)
        {
            classLoader=classLoader.getParent();
            Log.d("GuiShou","[onCreate] classLoader"+count++ +":"+classLoader.toString());
        }
    }

android 加载class Android 加载外部dex_加固

可以看见有两个ClassLoader实例,一个是BootClassLoader,系统启动时候创建的,一个是PathClassLoader,应用启动时创建的。由此也可以看出,一个运行的Android应用至少有两个ClassLoader。

创建类加载实例

动态加载外部Dex文件的时候,我们也可以使用自己创建的ClassLoader实例来加载dex里面的class,不过ClassLoader的创建方式有点特殊,我们先来看看它的构造方法

private ClassLoader(Void unused,ClassLoader parent){
       this.parent=parent;
}

创建一个ClassLoader的时候,需要使用一个现有的ClassLoader的实例作为新创建的实例的Parent。这样一来,一个Android应用,甚至整个Android系统里所有的ClassLoader实例都会被一棵树关联起来,这也是ClassLoader的双亲代理模型(Parent-Delegation Model)的特点

类加载器DexClassLoader和PathClassLoader

在Android中,ClassLoader是一个抽象类,在实际开发过程中,我们一般是使用其子类DexClassLoader和PathClassLoader来加载类的。

它们的不同之处是:

  • DexClassLoader可以加载jar/dex/apk,可以从SD卡中加载未安装的APK
  • PathClassLoader只能加载系统中已经安装过的APK

根据功能,我们更加倾向于使用DexClassLoader,所以接下来先使用DexClassLoader来加载一个简单的dex文件

使用类加载器动态加载dex文件

制作dex文件

为了能与Android的类有互动,先写这样一段代码

public class TestActivity {
    public void CreateVew(Activity ac){
        //创建一个TextView
        TextView tv=new TextView(ac);

        //创建布局 设置参数
        FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.WRAP_CONTENT,
                FrameLayout.LayoutParams.WRAP_CONTENT
        );

        //设置控件到顶端的距离
        params.topMargin=0;
        //设置控件的位置
        params.gravity= Gravity.TOP|Gravity.CENTER_HORIZONTAL;
        tv.setText("TestActivity View");

        //添加TextView到Avtivity中
        ac.addContentView(tv,params);
    }
}

android 加载class Android 加载外部dex_android 加载class_02

这段代码的效果就是显示一段TextView到Activity中

android 加载class Android 加载外部dex_加固_03

这次代码直接在Android项目中编译,然后在app的目录中查找class文件复制到自己的目录中即可。类所在的目录是app/build/intermediates/classes/debug/包名

接下来把当前的这个Activity进行反编译得到的smali文件转成一个dex文件放到这个工程的资源文件里

android 加载class Android 加载外部dex_加固_04

将apk用AndroidKiller打开,分析完成后打开smali文件路径,复制出一份副本

android 加载class Android 加载外部dex_java_05

删除其余的smali文件,只保留我们自己写的TestActivity类的smali,然后通过smali.jar将其转成dex文件

android 加载class Android 加载外部dex_加固_06

然后在main文件夹下新建一个assets文件夹,将dex文件复制到里面,并且删除TestActivity类

动态加载dex文件

现在我们已经有了一个制作好的dex文件,接着就需要将这个dex文件动态加载进来。想要动态加载dex,需要下面几个步骤。

  1. 将dex文件从自定义资源文件夹拷贝到程序目录下
  2. 创建一个DexClassLoader,加载dex
  3. 调用加载的dex中的class方法

首先拷贝dex文件

public String CopyDex(String dexName){
        //获取Asserts目录管理器
        AssetManager as=getAssets();

        //合成路径 data/data/包名/files/dex名称
        String path=getFilesDir()+ File.separator+dexName;

        Log.d("GuiShou",path);

        //创建文件流
        try {
            FileOutputStream out=new FileOutputStream(path);
            //打开文件
            InputStream is=as.open(dexName);
            //循环读取文件,拷贝到目标路径
            byte[] buffer=new byte[1024];
            int len=0;
            while ((len=is.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            //关闭文件
            out.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


        return path;
    }

接着创建DexClassLoader

public DexClassLoader GetLoader(String path){
        //创建dex的类加载器,返回DexClassLoader对象
        DexClassLoader dexClassLoader=new DexClassLoader(path,  //dex文件路径
                getCacheDir().toString(),          //优化后的dex文件路径
                null,       //原生库路径
                getClassLoader()    //父类加载器
                );
        return dexClassLoader;
    }

最后调用加载的dex中的class方法

public void  execClassMethod(DexClassLoader dex,String ClassName, String MethodName){

        try {
            //获取加载的类信息
            Class TestDex=dex.loadClass(ClassName);

            //获取构造方法 无参构造
            Constructor localConstructor=TestDex.getConstructor(new Class[]{});

            //调用构造方法 创建对象
            Object instance=localConstructor.newInstance(new Object[]{});

            //获取成员方法
            Method methodTest=TestDex.getDeclaredMethod(MethodName,new Class[]{Activity.class});

            //取消java访问检查,提高反射速度
            methodTest.setAccessible(true);

            //调用方法 this指针传进去
            methodTest.invoke(instance,new Object[]{this});


        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

最后调用以上三个函数

//加载dex
    public void loadDex(){
        //拷贝自定义资源中的dex到程序目录下
        String dexPath=CopyDex("test.dex");

        //创建一个DexClassLoader,加载dex
        DexClassLoader dexClassLoader=GetLoader(dexPath);

        //调用加载dex中的class方法
        execClassMethod(dexClassLoader,"com.example.classloaderdemo.TestActivity","CreateVew");
    }

到这里,代码就已经全部完成了。

接着运行程序,

android 加载class Android 加载外部dex_android 加载class_07

效果和之前写的dex文件代码一致,那么就说明我们已经完成了动态加载dex文件的过程。

完整步骤回顾

  1. 新建一个工程,创建TestActivity类,实现一个创建view的方法
  2. 编译生成apk,使用AndroidKiller反编译,将除了TestActivity.smali代码外的全部smali文件删除,编译生成dex文件
  3. 在项目main文件夹创建asserts文件夹,将生成的dex拷贝到这个目录下
  4. 在代码中完成以下步骤
  1. 拷贝自定义资源中的dex到程序目录下
  2. 创建一个dexClassLoader加载dex
  3. 调用加载dex中的class方法