一把小刀,直插 class 文件的小心脏

大家好,我是二哥呀,假期结束了,学起来吧!

今天我拿了一把小刀,准备解剖一下 Java 的 class 文件。

CS 的世界里流行着这么一句话,“计算机科学领域的任何问题都可以通过增加一个中间层来解决”。对于 Java 来说,JVM 就是这么一个产物,“Write once, Run anywhere”之所以能实现,靠得就是 JVM,它能在不同的操作系统下运行同一份源代码编译后的 class 文件。

Java 是跨平台的,JVM 作为中间层,自然要针对不同的操作系统提供不同的实现。拿 JDK 11 来说,它的实现就有上图中提到的这么多种。

通过不同操作系统的 JVM,我们的源代码就可以不用根据不同的操作系统编译成不同的二进制可执行文件了,跨平台的目标也就实现了。那这个 class 文件到底是什么玩意呢?它是怎么被 JVM 识别的呢?

我们用 IDEA 编写一段简单的 Java 代码,文件名为 Hello.java。

package com.itwanger.jvm;
class Hello {
    public static void main(String[] args) {
        System.out.println("Hello!");
    }
}

点击编译按钮后,IDEA 会帮我们自动生成一个名为 Hello.class 的文件,在 target/classes 的对应包目录下。直接双击打开后长下面这样子:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.itwanger.jvm;

class Hello {
    Hello() {
    }

    public static void main(String[] args) {
        System.out.println("Hello!");
    }
}

看起来和源代码很像,只是多了一个空的构造方法,对吧?它是 class 文件被 IDEA 自带的反编译工具 Fernflower 反编译后的样子。那真实的 class 文件长什么样子呢?

可以在 terminal 面板下用 xxd Hello.class 命令来查看。

咦?完全看不懂的样子呢。它是 class 文件的一种十六进制形式,xxd 这个命令的神奇之处就是它能将一个给定文件转换成十六进制形式。

01、魔数

第一行中有一串特殊的字符 cafebabe,它就是一个魔数,是 JVM 识别 class 文件的标志,JVM 会在验证阶段检查 class 文件是否以该魔数开头,如果不是则会抛出 ClassFormatError

魔数 cafebabe 的中文意思显而易见,咖啡宝贝,再加上 Java 的图标本来就是一个热气腾腾的咖啡,可见 Java 与咖啡的渊源有多深。

02、版本号

紧跟着魔数后面的四个字节 0000 0037 分别表示副版本号和主版本号。也就是说,主版本号为 55(0x37 的十进制),也就是 Java 11 对应的版本号,副版本号为 0。

上一个 LTS 版本是 Java 8,对应的主版本号为 52,也就是说 Java 9 是 53,Java 10 是 54,只不过 Java 9 和 Java 10 都是过渡版本,下一个 LTS 版本是 Java 17,预计 2021 年 9 月份推出。

03、常量池

紧跟在版本号之后的是常量池,字符串常量和较大的证书都会存储在常量池中,当使用这些数值时,会根据常量池中的索引来查找。

Java 定义了 boolean、byte、short、char 和 int 等基本数据类型,它们在常量池中都会被当做 int 来处理。我们来通过一段简单的 Java 代码了解下。

public class ConstantTest {
    public final boolean bool = true;
    public final char aChar = 'a';
    public final byte b = 66;
    public final short s = 67;
    public final int i = 68;
}

布尔值 true 的十六进制是 0x01、字符 a 的十六进制是 0x61,字节 66 的十六进制是 0x42,短整型 67 的十六进制是 0x43,整形 68 的十六进制是 0x44。所以编译生成的整形常量在 class 文件中的位置如下图所示。

第一个字节 0x03 表示常量的类型为 CONSTANT_Integer_info,是 JVM 中定义的 14 种常量类型之一,对应的还有 CONSTANT_Float_infoCONSTANT_Long_infoCONSTANT_Double_info,对应的标识分别是 0x04、0x05、0x06。

对于 int 和 float 来说,它们占 4 个字节;对于 long 和 double 来说,它们占 8 个字节。来个 long 型的最大值观察下。

public class ConstantTest {
    public final long ong = Long.MAX_VALUE;
}

来看一下它在 class 文件中的位置。05 开头,7f ff ff ff ff ff ff ff 结尾,果然占 8 个字节,以前知道 long 型会占 8 个字节,但没有直观的感受,现在有了。

接下来,我们再来看一段代码。

class Hello {
    public final String s = "hello";
}

“hello”是一个字符串,它的十六进制为 68 65 6c 6c 6f,我们来看一下它在 class 文件中的位置。

