使用BCEL库对Java字节码class文件byteCode进行反汇编和修改

本文介绍如何使用BCEL库对Java编译后的class文件进行字节码级的操作,包括提取和反汇编字节码,并通过修改特定字节码实现对原有程序逻辑的改变。

使用BCEL库,可以查看和修改Java编译后class字节源码

新建一个Java测试工程,写下这么一个测试类:

package ck;

import java.util.HashMap;
import java.util.Map;

public class LoginAct {
	public boolean login() {
		String userName = "get user name from request";		
		String pwd = "get password name from request";		
		String checkLicense = "call check license ";		
		Map<String,String> userMapInfo = null;		
		String dbPwd = "select password from db where userName=?";		
		if( checkLicense.equals("ok") && dbPwd.equals(pwd) ) {			
			userMapInfo = new HashMap<String, String>();			
			userMapInfo.put(userName, "crazy");			
			//select user_info from user_table into a map where username = xxx
		}
		//session.addAttribute("userInfo", userMapInfo );
		return userMapInfo != null;
	}
}

 

接下来,利用Bcel库,写一个简单的操作工具Java文件,它能提取class字节码并反汇编

Bcel库Maven地址

	<dependency>
	    <groupId>org.apache.bcel</groupId>
	    <artifactId>bcel</artifactId>
	    <version>6.2</version>
	</dependency>

本人写了一个 BcelDumpClassName.java 操作工具Java文件,代码参考摘自网上。然后经过我对byteCode一番研究和探索,结合Bcel库的codeToString 函数,我对网上的代码加入了修改后的codeToString 函数,使得输出结里和反汇编语句与JBE的一样,字节码也和DyJOE一致。

package gen;

import java.io.IOException;
import java.util.Iterator;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.TargetLostException;
import org.apache.bcel.util.ByteSequence;
import org.apache.bcel.util.InstructionFinder;

public class BcelDumpClassName {
	public static void main(String[] argv) {
		//String className = "java.lang.String";
		String className = "ck.LoginAct";
		
        try {
            JavaClass clazz  = Repository.lookupClass(className);
            Method[] methods = clazz.getMethods();
            ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());
    
            for (int i = 0; i < methods.length; i++) {
                if (!(methods[i].isAbstract() || methods[i].isNative())) {
                    MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), cp);
                    Method stripped = removeNOPs(mg);
                    if (stripped != null)      // Any NOPs stripped?
                        methods[i] = stripped; // Overwrite with stripped method
                  }
            }
            clazz.setConstantPool(cp.getFinalConstantPool());
            //输出bytecode汇编语句
            printCode(clazz.getMethods());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	
	public static String byte2hex(byte[] array) {
	    StringBuffer sb = new StringBuffer(64);  
 	    try {  
 	        for (int i = 0; i < array.length; i++) {  
 	           sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3));
 	           sb.append(' ');
 	        }
 	        return sb.toString();	 	        
 	    } catch (Exception e) {  
 	        return e.toString();
 	    }  
	}
	
	public static String byte2hex(byte[] array, int start, int end) {
	    StringBuffer sb = new StringBuffer(64);  
 	    try {  
 	        for (int i = start; i < end; i++) {  
 	           sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3));
 	           sb.append(' ');
 	        }
 	        return sb.toString();	 	        
 	    } catch (Exception e) {  
 	        return e.toString();
 	    }  
	}

	//Modify by RoadToExpert . https://blog.youkuaiyun.com/roadtotheexpert
	public static String codeToString(byte[] code, ConstantPool constant_pool, int index, int length, boolean verbose) {
		StringBuilder buf = new StringBuilder(code.length * 20);
		int hexLen = 12;
		int pre = 0;
		String hexs = null;
		int sindex = 0;
		try {
			ByteSequence stream = new ByteSequence(code);
			Throwable localThrowable3 = null;
			try {
				for (int i = 0; i < index; i++) {
					Utility.codeToString(stream, constant_pool, verbose);
				}
				for (int i = 0; stream.available() > 0; i++) {
					if ((length < 0) || (i < length)) {
						String indices = Utility.fillup("|" + stream.getIndex() + ":" + Integer.toHexString(stream.getIndex()), 8, true, ' ');
						String opcodeDesc = Utility.codeToString(stream, constant_pool, verbose);
						
						hexs = byte2hex(code, pre, stream.getIndex());
						pre = sindex;
						hexs = Utility.fillup(hexs, hexLen, true, ' ');
						buf.append(hexs).append(indices).append(opcodeDesc).append('\n');
						
						pre = stream.getIndex(); 
					}
				}
			} catch (Throwable localThrowable1) {
				localThrowable3 = localThrowable1;
				// throw localThrowable1;
			} finally {
				if (stream != null) {
					if (localThrowable3 != null) {
						try {
							stream.close();
						} catch (Throwable localThrowable2) {
							localThrowable3.addSuppressed(localThrowable2);
						}
					} else {
						stream.close();
					}
				}
			}
		} catch (IOException e) {
			throw new ClassFormatException("Byte code error: " + buf.toString(), e);
		}
		
		return buf.toString();				   
	}
	

	public static void printCode(Method[] methods) {
		if( methods == null || methods.length == 0 ) {
			return;
		}
		StringBuilder sb = new StringBuilder(128*methods.length);
		
		for(int i=0; i < methods.length; i++) {
			sb.append("--------------------------------------------------------------------------\n");
			Code code = methods[i].getCode();
			sb.append(byte2hex(code.getCode()));
			sb.append('\n');
			sb.append(methods[i]).append('\n');
			if(code != null) { 
				String s = codeToString(code.getCode(), code.getConstantPool(), 0, -1, true);
				sb.append(s);						
			}
			sb.append('\n');
		}
		System.out.println(sb.toString());
	}

    private static Method removeNOPs(MethodGen mg) {
        InstructionList il = mg.getInstructionList();
        InstructionFinder f = new InstructionFinder(il);
        String pat = "NOP+"; // Find at least one NOP
        InstructionHandle next = null;
        int count = 0;
        
        for (Iterator iter = f.search(pat); iter.hasNext();) {
            InstructionHandle[] match = (InstructionHandle[]) iter.next();
            InstructionHandle first = match[0];
            InstructionHandle last  = match[match.length - 1];
            
            if ((next = last.getNext()) == null) {
                break;
            }
            
            count += match.length;
            
            try {
                il.delete(first, last);
            } catch (TargetLostException e) {
            	for (InstructionHandle target : e.getTargets()) {
            		System.out.println(target.toString());
            	}
            }
        }

        Method m = null;
        if (count > 0) {
            System.out.println("Removed " + count + " NOP instructions from method " + mg.getName());
            m = mg.getMethod();
        }
        il.dispose(); // Reuse instruction handles
        return m;
    }
}

 

