一直对安全方面挺感兴趣的,以前拜读过《白帽子讲 web 安全》,现在做移动端 Android 测试,在一次关于热埋点的学习中接触到了 Xposed,之后也会关注一点 Android 安全方面的东西,最近学习了一点 Android 动态加载的机制,跟大家分享一下
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
http://blog.csdn.net/jiangwei0910410003/article/details/48415225
http://blog.csdn.net/itfootball/article/details/49806269
http://blog.csdn.net/androidsecurity/article/details/8664778
http://blog.csdn.net/jiangwei0910410003/article/details/17679823
还有一些工具: https://mp.weixin.qq.com/s?__biz=MjM5NjA0NjgyMA==&mid=2651061026&idx=1&sn=ae6aea9af3b082ea543e1051728bd0bc&scene=1&srcid=0530rqezgS1iLqK0W8tT8909&key=f5c31ae61525f82ea7ae094c2f43f3300c4f648faee145a20953616868278e738c3c425d7098c2cdfab164fbfe02339a&ascene=0&uin=MjQwNzI1NDc1&devicetype=iMac+MacBookPro12%2C1+OSX+OSX+10.10.5+build14F1605)&version=11020201&pass_ticket=ytjLePanA95PuTtMOQpYePApF6e0GJEgj%2BtLjnU6Q4Y%3D(
上面说到了类加载器,其实动态加载机制的核心就是类加载器的使用,在 java 里它是一种机制----反射(Reflection)
类加载器的基本职责,就是根据一个指定的类的名称,找到或者生成其对应的字节码,然后从这些字节码中定义出一个 java 类,即 java.lang.Class 的一个实例
说到类加载器,或者反射机制,涉及到下面三个类:
在常见的 java 项目(J2SE,J2EE)中,特别是跨项目合作的场景,经常会把 B 方的业务代码编译后的 class 文件打成一个 jar 包,A 在需要时动态加载 jar 包里的类。又或者要实现配置型的项目,会把需要加载的类(通常是一个接口)的名字写在一个配置文件中,自己程序里写关于这个接口的各种实现,运用动态加载机制,用父类(那个接口)引用指向子类(各种实现)对象,利用面向对象中的多态,来最大程度的实现可配置
在 Android 里并不能用常规 java 项目中那种自定义 ClassLoader,重写 ClassLoader 里各种方法的方式来实现动态加载,主要原因有以下两点:
在 Android 里,我们不是自己重写 ClassLoader,而是用下面这两种 ClassLoader 来实现动态加载,他们也都是 ClassLoader 的子类
关于动态加载,google 一下就会有挺多例子的,那我们就先实现一个最基本的例子,了解一下动态加载它到底是怎么一回事儿,它可以用来做什么
原材料准备:
首先,在 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 这里说起:
newInstance()
方法初始化一个对象,并用父类引用 dy 指向该对象,到此多态的实现条件已满足好了,我们把那个 dynamic_for_android.jar 包 push 到 sd 卡的根目录,执行命令:
adb push dynamic_for_android.jar sdcard/
然后,运行一下程序
点击按钮
成功啦!!看到这个还是非常兴奋的!
虽然只是一个特别小的 demo,但里面的一些步骤,一些坑,一些思想,只有一点点踩过来才能体会,Android 自身的框架决定了其动态加载不同于普通 java 项目,在这个 demo 里我们是直接把实现类 push 到 sdcard 的根目录,但如果从网上动态下载呢?反射的机制能让我们动态的定制功能,现在很多应用的换肤,添加模块都是通过这种方式,在安全方面,它还被应用到 apk 的加固,这个之后慢慢写~