最近由于公司项目需要,了解了很多关于类加载方面的知识,给项目带来了一些热部署方面的突破。 由于最近手头工作不太忙,同时驱于对更底层知识的好奇与渴求,因此决定学习了一下 class 文件结构,并通过一周的不懈努力,已经掌握了class 的文件结构,并用 java 实现了一个简单的反编译器:读取 class 文件,反编译成纯 java 代码。下面来看一下具体的实现思路和代码分析。

  1. class 文件是一种平台无关性的二进制文件,通过 IO 流可以读取成byte[],将字节数组转换为十六进制(字符串)之后,class 的数据结构便一目了然了,对 class 文件的解析即变成了对整个十六进制串的分割、解析。

  2. 那么如何分割呢?事实上,class 的文件采用一种“伪结构体”的形式来存储数据,这种“伪结构体”只有两种数据类型:无符号数和表(表中的数据也都是无符号数)。 表的概念我们都知道,那什么是无符号数呢?我们都知道,在计算机中最基本数据单位是字节,1字节(byte)= 8位(bit),也就是8个长度的二进制,而4个长度的二进制可以代表1个长度的十六进制,因此,两个十六进制代表一个字节,用无符号数标识即 :

      • u1代表一个字节,代表2长度的十六进制(如0x01);
      • u2代表两个字节,代表4长度的十六进制(如0x0001);
      • u4代表4个字节,代表8长度的十六进制(如0x00000001)

  3. 整个 class 文件就是一张表,表中的字段有:魔数、虚拟机的次版本、主版本、常量池的大小、常量池、访问标识、当前类、父类、实现的接口数量、接口集合、字段表数量、字段表集合、方法表数量、方法表集合、属性表数量、属性表集合。   其中,魔数、主次版本、常量池大小、访问标识、当前类、父类、表集合数量等都是无符号数。     常量池、字段表集合、方法表集合、属性表集合等都是表结构,有的表结构中的字段又嵌套了其他的表结构。  具体的无符号数大小和表结构在此不进行展开赘述,用一句话来说:class 文件的数据结构是一种表结构的嵌套。

  4. 上边对 class 文件的数据结构进行了简略的介绍,现在我们开始讨论如何解析并存储 class 文件。 我们可以按照class 文件中的各种表结构,建立相应的 Bean,例如 对于整个 class 文件,即class_info,我们可以建立如下的 bean:

public class Class_info {
private String magic; //魔数
private String minor_version; //虚拟机次版本
private String major_version; //虚拟机主版本
private int cp_count; //常量池大小
private Map<Integer, Constant_X_info> constant_pool_Map; //常量池
private String access_flag; //访问标识
private int this_class_index; //当前类索引
private int super_class_index; //父类索引
private int interfaces_count; //接口数量
private List<Integer> interfacesList; //接口集合
private int fields_count; //字段表数量
private List<Fields_info> fields_info_List; //字段表集合
private int Methods_count; //方法表数量
private List<Methods_info> methods_info_List; //方法表集合
private int attributes_count; //属性表数量
private List<Attribute_info> attributes; //属性表集合 public String getMagic() {
return magic;
}
public void setMagic(String magic) {
this.magic = magic;
}
  
  ..... 省略其他 get set
}

  将所有的表结构都搭建好后,我们可以开始对 class 文件读取到的 十六进制字符串进行切割,将切割到的数据填充到我们的 bean 中。在此,提供一种切割字符串的思路:创建一个静态指针,指向切割字符串的 start 位置,每次切割length 长度后,对指针进行初始化,即 start = start + length。如果要进行切割数据,那么只需要调用 cutString(int len) 就可以了。 代码如下:

   private static int start_pointer = 0;
private static String hexString = ""; // 十六进制串 private static String cutString(int len) {
String cutStr = hexString.substring(start_pointer, start_pointer + len);
// 初始化指针
start_pointer = start_pointer + len;
return cutStr;
}

  

  5. 请注意,上述虽然说起来简单,然而切割数据不可以弄错任何一个字节的长度,如果弄错任何一个字节的长度,那后边的数据完全是错位的,必须推倒重来! 经过一系列努力后,终于把所有的数据都进行切割并填充到了 bean 中,下面就是利用数据,拼装 java 源代码了。这一部分最重要的无非是方法体的拼装,在编译的过程中,编译器已经将 方法体中的java 语句编译成了字节码指令,完全是内存的堆栈操作,跟我们之前的 java 代码比完全变了形式和语法。那么,如何根据字节码指令,推导出java源代码呢?总结所有的 java 语法,无非是:

    • new 对象
    • 方法调用(静态方法、构造方法、成员方法、接口方法)
    • 参数传递
    • 计算、判断、赋值
    • 其他的语句(if for while try等)

  我们需要对阅读字节码指令相当熟练,需要达到1.看着 java 代码,推敲出编译后的字节码指令 2.看着字节码,反推敲出 java 代码。  在此基础上,进行大量的规律总结,这也是反编译最难、最核心的地方了。由于内容比较复杂,在此不进行赘述,可以查看笔者项目的源码。

  6. 笔者实现的简易反编译器已经开源到 github: https://github.com/MalcolmFF/Decompiler ,其中最重要的两个类为:com.xuanjie.app.App.java(main 方法所在类,解析 class 文件将数据存储到 bean 中) 和 com.xuanjie.core.SrcCreator.java(用于 java 源代码的拼装)。

     欢迎读者进行赏阅,提出建议一起维护完善。