前面还有 3 个字节,第一个字节 0x01 是标识,标识类型为 CONSTANT_Uft8_info,第二个和第三个自己 0x00 0x05 用来表示第三部分字节数组的长度。

CONSTANT_Uft8_info 类型对应的,还有一个 CONSTANT_String_info,用来表示字符串对象(之前代码中的 s),标识是 0x08。前者存储了字符串真正的值,后者并不包含字符串的内容,仅仅包含了一个指向常量池中 CONSTANT_Uft8_info 的索引。来看一下它在 class 文件中的位置。

CONSTANT_String_info 通过索引 19 来找到 CONSTANT_Uft8_info

除此之外,还有 CONSTANT_Class_info,用来表示类和接口,结构和 CONSTANT_String_info 类似,第一个字节是标识,值为 0x07,后面两个字节是常量池索引,指向 CONSTANT_Utf8_info——字符串存储的是类或者接口的全路径限定名。

拿 Hello.java 类来说,它的全路径限定名为 com/itwanger/jvm/Hello,对应的十六进制为“636f6d2f697477616e6765722f6a766d2f48656c6c6f”,是一串 CONSTANT_Uft8_info,指向它的 CONSTANT_Class_info 在 class 文件中的什么位置呢?

先不着急,这里给大家介绍一款可视化字节码的工具 jclasslib bytecode viewer,可以直接在 IDEA 的插件市场安装。安装完成后,选中 class 文件,然后在 View 菜单里找到 Show Bytecode With Jclasslib 子菜单,就可以查看 class 文件的关键信息了。

从上图中可以看到,常量池的总大小为 23,索引为 04 的 CONSTANT_Class_info 指向的是是索引为 21 的 CONSTANT_Uft8_info,值为 com/itwanger/jvm/Hello。21 的十六进制为 0x15,有了这个信息,我们就可以找到 CONSTANT_Class_info 在 class 文件中的位置了。

0x07 是第一个字节,CONSTANT_Class_info 的标识符,然后是两个字节,标识索引。

还有 CONSTANT_NameAndType_info,用来标识字段或方法,标识符为 12,对应的十六进制是 0x0c。后面还有 4 个字节,前两个是字段或者方法的索引,后两个是字段或方法的描述符,也就是字段或者方法的类型。

来看下面这段代码。

class Hello {
    public void testMethod(int id, String name) {
    }
}

用 jclasslib 可以看到 CONSTANT_NameAndType_info 包含的索引有两个。

一个是 4,一个是 5,可以通过下图来表示 CONSTANT_NameAndType_info 的构成。

对应 class 文件中的位置如下图所示。

接下来是 CONSTANT_Fieldref_infoCONSTANT_Methodref_infoCONSTANT_InterfaceMethodref_info,它们三个的结构比较类似,可以通过下面的伪代码来表示。

CONSTANT_*ref_info {
  u1 tag;
  u2 class_index;
  u2 name_and_type_index;
}

学过 C 语言的符号表(Symbol Table)的话,对这段伪代码并不会陌生。

  • tag 为标识符,Fieldref 的为 9,也就是十六进制的 0x09;Methodref 的为 10,也就是十六进制的 0x0a;InterfaceMethodref 的为 11, 也就是十六进制的 0x0b。
  • class_index 为 CONSTANT_Class_info 的常量池索引,表示字段 | 方法 | 接口方法所在的类信息。
  • name_and_type_index 为 CONSTANT_NameAndType_info 的常量池索引,拿 Fieldref 来说,表示字段名和字段类型;拿 Methodref 来说,表示方法名、方法的参数和返回值类型;拿 InterfaceMethodref 来说,表示接口方法名、接口方法的参数和返回值类型。

还有 CONSTANT_MethodHandle_infoCONSTANT_MethodType_infoCONSTANT_InvokeDynamic_info,我就不再一一说明了,大家也可以拿把小刀去试一试。

啊,class 文件中最复杂的常量池部分就算是解剖完了,真不容易!

04、访问标记

紧跟着常量池之后的区域就是访问标记(Access flags),这个标记用于识别类或接口的访问信息,比如说到底是 class 还是 interface?是 public 吗?是 abstract 抽象类吗?是 final 类吗?等等。总共有 16 个标记位可供使用,但常用的只有其中 7 个。

来看一个简单的枚举代码。

public enum Color {
    RED,GREEN,BLUE;
}

通过 jclasslib 可以看到访问标记的信息有 0x4031 [public final enum]

