dexClassLoader

android加载器(一般作为插件开发)

什么是类加载器?

类加载器(class loader)是 Java™中的一个很重要的概念。类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。
Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

1
对于Java程序来说,编写程序就是编写类,运行程序也就是运行类(编译得到的class文件),其中起到关键作用的就是类加载器ClassLoader

Dalvik虚拟机类加载机制
Dalvik的虚拟机不能用ClassCload直接加载.dex,Android从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader;而这两个类就是我们加载dex文件的关键,这两者的区别是:

1
2
1.DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
2.PathClassLoader:要传入系统中apk的存放Path,所以只能加载已经安装的apk文件。

这里(参考)[https://segmentfault.com/a/1190000004062880]讲得很详细

生成外部dex文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
// PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}

这两者只是简单的对BaseDexClassLoader做了一下封装

平时进行动态加载开发的时候,使用DexClassLoader就够了。但我们也可以创建自己的类去继承ClassLoader,需要注意的是loadClass方法并不是final类型的,所以我们可以重载loadClass方法并改写类的加载逻辑。

动态加载进来的class如何使用,一般有2种办法

一种是使用反射调用,这种我不多做介绍。
一种是使用接口编程的方式来调用对应的方法,毕竟.dex文件也是我们自己维护的,所以可以把方法抽象成公共接口,把这些接口也复制到主项目里面去,就可以通过这些接口调用动态加载得到的实例的方法了。

源码包下面新建一个包名称是dynamic,然后在dynamic下新建一个interface接口Dynamic,里面有个接口方法,就叫DynamicXor(String st)吧,返回一个String,到时候我们可以通过Toast弹出来,Dynamic.java:

1
2
3
4
5
package com.ctf.test.mydemo.dynamic;
public interface Dynamic {
String DynamicXor(String st);
}

接着在dynamic目录下新建一个imp包,创建Impdynamic.java,并实现Dynamic接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ctf.test.mydemo.dynamic.imp;
import com.ctf.test.mydemo.dynamic.Dynamic;
public class Impdynamic implements Dynamic{
@Override
public String DynamicXor(String st) {
byte[] sbyte = st.getBytes();
for(int i=0;i<st.length();i++)
{
sbyte[i]= (byte) (sbyte[i]^(i+st.length()));
}
return new String(sbyte);
}
}

点击Build -> make project,这时候会在build\intermediates\classes\debug目录下生成对应的classes文件。
好了我们要把DynamicImpl这个class转换成Dalvik可识别的dex文件,分两步:

1
2
1.先导出DynamicImpl这个类为jar包的形式;
2.通过android sdk自带的dx.jar工具转换jar包为dex文件。

打包jar

打开app目录下的build.gradle文件最后,加上以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//删除dynamic.jar包任务
task clearJar(type: Delete) {
delete 'libs/dynamic.jar'
}
//打包任务
task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
//指定生成的jar名
baseName 'dynamic'
//从哪里打包class文件 上面生成的目录 com.ctf.test.mydemo.dynamic.imp
from('build/intermediates/classes/debug/com/ctf/test/mydemo/dynamic/')
//打包到jar后的目录结构
into('com/ctf/test/mydemo/dynamic/')
//去掉不需要打包的目录和文件
exclude('test/', 'Dynamic.class', 'BuildConfig.class', 'R.class')
//去掉R$开头的文件
exclude{ it.name.startsWith('R$');}
}
makeJar.dependsOn(clearJar, build)

打开AS的 terminal窗口:

1
2
terminal:>>cd app
terminal:>>gradle makeJar //可能需要配置环境变量gradle,看一下.gradle版本是多少,我是4.4的

执行成功,出现Build Successfully,会在生成app/build/libs/dynamic.jar

jar转成dex

使用sdk提供dx.jar将导出的dynamic.jar转换成Dalvik可识别的dex格式,sdk已经将dx.jar放到build-tools\27.0.3\lib目录下,将dynamic.jar复制到dx.jar目录下,AS终端运行

1
2
3
terminal:>>cd F:\a_job\tools\adt-bundle-windows\sdk\build-tools\27.0.3\lib
terminal:>>f:
terminal:>>dx --dex --output=dynamic_dex.jar dynamic.jar

在dx.jar目录下会生成一个dynamic_dex.jar文件(Davilk虚拟机可执行的dex文件,因为这条命令同时会打包dex文件,因此后缀是jar),接下来就是要在demo中使用这个dex文件。

主程序

新建一个项目,在app/src/main/新建一个assets目录,把刚刚生成的dynamic_dex.jar文件放到assets目录下

FileUtils.java

新建一个FileUtils.java,FileUtils类是从assets目录下copy文件到app/data/cache目录
FileUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.ctf.test.dynamic2;
import android.content.Context;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class FileUtils {
public static void copyFiles(Context context, String fileName, File desFile) {
InputStream in = null;
OutputStream out = null;
try {
in = context.getApplicationContext().getAssets().open(fileName);
out = new FileOutputStream(desFile.getAbsolutePath());
byte[] bytes = new byte[1024];
int i;
while ((i = in.read(bytes)) != -1)
out.write(bytes, 0 , i);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (in != null)
in.close();
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static boolean hasExternalStorage() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
/**
* 获取缓存路径
*
* @param context
* @return 返回缓存文件路径
*/
public static File getCacheDir(Context context) {
File cache;
if (hasExternalStorage()) {
cache = context.getExternalCacheDir();
} else {
cache = context.getCacheDir();
}
if (!cache.exists())
cache.mkdirs();
return cache;
}
}

main.java

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.ctf.test.classandroid;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText sstr = (EditText) findViewById(R.id.input_flag);
final Button ok = (Button) findViewById(R.id.input_ok);
ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//flag=This_1s_dexClassLoader
String st = sstr.getText().toString().trim();
if(encrypt.check1(loadDexClass(st)))
{
Toast.makeText(MainActivity.this, getResources().getString(R.string.success), Toast.LENGTH_SHORT).show();
}else
{
Toast.makeText(MainActivity.this, getResources().getString(R.string.fail), Toast.LENGTH_SHORT).show();
}
}
});
}
/**
* 加载dex文件中的class,并调用其中的方法
*/
private String loadDexClass(String st) {
File cacheFile = FileUtils.getCacheDir(getApplicationContext());
String internalPath = cacheFile.getAbsolutePath() + File.separator + getResources().getString(R.string.dexname);
File desFile = new File(internalPath);
try {
if (!desFile.exists()) {
desFile.createNewFile();
FileUtils.copyFiles(this, getResources().getString(R.string.dexname), desFile);
}
} catch (IOException e) {
e.printStackTrace();
}
Context context=getApplicationContext();//获取Context对象;
File dexOutputDir = context.getDir("dex", 0);
String dexOutputPath = dexOutputDir.getAbsolutePath();
//下面开始加载dex class 4.1以上不能读取
DexClassLoader dexClassLoader = new DexClassLoader(internalPath, dexOutputPath, null, getClassLoader());
try {
String s = getResources().getString(R.string.PluginClass);
Class clazz = dexClassLoader.loadClass(s);
Object obj = clazz.newInstance() ;
Method action = clazz.getMethod(getResources().getString(R.string.function), new Class[]{String.class}) ;
String res = (String)action.invoke(obj, st);
return res;
} catch (Exception e) {
e.printStackTrace();//抛出ClassNotFoundException的异常
}
return "";
}
}

其他

activity_main.xml配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
activity_main.xml
{
.....
<EditText
android:id="@+id/input_flag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="80dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="100dp"
android:ems="10"
android:inputType="textPersonName"
android:hint="input your flag"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UnknownId" />
<Button
android:id="@+id/input_ok"
android:layout_width="102dp"
android:layout_height="45dp"
android:layout_marginBottom="276dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:text="确认"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.458"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="MissingConstraints" />
}

strings配置如下:

1
2
3
4
5
6
7
8
<resources>
<string name="app_name">dynamic2</string>
<string name="dexname">dynamic_dex.jar</string>
<string name="success">Success</string>
<string name="fail">Fail</string>
<string name="PluginClass">com.ctf.test.mydemo.dynamic.imp.ImpDynamic</string>
<string name="function">DynamicXor</string>
</resources>

check1.java

1
2
3
4
5
6
7
8
9
10
11
12
public static boolean check1(String st) {
byte[] enc = {67, 112, 112, 105, 68, 45, 110, 65, 91, 69, 89, 97, 79, 69, 86, 85, 107, 71, 72, 78, 78, 94, 109};
byte[] srtbyte = st.getBytes();
if(st.length()!=enc.length)
return false;
for(int i=0;i<enc.length;i++)
{
if(enc[i]!=srtbyte[i])
return false;
}
return true;
}

申请权限

AndroidManifest中申请权限,

1
2
3
4
5
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
...
</application>

参考

https://blog.csdn.net/wy353208214/article/details/50859422

Donate
-------------本文结束感谢您的阅读-------------