上面的 Main函数里指定了对LoginAct进行JAVA反汇编码,运行 BcelDumpClassName 工具后,可以看到控制台输出如下信息:

 

--------------------------------------------------------------------------
2A B7 00 08 B1 
public void <init>()
2A          |0:0    aload_0
B7 00 08    |1:1    invokespecial	java.lang.Object.<init> ()V (8)
B1          |4:4    return

--------------------------------------------------------------------------
12 10 4C 12 12 4D 12 14 4E 01 3A 04 12 16 3A 05 2D 12 18 B6 00 1A 99 00 20 19 05 2C B6 00 1A 99 00 17 BB 00 20 59 B7 00 22 3A 04 19 04 2B 12 23 B9 00 25 03 00 57 19 04 C6 00 05 04 AC 03 AC 
public boolean login()
12 10       |0:0    ldc		"get user name from request" (16)
4C          |2:2    astore_1
12 12       |3:3    ldc		"get password name from request" (18)
4D          |5:5    astore_2
12 14       |6:6    ldc		"call check license " (20)
4E          |8:8    astore_3
01          |9:9    aconst_null
3A 04       |10:a   astore		%4
12 16       |12:c   ldc		"select password from db" (22)
3A 05       |14:e   astore		%5
2D          |16:10  aload_3
12 18       |17:11  ldc		"ok" (24)
B6 00 1A    |19:13  invokevirtual	java.lang.String.equals (Ljava/lang/Object;)Z (26)
99 00 20    |22:16  ifeq		#54
19 05       |25:19  aload		%5
2C          |27:1b  aload_2
B6 00 1A    |28:1c  invokevirtual	java.lang.String.equals (Ljava/lang/Object;)Z (26)
99 00 17    |31:1f  ifeq		#54
BB 00 20    |34:22  new		<java.util.HashMap> (32)
59          |37:25  dup
B7 00 22    |38:26  invokespecial	java.util.HashMap.<init> ()V (34)
3A 04       |41:29  astore		%4
19 04       |43:2b  aload		%4
2B          |45:2d  aload_1
12 23       |46:2e  ldc		"crazy" (35)
B9 00 25 03 00 |48:30  invokeinterface	java.util.Map.put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; (37)	3	0
57          |53:35  pop
19 04       |54:36  aload		%4
C6 00 05    |56:38  ifnull		#61
04          |59:3b  iconst_1
AC          |60:3c  ireturn
03          |61:3d  iconst_0
AC          |62:3e  ireturn


 