对应 class 文件中的位置如下图所示。

05、this_class、super_class、interfaces

这三部分用来确定类的继承关系,this_class 为当前类的索引,super_class 为父类的索引,interfaces 为接口。

来看下面这段简单的代码,没有接口,默认继承 Object 类。

class Hello {
    public static void main(String[] args) {
        
    }
}

通过 jclasslib 可以看到类的继承关系。

  • this_class 指向常量池中索引为 2 的 CONSTANT_Class_info
  • super_class 指向常量池中索引为 3 的 CONSTANT_Class_info
  • 由于没有接口,所以 interfaces 的信息为空。

对应 class 文件中的位置如下图所示。

06、字段表

一个类中定义的字段会被存储在字段表(fields)中,包括静态的和非静态的。

来看这样一段代码。

public class FieldsTest {
    private String name;
}

字段只有一个,修饰符为 private,类型为 String,字段名为 name。可以用下面的伪代码来表示 field 的结构。

field_info {
  u2 access_flag;
  u2 name_index;
  u2 description_index;
}
  • access_flag 为字段的访问标记,比如说是不是 public | private | protected,是不是 static,是不是 final 等。
  • name_index 为字段名的索引,指向常量池中的 CONSTANT_Utf8_info, 比如说上例中的值就为 name。
  • description_index 为字段的描述类型索引,也指向常量池中的 CONSTANT_Utf8_info,针对不同的数据类型,会有不同规则的描述信息。

1)对于基本数据类型来说,使用一个字符来表示,比如说 I 对应的是 int,B 对应的是 byte。

2)对于引用数据类型来说,使用 L***; 的方式来表示,L 开头,; 结束,比如字符串类型为 Ljava/lang/String;

