简介

ASMDEX官方地址:http://asm.ow2.org/

在2.3章中我给大家介绍了ASM,细心的童鞋应该能发现,ASM与ASMDEX的官方地址是同一个。ASM是针对class文件,而Java在很久以前就已经有了,所以说ASM已经非常的成熟。而ASMDEX是针对的Dex文件的,Android的出现也没多少年,因此ASMDEX到现在,也只有1.0版,相对来说并不稳定。

ASMDEX的设计与ASM类似,因此学过ASM的童鞋,学习ASMDEX应该是没多少难度的。本文假设大家已经学过ASM,还没学过的童鞋,请参考2.3章。

Java与Android

这里强调一下Java与Android的关系。

Java是一门编程语言,我们在编写完Java代码后,一般使用javac工具(由JDK提供)把Java代码转换为字节码(bytecode),存放在相应的.class文件中,由JVM来识别运行。JDK是用来开发及运行Java应用程序的工具集。

Android是一个平台,Android平台上的开发主要支持Java语言和C/C++语言,开发工具集分别对应为SDK与NDK。在使用Java语言开发的过程中,我们仍需要通过javac工具把Java代码编译成的字节码,然后通过dx工具(由SDK提供)把多个.class文件转换为一个.dex文件。

所以说,在Android应用程序中,我们的程序代码均放在一个Dex文件中(分Dex的情况不考虑)。

ASMDEX中的常用类

  • ApplicationReader:Dex文件读入类,类似于ASM的ClassReader类。
  • ApplicationVisitor:应用程序访问者接口。
  • ApplicationNode:代表整个应用结点的结构,继承自ApplicationVisitor。
  • ApplicationWriter:Dex文件写入类。

由于dex文件里包含了应用程序中的所有类,而ASMDEX的输入来源于ApplicationReader,所以ASMDEX中没有ClassReader类。

至于其它像ClassNode、ClassVisitor之类的类,ASMDEX中都包含,而且设计是一样的。

#实例

由于已经有了ASM的基础,ASMDEX的很多写法都与ASM中一致,这里我直接给个修改类名、方法名以及属性名的例子。

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
public class Main implements Opcodes {
public static void main(String[] args) throws IOException {
// 这里的输入来源于dex文件,dex文件在apk文件中,apk是个zip包,怎么解压大家看着办
ApplicationReader ar = new ApplicationReader(ASM4, new FileInputStream("classes.dex"));
ApplicationWriter aw = new ApplicationWriter();
ar.accept(new ApplicationVisitor(ASM4, aw) {
@Override
public ClassVisitor visitClass(int access, String name, String[] signature, String superName, String[] interfaces) {
return new ClassVisitor(ASM4, super.visitClass(access, getNewName(name), signature, superName, interfaces)) {
@Override
public FieldVisitor visitField(int access, String name, String desc, String[] signature, Object value) {
return super.visitField(access, getNewName(name), desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String[] signature, String[] exceptions) {
return super.visitMethod(access, getNewName(name), desc, signature, exceptions);
}
};
}
// 得到一个新的名称
private String getNewName(String name) {
return "_" + name.intern().hashCode();
}
}, 0);
byte[] code = aw.toByteArray();
FileOutputStream fos = new FileOutputStream("new_classes.dex");
fos.write(code);
fos.close();
}
}

代码没什么好解释的,大家看注释就行。另外关于如何修改dalvik bytecode,其实与ASM都差不多,大家自己试试就行。

ASMDEX的问题

在ASM中,我封装了一个AsmVerify类,其内部使用了ASM框架CheckClassAdapter、Textifier、TraceMethodVisitor、Analyzer等工具类来实现。但ASMDEX并没提供这么多工具类,只提供了打印相关的类如AsmDexifierMethodVisitor。因此要封装出类似AsmVerify的类,还是有一定难度的,有兴趣的童鞋可以自己试着封装一下。

此外,前面已经提到过了,ASMDEX只发布了1.0版本,所以还是有很多Bug的,就如对JNI支持有问题、增加指令后的对齐问题等等。由于ASMDEX是开源的,所以有兴趣的童鞋可以自己试着去修复。

前面介绍dalvik bytecode的时候有提过,有些指令对寄存器的范围是有限制的。大家想像一下这样一个场景:

假如我需要在一个方法中增加一段代码,假设这个方法寄存器v0v14是临时变量,寄存器v15vN是参数。而这段代码中需要一些临时寄存器,但我们要分析现有代码中哪些寄存器是可用的,这个要分析上下文,人工分析还好说,但如果要写程序分析的话,很有难度吧,而万一寄存器都不可用了呢?那么理所当然最好的方式,肯定是增加一些临时寄存器了,对吧?

设想下,如果我们增加一个临时寄存器,这时寄存器v0v15是临时变量了,v16vN+1是参数,其中v15是新增的,因此它把参数所在的寄存器(v15vN)都挤后了一位。这样的话,那使用到参数的指令,是不是也要改一下?假设某指令只支持使用前16个(即v0v15)寄存器,而现在本来它使用了v15的寄存器,改成v16后,就肯定有问题了。

不知道大家看懂没,总的意思就是说使用ASMDEX时,如果需要增加临时变量的话,会有很大的编码难度,除非要处理的代码都是已知的简单代码。