Day9-java字节码

熬夜刷java,我爱了
asm javassit agent yyds

https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html

0x01 java class文件格式

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

这是jvm虚拟机规范中规定的class文件的固定格式

在JVM规范中u1u2u4分别表示的是1、2、4个字节的无符号数.

首先开始magic是用来区分class文件格式的,它有固定的值0xCAFEBABE.接下来就是minor_version和major_version表示版本.

For a class file whose major_version is 56 or above, the minor_version must be 0 or 65535.
For a class file whose major_version is between 45 and 55 inclusive, the minor_version may be any value.

遵循上面这个标准.

constant_pool_count常量池计数器,表示常量池中的数量+1,其中long和double类型占据两个字节

cp_info constant_pool[constant_pool_count-1];

其中cp_info是一种数据结构

cp_info {
   u1 tag;
   u1 info[];
}

其中的tag表示存储的类型.

Table 4.4-B. Constant pool tags (by tag)

Constant KindTagclass file formatJava SE
CONSTANT_Utf8145.31.0.2
CONSTANT_Integer345.31.0.2
CONSTANT_Float445.31.0.2
CONSTANT_Long545.31.0.2
CONSTANT_Double645.31.0.2
CONSTANT_Class745.31.0.2
CONSTANT_String845.31.0.2
CONSTANT_Fieldref945.31.0.2
CONSTANT_Methodref1045.31.0.2
CONSTANT_InterfaceMethodref1145.31.0.2
CONSTANT_NameAndType1245.31.0.2
CONSTANT_MethodHandle1551.07
CONSTANT_MethodType1651.07
CONSTANT_Dynamic1755.011
CONSTANT_InvokeDynamic1851.07
CONSTANT_Module1953.09
CONSTANT_Package2053.09

access_flags访问标志,表示某个对象的访问权限

标志名十六进制值描述
ACC_PUBLIC0x0001声明为public
ACC_FINAL0x0010声明为final
ACC_SUPER0x0020废弃/仅JDK1.0.2前使用
ACC_INTERFACE0x0200声明为接口
ACC_ABSTRACT0x0400声明为abstract
ACC_SYNTHETIC0x1000声明为synthetic,表示该class文件并非由Java源代码所生成
ACC_ANNOTATION0x2000标识注解类型
ACC_ENUM0x4000标识枚举类型

this_class当前类的名字

super_class父类的名java/lang/Object类的super_class的为0,其他的都必须有一个索引.
interfaces_count当前类实现和继承的接口数目

u2 interfaces[interfaces_count];表示的是所有接口数组

u2 fields_count;表示的是当前class中的成员变量个数

field_info fields[fields_count];表示的是当前类的所有成员变量,field_info表示的是成员变量对象。

filed_info数据结构

field_info {
   u2 access_flags; 表示的是成员变量的修饰符;
   u2 name_index; 表示的是成员变量的名称;
   u2 descriptor_index;表示的是成员变量的描述符;
   u2 attributes_count;表示的是成员变量的属性数量
   attribute_info attributes[attributes_count]; 表示的是成员变量的属性信息;
}

u2 methods_count; method_info methods[methods_count]; 和上面的差不多相同

Person类中定义了3个字段 age、name、gender它们是类成员变量,但它们不全是属性;那什么是属性?
属性的定义规则是:set/get方法名,去掉set/get后,将剩余部分首字母小写得到的字符串就是这个类的属性。

attribute_info attributes[attributes_count];表示的是当前class文件的所有属性,attribute_info是一个非常复杂的数据结构,存储着各种属性信息。

attribute_info数据结构:

attribute_info {
   u2 attribute_name_index;
   u4 attribute_length;
   u1 info[attribute_length];
}

u2 attribute_name_index;表示的是属性名称索引,读取attribute_name_index值所在常量池中的名称可以得到属性名称。

Java15属性表:

属性名称章节
ConstantValue Attribute§4.7.2
Code Attribute§4.7.3
StackMapTable Attribute§4.7.4
Exceptions Attribute§4.7.5
InnerClasses Attribute§4.7.6
EnclosingMethod Attribute§4.7.7
Synthetic Attribute§4.7.8
Signature Attribute§4.7.9
SourceFile Attribute§4.7.10
SourceDebugExtension Attribute§4.7.11
LineNumberTable Attribute§4.7.12
LocalVariableTable Attribute§4.7.13
LocalVariableTypeTable Attribute§4.7.14
Deprecated Attribute§4.7.15
RuntimeVisibleAnnotations Attribute§4.7.16
RuntimeInvisibleAnnotations Attribute§4.7.17
RuntimeVisibleParameterAnnotations Attribute§4.7.18
RuntimeInvisibleParameterAnnotations Attribute§4.7.19
RuntimeVisibleTypeAnnotations Attribute§4.7.20
RuntimeInvisibleTypeAnnotations Attribute§4.7.21
AnnotationDefault Attribute§4.7.22
BootstrapMethods Attribute§4.7.23
MethodParameters Attribute§4.7.24
Module Attribute§4.7.25
ModulePackages Attribute§4.7.26
ModuleMainClass Attribute§4.7.27
NestHost Attribute§4.7.28
NestMembers Attribute§4.7.29

属性对象

属性表是动态的,新的JDK版本可能会添加新的属性值。每一种属性的数据结构都不相同,所以读取到属性名称后还需要根据属性的类型解析不同属性表中的值。比如Code Attribute中存储了类方法的异常表、字节码指令集、属性信息等重要信息。

0x02 ClassByteCodeParser

解析魔数

