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());
}
}
}
一个是BootClassLoader,系统启动的时候创建的,另一个是PathClassLoader,应用启动时创建。
动态加载外部的dex文件的时候,我们也可以使用自己创建的ClassLoader来加载dex里面的Class。
在使用ClassLoader时我们需要注意一些问题
- loadClass方法使用时需要经过几个步骤,通过查看源码可以看出:先查询当前ClassLoader是否加载过此类,如果没有,查询Parent是否加载过此类,如果继承线路上都没有加载过此类,才由Child执行类的加载工作。
- 同一个Class=相同的Classname+PackageName+ClassLoader
-
ClassLoader是一个抽象类,我们一般使用其具体的子类DexClassLoader,PathClassLoader,不同是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);
}
}
如图
使用反编译工具,生成Smali文件,删除所有Smali文件,只保留MyTestActivity类。使用Smali.jar工具生成dex文件。详情见图
CMD命令:java -jar ./smali.jar smali1 -o MytestActivty.dex
现在将生成的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了!