多读书多实践,勤思考善领悟

Java逆向基础之二.操作数栈

本文于2144天之前发表,文中内容可能已经过时。

本地变量和操作数栈

本地变量数组(Local Variable Array)

本地变量的数组包括方法执行所需要的所有变量,包括 this 的引用,所有方法参数和其他本地定义的变量。对于那些方法(静态方法 static method)参数是以零开始的,对于实例方法,零为 this 保留。

所有的类型都在本地变量数组中占一个槽(entry),而 long 和 double 会占两个连续的槽,因为它们有双倍宽度(64-bit 而不是 32-bit)。

操作数栈(Operand Stack)

操作数栈在执行字节码指令的时候使用,它和通用寄存器在 native CPU 中使用的方式类似。大多数 JVM 字节码通过 pushing,popping,duplicating,swapping,或生产消费值的操作使用操作数栈。

1. 看一个运算的例子

1
2
3
4
5
6
7
public class calc
{
public static int half(int a)
{
return a/2;
}
}

编译

1
javac calc.java

反编译

1
javap -c -verbose calc.class

反编译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
major version: 52
...
public static int half(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iload_0
1: iconst_2
2: idiv
3: ireturn
LineNumberTable:
line 5: 0

iload_0 第0个变量(即变量a)压入操作数栈

1
2
3
4
5
+-------+
| stack |
+-------+
| a |
+-------+

iconst_2 将2压入操作数栈

1
2
3
4
5
6
+-------+
| stack |
+-------+
| 2 |
| a |
+-------+

idiv 操作数栈中的前两个int相除,并将结果压入操作数栈顶

1
2
3
4
5
+-------+
| stack |
+-------+
| result|
+-------+

ireturn 返回栈顶元素

2. 例子2,复杂一点的例子,处理双精度的值

1
2
3
4
5
6
7
public class calc
{
public static double half_double(double a)
{
return a/2.0;
}
}

反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
major version: 52
...
#2 = Double 2.0d
...
public static double half_double(double);
descriptor: (D)D
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: dload_0
1: ldc2_w #2 // double 2.0d
4: ddiv
5: dreturn
LineNumberTable:
line 5: 0

ldc2_w指令是从常量区装载2.0d,另外,其他三条指令有d前缀,意思是他们使用double数据类型。

3. 例子3,两个参数

1
2
3
4
5
6
7
public class calc
{
public static int sum(int a, int b)
{
return a+b;
}
}

反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
major version: 52
...
public static int sum(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
LineNumberTable:
line 5: 0

iload_0 第0个变量(即变量a)压入操作数栈

1
2
3
4
5
+-------+
| stack |
+-------+
| a |
+-------+

iload_1 第1个变量(即变量b)压入操作数栈

1
2
3
4
5
6
+-------+
| stack |
+-------+
| b |
| a |
+-------+

iadd 操作数栈中的前两个int相加,并将结果压入操作数栈顶

1
2
3
4
5
+-------+
| stack |
+-------+
| result|
+-------+

ireturn 返回栈顶元素

4. 例子4,类型改为长整型

1
2
3
4
5
6
7
public class calc
{
public static long lsum(long a, long b)
{
return a+b;
}
}

反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
major version: 52
...
public static long lsum(long, long);
descriptor: (JJ)J
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=4, args_size=2
0: lload_0
1: lload_2
2: ladd
3: lreturn
LineNumberTable:
line 5: 0

可以看到压入第二个参数的时候为lload_2,可见lload_0占了两个槽(entry)

5. 例子5,混合运算

1
2
3
4
5
6
7
public class calc
{
public static int mult_add(int a, int b, int c)
{
return a*b+c;
}
}

反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
major version: 52
...
public static int mult_add(int, int, int);
descriptor: (III)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=3
0: iload_0
1: iload_1
2: imul
3: iload_2
4: iadd
5: ireturn
LineNumberTable:
line 5: 0

iload_0 第0个变量(即变量a)压入操作数栈

1
2
3
4
5
+-------+
| stack |
+-------+
| a |
+-------+

iload_1 第1个变量(即变量b)压入操作数栈

1
2
3
4
5
6
+-------+
| stack |
+-------+
| b |
| a |
+-------+

imul 操作数栈中的前两个int相乘,并将结果压入操作数栈顶

1
2
3
4
5
+-------+
| stack |
+-------+
|result1|
+-------+

iload_2 第2个变量(即变量c)压入操作数栈

1
2
3
4
5
6
+-------+
| stack |
+-------+
| c |
|result1|
+-------+

iadd 操作数栈中的前两个int相加,并将结果压入操作数栈顶

1
2
3
4
5
+-------+
| stack |
+-------+
|result2|
+-------+

ireturn 返回栈顶元素


参考资料
http://www.vuln.cn/7115