Android 动态加载初探

上一篇 / 下一篇  2017-07-18 16:10:51 / 个人分类:安全测试工具

  引言
  一直对安全方面挺感兴趣的,以前拜读过《白帽子讲web安全》,现在做移动**droid测试,在一次关于热埋点的学习中接触到了Xposed,之后也会关注一点Android 安全方面的东西,最近学习了一点Android动态加载的机制,跟大家分享一下
  一点概念
  java程序是跑在虚拟机中的,原生java虚拟机叫JVM(当然也分很多厂家,很多种类),Android也是java程序,但Android的虚拟机,叫Dalvik,Android 5.0 之后,舍弃了Dalvik,用**T(Android Runtime)
  一般来说,虚拟机使用java类的方法如下:java源程序(.java文件)在经过java编译器编译之后,就被转换成java字节码(JVM对应.class文件,Dalvik对应.dex文件),类加载器负责读取java字节码,并转换成java.lang.Class的一个实例,每个这样的实例用来表示一个java类
  动态加载:在运行时动态加载或者重载类
  类加载器
  上面说到了类加载器,其实动态加载机制的核心就是类加载器的使用,在java里它是一种机制----反射(Reflection)
  类加载器的基本职责,就是根据一个指定的类的名称,找到或者生成其对应的字节码,然后从这些字节码中定义出一个java类,即java.lang.Class的一个实例
  说到类加载器,或者反射机制,涉及到下面三个类:
  java.lang.Class
  java.lang.ClassLoader
  java.lang.reflect
  应用
  在常见的java项目(J2SE,J2EE)中,特别是跨项目合作的场景,经常会把B方的业务代码编译后的class文件打成一个jar包,A在需要时动态加载jar包里的类。又或者要实现配置型的项目,会把需要加载的类(通常是一个接口)的名字写在一个配置文件中,自己程序里写关于这个接口的各种实现,运用动态加载机制,用父类(那个接口)引用指向子类(各种实现)对象,利用面向对象中的多态,来最大程度的实现可配置
  但是
  在Android里并不能用常规java项目中那种自定义ClassLoader,重写ClassLoader里各种方法的方式来实现动态加载,主要原因有以下两点:
  Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。而这个本地方法除了抛出一个“UnsupportedOperationException”之外,什么都没做,甚至连返回值都为空
  在Android中,一个类不仅仅是一个类,为啥?比如Android里面的组件之一的Activity,它在Android整体框架下,是有各种生命周期,内置了各种回调、监听器的,但是动态加载只能根据类名找到或者生成对应的字节码,然后从字节码中定义一个java类,一个普普通通的java类,即加载出来它就不再是什么Activity,Service了
  肿么办
  在Android里,我们不是自己重写ClassLoader,而是用下面这两种ClassLoader来实现动态加载,他们也都是ClassLoader的子类
  DexClassLoader(提供了一个释放.dex文件的路径)
  PathClassLoader(默认,提供完整路径后会把'.'替换成'/')
  Demo
  关于动态加载,google一下就会有挺多例子的,那我们就先实现一个最基本的例子,了解一下动态加载它到底是怎么一回事儿,它可以用来做什么
  原材料准备:
  Eclipse
  Android Studio
  Android SDK
  首先,在Eclipse里建一个项目,项目结构长这样:
  我新建了一个Android 5的lib,里面放的是Android SDK里api level=22的android.jar,各个版本大家可以到自己Android SDK路径下的platforms里面找
  项目里有一个package,com.fenfenzhong.interfaces,这是沿用了普通java项目的做法,即先定义接口,到时候可以根据这个接口写各种不同的实现类,充分利用多态
  还有另外一个package,com.fenfenzhong.impl,这个是专门放实现类
  com.fenfenzhong.interfaces.IDynamic
  package com.fenfenzhong.interfaces;
  import android.app.Activity;
  public interface IDynamic {
      public void init(Activity activity);
      public void clickMe();
  }
  com.fenfenzhong.impl.Dynamic
  package com.fenfenzhong.impl;
  import android.app.Activity;
  import android.widget.Toast;
  import com.fenfenzhong.interfaces.IDynamic;
  //动态类的实现
  public class Dynamic implements IDynamic {
      private Activity mActivity;
      @Override
      public void init(Activity activity) {
          mActivity = activity;
      }
      @Override
      public void clickMe() {
          Toast.makeText(mActivity, "hello dynamic", 1500).show(); 
      }
  }
  将上面的com.fenfenzhong.impl.Dynamic 这个动态类打包,一般jar包里放的是字节码,是编译好的.class文件,但那是为了不暴露源码,这里我们直接打包源文件,即.java文件
  打好的jar包,我命名为dynamic.jar
  然后我们把这个jar包放到Android SDK build-tools 下某个版本里(随意),build-tools里有很多很厉害的工具,比如aapt,zipalign,但这次我们要用dx这个工具
  执行
  dx --dex --output=dynamic_for_android.jar dynamic.jar
  这条指令的操作就是把dynamic.jar先打成一个.dex文件,以便Dalvik虚拟机能认识,再把这个.dex打成一个jar包,即dynamic_for_android.jar,这个保管好,待会要用
  接下来,我们需要一个连接宿主项目和动态实现类的桥梁,当然就是com.fenfenzhong.interfaces.IDynamic这个接口啦,再打一个jar包
  命名为dynamic_interface.jar
  在Android Studio新建一个项目,叫DynamicApp,把dyncmic_interface.jar导入到项目的libs里面
  这个项目相当简单,就一个按钮,但我们想实现的是:点击这个按钮的时候,调用的是动态类中的实现方法
  package com.fenfenzhong.dynamicapp;
  import android.content.Context;
  import android.os.Environment;
  import android.support.v7.app.AppCompatActivity;
  import android.os.Bundle;
  import android.util.Log;
  import android.view.View;
  import android.widget.Button;
  import android.widget.Toast;
  import com.fenfenzhong.interfaces.IDynamic;
  import java.io.File;
  import dalvik.system.DexClassLoader;
  public class MainActivity extends AppCompatActivity {
      private IDynamic dy;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          Button clickMeBtn = (Button) findViewById(R.id.click_me);
          String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "dynamic_for_android.jar";
          Log.i("dex", dexPath);
          Context context=getApplicationContext();
          File dexOutputDir = context.getDir("dex", 0);
  //        String dexOutputDirs = Environment.getExternalStorageDirectory().toString();//如果使用这种outputdir的话,可能报异常
          DexClassLoader dcl = new DexClassLoader(dexPath,dexOutputDir.getAbsolutePath(),null,getClassLoader());
          try {
              Class dynamicClass = dcl.loadClass("com.fenfenzhong.impl.Dynamic");
              dy = (IDynamic)dynamicClass.newInstance();
              if(dy != null) {
                  dy.init(MainActivity.this);
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
          clickMeBtn.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  if(dy != null) {
                      dy.clickMe();
                  }else {
                      Toast.makeText(getApplicationContext(), "cannot load class", Toast.LENGTH_LONG).show();
                  }
              }
          });
      }
  }
  以上便是这个项目所有的代码,在最外层定义一个private IDynamic dy,这个dy就是父类(接口)引用
  主要从Sring dexPath这里说起:
  我们先定义dexPath为外部存储根目录下的dynamic_for_android.jar,即指定到时候从这里去拿dex文件
  dexOutputDir这个变量主要是指定释放dex文件后的路径,因为我们使用DexClassLoader,上面我说到,这种ClassLoader需要这么一个路径
  DexClassLoader dcl 这里,就初始化了一个实例,叫dcl,根据上面对ClassLoader的描述,我们可以指定一个类名,通过这个实例去找,或者生成该类的字节码
  利用反射机制的newInstance()方法初始化一个对象,并用父类引用dy 指向该对象,到此多态的实现条件已满足
  这个Activity里有一个按钮,但它的onClick方法,调用的是dy的clickMe()
  好了,我们把那个dynamic_for_android.jar包push到sd卡的根目录,执行命令:
  adb push dynamic_for_android.jar sdcard/
  然后,运行一下程序
  点击按钮
  成功啦!!看到这个还是非常兴奋的!
  总结
  虽然只是一个特别小的demo,但里面的一些步骤,一些坑,一些思想,只有一点点踩过来才能体会,Android自身的框架决定了其动态加载不同于普通java项目,在这个demo里我们是直接把实现类push到sdcard的根目录,但如果从网上动态下载呢?反射的机制能让我们动态的定制功能,现在很多应用的换肤,添加模块都是通过这种方式,在安全方面,它还被应用到apk的加固,这个之后慢慢写~

TAG:

 

评分:0

我来说两句

日历

« 2024-04-21  
 123456
78910111213
14151617181920
21222324252627
282930    

我的存档

数据统计

  • 访问量: 7847
  • 日志数: 4
  • 建立时间: 2017-07-17
  • 更新时间: 2017-07-18

RSS订阅

Open Toolbar