写在前面
本文看下如何获取class字节码文件的魔数和版本号信息。
1:正文
需要对class字节码的结构有一定的了解,可以参考这篇文章 。
直接看代码:
package org.example;
import java.math.BigInteger;
public class TTTT {
//取部分字节码:java.lang.String
private static byte[] classData = {
-54, -2, -70, -66, 0, 0, 0, 52, 2, 26, 3, 0, 0, -40, 0, 3, 0, 0, -37, -1, 3, 0, 0, -33, -1, 3, 0, 1, 0, 0, 8, 0,
59, 8, 0, 83, 8, 0, 86, 8, 0, 87, 8, 0, 110, 8, 0, -83, 8, 0, -77, 8, 0, -49, 8, 0, -47, 1, 0, 3, 40, 41, 73, 1,
0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 59, 1, 0, 20, 40, 41,
76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 3, 40, 41, 86, 1, 0, 3,
40, 41, 90, 1, 0, 4, 40, 41, 91, 66, 1, 0, 4, 40, 41, 91, 67, 1, 0, 4, 40, 67, 41, 67, 1, 0, 21, 40, 68, 41, 76,
106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 4, 40, 73, 41, 67, 1, 0, 4};
public static void main(String[] args) {
// 显示值是按照有符号数来展示的,为了获取原始的无符号数对应的值,需要与上0xFF,即11111111,这样就转换为无符号数了(不知道为啥啊,反正就这样!!!)。
System.out.println("* byte字节码与运算原值(-54)换行后(-54 & 0x0FF):" + (-54 & 0x0FF));
//校验魔数
readAndCheckMagic();
//校验版本号
readAndCheckVersion();
}
/** * 前四个字节是魔术!!! * 校验魔数 * <p> * 很多文件格式都会规定满足该格式的文件必须以某几个固定字节开头,这几个字节主要起到标识作用,叫作魔数(magic number)。 * 例如; * PDF文件以4字节“%PDF”(0x25、0x50、0x44、0x46)开头, * ZIP文件以2字节“PK”(0x50、0x4B)开头 * class文件以4字节“0xCAFEBABE”开头 */
private static void readAndCheckMagic() {
System.out.println("\r\n------------ 校验魔数 ------------");
// 从class字节码中读取前四位,就是魔数,u4类型
byte[] magic_byte = new byte[4];
System.arraycopy(classData, 0, magic_byte, 0, 4);
// 将4位byte字节转成16进制字符串
String magicNumberInHex = new BigInteger(1, magic_byte).toString(16);
System.out.println("class文件魔数16进制表示为:" + magicNumberInHex);
// magicNumberInHex 是16进制的字符串,cafebabe,因为java中没有无符号整型,所以如果想要无符号只能放到更高位中
long magic_unsigned_int32 = Long.parseLong(magicNumberInHex, 16);
System.out.println("魔术转换为无符号表示(Java没有无符号数,所以转long来表示)magic_unsigned_int32:" + magic_unsigned_int32);
//魔数比对,一种通过字符串比对,另外一种使用假设的无符号16进制比较。如果使用无符号比较需要将0xCAFEBABE & 0x0FFFFFFFFL与运算
System.out.println("0xCAFEBABE & 0x0FFFFFFFFL(这样也可以转换为正整数表示):" + (0xCAFEBABE & 0xFFFFFFFFL));
// 通过比较字节码中获取的和cafebabe的实际值,得出是否是一个class字节码文件(当然不一定合法,因为后续还有很多内容)
if (magic_unsigned_int32 == (0xCAFEBABE & 0x0FFFFFFFFL)) {
System.out.println("class字节码魔数无符号16进制数值一致校验通过");
} else {
System.out.println("class字节码魔数无符号16进制数值一致校验拒绝");
}
}
/** * * 校验版本号 * <p> * 魔数之后是class文件的次版本号和主版本号,都是u2类型。假设某class文件的主版本号是M,次版本号是m,那么完整的版本号可以 * 表示成“M.m”的形式。次版本号只在J2SE 1.2之前用过,从1.2开始基本上就没有什么用了(都是0)。主版本号在J2SE 1.2之前是45, * 从1.2开始,每次有大版本的Java版本发布,都会加1{45、46、47、48、49、50、51、52} */
private static void readAndCheckVersion() {
System.out.println("\r\n------------ 校验版本号 ------------");
//从class字节码第4位开始读取,读取2位
byte[] minor_byte = new byte[2];
// u2 5,6两个字节是minor version信息
System.arraycopy(classData, 4, minor_byte, 0, 2);
//将2位byte字节转成16进制字符串
String minor_hex_str = new BigInteger(1, minor_byte).toString(16);
System.out.println("小版本号16进制表示:" + minor_hex_str);
//minor_unsigned_int32 转成无符号16进制
int minor_unsigned_int32 = Integer.parseInt(minor_hex_str, 16);
System.out.println("小版本号十进制整数表示:" + minor_unsigned_int32);
//从class字节码第6位开始读取,读取2位
byte[] major_byte = new byte[2];
// u2 7,8两个字节是major version信息
System.arraycopy(classData, 6, major_byte, 0, 2);
//将2位byte字节转成16进制字符串
String major_hex_str = new BigInteger(1, major_byte).toString(16);
System.out.println("主版本号16进制表示:" + major_hex_str);
//major_unsigned_int32 转成无符号16进制
int major_unsigned_int32 = Integer.parseInt(major_hex_str, 16);
System.out.println("主版本号整数表示:" + major_unsigned_int32);
System.out.println("完整版本号:" + major_unsigned_int32 + "." + minor_unsigned_int32);
}
}
详细的看注释吧,有什么不明白的可留言!
运行如下:
* byte字节码与运算原值(-54)换行后(-54 & 0x0FF):202
------------ 校验魔数 ------------
class文件魔数16进制表示为:cafebabe
魔术转换为无符号表示(Java没有无符号数,所以转long来表示)magic_unsigned_int32:3405691582
0xCAFEBABE & 0x0FFFFFFFFL(这样也可以转换为正整数表示):3405691582
class字节码魔数无符号16进制数值一致校验通过
------------ 校验版本号 ------------
小版本号16进制表示:0
小版本号十进制整数表示:0
主版本号16进制表示:34
主版本号整数表示:52
完整版本号:52.0
Process finished with exit code 0
文章评论