Java逆向系列-基础指令:条件跳转 位运算 循环
条件跳转的例子,绝对值
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);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: iload_0
1: ifge 7
4: iload_0
5: ineg
6: ireturn
7: iload_0
8: ireturn
其中ifge 7 意思是,当栈顶的值大于等于0的时候跳转到偏移位7,任何的ifXX指令都会将栈中的值弹出用于进行比较
其中ineg 意思是将栈顶int型数值取负并将结果压入栈顶
再看一个例子,取最小值
public class min
{
public static int min (int a, int b)
{
if (a>b)
return b;
return a;
}
}
反编译
public static int min(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: if_icmple 7
5: iload_1
6: ireturn
7: iload_0
8: ireturn
if_icmple会从栈中弹出两个值进行比较,如果第二个(a,iload_0)小于或者等于第一个(b,iload_1),那么跳转到偏移位7
从上面例子可以看出在Java代码中if条件中的测试与在字节码中是完全相反的
max函数例子
public class max
{
public static int max (int a, int b)
{
if (a>b)
return a;
return b;
}
}
反编译
public static int max(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: if_icmple 7
5: iload_0
6: ireturn
7: iload_1
8: ireturn
代码和min的差不多,唯一的区别是最后两个iload指令(偏移位5和偏移位7)互换了
更复杂的例子
public class cond
{
public static void f(int i)
{
if (i<100)
System.out.print("<100");
if (i==100)
System.out.print("==100");
if (i>100)
System.out.print(">100");
if (i==0)
System.out.print("==0");
}
}
反编译
public static void f(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iload_0
1: bipush 100
3: if_icmpge 14
6: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #3 // String <100
11: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
14: iload_0
15: bipush 100
17: if_icmpne 28
20: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc #5 // String ==100
25: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
28: iload_0
29: bipush 100
31: if_icmple 42
34: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #6 // String >100
39: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
42: iload_0
43: ifne 54
46: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
49: ldc #7 // String ==0
51: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
54: return
if_icmpge 14 栈顶弹出两个值,并且比较两个数值,如果第的二个值大于或等于第一个,跳转到偏移位14
if_icmpne 28 栈顶弹出两个值,并且比较两个数值,如果第的二个值不等于第一个,跳转到偏移位28
ifne 54 当栈顶int型数值不等于0时跳转到偏移位54
传参例子
public class minmax
{
public static int min (int a, int b)
{
if (a>b)
return b;
return a;
}
public static int max (int a, int b)
{
if (a>b)
return a;
return b;
}
public static void main(String[] args)
{
int a=123, b=456;
int max_value=max(a, b);
int min_value=min(a, b);
System.out.println(min_value);
System.out.println(max_value);
}
}
反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: bipush 123
2: istore_1
3: sipush 456
6: istore_2
7: iload_1
8: iload_2
9: invokestatic #2 // Method max:(II)I
12: istore_3
13: iload_1
14: iload_2
15: invokestatic #3 // Method min:(II)I
18: istore 4
20: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
23: iload 4
25: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
28: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
31: iload_3
32: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
35: return
istore_1 将栈顶元素弹出并存到本地变量数组1号元素,因为0号给了this
位操作
public class bitop
{
public static int set (int a, int b)
{
return a | 1<<b;
}
public static int clear (int a, int b)
{
return a & (~(1<<b));
}
}
反编译
public static int set(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=2
0: iload_0
1: iconst_1
2: iload_1
3: ishl
4: ior
5: ireturn
LineNumberTable:
line 5: 0
public static int clear(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=2
0: iload_0
1: iconst_1
2: iload_1
3: ishl
4: iconst_m1
5: ixor
6: iand
7: ireturn
LineNumberTable:
line 10: 0
set函数的指令解释
0: iload_0 //载入第0个参数即a,压入栈
1: iconst_1 //数字1压入栈
2: iload_1 ///载入第1个参数即b,压入栈
3: ishl //弹出栈顶两个元素,将int型数值左移位指定位数并将结果压入栈顶,第一个弹出值(b)为左移的位数,第二个弹出的值(1)为要对其进行左移的数,即对1左移b位
4: ior //将栈顶两int型数值作“按位或”并将结果压入栈顶
5: ireturn //返回栈顶值
clear函数中iconst_m1指令将-1压入栈,-1就是16进制的0xFFFFFFFF, ixor是进行异或操作,操作结果相当于取反
将上面的例子扩展成long类型
public class longbitop
{
public static long lset (long a, int b)
{
return a | 1<<b;
}
public static long lclear (long a, int b)
{
return a & (~(1<<b));
}
}
反编译
public static long lset(long, int);
descriptor: (JI)J
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=2
0: lload_0
1: iconst_1
2: iload_2
3: ishl
4: i2l
5: lor
6: lreturn
LineNumberTable:
line 5: 0
public static long lclear(long, int);
descriptor: (JI)J
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=2
0: lload_0
1: iconst_1
2: iload_2
3: ishl
4: iconst_m1
5: ixor
6: i2l
7: land
8: lreturn
LineNumberTable:
line 9: 0
需要注意的指令
i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶
32位需要升级为64位值时,会使用i21指令把整型扩展成64位长整型.
循环
看个简单的例子
public class Loop
{
public static void main(String[] args)
{
for (int i = 1; i <= 10; i++)
{
System.out.println(i);
}
}
}
反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpgt 21
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: iinc 1, 1
18: goto 2
21: return
指令解释
0: iconst_1 //常数1压入栈
1: istore_1 //栈顶弹出存入本地变量数组1号元素
2: iload_1 //本地变量1号元素压入栈顶
3: bipush 10 //常数10压入栈顶
5: if_icmpgt 21 // 比较栈顶两int型数值大小,当结果大于0时跳转到偏移位21,比较都是拿第二个弹出值与第一个比较即i与10比较
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V //8~12这里输出变量i
15: iinc 1, 1 //将指定int型变量增加指定值(常用于i++,i--,i+=2) ,这里指本地变量1号元素加1
18: goto 2 //跳转到偏移位2
21: return
多说一句,我们调用println打印数据类型是整型,我们看注释,“(I)V”,I的意思是整型,V的意思是返回void
再看个复杂的,斐波那契数列
public class Fibonacci
{
public static void main(String[] args)
{
int limit = 20, f = 0, g = 1;
for (int i = 1; i <= limit; i++)
{
f = f + g;
g = f - g;
System.out.println(f);
}
}
}
反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: bipush 20
2: istore_1
3: iconst_0
4: istore_2
5: iconst_1
6: istore_3
7: iconst_1
8: istore 4
10: iload 4
12: iload_1
13: if_icmpgt 37
16: iload_2
17: iload_3
18: iadd
19: istore_2
20: iload_2
21: iload_3
22: isub
23: istore_3
24: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
27: iload_2
28: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
31: iinc 4, 1
34: goto 10
37: return
拿几个指令说一下
if_icmpgt 37 比较栈顶两int型数值大小,当结果大于0时跳转到偏移位37,比较都是拿栈的第二个弹出值(本地变量4号)与第一个比较(本地变量1号)
isub 栈顶的两个元素相减并将结果压入栈顶,谁减谁,仍然是栈顶弹出的第二个减第一个
iinc 4, 1 表示是本地变量4号元素加1,结果存在本地变量4号
这里的英文作者又吐槽了一下
8: istore 4
10: iload 4
这两句中的iload 4,感觉多余。。
switch例子
public class tableswitch {
public static void f(int a) {
switch (a) {
case 0:
System.out.println("zero");
break;
case 1:
System.out.println("one\n");
break;
case 2:
System.out.println("two\n");
break;
case 3:
System.out.println("three\n");
break;
case 4:
System.out.println("four\n");
break;
default:
System.out.println("something unknown\n");
break;
}
}
}
反编译
public static void f(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iload_0
1: tableswitch { // 0 to 4
0: 36
1: 47
2: 58
3: 69
4: 80
default: 91
}
36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
39: ldc #3 // String zero
41: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: goto 99
47: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
50: ldc #5 // String one\n
52: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: goto 99
58: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
61: ldc #6 // String two\n
63: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: goto 99
69: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
72: ldc #7 // String three\n
74: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
77: goto 99
80: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
83: ldc #8 // String four\n
85: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
88: goto 99
91: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
94: ldc #9 // String something unknown\n
96: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
99: return
其中需要注意的是tableswitch是在JVM级别上直接支持switch 语句,非常有意思的是,编译器编译的时候,会根据case条件的不同翻译成tableswitch或者lookupswitch
1: tableswitch { // 0 to 4
0: 36
1: 47
2: 58
3: 69
4: 80
default: 91
}
的意思,将栈顶元素与大括号内冒号前的元素逐个比较,若相等,则跳转到右边对应的偏移块号
break对应其中的goto 99,如果没有这句,栈顶元素会继续和后面的大括号内后续元素进行比较
本文参考:逆向工程权威指南.下册.pdf 和http://blog.51cto.com/7317859/2105269