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指令返回

更多的相關內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM