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中可能出现栈溢出吗?不可能,数组长度实际就代表有多少个对象,数组的边界是可控的,而发生越界访问的情况时,会抛出异常

原文地址:http://blog.51cto.com/7317859/2105841

时间: 2024-10-03 07:33:13

Java逆向基础之数组的相关文章

java SE 基础复习-数组

数组 数组(Array):相同类型数据的集合. 定义数组 方式1(推荐,更能表明数组类型) type[] 变量名 = new type[数组中元素的个数]; 比如: int[] a = new int[10]; 数组名,也即引用a,指向数组元素的首地址. 方式2(同C语言) type变量名[] = new type[数组中元素的个数]; 如: int a[] = new int[10]; 方式3 定义时直接初始化 type[] 变量名 = new type[]{逗号分隔的初始化值}; 其中红色部

Java逆向基础之操作数栈

本文参考:http://www.vuln.cn/7115 本地变量和操作数栈 本地变量数组(Local Variable Array) 本地变量的数组包括方法执行所需要的所有变量,包括 this 的引用,所有方法参数和其他本地定义的变量.对于那些方法(静态方法 static method)参数是以零开始的,对于实例方法,零为 this 保留. 所有的类型都在本地变量数组中占一个槽(entry),而 long 和 double 会占两个连续的槽,因为它们有双倍宽度(64-bit 而不是 32-bi

Java逆向基础之条件跳转位运算循环

本文参考:http://www.vuln.cn/7117 条件跳转的例子,绝对值 public class abs {     public static int abs(int a)     {         if (a<0)             return -a;         return a;     } } 编译 javac abs.java 反编译 javap -c -verbose abs.class   public static int abs(int);     d

Java逆向基础之异常

本文参考:http://www.vuln.cn/7116 本文参考:<Reverse Engineering for Beginners>Dennis Yurichev著 异常 由之前月份处理修改的例子 //清单1IncorrectMonthException.java public class IncorrectMonthException extends Exception { private int index; public IncorrectMonthException(int in

Java逆向基础之动态生成类

为什么有这个东西,一方面时AOP框架的需要,另一方面是增加软件逆向的难度 动态生成类的技术目前大体上分为两类,一类是通过操作字节码框架如cglib/Javassist去实现,另一类就是JNI方式,调用dll/so库,内存中动态还原.这两种方式都能实现隐藏类 看一个Javassist动态生成类的例子 package com.vvvtimes; import java.lang.reflect.Modifier; import javassist.ClassPool; import javassis

Java逆向基础之初识javaagent

首先说一下javaagent是什么 javaagent是一种能够在不影响正常编译的情况下,修改字节码. 在逆向中javaagent可以完成对类的拦截和增强. 看一个例子 在Eclipse新建如下MyAgent结构的项目 MyAgent.java文件内容 package com.vvvtimes.demo.agent; import java.lang.instrument.Instrumentation; public class MyAgent {     public static void

Java逆向基础之导出内存中的类一

为什么需要这个,因为在之前的博文中提到,为了增加逆向的难度,部分软件会对部分关键方法和类进行隐藏,所以我们需要把这个类从内存中拿出来. 本文介绍使用javaagent的方法,下一篇介绍dumpclass,两种方法各有利弊. 本文需要用到第三方jar为:javassist-3.20.0-GA.jar,不过我们需要的是它的源码javassist-3.20.0-GA-sources.jar 新建名为DumpClassAgent的项目,项目结构如下 将下载到的源码复制到src目录下 DumpClassA

Java逆向基础之JDB动态调试

本文参考:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jdb.html JDB在有源代码的时候可以实现调试远程机器上的java程序,但是在逆向中源码很难分析出来,不过还是能够调试得到一部分信息的 本文主要获取两个信息 1.动态调用混淆类中的方法,因为实践过程中我们去手动补全相关代码比较难 2.打印调用栈 以下例子以调试ZKM.jar为例 JDB无源代码调试调用方法 启动Xdebug调试 java -Xdebug -X

Java逆向基础之AspectJ的Around方法修改方法体

在逆向中,我们往往通过修改某个方法达到目的,在javaassist中有insertBefore,insertAfter,setBody,在AspectJ中也可以通过Around实现类似的功能. 看一个简单的例子 java文件Main.java //Main.java package com.vvvtimes; public class Main { public int add(int x, int y) { return x + y; } public int add(int x, int y