class 文件反编译器的 java 实现的更多相关文章

  1. .Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) 通过MVC控制器导出导入Excel文件(可用于java SSH架构)

    .Net MVC  导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) [原文地址] 通过MVC控制器导出导入Excel文件(可用于java SSH架构)   public cl ...

  2. HTML文件中使用Java程序

    HTML文件中使用Java程序:简而言之,在HTML文件中引入java应用程序,并通过javascript调用其方法.   一. 运行环境 1.JAVA_HOME.CLASSPATH.PATH配置正确 ...

  3. 将WSDL文件生成的Java文件

  4. 由于jsp include 很多文件后导致java类大小超过65535 bytes 的解决方法(转载)

    昨天,我遇到了一個讓我很頭疼的問題. 我做了一個共通的jsp,單只測它是ok的,可是,放在別的jsp中include它,就會報錯如標題所示:The code of method _jspService ...

  5. Windows文件路径转换为java中可识别的文件路径的转义方法,(另附转义多种格式)

    ps:欢迎加qq好友:2318645572,交流学习 一:路径转化 Windows中的文件路径格式为 D:\eclipse\apache-tomcat-7.0.67\wtpwebapps\... Ja ...

  6. 使用maven根据JSON文件自动生成Java POJO类(Java Bean)源文件

    根据JSON文件自动生成Java POJO类(Java Bean)源文件 本文介绍使用程序jsonschema2pojo来自动生成Java的POJO类源文件,本文主要使用maven,其他构建工具请参考 ...

  7. XML概念定义以及如何定义xml文件编写约束条件java解析xml DTD XML Schema JAXP java xml解析 dom4j 解析 xpath dom sax

    本文主要涉及:xml概念描述,xml的约束文件,dtd,xsd文件的定义使用,如何在xml中引用xsd文件,如何使用java解析xml,解析xml方式dom sax,dom4j解析xml文件 XML来 ...

  8. java编译需要文件后缀名.java 而运行不需要后缀名.class

    对于java源文件HelloWorld.java编译命令:javac HelloWorld.java运行命令:java HelloWorld 编译需要文件后缀名.java 而运行不需要后缀名.clas ...

  9. Protocol Buffer使用转换工具将proto文件转换成Java文件流程及使用

    Client与Server的网络通信协议传输使用google protobuf,服务器端使用的是Java 一. Protocol Buffersprotobuf全称Google Protocol Bu ...

随机推荐

  1. C++11新语法糖之尾置返回类型

    C++11的尾置返回类型初衷是为了方便复杂函数的声明和定义,但是当复杂度稍微提升一些的时候很明显能注意到这种设计的作用微乎其微. 首先考虑如下代码: C++ //返回指向数组的指针 auto func ...

  2. 转:一篇讲线上优化查 CPU的脚本

    原文链接:https://my.oschina.net/leejun2005/blog/1524687   摘要: 本文主要针对 Java 服务而言 0.背景 经常做后端服务开发的同学,或多或少都遇到 ...

  3. 507. Perfect Number

    We define the Perfect Number is a positive integer that is equal to the sum of all its positive divi ...

  4. IDA分析脱壳后丢失导入表的PE

    1. 问题 一些程序经过脱壳后(如用OD的dump插件),一些导入表信息丢失了,导致拖入IDA后看不到API的信息(如右图所示,第一个红圈处实际是GetCurrentProcessId),给分析造成极 ...

  5. Docker -- 安全/部分命令/Daemon

    Docker -- 终极指南 1.安装过程 -- Docker -- docker pull 镜像 -- docker images 列出镜像    -- docker run --rm -ti ub ...

  6. jmeter远程分布执行遇到的网卡坑(A Test is currently running,stop or ....)

    周末加班做一个项目app的性能测试,单机负载时由于公司给每个人的网络带宽上传下载流量就1M,300个用户并发就已经网络IO饱和了,虽然和相关部门协调过资源问题,但是收效甚微,因此打算先用分布部署压力机 ...

  7. eval基础,基础用法及解析json

    <body> <!-- eval 的使用:eval(string) 计算某个字符串,并执行其中的js代码 字符串上运用 eval() eval("x = 10;y = 2; ...

  8. Eclipse项目分组管理

    对于eclipse相信对于一个java开发人员,一定不陌生.eclipse可以通过工作空间(Workspace)将不同的项目进行分开管理,相信这一点大家一定很熟悉,用过idea的小伙伴,一定发现了,i ...

  9. android inline hook

    最近终于沉下心来对着书把hook跟注入方面的代码敲了一遍,打算写几个博客把它们记录下来. 第一次介绍一下我感觉难度最大的inline hook,实现代码参考了腾讯GAD的游戏安全入门. inline ...

  10. JavaScript 计算指定月份有多少天

    用 js 画工作日历的时候,需要用 js 计算指定月份一共有多少天 在网上找了些方法,都比较繁琐,后来灵机一动,想到一个偷懒的办法,分享一下 一.原理分析 要想得到某月有多少天,只需要获取到当月最后一 ...