Android APK 加固之动态加载dex(一)

Android APK 加固之动态加载dex(一)

前情

最近正在学习Android 安全,为了巩固知识,我会在博客上把Android加固这一方面由浅入深全部总结出来,一直到在内存中加载dex结束。

1.类加载器

Android虚拟机在运行程序时,虚拟机需要把Class加载到内存中才行,完成这一加载工作的就是类加载器—ClassLoader。
一个运行的Android程序中至少有两个Classloader。打印代码如下:
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    int count = 0;
    ClassLoader classLoader = getClassLoader();
    if(classLoader!=null){
        Log.i("MainActivity","classLoader"+count++ +":"+classLoader.toString());
        while (classLoader.getParent()!=null){
            classLoader = classLoader.getParent();
            Log.i("MainActivity","classLoader"+count++ +":"+classLoader.toString());
        }
    }
}

Android APK 加固之动态加载dex(一)
一个是BootClassLoader,系统启动的时候创建的,另一个是PathClassLoader,应用启动时创建。
动态加载外部的dex文件的时候,我们也可以使用自己创建的ClassLoader来加载dex里面的Class。
在使用ClassLoader时我们需要注意一些问题

  1. loadClass方法使用时需要经过几个步骤,通过查看源码可以看出:先查询当前ClassLoader是否加载过此类,如果没有,查询Parent是否加载过此类,如果继承线路上都没有加载过此类,才由Child执行类的加载工作。
  2. 同一个Class=相同的Classname+PackageName+ClassLoader
  3. ClassLoader是一个抽象类,我们一般使用其具体的子类DexClassLoaderPathClassLoader,不同是DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;PathClassLoader只能加载系统中已经安装过的apk,根据功能我们更加倾向于使用DexClassLoader
    当我们了解完这些问题,就可以加载一个简单的Dex文件了

制作dex文件

写这样一段代码,生成apk

public class MyTestActivity {
    public static void createTextView(Activity activity){
        // 1. 创建TextView对象
        TextView textView = new TextView(activity);
        // 2. 创建属性对象
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        );
        params.gravity = Gravity.TOP | Gravity.CENTER;
        // 3. 设置内容
        textView.setText("动态创建的TextView");
        // 4. 将动态创建的控件添加到activity
        activity.addContentView(textView,params);
    }
    }

如图
Android APK 加固之动态加载dex(一)
使用反编译工具,生成Smali文件,删除所有Smali文件,只保留MyTestActivity类。使用Smali.jar工具生成dex文件。详情见图
Android APK 加固之动态加载dex(一)
CMD命令:java -jar ./smali.jar smali1 -o MytestActivty.dex
现在将生成的dex文件拷贝到项目中
Android APK 加固之动态加载dex(一)
将原项目中的MyTestActivity.jar文件删除
然后开始写代码,将assets目录中的文件拷贝到缓存目录中

private String copyDex(String dexName) {
        //获取assets的文件管理器
        AssetManager assetManager = getAssets();
        //构造新的路劲
        String path = getFilesDir()+ File.separator+dexName;
        //循环读取写入

        try {
            //打开文件
            InputStream inputStream = assetManager.open(dexName);
            //写入文件
            FileOutputStream outputStream = new FileOutputStream(path);
            byte bytes[]=new byte[1024];
            int nRet = -1;
            while(true){
                nRet = inputStream.read(bytes,0,1024);
                if(nRet==-1)break;
                outputStream.write(bytes,0,nRet);
            }
            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
            return  null;
        }
        return path;
    }

使用DexClassLoader加载dex文件

   private DexClassLoader loadDex(String path) {
        DexClassLoader dexClassLoader = new DexClassLoader(
                path,//dex路径
                getCacheDir().toString(),//优化之后的文件的路径
                getPackageResourcePath(),//原生库路径
                getClassLoader()//父类加载器
        );
        return dexClassLoader;
    }

使用Java反射基址,动态加载dex并调用。
整合代码如下:

    private void ctrlDex(){
        //将资源目录的dex拷贝到安装目录
        String path = copyDex("MyTestActivity.dex");
        if(path==null){
            Toast.makeText(this,"拷贝出错",Toast.LENGTH_SHORT).show();
        }
        //使用DexClassLoader加载dex
        DexClassLoader dexClassLoader = loadDex(path);
        //调用dex中函数//createTextView

        try {
            //使用DexClassLoader加载对应类
            Class clzDex = dexClassLoader.loadClass("com.bluelesson.pack001.MyTestActivity");
            //使用Java反射调用对应的类的函数
            //获取方法对象
            Method method = clzDex.getDeclaredMethod("createTextView",Activity.class);
            //调用方法
            //取消访问受限
            method.setAccessible(true);
            //调用
            method.invoke(null,this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

运行效果如第一次运行效果一样就成功了。

这只是动态加载了一个类,下一步就要动态加载一个Activity了!