this.dis = new DataInputStream(in);
// u4 magic;
int magic = dis.readInt();
// 校验文件魔数
if (0xCAFEBABE == magic) {
this.magic = magic;

解析版本

// u2 minor_version
this.minor = dis.readUnsignedShort();

// u2 major_version;
this.major = dis.readUnsignedShort();

解析常量池parseConstantPool();方法实现

u2             constant_pool_count;
cp_info        constant_pool[constant_pool_count-1];

分为下面几步

  • 读取常量池的数量u2 constant_pool_count;

    cp_info {
       u1 tag;
       u1 info[];
    }
    
  • 读取tag,根据不同的tag进行解析

  • 解析常量池中的对象

  • 链接常量池中的对象

	private void parseConstantPool() throws IOException {
		// u2 constant_pool_count;
		this.poolCount = dis.readUnsignedShort();

		// cp_info constant_pool[constant_pool_count-1];
		for (int i = 1; i <= poolCount - 1; i++) {
//          cp_info {
//              u1 tag;
//              u1 info[];
//          }

			int      tag      = dis.readUnsignedByte();
			Constant constant = Constant.getConstant(tag);

			if (constant == null) {
				throw new RuntimeException("解析常量池异常,无法识别的常量池类型:" + tag);
			}

			// 解析常量池对象
			parseConstantItems(constant, i);

			// Long和Double是宽类型,占两位
			if (CONSTANT_LONG == constant || CONSTANT_DOUBLE == constant) {
				i++;
			}
		}

		// 链接常量池中的引用
		linkConstantPool();
	}

处理

private void parseConstantItems(Constant constant, int index) throws IOException {
		Map<String, Object> map = new LinkedHashMap<>();

		switch (constant) {
			case CONSTANT_UTF8:
//                  CONSTANT_Utf8_info {
//                      u1 tag;
//                      u2 length;
//                      u1 bytes[length];
//                  }

				int length = dis.readUnsignedShort();
				byte[] bytes = new byte[length];
				dis.read(bytes);

				map.put("tag", CONSTANT_UTF8);
				map.put("value", new String(bytes, UTF_8));
				break;
			case CONSTANT_INTEGER:
//                  CONSTANT_Integer_info {
//                      u1 tag;
//                      u4 bytes;
//                  }

				map.put("tag", CONSTANT_INTEGER);
				map.put("value", dis.readInt());
				break;
			case CONSTANT_FLOAT:
//                  CONSTANT_Float_info {
//                      u1 tag;
//                      u4 bytes;
//                  }

				map.put("tag", CONSTANT_FLOAT);
				map.put("value", dis.readFloat());
				break;
			case CONSTANT_LONG:
//                  CONSTANT_Long_info {
//                      u1 tag;
//                      u4 high_bytes;
//                      u4 low_bytes;
//                  }

				map.put("tag", CONSTANT_LONG);
				map.put("value", dis.readLong());
				break;
			case CONSTANT_DOUBLE:
//                  CONSTANT_Double_info {
//                      u1 tag;
//                      u4 high_bytes;
//                      u4 low_bytes;
//                  }

				map.put("tag", CONSTANT_DOUBLE);
				map.put("value", dis.readDouble());
				break;
			case CONSTANT_CLASS:
//                  CONSTANT_Class_info {
//                      u1 tag;
//                      u2 name_index;
//                  }

				map.put("tag", CONSTANT_CLASS);
				map.put("nameIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_STRING:
//                  CONSTANT_String_info {
//                      u1 tag;
//                      u2 string_index;
//                  }

				map.put("tag", CONSTANT_STRING);
				map.put("stringIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_FIELD_REF:
//                  CONSTANT_Fieldref_info {
//                      u1 tag;
//                      u2 class_index;
//                      u2 name_and_type_index;
//                  }

				map.put("tag", CONSTANT_FIELD_REF);
				map.put("classIndex", dis.readUnsignedShort());
				map.put("nameAndTypeIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_METHOD_REF:
//                  CONSTANT_Methodref_info {
//                      u1 tag;
//                      u2 class_index;
//                      u2 name_and_type_index;
//                  }

				map.put("tag", CONSTANT_METHOD_REF);
				map.put("classIndex", dis.readUnsignedShort());
				map.put("nameAndTypeIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_INTERFACE_METHOD_REF:
//                  CONSTANT_InterfaceMethodref_info {
//                      u1 tag;
//                      u2 class_index;
//                      u2 name_and_type_index;
//                  }

				map.put("tag", CONSTANT_INTERFACE_METHOD_REF);
				map.put("classIndex", dis.readUnsignedShort());
				map.put("nameAndTypeIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_NAME_AND_TYPE:
//                  CONSTANT_NameAndType_info {
//                      u1 tag;
//                      u2 name_index;
//                      u2 descriptor_index;
//                  }

				map.put("tag", CONSTANT_NAME_AND_TYPE);
				map.put("nameIndex", dis.readUnsignedShort());
				map.put("descriptorIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_METHOD_HANDLE:
//                  CONSTANT_MethodHandle_info {
//                      u1 tag;
//                      u1 reference_kind;
//                      u2 reference_index;
//                  }

				map.put("tag", CONSTANT_METHOD_HANDLE);
				map.put("referenceKind", dis.readUnsignedByte());
				map.put("referenceIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_METHOD_TYPE:
//                  CONSTANT_MethodType_info {
//                      u1 tag;
//                      u2 descriptor_index;
//                  }

				map.put("tag", CONSTANT_METHOD_TYPE);
				map.put("descriptorIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_DYNAMIC:
//                  CONSTANT_Dynamic_info {
//                      u1 tag;
//                      u2 bootstrap_method_attr_index;
//                      u2 name_and_type_index;
//                  }

				map.put("tag", CONSTANT_DYNAMIC);
				map.put("bootstrapMethodAttrIdx", dis.readUnsignedShort());
				map.put("nameAndTypeIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_INVOKE_DYNAMIC:
//                  CONSTANT_InvokeDynamic_info {
//                      u1 tag;
//                      u2 bootstrap_method_attr_index;
//                      u2 name_and_type_index;
//                  }

				map.put("tag", CONSTANT_INVOKE_DYNAMIC);
				map.put("bootstrapMethodAttrIdx", dis.readUnsignedShort());
				map.put("nameAndTypeIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_MODULE:
//                  CONSTANT_Module_info {
//                      u1 tag;
//                      u2 name_index;
//                  }

				map.put("tag", CONSTANT_MODULE);
				map.put("nameIndex", dis.readUnsignedShort());
				break;
			case CONSTANT_PACKAGE:
//                  CONSTANT_Package_info {
//                      u1 tag;
//                      u2 name_index;
//                  }

				map.put("tag", CONSTANT_PACKAGE);
				map.put("nameIndex", dis.readUnsignedShort());
				break;
		}

		constantPoolMap.put(index, map);
	}

最后的处理链接是因为:常量池很多存储了对于其他变量的引用,需要对他进行解析。

,为了能够直观的看到常量池ID为1的对象信息我们就必须要将所有使用索引方式链接的映射关系改成直接字符串引用。

	private void linkConstantPool() {
		for (Integer id : constantPoolMap.keySet()) {
			Map<String, Object> valueMap = constantPoolMap.get(id);

			if (!valueMap.containsKey("value")) {
				Map<String, Object> newMap = new LinkedHashMap<>();

				for (String key : valueMap.keySet()) {
					if (key.endsWith("Index")) {
						Object value = recursionValue((Integer) valueMap.get(key));

						if (value != null) {
							String newKey = key.substring(0, key.indexOf("Index"));

							newMap.put(newKey + "Value", value);
						}
					}
				}

				valueMap.putAll(newMap);
			}
/**
 * 通过常量池中的索引ID和名称获取常量池中的值
 *
 * @param index 索引ID
 * @return 常量池对象值
 */
private Object getConstantPoolValue(int index) {
     if (constantPoolMap.containsKey(index)) {
        Map<String, Object> dataMap  = constantPoolMap.get(index);
        Constant            constant = (Constant) dataMap.get("tag");

        switch (constant) {
           case CONSTANT_UTF8:
           case CONSTANT_INTEGER:
           case CONSTANT_FLOAT:
           case CONSTANT_LONG:
           case CONSTANT_DOUBLE:
              return dataMap.get("value");
           case CONSTANT_CLASS:
           case CONSTANT_MODULE:
           case CONSTANT_PACKAGE:
              return dataMap.get("nameValue");
           case CONSTANT_STRING:
              return dataMap.get("stringValue");
           case CONSTANT_FIELD_REF:
           case CONSTANT_METHOD_REF:
           case CONSTANT_INTERFACE_METHOD_REF:
              return dataMap.get("classValue") + "." + dataMap.get("nameAndTypeValue");
           case CONSTANT_NAME_AND_TYPE:
           case CONSTANT_METHOD_TYPE:
              return dataMap.get("descriptorValue");
           case CONSTANT_METHOD_HANDLE:
              return dataMap.get("referenceValue");
           case CONSTANT_DYNAMIC:
           case CONSTANT_INVOKE_DYNAMIC:
              return dataMap.get("bootstrapMethodAttrValue") + "." + dataMap.get("nameAndTypeValue");
           default:
              break;
        }
     }

     return null;
}

后面这个方法是为了方便直接获取常量池中的数据而封装的。

  • 访问标志

    this.accessFlags = dis.readUnsignedShort();
    
  • 类名称

    this.thisClass = (String) getConstantPoolValue(dis.readUnsignedShort());
    
  • 类的父类名称解析

    注意index=0的时候,Obejct没有父类

    int superClassIndex = dis.readUnsignedShort();
    // 当解析Object类的时候super_class为0
    if (superClassIndex != 0) {
       this.superClass = (String) getConstantPoolValue(superClassIndex);
    } else {
       this.superClass = "java/lang/Object";
    }
    
  • 接口解析

    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    
    // u2 interfaces_count;
    this.interfacesCount = dis.readUnsignedShort();
    
    // 创建接口Index数组
    this.interfaces = new String[interfacesCount];
    
    // u2 interfaces[interfaces_count];
    for (int i = 0; i < interfacesCount; i++) {
        int index = dis.readUnsignedShort();
    
        // 设置接口名称
        this.interfaces[i] = (String) getConstantPoolValue(index);
    }
    
  • 变量/成员方法解析

    // u2 fields_count;
    this.fieldsCount = dis.readUnsignedShort();
    
    // field_info fields[fields_count];
    for (int i = 0; i < this.fieldsCount; i++) {
        //        field_info {
        //          u2 access_flags;
        //          u2 name_index;
        //          u2 descriptor_index;
        //          u2 attributes_count;
        //          attribute_info attributes[attributes_count];
        //        }
        this.fieldList.add(readFieldOrMethod());
    }
    
  • 属性解析

    https://zhishihezi.net/endpoint/richtext/43a3ddd294c129cc60389fc491d44c38?event=436b34f44b9f95fd3aa8667f1ad451b173526ab5441d9f64bd62d183bed109b0ea1aaaa23c5207a446fa6de9f588db3958e8cd5c825d7d5216199d64338d9d00c167a590fe7993863a9dc2252cd392e842bc8a14c5c53993a3fc9f72b0aeb13fb587c648e7046c336d61878438df1249f87cbe30a7202f8bf6ab9bcc58ad1c5e0381bc6e50e0cf0c7c805c09000f5f0c80c800e88fe4766e244ea31a9e07b2639c7c8884959340bf9b5975c577ce6243c8a09419cb9eb6183d1456258bf87328787fb1fb85c348d420a1d6b09f7d3f0815853c717a7ed1675dc36adddb8da53542c004db542e66a1a33c2ab4c554576589a16ed6dca72e89cf74091a89180688#0

    直接参考文章。

    Code属性用于表示成员方法的代码部分,Code中包含了指令集(byte数组),JVM调用成员方法时实际上就是执行的Code中的指令,而反编译工具则是把Code中的指令翻译成了Java代码

0x03 Java虚拟机指令集

https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-6.html#jvms-6.5

速查

Java虚拟机指令表

十六进制助记符指令说明
0x00nop什么都不做
0x01aconst_null将null推送至栈顶
0x02iconst_m1将int型-1推送至栈顶
0x03iconst_0将int型0推送至栈顶
0x04iconst_1将int型1推送至栈顶
0x05iconst_2将int型2推送至栈顶
0x06iconst_3将int型3推送至栈顶
0x07iconst_4将int型4推送至栈顶
0x08iconst_5将int型5推送至栈顶
0x09lconst_0将long型0推送至栈顶
0x0alconst_1将long型1推送至栈顶
0x0bfconst_0将float型0推送至栈顶
0x0cfconst_1将float型1推送至栈顶
0x0dfconst_2将float型2推送至栈顶
0x0edconst_0将double型0推送至栈顶
0x0fdconst_1将double型1推送至栈顶
0x10bipush将单字节的常量值(-128~127)推送至栈顶
0x11sipush将一个短整型常量值(-32768~32767)推送至栈顶
0x12ldc将int, float或String型常量值从常量池中推送至栈顶
0x13ldc_w将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0x14ldc2_w将long或double型常量值从常量池中推送至栈顶(宽索引)
0x15iload将指定的int型本地变量推送至栈顶
0x16lload将指定的long型本地变量推送至栈顶
0x17fload将指定的float型本地变量推送至栈顶
0x18dload将指定的double型本地变量推送至栈顶
0x19aload将指定的引用类型本地变量推送至栈顶
0x1aiload_0将第一个int型本地变量推送至栈顶
0x1biload_1将第二个int型本地变量推送至栈顶
0x1ciload_2将第三个int型本地变量推送至栈顶
0x1diload_3将第四个int型本地变量推送至栈顶
0x1elload_0将第一个long型本地变量推送至栈顶
0x1flload_1将第二个long型本地变量推送至栈顶
0x20lload_2将第三个long型本地变量推送至栈顶
0x21lload_3将第四个long型本地变量推送至栈顶
0x22fload_0将第一个float型本地变量推送至栈顶
0x23fload_1将第二个float型本地变量推送至栈顶
0x24fload_2将第三个float型本地变量推送至栈顶
0x25fload_3将第四个float型本地变量推送至栈顶
0x26dload_0将第一个double型本地变量推送至栈顶
0x27dload_1将第二个double型本地变量推送至栈顶
0x28dload_2将第三个double型本地变量推送至栈顶
0x29dload_3将第四个double型本地变量推送至栈顶
0x2aaload_0将第一个引用类型本地变量推送至栈顶
0x2baload_1将第二个引用类型本地变量推送至栈顶
0x2caload_2将第三个引用类型本地变量推送至栈顶
0x2daload_3将第四个引用类型本地变量推送至栈顶
0x2eiaload将int型数组指定索引的值推送至栈顶
0x2flaload将long型数组指定索引的值推送至栈顶
0x30faload将float型数组指定索引的值推送至栈顶
0x31daload将double型数组指定索引的值推送至栈顶
0x32aaload将引用型数组指定索引的值推送至栈顶
0x33baload将boolean或byte型数组指定索引的值推送至栈顶
0x34caload将char型数组指定索引的值推送至栈顶
0x35saload将short型数组指定索引的值推送至栈顶
0x36istore将栈顶int型数值存入指定本地变量
0x37lstore将栈顶long型数值存入指定本地变量
0x38fstore将栈顶float型数值存入指定本地变量
0x39dstore将栈顶double型数值存入指定本地变量
0x3aastore将栈顶引用型数值存入指定本地变量
0x3bistore_0将栈顶int型数值存入第一个本地变量
0x3cistore_1将栈顶int型数值存入第二个本地变量
0x3distore_2将栈顶int型数值存入第三个本地变量
0x3eistore_3将栈顶int型数值存入第四个本地变量
0x3flstore_0将栈顶long型数值存入第一个本地变量
0x40lstore_1将栈顶long型数值存入第二个本地变量
0x41lstore_2将栈顶long型数值存入第三个本地变量
0x42lstore_3将栈顶long型数值存入第四个本地变量
0x43fstore_0将栈顶float型数值存入第一个本地变量
0x44fstore_1将栈顶float型数值存入第二个本地变量
0x45fstore_2将栈顶float型数值存入第三个本地变量
0x46fstore_3将栈顶float型数值存入第四个本地变量
0x47dstore_0将栈顶double型数值存入第一个本地变量
0x48dstore_1将栈顶double型数值存入第二个本地变量
0x49dstore_2将栈顶double型数值存入第三个本地变量
0x4adstore_3将栈顶double型数值存入第四个本地变量
0x4bastore_0将栈顶引用型数值存入第一个本地变量
0x4castore_1将栈顶引用型数值存入第二个本地变量
0x4dastore_2将栈顶引用型数值存入第三个本地变量
0x4eastore_3将栈顶引用型数值存入第四个本地变量
0x4fiastore将栈顶int型数值存入指定数组的指定索引位置
0x50lastore将栈顶long型数值存入指定数组的指定索引位置
0x51fastore将栈顶float型数值存入指定数组的指定索引位置
0x52dastore将栈顶double型数值存入指定数组的指定索引位置
0x53aastore将栈顶引用型数值存入指定数组的指定索引位置
0x54bastore将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55castore将栈顶char型数值存入指定数组的指定索引位置
0x56sastore将栈顶short型数值存入指定数组的指定索引位置
0x57pop将栈顶数值弹出 (数值不能是long或double类型的)
0x58pop2将栈顶的一个(long或double类型的)或两个数值弹出(其它)
0x59dup复制栈顶数值并将复制值压入栈顶
0x5adup_x1复制栈顶数值并将两个复制值压入栈顶
0x5bdup_x2复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5cdup2复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
0x5ddup2_x1<待补充>
0x5edup2_x2<待补充>
0x5fswap将栈最顶端的两个数值互换(数值不能是long或double类型的)
0x60iadd将栈顶两int型数值相加并将结果压入栈顶
0x61ladd将栈顶两long型数值相加并将结果压入栈顶
0x62fadd将栈顶两float型数值相加并将结果压入栈顶
0x63dadd将栈顶两double型数值相加并将结果压入栈顶
0x64isub将栈顶两int型数值相减并将结果压入栈顶
0x65lsub将栈顶两long型数值相减并将结果压入栈顶
0x66fsub将栈顶两float型数值相减并将结果压入栈顶
0x67dsub将栈顶两double型数值相减并将结果压入栈顶
0x68imul将栈顶两int型数值相乘并将结果压入栈顶
0x69lmul将栈顶两long型数值相乘并将结果压入栈顶
0x6afmul将栈顶两float型数值相乘并将结果压入栈顶
0x6bdmul将栈顶两double型数值相乘并将结果压入栈顶
0x6cidiv将栈顶两int型数值相除并将结果压入栈顶
0x6dldiv将栈顶两long型数值相除并将结果压入栈顶
0x6efdiv将栈顶两float型数值相除并将结果压入栈顶
0x6fddiv将栈顶两double型数值相除并将结果压入栈顶
0x70irem将栈顶两int型数值作取模运算并将结果压入栈顶
0x71lrem将栈顶两long型数值作取模运算并将结果压入栈顶
0x72frem将栈顶两float型数值作取模运算并将结果压入栈顶
0x73drem将栈顶两double型数值作取模运算并将结果压入栈顶
0x74ineg将栈顶int型数值取负并将结果压入栈顶
0x75lneg将栈顶long型数值取负并将结果压入栈顶
0x76fneg将栈顶float型数值取负并将结果压入栈顶
0x77dneg将栈顶double型数值取负并将结果压入栈顶
0x78ishl将int型数值左移位指定位数并将结果压入栈顶
0x79lshl将long型数值左移位指定位数并将结果压入栈顶
0x7aishr将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7blshr将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7ciushr将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7dlushr将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7eiand将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7fland将栈顶两long型数值作“按位与”并将结果压入栈顶
0x80ior将栈顶两int型数值作“按位或”并将结果压入栈顶
0x81lor将栈顶两long型数值作“按位或”并将结果压入栈顶
0x82ixor将栈顶两int型数值作“按位异或”并将结果压入栈顶
0x83lxor将栈顶两long型数值作“按位异或”并将结果压入栈顶
0x84iinc将指定int型变量增加指定值(i++, i–, i+=2)
0x85i2l将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0x86i2f将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0x87i2d将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0x88l2i将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0x89l2f将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8al2d将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8bf2i将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8cf2l将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8df2d将栈顶float型数值强制转换成double型数值并将结果压入栈顶
0x8ed2i将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8fd2l将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0x90d2f将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0x91i2b将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0x92i2c将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0x93i2s将栈顶int型数值强制转换成short型数值并将结果压入栈顶
0x94lcmp比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0x95fcmpl比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96fcmpg比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97dcmpl比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98dcmpg比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x99ifeq当栈顶int型数值等于0时跳转
0x9aifne当栈顶int型数值不等于0时跳转
0x9biflt当栈顶int型数值小于0时跳转
0x9cifge当栈顶int型数值大于等于0时跳转
0x9difgt当栈顶int型数值大于0时跳转
0x9eifle当栈顶int型数值小于等于0时跳转
0x9fif_icmpeq比较栈顶两int型数值大小,当结果等于0时跳转
0xa0if_icmpne比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1if_icmplt比较栈顶两int型数值大小,当结果小于0时跳转
0xa2if_icmpge比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3if_icmpgt比较栈顶两int型数值大小,当结果大于0时跳转
0xa4if_icmple比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5if_acmpeq比较栈顶两引用型数值,当结果相等时跳转
0xa6if_acmpne比较栈顶两引用型数值,当结果不相等时跳转
0xa7goto无条件跳转
0xa8jsr跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9ret返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用)
0xaatableswitch用于switch条件跳转,case值连续(可变长度指令)
0xablookupswitch用于switch条件跳转,case值不连续(可变长度指令)
0xacireturn从当前方法返回int
0xadlreturn从当前方法返回long
0xaefreturn从当前方法返回float
0xafdreturn从当前方法返回double
0xb0areturn从当前方法返回对象引用
0xb1return从当前方法返回void
0xb2getstatic获取指定类的静态域,并将其值压入栈顶
0xb3putstatic为指定的类的静态域赋值
0xb4getfield获取指定类的实例域,并将其值压入栈顶
0xb5putfield为指定的类的实例域赋值
0xb6invokevirtual调用实例方法
0xb7invokespecial调用超类构造方法,实例初始化方法,私有方法
0xb8invokestatic调用静态方法
0xb9invokeinterface调用接口方法
0xba
0xbbnew创建一个对象,并将其引用值压入栈顶
0xbcnewarray创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
0xbdanewarray创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
0xbearraylength获得数组的长度值并压入栈顶
0xbfathrow将栈顶的异常抛出
0xc0checkcast检验类型转换,检验未通过将抛出ClassCastException
0xc1instanceof检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc2monitorenter获得对象的锁,用于同步方法或同步块
0xc3monitorexit释放对象的锁,用于同步方法或同步块
0xc4wide<待补充>
0xc5multianewarray创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
0xc6ifnull为null时跳转
0xc7ifnonnull不为null时跳转
0xc8goto_w无条件跳转(宽索引)
0xc9jsr_w跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶

0x04 java类字节码编辑-ASM

https://asm.ow2.io/asm4-guide.pdf

MethodVisitor和AdviceAdapter

  • AdviceAdapter类实现了一些非常有价值的方法,如:onMethodEnter(方法进入时回调方法)、onMethodExit(方法退出时回调方法),如果我们自己实现很容易掉进坑里面,因为这两个方法都是根据条件推算出来的。比如我们如果在构造方法的第一行直接插入了我们自己的字节码就可能会发现程序一运行就会崩溃,因为Java语法中限制我们第一行代码必须是super(xxx)

  • 使用AdviceAdapter可以直接调用mv.newLocal(type)计算出本地变量存储的位置,为我们省去了许多不必要的麻烦。

这样就通过asm实现了遍历一个类的基础信息

注意依赖

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
public class ASMVisitorTest {
    public static void main(String[] args) throws IOException {
        String className="com.anbai.sec.bytecode.TestHelloWorld";
        //创建classReader对象,用来解析类对象 可以传入 类名 二进制 输入流
        ClassReader cr = new ClassReader(className);
        System.out.println(
                "解析的类名: " + cr.getClassName() + ", 父类: " + cr.getSuperName() +
                        ",实现的接口: " + Arrays.toString(cr.getInterfaces())
        );
        System.out.println("-----------------------------------------------------------------------------");
        //使用自定义的ClassVisitor访问这对象,访问该类文件的结构
        cr.accept(new ClassVisitor(ASM9) {
            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                System.out.println(
                        "变量描述符: " + access + "\t 类名:" + name + "\t 父类名: " + superName +
                                "\t 实现的接口:" + Arrays.toString(interfaces)
                );
                System.out.println("-----------------------------------------------------------------------------");
                super.visit(version, access, name, signature, superName, interfaces);
            }

            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                System.out.println(
                        "变量描述符: " + access + "\t 变量名称:" + name + "\t 描述符:" + descriptor+ "\t 默认值:"+ value
                );
                return super.visitField(access, name, descriptor, signature, value);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                System.out.println(
                        "变量描述符: " + access + "\t 方法名称:" + name + "\t 描述符:" + descriptor +
                                "抛出的异常: " + Arrays.toString(exceptions)
                );
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        },EXPAND_FRAMES);//该标识用于设置扩展栈帧图。默认栈图以它们原始格式(V1_6以下使用扩展格式,其他使用压缩格式)被访问
    }
}

修改类名/方法名称/描述符号

注意修改了类和及其属性之后,需要重新计算max_stackmax_locals,但是如果手撸的化,一般都会撸脱皮,所以我们可以使用它自带的方法来达到效果. COMPUTE_FRAMES

package com.anbai.sec.bytecode.asm;


import org.apache.commons.io.FileUtils;
import org.objectweb.asm.*;

import java.io.File;
import java.io.IOException;

import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.Opcodes.*;

public class ASMWriterTest {
    public static void main(String[] args) throws IOException {
        String className="com.anbai.sec.bytecode.TestHelloWorld";
        final String newclassName="JavaAsmTest";

        ClassReader cr = new ClassReader(className);
        final ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(ASM9,cw) {
            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                super.visit(version, access, name, signature, superName, interfaces);
            }

            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                return super.visitField(access, name, descriptor, signature, value);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                if(name.equals("hello")){
                    access = access & ~ACC_PUBLIC | ACC_PRIVATE;//如果是public属性,就改成private
                    return super.visitMethod(access, "hi", descriptor, signature, exceptions);//将hello方法改成了hi
                }
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        },ClassReader.EXPAND_FRAMES);
        File classFilePath = new File(new File(System.getProperty("user.dir"), "javaweb-sec-source/javase/src/main/java/com/anbai/sec/bytecode/asm/"), newclassName + ".class");
        byte[] classBytes = cw.toByteArray();
        FileUtils.writeByteArrayToFile(classFilePath,classBytes);
    }
}
private String hi(String content) {
        String str = "Hello:";
        return str + content;
    }

修改类方法字节码

利用场景:

  • RASP: 需要在java底层api方法执行之前插入自身的检测代码,实现动态拦截恶意攻击
  • APM: 统计代码的执行时间

https://www.cnblogs.com/beansoft/p/15495933.html 可以通过idea的插件来操作,可以说是yyds了!.

image-20220107112225134

用插件之后,就可以缺啥补啥了,来构建新的方法,生成全新的class文件。

0x05 java类字节码编辑-Javassist

API和特殊标识符

描述
ClassPoolClassPool是一个存储CtClass的容器,如果调用get方法会搜索并创建一个表示该类的CtClass对象
CtClassCtClass表示的是从ClassPool获取的类对象,可对该类就行读写编辑等操作
CtMethod可读写的类方法对象
CtConstructor可读写的类构造方法对象
CtField可读写的类成员变量对象

Javassist使用了内置的标识符来表示一些特定的含义,如:$_表示返回值。我们可以在动态插入类代码的时候使用这些特殊的标识符来表示对应的对象。

表达式描述
$0, $1, $2, ...this和方法参数
$argsObject[]类型的参数数组
$$所有的参数,如m($$)等价于m($1,$2,...)
$cflow(...)cflow变量
$r返回类型,用于类型转换
$w包装类型,用于类型转换
$_方法返回值
$sig方法签名,返回java.lang.Class[]数组类型
$type返回值类型,java.lang.Class类型
$class当前类,java.lang.Class类型

读取类/成员变量/方法信息

package com.anbai.sec.bytecode.javassist;

import javassist.*;

import java.util.Arrays;

public class JavassistClassVisTest {
    public static void main(String[] args) throws NotFoundException {
        String className = "com.anbai.sec.bytecode.TestHelloWorld";
        //先创建classpool
        ClassPool classPool = ClassPool.getDefault();

        CtClass ctClass = classPool.get(className);
        System.out.println(
                "类名:" + ctClass.getName() + ", 父类: " + ctClass.getSuperclass().getName() +
                        ", 实现的接口: " + Arrays.toString(ctClass.getInterfaces())
        );
        System.out.println("--------------------------------------------------------");

        //获取所有构造方法
        CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors();

        //获取所有的成员变量
        CtField[] ctFields = ctClass.getDeclaredFields();

        //获取所有的成员方法
        CtMethod[] ctMethods = ctClass.getDeclaredMethods();

        //输出所有成员方法
        for (CtMethod ctMethod:
             ctMethods) {
            System.out.println(ctMethod.getMethodInfo());
        }

        System.out.println("--------------------------------------------------------");

        for (CtField ctField: ctFields
             ) {
            System.out.println(ctField.getFieldInfo());
        }
        System.out.println("--------------------------------------------------------");
    }
}

修改类方法

  public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        String className = "com.anbai.sec.bytecode.TestHelloWorld";
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get(className);
        CtMethod hello = ctClass.getDeclaredMethod("hello", new CtClass[]{classPool.get("java.lang.String")});
        hello.setModifiers(Modifier.PRIVATE);
        hello.insertBefore("System.out.println($1);"); //输出hello方法的第一个参数
        hello.insertAfter("System.out.println($_); return \"Return:\" + $_;");
        File classFilePath = new File(new File(System.getProperty("user.dir"), "javaweb-sec-source/javase/src/main/java/com/anbai/sec/bytecode/"), "TestHelloWorld.class");
        byte[] bytes = ctClass.toBytecode();
        FileUtils.writeByteArrayToFile(classFilePath,bytes);

动态创建java类

几乎就是makeClass然后利用CtMethod+CtFiled等类的make方法和ctClassaddxxx方法,来添加到代码

0x06 java class反编译

等后面学习java逆向的时候再来吧。暂时需求不大。

https://zhishihezi.net/endpoint/richtext/1afed581c026296036d7f448c5e8ba34?event=436b34f44b9f95fd3aa8667f1ad451b173526ab5441d9f64bd62d183bed109b0ea1aaaa23c5207a446fa6de9f588db3958e8cd5c825d7d5216199d64338d9d005ed286fb570812d9c7e97a465fb54fc35493b08171b71a6e4c81021980de9ca692a60eea2b74f513456a6aa08c7d8c08892e78996afac0302a321db71903ed0488fc10c50dc657df41756023d5db9c7ac0e3d0b81aed217f0712631bb713d3fd41904d2cc41916cb059c22ac6f50276c89b456755f080a389fc8e1a7e6ba6962d522f305553f26e863cc39f8ab9ec0521ce7f00e407263fd8e6791da9139929f58f047f67ad12006164a6a30e00aeae64d7a5146973fd9f218b41467b4cb3617#0

0x07 java Agent机制

运行模式:

  • -javaagentAPI实现方式或者-agentpath/-agentlib参数
  • attach,可以对于正在运行的java进程附加agent。

java agent

和普通的class没有什么区别,只是普通的class文件以main方法作为程序的入口,agent以premain(Agent模式)和agentmain(Attach模式)作为Agent程序的入口。二者参数上 一模一样。

必须以jar包的形式运行或加载,并且包含/META-INF/MANIFEST.MF,该文件必须定义好究竟以哪个模式来运行,如果需要agent来修改jvm修改过的字节码,那么还需要设置Can-Retransform-Classes: true 或者Can-Redefine-Classes: true

Instrumentation

这是检测运行在JVM程序中的java api,我们可以实现以下功能。(摘抄自大哥的盒子)

  1. 动态添加或移除自定义的ClassFileTransformeraddTransformer/removeTransformer),JVM会在类加载时调用Agent中注册的ClassFileTransformer
  2. 动态修改classpathappendToBootstrapClassLoaderSearchappendToSystemClassLoaderSearch),将Agent程序添加到BootstrapClassLoaderSystemClassLoaderSearch(对应的是ClassLoader类的getSystemClassLoader方法,默认是sun.misc.Launcher$AppClassLoader)中搜索;
  3. 动态获取所有JVM已加载的类(getAllLoadedClasses);
  4. 动态获取某个类加载器已实例化的所有类(getInitiatedClasses)。
  5. 重定义某个已加载的类的字节码(redefineClasses)。
  6. 动态设置JNI前缀(setNativeMethodPrefix),可以实现Hook native方法。(安卓逆向常用)
  7. 重新加载某个已经被JVM加载过的类字节码retransformClasses)。

ClassFileTransformer

/*
 * 灵蜥Java Agent版 [Web应用安全智能防护系统]
 * ----------------------------------------------------------------------
 * Copyright © 安百科技(北京)有限公司
 */
package com.anbai.sec.agent;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.List;

/**
 * Creator: yz
 * Date: 2020/1/2
 */
public class CrackLicenseAgent {

    /**
     * 需要被Hook的类
     */
    private static final String HOOK_CLASS = "com.anbai.sec.agent.CrackLicenseTest";

    /**
     * Java Agent模式入口
     *
     * @param args 命令参数
     * @param inst Instrumentation
     */
    public static void premain(String args, final Instrumentation inst) {
        loadAgent(args, inst);
    }

    /**
     * Java Attach模式入口
     *
     * @param args 命令参数
     * @param inst Instrumentation
     */
    public static void agentmain(String args, final Instrumentation inst) {
        loadAgent(args, inst);
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            List<VirtualMachineDescriptor> list = VirtualMachine.list();

            for (VirtualMachineDescriptor desc : list) {
                System.out.println("进程ID:" + desc.id() + ",进程名称:" + desc.displayName());
            }

            return;
        }

        // Java进程ID
        String pid = args[0];

        try {
            // 注入到JVM虚拟机进程
            VirtualMachine vm = VirtualMachine.attach(pid);

            // 获取当前Agent的jar包路径
            URL agentURL = CrackLicenseAgent.class.getProtectionDomain().getCodeSource().getLocation();
            String agentPath = new File(agentURL.toURI()).getAbsolutePath();

            // 注入Agent到目标JVM
            vm.loadAgent(agentPath);
            vm.detach();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载Agent
     *
     * @param arg  命令参数
     * @param inst Instrumentation
     */
    private static void loadAgent(String arg, final Instrumentation inst) {
        // 创建ClassFileTransformer对象
        ClassFileTransformer classFileTransformer = createClassFileTransformer();

        // 添加自定义的Transformer,第二个参数true表示是否允许Agent Retransform,
        // 需配合MANIFEST.MF中的Can-Retransform-Classes: true配置
        inst.addTransformer(classFileTransformer, true);

        // 获取所有已经被JVM加载的类对象
        Class[] loadedClass = inst.getAllLoadedClasses();

        for (Class clazz : loadedClass) {
            String className = clazz.getName();

            if (inst.isModifiableClass(clazz)) {
                // 使用Agent重新加载HelloWorld类的字节码
                if (className.equals(HOOK_CLASS)) {
                    try {
                        inst.retransformClasses(clazz);
                    } catch (UnmodifiableClassException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static ClassFileTransformer createClassFileTransformer() {
        return new ClassFileTransformer() {

            /**
             * 类文件转换方法,重写transform方法可获取到待加载的类相关信息
             *
             * @param loader              定义要转换的类加载器;如果是引导加载器,则为 null
             * @param className           类名,如:java/lang/Runtime
             * @param classBeingRedefined 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
             * @param protectionDomain    要定义或重定义的类的保护域
             * @param classfileBuffer     类文件格式的输入字节缓冲区(不得修改)
             * @return 字节码byte数组。
             */
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer) {

                // 将目录路径替换成Java类名
                className = className.replace("/", ".");

                // 只处理com.anbai.sec.agent.CrackLicenseTest类的字节码
                if (className.equals(HOOK_CLASS)) {
                    try {
                        ClassPool classPool = ClassPool.getDefault();

                        // 使用javassist将类二进制解析成CtClass对象
                        CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

                        // 使用CtClass对象获取checkExpiry方法,类似于Java反射机制的clazz.getDeclaredMethod(xxx)
                        CtMethod ctMethod = ctClass.getDeclaredMethod(
                                "checkExpiry", new CtClass[]{classPool.getCtClass("java.lang.String")}
                        );

                        // 在checkExpiry方法执行前插入输出License到期时间代码
                        ctMethod.insertBefore("System.out.println(\"License到期时间:\" + $1);");

                        // 修改checkExpiry方法的返回值,将授权过期改为未过期
                        ctMethod.insertAfter("return false;");

                        // 修改后的类字节码
                        classfileBuffer = ctClass.toBytecode();
                        File classFilePath = new File(new File(System.getProperty("user.dir"), "javaweb-sec-source/javasec-agent/src/main/java/com/anbai/sec/agent/"), "CrackLicenseTest.class");

                        // 写入修改后的字节码到class文件
                        FileOutputStream fos = new FileOutputStream(classFilePath);
                        fos.write(classfileBuffer);
                        fos.flush();
                        fos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                return classfileBuffer;
            }
        };
    }

}

假如我以agent的方法运行

java -javaagent:target/javasec-agent.jar -classpath target/test-classes/ com.anbai.sec.agent.CrackLicenseTest

一定要记得指定classpath那么程序现在开始运行了。

image-20220107214522397

首先创建一个classfiletransformer

image-20220107214607254

重新构建的时候一定要记得好好复写transform方法

 /**
 * 类文件转换方法,重写transform方法可获取到待加载的类相关信息
 *
 * @param loader              定义要转换的类加载器;如果是引导加载器,则为 null
 * @param className           类名,如:java/lang/Runtime
 * @param classBeingRedefined 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
 * @param protectionDomain    要定义或重定义的类的保护域
 * @param classfileBuffer     类文件格式的输入字节缓冲区(不得修改)
 * @return 字节码byte数组。
 */

首先判断是不是我们要hook的类

image-20220107214751926

然后将字节码转换为ctclass进行处理

image-20220107214835764

进行更改

image-20220107214939850

进行重置

image-20220107214928720

这也是一个正常的hook过程,一会来实现一个简单的hook。

假如 我们用 Attach模式

用java自带的代码查看当前jvm里面运行的类

List<VirtualMachineDescriptor> list = VirtualMachine.list();

for (VirtualMachineDescriptor desc : list) {
    System.out.println("进程ID:" + desc.id() + ",进程名称:" + desc.displayName());
}

注入的过程

// Java进程ID
String pid = args[0];

// 设置Agent文件的绝对路径
String agentPath = "/xxx/agent.jar";

// 注入到JVM虚拟机进程
VirtualMachine vm = VirtualMachine.attach(pid);

// 注入Agent到目标JVM
vm.loadAgent(agentPath);
vm.detach();

运行

java -classpath $JAVA_HOME/lib/tools.jar:target/javasec-agent.jar com.anbai.sec.agent.CrackLicenseAgent

-E3slgRCh-1641563536653)]

重新构建的时候一定要记得好好复写transform方法

 /**
 * 类文件转换方法,重写transform方法可获取到待加载的类相关信息
 *
 * @param loader              定义要转换的类加载器;如果是引导加载器,则为 null
 * @param className           类名,如:java/lang/Runtime
 * @param classBeingRedefined 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
 * @param protectionDomain    要定义或重定义的类的保护域
 * @param classfileBuffer     类文件格式的输入字节缓冲区(不得修改)
 * @return 字节码byte数组。
 */

首先判断是不是我们要hook的类

[外链图片转存中…(img-DsL1tIUo-1641563536654)]

然后将字节码转换为ctclass进行处理

[外链图片转存中…(img-6DOKyjYO-1641563536654)]

进行更改

[外链图片转存中…(img-KZ4DmfcS-1641563536654)]

进行重置

[外链图片转存中…(img-4y6voy9S-1641563536655)]

这也是一个正常的hook过程,一会来实现一个简单的hook。

假如 我们用 Attach模式

用java自带的代码查看当前jvm里面运行的类

List<VirtualMachineDescriptor> list = VirtualMachine.list();

for (VirtualMachineDescriptor desc : list) {
    System.out.println("进程ID:" + desc.id() + ",进程名称:" + desc.displayName());
}

注入的过程

// Java进程ID
String pid = args[0];

// 设置Agent文件的绝对路径
String agentPath = "/xxx/agent.jar";

// 注入到JVM虚拟机进程
VirtualMachine vm = VirtualMachine.attach(pid);

// 注入Agent到目标JVM
vm.loadAgent(agentPath);
vm.detach();

运行

java -classpath $JAVA_HOME/lib/tools.jar:target/javasec-agent.jar com.anbai.sec.agent.CrackLicenseAgent
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值