Java逆向入门(一)
Java是一门"半编译半解释"型语言.通过使用jdk提供的javac编译器可以将Java源码编译为Java虚拟机(Java Virtual Machine, JVM)可读的字节码(bytecode),即*.class文件.
学习字节码可以使你更好的理解Java虚拟机的行为,甚至对学习其它基于Java虚拟机的语言(如:Scala,Clojure,Kotlin等)有很大的帮助
入门
实际上*.class文件并不是人类可读的文件格式,我们可以使用JDK提供的反会汇编器javap来分析字节码
Hello.java
class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
这可能是大家接触的第一个Java程序,我们先使用=> javac Hello.java编译得到Hello.class文件.然后使用javap -c -verbose将文件反汇编
常量池
其中Constant pool就是传说中的常量池.
常量池可以看作是一个数组,#后面的数字代表数组的索引.
=后面是数组的值.
第一列代表这个常量的tag,第二列会根据tag的不同而不同.
以Class为例,tag为Class的常量结构体为
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
name_index 表示一个常量池中的有效索引,这个索引的tag必须是Utf8
在#5位置上的name_index为常量池中#21位置中的常量,实际上保存的就是这个类的类名.类名会使用正斜杠/代替.来表示类型的完整名称
关于更多常量池的介绍可以查看
The Java Virtual Machine Specification 4.4
通过查看反汇编过的字节码我们可以发现这个类有两个方法,一个是无参的构造器
Hello(),另一个是main方法
descriptor
descriptor中描述这个参数和方法类型(返回值类型).其中()中代表的是这个方法的参数,后面跟的是这个方法的返回值类型,V代表void,即无返回值
下表列出了一些返回值符号对应的含义
需要注意的是引用类型名和常量池中类型的命名方式一致
比如main方法的参数描述符和类型描述符为([Ljava/lang/String;)V,代表这个方法接受一个String[]类型参数,并且无返回值.
flags
flags中描述了这个方法的访问权限和基本属性,下表列出flags描述符对应的含义
在上面我们定义的程序中并没有显示的定义构造器,这里的构造器属于编译器自动生成.但是jvm标准规定类型的初始化等与人工实现无关的方法可以不用加ACC_SYNTHETIC.注
code
code是方法的代码部分
stack=2, locals=1, args_size=1,分别代表操作数栈的深度,局部变量表大小和方法参数个数.其中实例方法的第一个局部变量和参数是this.局部变量表中每个参数大小都是32位,所以long和double会占用局部变量表中两个连续的位置
在构造器中,aload_0表示将第一个参数压入栈中,即this.然后会使用invokespecial指令调用一个特殊的初始化方法java/lang/Object."<init>":()V
在main函数中,先使用getstatic指令获取java/lang/System.out:Ljava/io/PrintStream的静态域,再将它压入栈中(java/io/PrintStream的实例).然后再使用ldc将常量池中的字符串指针(即"Hello World")压入栈中,ldc指令表示将一个常量池中的对象压入操作数栈中.
接着,使用invokevirtual调用java/io/PrintStream.println,invokevirtual指令用于调用实例的方法.最终调用return指令返回