这里可以看到第|56:38行,对应的汇编是:   ifnull        #61

简单对比LoginAct源码,就可以知道它实际对应的就是  return userMapInfo != null;
 

如果把字节码 ifnull 改为  ifnonnull ,那源码不就变成 return userMapInfo == null , 这样随便输入都可以 login 通过啦!

那如何才能知道 ifnull ifnonnull 对应是哪个字节码?

在org.apache.bcel.Const源码,已经给出OpcodeNames,对应如下数组

public static final String[] OPCODE_NAMES = { "nop", "aconst_null", "iconst_m1", "iconst_0", "iconst_1", "iconst_2", "iconst_3", "iconst_4", "iconst_5", "lconst_0", "lconst_1", "fconst_0", "fconst_1", "fconst_2", "dconst_0", "dconst_1", "bipush", "sipush", "ldc", "ldc_w", "ldc2_w", "iload", "lload", "fload", "dload", "aload", "iload_0", "iload_1", "iload_2", "iload_3", "lload_0", "lload_1", "lload_2", "lload_3", "fload_0", "fload_1", "fload_2", "fload_3", "dload_0", "dload_1", "dload_2", "dload_3", "aload_0", "aload_1", "aload_2", "aload_3", "iaload", "laload", "faload", "daload", "aaload", "baload", "caload", "saload", "istore", "lstore", "fstore", "dstore", "astore", "istore_0", "istore_1", "istore_2", "istore_3", "lstore_0", "lstore_1", "lstore_2", "lstore_3", "fstore_0", "fstore_1", "fstore_2", "fstore_3", "dstore_0", "dstore_1", "dstore_2", "dstore_3", "astore_0", "astore_1", "astore_2", "astore_3", "iastore", "lastore", "fastore", "dastore", "aastore", "bastore", "castore", "sastore", "pop", "pop2", "dup", "dup_x1", "dup_x2", "dup2", "dup2_x1", "dup2_x2", "swap", "iadd", "ladd", "fadd", "dadd", "isub", "lsub", "fsub", "dsub", "imul", "lmul", "fmul", "dmul", "idiv", "ldiv", "fdiv", "ddiv", "irem", "lrem", "frem", "drem", "ineg", "lneg", "fneg", "dneg", "ishl", "lshl", "ishr", "lshr", "iushr", "lushr", "iand", "land", "ior", "lor", "ixor", "lxor", "iinc", "i2l", "i2f", "i2d", "l2i", "l2f", "l2d", "f2i", "f2l", "f2d", "d2i", "d2l", "d2f", "i2b", "i2c", "i2s", "lcmp", "fcmpl", "fcmpg", "dcmpl", "dcmpg", "ifeq", "ifne", "iflt", "ifge", "ifgt", "ifle", "if_icmpeq", "if_icmpne", "if_icmplt", "if_icmpge", "if_icmpgt", "if_icmple", "if_acmpeq", "if_acmpne", "goto", "jsr", "ret", "tableswitch", "lookupswitch", "ireturn", "lreturn", "freturn", "dreturn", "areturn", "return", "getstatic", "putstatic", "getfield", "putfield", "invokevirtual", "invokespecial", "invokestatic", "invokeinterface", "invokedynamic", "new", "newarray", "anewarray", "arraylength", "athrow", "checkcast", "instanceof", "monitorenter", "monitorexit", "wide", "multianewarray", "ifnull", "ifnonnull", "goto_w", "jsr_w", "breakpoint", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "<illegal opcode>", "impdep1", "impdep2" };

查上表得知,ifnull对应索引是198,16进制是C6 

ifnonnull对应索引是199,16进制是C7

找到这行

C6 00 05    |56:38  ifnull		#61

 

没有错,就是把上面的 C6 改成 C7 ,这样源码就变成 return userMapInfo == null 

 

。。。

但仔细想一下,因为源码中的 userMapInfo 没加载。即便 login() 返回 true 意义也不大。

再看下代码,关注点就定位到  dbPwd.equals(pwd) 这一行,查找其对应的反汇编码。分析点就落在这两行语句
 

B6 00 1A    |28:1c  invokevirtual	java.lang.String.equals (Ljava/lang/Object;)Z (26)
99 00 17    |31:1f  ifeq		#54

就像前面的操作那样,只要把 ifeq 变成 ifne,源码就会从原来的 dbPwd.equals(pwd) 变成  !dbPwd.equals(pwd)

查上面的OPCODE_NAMES 可知 ifeq 是153(0x99 )。改成 ifne 154(0x9A)

这样,外部登陆者,只要userName写正确,dbPwd写什么都可以了。 (^_ ^)
 

 

 

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值