1. 程式人生 > >Java逆向基礎之數組

Java逆向基礎之數組

java數組

本文參考:http://www.vuln.cn/7116

本文參考:《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逆向基礎之數組