3)对于数组来说,会用一个前置的 [ 来表示,比如说字符串数组为 [Ljava/lang/String;

对应到 class 文件中的位置如下图所示。

07、方法表

方法表和字段表类似,区别是用来存储方法的信息,包括方法名,方法的参数,方法的签名。

就拿 main 方法来说吧。

public class MethodsTest {
    public static void main(String[] args) {
        
    }
}

先用 jclasslib 看一下大概的信息。

  • 访问标记是 public static 的。
  • 方法名为 main。
  • 方法的参数为字符串数组;返回类型为 Void。

对应到 class 文件中的位置如下图所示。

08、属性表

属性表是 class 文件中的最后一部分,通常出现在字段和方法中。

来看这样一段代码。

public class AttributeTest {
    public static final int DEFAULT_SIZE = 128;
}

只有一个常量 DEFAULT_SIZE,它属于字段中的一种,就是加了 final 的静态变量。先通过 jclasslib 看一下它当中一个很重要的属性——ConstantValue,用来表示静态变量的初始值。

  • Attribute name index 指向常量池中值为“ConstantValue”的常量。
  • Attribute length 的值为固定的 2,因为索引只占两个字节的大小。
  • Constant value index 指向常量池中具体的常量,如果常量类型为 int,指向的就是 CONSTANT_Integer_info

我画了一副图,可以完整的表示字段的结构,包含属性表在内。

对应到 class 文件中的位置如下图所示。

来看下面这段代码。

public class MethodCode {
    public static void main(String[] args) {
        foo();
    }

    private static void foo() {
    }
}

main 方法中调用了 foo 方法。通过 jclasslib 看一下它当中一个很重要的属性——Code, 方法的关键信息都存储在里面。

  • Attribute name index 指向常量池中值为“Code”的常量。
  • Attribute length 为属性值的长度大小。
  • bytecode 存储真正的字节码指令。
  • exception table 表示方法内部的异常信息。
  • maximum stack size 表示操作数栈的最大深度,方法执行的任意期间操作数栈深度都不会超过这个值。
  • maximum local variable 表示临时变量表的大小,注意,并不等于方法中所有临时变量的数量之和,当一个作用域结束,内部的临时变量占用的位置就会被替换掉。
  • code length 表示字节码指令的长度。

对应 class 文件中的位置如下图所示。

到此为止,class 文件的内部算是剖析得差不多了,希望能对大家有所帮助。第一次拿刀,手有点颤,如果哪里有不足的地方,欢迎大家在评论区毫不留情地指出来!

文末给大家推荐一份 GitHub 上星标 115k+ 的 Java 学习教程吧,我亲自整理的,包括 Java 基础、Java 容器、Java 并发、Java 虚拟机和 Java IO,可以说非常全面。


我是沉默王二,Java 领域的优质创作者,CSDN 粉丝已经突破了 20 万+,应该说很强了,希望我整理的这份 Java 学习资料能帮助到大家。

太赞了,GitHub 上标星 115k+ 的 Java 教程!

这篇内容超级硬核,对 class 文件解剖了很长时间,累坏了,记得帮我点赞鼓励下啦~

相关推荐
小刀娱乐网源是asp+access/mssql架构网站系统,电脑版,手机版,平板版无缝切换,一个后台同步管理,整站生成静态利于搜索收录,dreamweaver打开可视化修改。 专为制作“小刀娱乐网、QQ教程、易语言教程网、LOL教程、QQ业务、代分享、教程发布”等等图片文字类型的网站而打造。 程序前台有首页、列表页、内容页、会员登录、会员注册、会员个人中心、会员积分体系、会员投稿、投稿编辑、会员签到、在线留言、文章评论、整站搜索等功能。 后台具备,批量数据采集、服务器信息、修改管理员个人资料、安全退出、一键安装模板、一键安装插件、更新缓存、站点设置、上传logo、上传背景图、管理员管理、程序一键升级、动态模式,静态模式,伪静态模式数据库管理、广告管理、友情链接管理、后台操作日志、栏目管理、新增文章、文章列表、评论管理、留言管理、添加会员、会员管理等功能。 主要功能: 程序在完全自主知识产权,享有计算机软件著作权证书的天人文章管理系统的框架下开发,在天人文章管理系统的功能上新增了如下: 采集功能专为采集xx娱乐网、xxQQ技术网而进行优化,可采集到这些网站的图片、网盘链接等。 前台电脑版整站显示手机版二维,在顶部“手机版”字样,鼠标经过即显示二维,扫描即可访问手机版。 列表页分为单列宽屏与双列2种模式,可在后台--栏目管理--宽屏,中切换 内容页分为单列宽屏与双列2种模式,可在后台--文章列表--页面底部--宽屏,中切换。或到后台--文章编辑页面--页面底部--宽屏,中切换 前台版面按照行业靠前的“小刀娱乐网”进行制作,方便您的访客更好的熟悉网站。 前台所有页面代经过优化,加载速度更快,页面体积更小 前台页面没有iframe、frame等影响搜索引擎抓取与收录的代,对搜索引擎十分友好 后台应用中心可安装,模板、扫打赏插件、手机版与电脑版智能管理插件、屏蔽复制与鼠标右键插件、老y文章系统数据迁移至天人工具、OK3W文章系统数据迁移至天人工具、用户注册后自动登录插件、悬浮贴边客服插件、会员前台全功能编辑器插件、广告可视化管理插件、前台底部自定义内容插件、畅言、友言、多说万能评论插件、电脑版整站背景图插件、万能伪静态规则生成插件等等 可自动根据超链接生成下载按钮,不需要手动填写网盘名称,会自动识别。(方法:在后台文章编辑页面点击右上角第2行向左数第3个小锁链按钮插入链接,或直接在HTML代模式插入链接代即可,会根据具体的网盘自动生成网盘名称) 可在官方数据采集平台批量采集每日更新的海量内容 小提示: 1、修改程序源代前请查看压缩包中开发说明 2、官网有关于本程序的使用教程及操作技巧 后台登录地址:http://你的网址/admin 登录账号:admin 登录密:admin 小刀娱乐网源(带手机版) 更新日志: 3.61更新: 优化自动生成按钮功能,非网盘链接不再生成按钮,只生成网盘链接的按钮(速度网络www.sudujun.com)
小刀娱乐网源是php+mysql架构网站系统,电脑版,手机版,平板版无缝切换,一个后台同步管理,整站生成静态利于搜索收录,dreamweaver打开可视化修改。 专为制作“小刀娱乐网、QQ教程、易语言教程网、LOL教程、QQ业务、代分享、教程发布”等等图片文字类型的网站而打造。 程序前台有首页、列表页、内容页、会员登录、会员注册、会员个人中心、会员积分体系、会员投稿、投稿编辑、会员签到、在线留言、文章评论、整站搜索等功能。 后台具备,批量数据采集、服务器信息、修改管理员个人资料、安全退出、一键安装模板、一键安装插件、更新缓存、站点设置、上传logo、上传背景图、管理员管理、程序一键升级、动态模式,静态模式,伪静态模式数据库管理、广告管理、友情链接管理、后台操作日志、栏目管理、新增文章、文章列表、评论管理、留言管理、添加会员、会员管理等功能。 主要功能: 程序在完全自主知识产权,享有计算机软件著作权证书的天人文章管理系统的框架下开发,在天人文章管理系统的功能上新增了如下: 采集功能专为采集xx娱乐网、xxQQ技术网而进行优化,可采集到这些网站的图片、网盘链接等。 前台电脑版整站显示手机版二维,在顶部“手机版”字样,鼠标经过即显示二维 ,扫描即可访问手机版。 列表页分为单列宽屏与双列2种模式,可在后台--栏目管理--宽屏,中切换 内容页分为单列宽屏与双列2种模式,可在后台--文章列表--页面底部--宽屏,中切换。或到后台--文章编辑页面--页面底部--宽屏,中切换 前台版面按照行业靠前的“小刀娱乐网”进行制作,方便您的访客更好的熟悉网站。 前台所有页面代经过优化,加载速度更快,页面体积更小 前台页面没有iframe、frame等影响搜索引擎抓取与收录的代,对搜索引擎十分友好 可自动根据超链接生成下载按钮,不需要手动填写网盘名称,会自动识别。(方法:在后台文章编辑页面点击右上角第2行向左数第3个小锁链按钮插入链接,或直接在HTML代模式插入链接代即可,会根据具体的网盘自动生成网盘名称) 可在官方数据采集平台批量采集每日更新的海量内容 本源为dede程序 安装网站请运行/install,根据提示操作即可
大家在用代理猎手软件搜索代理服务器的时候,大多数搜到的是“要密“。free得很少,甚至没有!而且最苦恼的是好不容易搜到的free的代理,没过几天就挂掉了。如果破了加密的代理,那可是一劳永逸的事情了,要知道代理的密是不会轻易更换的!!这次我就写一下我破解代理时选择目标代理的经验! 第一步:确定一个好大学的ip地址范围 不同专业的朋友可能感兴趣的大学不同,我是学医的,在学习过程中感觉耶鲁大学的代理最好,其数据库很全面,几乎能下载所有的电子期刊全文。我就已耶鲁大学为例吧。从ip地址列表中找到耶鲁大学的ip地址段 130.132.1.1------130.132.255.254 第二步:搜索加密的代理 这一步比较简单,用代理猎手搜索这个IP段的代理,搜索端口设定为8000,8080,8888,3128,不要设定80。我曾经用小刀破过一些端口为80的代理,可是用不起来,具体原因不太清楚,可能是80端口与web服务器端口相同的原因吧!(例如:我以前破解的一个代理130.127.9.28:80 用户名和密都是today, 可是用不起来)所以我认为80端口不用搜了,节省时间嘛。搜完以后,精简结果只要“要密”的代理(当然free的可要先记下来),导出结果,保存成文件如:yalepo.txt 第三步:选择可以破解的代理 用ad的proxy analyzer来分析我们的代理列表yalepo.txt,观察accuracy一栏中的结果,其中有很多不同的提示:如forbiden,not found,time out ,unauthourized等等。我们想看到的是最后一类的,含有unauthourized字符的代理,这样的代理破解后才能用,否则破解了也不能使用(例如:211.65.120.18:8080 这个代理,用户名和密都是test,用小刀可以破解,大家不妨试一试。但是我们无法使用) 第四步:破解 按照小刀的说明进行破解。将目标代理复制到小刀中的目标代理中去,再导入用户名列表,密列表不导入的时候默认用户名和密相同。并行数目根据自己的网速和机器情况调整,然后开始扫描------ 有了以上的东西大家可以开始破解了,一定要有耐心,我也有经连续破解了4整天没有结果的情况!对破解代理来说:运气+耐心=成功。 用法: 1.用户列表:用户名列表文件导入 2.密列表:口令列表文件导入,可不选,若无密文件,就以与用户名相同的密来穷举,这一般就可以了,或用123456、12345、1999、8888等常用的密试试就可以了。 3.目标代理:欲检验的代理服务器,若端口不为80,请添入端口号,如proxy.net.cn:8080或1.1.1.1:8080 4.并行数目:多线程处理,此为最大线程数,越大越快,但设置太大有可能会停止响应哦,我的机器我开到64,若好机只管开,只要不死机:-) 5.验证条件:必须添入 "http://"字样 其他:本程序可能会出现误报成功,是因为代理服务器的Cache所至,但不会漏过合适的用户名与口令。 性能:有网友问及速度,我没有同类软件相类比,不过我想如都是多线程,速度应该是差不多的,在本人MMX 166、24 M、2.1G、33.6 MODEM,线程开到64,速度大概每分钟1000个验证。至于是否能够成功通过,这需要运气。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值