Java逆向基礎之數組
本文參考:《Reverse Engineering for Beginners》Dennis Yurichev著
數組
簡單的例子
創建一個長度是10的整型的數組,對其初始化
public class ArrayInit { public static void main(String[] args) { int a[] = new int[10]; for (int i = 0; i < 10; i++) a[i] = i; dump(a); } public static void dump(int a[]) { for (int i = 0; i < a.length; i++) System.out.println(a[i]); } }
編譯
javac ArrayInit.java
反編譯
javap -c -verbose ArrayInit.class
main函數部分
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: bipush 10 2: newarray int 4: astore_1 5: iconst_0 6: istore_2 7: iload_2 8: bipush 10 10: if_icmpge 23 13: aload_1 14: iload_2 15: iload_2 16: iastore 17: iinc 2, 1 20: goto 7 23: aload_1 24: invokestatic #2 // Method dump:([I)V 27: return
指令解釋
0: bipush 10 //將10壓入棧頂
2: newarray int //將10彈出操作數棧,作為長度,創建一個元素類型為int, 維度為1的數組,並將數組的引用壓入操作數棧
4: astore_1 //將數組的引用從操作數棧中彈出,保存在索引為1的局部變量(即a)中
5: iconst_0 //將0壓入棧
6: istore_2 //將0從操作數棧中彈出,保存在索引為2的局部變量(即i)中
7: iload_2 //將索引為2的局部變量(即i)壓入操作數棧
8: bipush 10 //將10壓入操作數棧
10: if_icmpge 23 //棧頂彈出兩個值,並且比較兩個數值,如果第的二個值大於或等於第一個,跳轉到偏移位23
13: aload_1 //將索引為1的局部變量(即a)壓入操作數棧
14: iload_2 //將索引為2的局部變量(即i)壓入操作數棧
15: iload_2 //將索引為2的局部變量(即i)壓入操作數棧
16: iastore //棧頂彈出兩個值,將棧頂int型數值存入指定數組的指定索引位置,這裏都是彈出的i究竟那個是要存的數哪個是索引位置呢,經過測試,第一個彈出來的是要存的數,第二個彈出的是索引位置
17: iinc 2, 1 //將索引為2的局部變量(即i)加1
20: goto 7 //跳轉到偏移位7
23: aload_1 //將索引為1的局部變量(即a)壓入操作數棧
24: invokestatic #2 // Method dump:([I)V //將數組的引用a從操作數棧彈出,調用dump方法並傳參
27: return //返回
再看dump函數的反編譯結果
public static void dump(int[]); descriptor: ([I)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: iconst_0 1: istore_1 2: iload_1 3: aload_0 4: arraylength 5: if_icmpge 23 8: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_0 12: iload_1 13: iaload 14: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 17: iinc 1, 1 20: goto 2 23: return
指令解釋
0: iconst_0 //將0壓入棧頂
1: istore_1 //將0從操作數棧中彈出,保存在索引為1的局部變量(即i)中
2: iload_1 //將索引為1的局部變量(即i)壓入操作數棧
3: aload_0 //將索引為0的局部變量(即參數a)壓入操作數棧
4: arraylength //將數組引用a從棧頂彈出,計算a數組的長度值並將長度值壓入棧頂
5: if_icmpge 23 //棧頂彈出兩個值,並且比較兩個數值,如果第的二個值大於或等於第一個,跳轉到偏移位23
8: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 獲得System.out的引用
11: aload_0 //將索引為0的局部變量(即a)壓入操作數棧
12: iload_1 //將索引為1的局部變量(即i)壓入操作數棧
13: iaload //棧頂彈出兩個值,將int型數組指定索引的值推送至棧頂,這裏索引為第一個彈出值,數組引用為第二個彈出值
14: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 調用println方法,需要從棧彈兩個參數傳值
17: iinc 1, 1 //將索引為1的局部變量(即i)加1
20: goto 2 //跳轉到偏移位2
23: return //返回
數組元素的求和
例子
public class ArraySum { public static int f(int[] a) { int sum = 0; for (int i = 0; i < a.length; i++) sum = sum + a[i]; return sum; } }
反編譯
public static int f(int[]); descriptor: ([I)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: iconst_0 1: istore_1 2: iconst_0 3: istore_2 4: iload_2 5: aload_0 6: arraylength 7: if_icmpge 22 10: iload_1 11: aload_0 12: iload_2 13: iaload 14: iadd 15: istore_1 16: iinc 2, 1 19: goto 4 22: iload_1 23: ireturn
部分指令解釋
10: iload_1 //將索引為1的局部變量(即sum)壓入操作數棧
11: aload_0 //將索引為0的局部變量(即a,數組的引用)壓入操作數棧
12: iload_2 //將索引為2的局部變量(即i)壓入操作數棧
13: iaload //棧頂彈出兩個值,將int型數組指定索引的值推送至棧頂,這裏索引為第一個彈出值,數組引用為第二個彈出值
14: iadd //將棧頂兩int型數值相加並將結果壓入棧頂,即a[i]+sum
main()方法的參數作為唯一參數例子
public class UseArgument { public static void main(String[] args) { System.out.print("Hi, "); System.out.print(args[1]); System.out.println(". How are you?"); } }
反編譯
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hi, 5: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_0 12: iconst_1 13: aaload 14: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 17: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 20: ldc #5 // String . How are you? 22: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 25: return
部分指令解釋
11: aload_0 //將索引為0的局部變量(即arg,數組的引用)壓入操作數棧
12: iconst_1 //將1壓入棧頂
13: aaload //棧頂彈出兩個值,將引用型數組指定索引的值推送至棧頂,這裏索引為第一個彈出值,數組引用為第二個彈出值
初始化字符串數組
class Month { public static String[] months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; public String get_month(int i) { return months[i]; }; }
反編譯
get_month()函數很簡單
public java.lang.String get_month(int); descriptor: (I)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: getstatic #2 // Field months:[Ljava/lang/String; 3: iload_1 4: aaload 5: areturn
指令解釋
0: getstatic #2 // Field months:[Ljava/lang/String; //獲得靜態變量months的引用
3: iload_1 //將索引為1的局部變量(即參數i)壓入操作數棧
4: aaload //棧頂彈出兩個值,將引用型數組指定索引的值推送至棧頂,這裏索引為第一個彈出值,數組引用為第二個彈出值
5: areturn //棧頂彈出一個值返回
再看month[]數值是如果初始化的
static {}; descriptor: ()V flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: bipush 12 2: anewarray #3 // class java/lang/String 5: dup 6: iconst_0 7: ldc #4 // String January 9: aastore 10: dup 11: iconst_1 12: ldc #5 // String February 14: aastore 15: dup 16: iconst_2 17: ldc #6 // String March 19: aastore 20: dup 21: iconst_3 22: ldc #7 // String April 24: aastore 25: dup 26: iconst_4 27: ldc #8 // String May 29: aastore 30: dup 31: iconst_5 32: ldc #9 // String June 34: aastore 35: dup 36: bipush 6 38: ldc #10 // String July 40: aastore 41: dup 42: bipush 7 44: ldc #11 // String August 46: aastore 47: dup 48: bipush 8 50: ldc #12 // String September 52: aastore 53: dup 54: bipush 9 56: ldc #13 // String October 58: aastore 59: dup 60: bipush 10 62: ldc #14 // String November 64: aastore 65: dup 66: bipush 11 68: ldc #15 // String December 70: aastore 71: putstatic #2 // Field months:[Ljava/lang/String; 74: return
部分指令解釋
0: bipush 12 //將12壓入棧頂
2: anewarray #3 // class java/lang/String //棧頂彈出一個值,創建一個引用型(如類,接口,數組)的數組,並將其引用值壓入棧頂 ,數組大小由彈出值決定,數組類型由#3決定
5: dup //復制棧頂數值並將復制值壓入棧頂 ,這裏復制的是數組的引用
6: iconst_0 //將0壓入棧頂
7: ldc #4 // String January //將字符串壓入棧頂
9: aastore //棧頂彈出兩個值,將棧頂引用型數值存入指定數組的指定索引位置,這裏都是彈出的i究竟那個是要存的數哪個是索引位置呢,經過測試,第一個彈出來的是要存的數,第二個彈出的是索引位置
10: dup //復制棧頂數值並將復制值壓入棧頂 ,這裏復制的是數組的引用
可變參數
可變參數實際上就是數組
public class VarParam { public static void f(int... values) { for (int i = 0; i < values.length; i++) System.out.println(values[i]); } public static void main(String[] args) { f(1, 2, 3, 4, 5); } }
反編譯
f()函數
public static void f(int...); descriptor: ([I)V flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code: stack=3, locals=2, args_size=1 0: iconst_0 1: istore_1 2: iload_1 3: aload_0 4: arraylength 5: if_icmpge 23 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_0 12: iload_1 13: iaload 14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 17: iinc 1, 1 20: goto 2 23: return
可以看到
3: aload_0 //將索引為0的局部變量(即參數int... values這個數組的引用)壓入操作數棧
再看main()的反編譯
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: iconst_5 1: newarray int 3: dup 4: iconst_0 5: iconst_1 6: iastore 7: dup 8: iconst_1 9: iconst_2 10: iastore 11: dup 12: iconst_2 13: iconst_3 14: iastore 15: dup 16: iconst_3 17: iconst_4 18: iastore 19: dup 20: iconst_4 21: iconst_5 22: iastore 23: invokestatic #4 // Method f:([I)V 26: return
可以看到數組是在main()中用newarray指令構造的,填充完整個數組之後調用f()
隨便提一句,數組對象並不是在main()中銷毀的,在整個java中也沒有被析構。因為JVM的垃圾收集齊不是自動的,當他感覺需要的時候。
再看一個 format()方法
方法定義
public PrintStream format(String format, Object... args)
它接收兩個參數,一個是格式,另一個是對象數組
看一個例子
public class format { public static void main(String[] args) { int i = 123; double d = 123.456; System.out.format("int: %d double: %f.%n", i, d); } }
反編譯
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=7, locals=4, args_size=1 0: bipush 123 2: istore_1 3: ldc2_w #2 // double 123.456d 6: dstore_2 7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #5 // String int: %d double: %f.%n 12: iconst_2 13: anewarray #6 // class java/lang/Object 16: dup 17: iconst_0 18: iload_1 19: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: aastore 23: dup 24: iconst_1 25: dload_2 26: invokestatic #8 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; 29: aastore 30: invokevirtual #9 // Method java/io/PrintStream.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 33: pop 34: return
這裏的中文翻譯估計是機器翻的,就簡單解釋一下指令意思
0: bipush 123 //將123壓入棧頂
2: istore_1 //棧頂彈出存入本地變量數組1號元素
3: ldc2_w #2 // double 123.456d //將123.456d壓入棧頂
6: dstore_2 //棧頂彈出存入本地變量數組2號元素
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; //獲得System.out的引用壓入棧頂
10: ldc #5 // String int: %d double: %f.%n //將字符串int: %d double: %f.%n壓入棧頂
12: iconst_2 //將2壓入棧頂
13: anewarray #6 // class java/lang/Object //棧頂彈出2,構造個數為2的對象數組,將數組對象的引用壓入棧頂
16: dup //復制棧頂數值並將復制值壓入棧頂 ,這裏復制的是數組的引用
17: iconst_0 //將0壓入棧頂
18: iload_1 //載入第1個參數即i,壓入棧
19: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; //將棧頂的i彈出,調用Integer.valueOf,返回int值壓入棧
22: aastore //將棧頂引用型數值存入指定數組的指定索引位置,這裏彈出了三個值,分別是i值,0,數組的引用,將i存入數組0位置
23: dup //復制棧頂數值並將復制值壓入棧頂 ,這裏復制的是數組的引用
24: iconst_1 //將1壓入棧頂
25: dload_2 //載入第2個參數即d,壓入棧
26: invokestatic #8 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; //將棧頂的d彈出,調用Integer.valueOf,返回Double值壓入棧
29: aastore //將棧頂引用型數值存入指定數組的指定索引位置,這裏彈出了三個值,分別是d值,1,數組的引用,將d存入數組1位置
30: invokevirtual #9 // Method java/io/PrintStream.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; //調用format方法,這裏彈出了三個值
數組的引用,字符串int: %d double: %f.%n,System.out的引用,調用後返回PrintStream類型的對象到棧頂
33: pop 彈出棧頂
34: return 返回
二維數組
例子
public class twoDArray { public static void main(String[] args) { int[][] a = new int[5][10]; a[1][2] = 3; } public static int get12(int[][] in) { return in[1][2]; } }
反編譯
main方法
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: iconst_5 1: bipush 10 3: multianewarray #2, 2 // class "[[I" 7: astore_1 8: aload_1 9: iconst_1 10: aaload 11: iconst_2 12: iconst_3 13: iastore 14: return
指令解釋
0: iconst_5 //將5壓入棧頂
1: bipush 10 //將2壓入棧頂
3: multianewarray #2, 2 // class "[[I" //創建指定類型和指定維度的多維數組(執行該指令時,操作棧中必須包含各維度的長度值),並將其引用值壓入棧頂,#2的值為"[[I"指定類型,後面的2指定維數是二維,5和10已經壓入棧頂,調用這個的時候5和10會先出棧,指定1維和2維的長度
7: astore_1 //將二維數組的引用,保存在索引為1的局部變量(即a)中
8: aload_1 //將索引為1的局部變量(即a)壓入操作數棧
9: iconst_1 //將1壓入棧頂
10: aaload //棧頂彈出兩個值,將引用型數組指定索引的值推送至棧頂,這裏索引為第一個彈出值1,數組引用為第二個彈出值a,結果是將a[1]這個數組引用送入棧頂
11: iconst_2 //將2壓入棧頂
12: iconst_3 //將3壓入棧頂
13: iastore //棧頂彈出兩個值,將棧頂int型數值存入指定數組的指定索引位置,這裏都是彈出的i究竟那個是要存的數哪個是索引位置呢,經過測試,第一個彈出來的是要存的數3,第二個彈出的是索引位置3,原始數組是a[1],即將3存入a[1]的第2位置,即將3存入a[1][2]
14: return
從上面看出訪問二維的要先獲取1維的引用放到棧頂再操作
get12方法
public static int get12(int[][]); descriptor: ([[I)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: iconst_1 2: aaload 3: iconst_2 4: iaload 5: ireturn
指令解釋
0: aload_0 //將索引為0的局部變量(即參數in,是一個二維數組的引用)壓入操作數棧
1: iconst_1 //將1壓入棧頂
2: aaload //棧頂彈出兩個值,將引用型數組指定索引的值推送至棧頂,這裏索引為第一個彈出值1,數組引用為第二個彈出值in數組引用,結果是將a[1]這個數組引用送入棧頂
3: iconst_2 //將2壓入棧頂
4: iaload //棧頂彈出兩個值,將int型數組指定索引的值推送至棧頂,這裏索引2為第一個彈出值,數組引用a[1]為第二個彈出值,即將a[1][2]的值送入棧頂
5: ireturn //返回棧頂的值
三維數組
public class threeDArray { public static void main(String[] args) { int[][][] a = new int[5][10][15]; a[1][2][3] = 4; get_elem(a); } public static int get_elem(int[][][] a) { return a[1][2][3]; } }
反編譯
main()方法
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: iconst_5 1: bipush 10 3: bipush 15 5: multianewarray #2, 3 // class "[[[I" 9: astore_1 10: aload_1 11: iconst_1 12: aaload 13: iconst_2 14: aaload 15: iconst_3 16: iconst_4 17: iastore 18: aload_1 19: invokestatic #3 // Method get_elem:([[[I)I 22: pop 23: return
它用了兩個aaload去找一維和二維的引用
get_elem方法
public static int get_elem(int[][][]); descriptor: ([[[I)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: iconst_1 2: aaload 3: iconst_2 4: aaload 5: iconst_3 6: iaload 7: ireturn
get_elem方法也使用了兩個aaload去找一維和二維的引用
在java中可能出現棧溢出嗎?不可能,數組長度實際就代表有多少個對象,數組的邊界是可控的,而發生越界訪問的情況時,會拋出異常
Java逆向基礎之數組