dex是Android系统下APK存放代码的一种文件格式,按照其独有的格式将代码与代码用到的变量进行布局,类似于JAR文件。dex所存放的代码格式是Dalvik字节码,它是Android平台所识别并执行的字节码,与JAR中使用的JVM字节码不互相兼容。
dex文件除了直接被编译进APK中以外还可以通过动态加载来执行其中的代码。这使得可以做到很多事情,如:APK瘦身、热修复、插件化、应用加固,而逆向中对dex加载部分主要涉及的是应用加固。这意味着它可以将部分重要的代码加密进dex并动态加载,或在线下载并加载。
dex文件加载所需的类是DexClassLoader,它的原型为:
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)
-
dexPath:描述dex文件的路径。
-
optimizedDirectory:描述存放优化后的 dex的路径。
-
libraryPath:描述存放native 库的路径,可以为空。
-
parent:描述父加载器
加载成功后将会获得一个类加载器,通过这个类加载器可以得到dex中的类。
但在这之前需要获得一个dex,它将装载我们希望动态加载的代码。
现在先新建一个空界面的APP:
其中只包含最基础的代码,方便测试Dex加载器。
随后我们找一个不会编译进APP的路径做dex
编写如下代码,用一个弹出消息假设它是一个需要被保护的代码, 要注意包路径要与文件夹路径要一致。
随后着手生成dex:
-
由于dex代码中有android的东西因此需要Android SDK。
-
找到AndroidSDK的目录platformsandroid-你希望的版本android.jar。
-
将它改为zip文件后解压将其作为classpath。
-
另外还需要确认jdk和AndroidSDK的环境变量是否配置正确,然后就可以开始生成。
-
使用命令 javac -classpath 解压AndroidSDK的路径 -source 源java版本 -target 目标java版本。
DEMO:
E:workDexLoaderappsrcdex> javac -classpath D:AndroidSDKplatformsandroid-28androidextra -target 8 -source 8 comexampledexloaderdexSecurity.java
随后会在comexampledexloaderdex目录下生成.class文件。
继续将class打包到dex中,使用命令“dx --dex --output Security.dex .”将当前目录下的所有class打包进dex,并生成 Security.dex文件。
现在我们需要在代码中调用这个dex。
可以将dex放在任何地方来加载,这里演示在APP缓存目录加载。
我准备把dex放在资源目录下,这也是加壳程序经常做的事情。
新建一个assets目录,并将dex放进去,随后我们可以通过方法getAssets获得这个目录中的文件。
DexClassLoader的参数需要一个文件目录,而assets不能满足这个参数,因此还需要将assets中的dex文件拷贝到一个临时的目录中。
APP有两个缓存目录,在/data/user/0/包名/cache与/data/user/0/包名/code_cache,分别为通用缓存与代码缓存。可能随着Android版本变化而变化,因此我们需要用代码来获得这些地址。
MainActivity 继承自Context因此可以通过getCacheDir与getCodeCacheDir获得。
得到缓存路径
File cacheDir = this.getCacheDir();
File securityCachePath = new File(cacheDir, "Security.dex");
将文件从assets拷贝到临时目录
try (InputStream resourceDex = getAssets().open("Security.dex")) {
try (FileOutputStream cacheDex = new FileOutputStream(securityCachePath)) {
byte[] buffer = new byte[1024];
int length;
while ((length = resourceDex.read(buffer)) > 0) {
cacheDex.write(buffer, 0, length);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
使用DexClassLoader从缓存目录中加载dex,并将优化好的dex放入代码缓存,设置父类类加载器为当前类加载器。
DexClassLoader loader = new DexClassLoader(
securityCachePath.getAbsolutePath(),
this.getCodeCacheDir().getAbsolutePath(),
null,
getClassLoader()
);
通过加载器的方法loadClass可以获得dex中的某个类。随后通过反射的方式就可以新建实例与调用方法了。
try {
Class<?> securityClass = loader.loadClass("com.example.dexloader.dex.Security");
Method securityRunMethod = securityClass.getDeclaredMethod("Run", Context.class);
securityRunMethod.invoke(null, this);
} catch (ClassNotFoundException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
实测结果:
之后介绍xposed与frida后将会详细介绍DEX类加载器的底层实现与HOOK加载器脱壳。
原文始发于微信公众号(锋刃科技):DEX加载器代码解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论