GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

文件格式分析 --- 最小class

为什么要研究文件格式,就像我们在学习到一定程度会学习汇编,不是用汇编写代码,而是理解底层原理有助于我们理解程序的执行流程

class是java编译后的可执行的字节码文件。

解析class的工具有javap,010editor,idapro

下面是java

package com.example;

public class {
    public static void main(String[] args) {
        System.out.println("Hello World by gk");
    }
}

 编译后的二进制

 我们可以看出整个文件最大的部分是 常量池和方法

 常量池

常量池[0] - CONSTANT_Methodref
标记字节:0A(表示方法引用)
类索引:00 02(指向常量池#2,即Object类)
名称类型索引:00 03(指向常量池#3,即构造方法的名称和类型)
常量池[
1] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 04(指向常量池#4,即"java/lang/Object"字符串)
常量池[
2] - CONSTANT_NameAndType 标记字节:0C(表示名称和类型) 名称索引:00 05(指向常量池#5,即"") 描述符索引:00 06(指向常量池#6,即"()V"
常量池[
3] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 10(表示长度16) 字符串内容:"java/lang/Object"的UTF8编码
常量池[
4] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 06(表示长度6) 字符串内容:"<init>"的UTF8编码
常量池[
5] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 03(表示长度3) 字符串内容:"()V"的UTF8编码
常量池[
6] - CONSTANT_Fieldref 标记字节:09(表示字段引用) 类索引:00 07(指向常量池#7,即System类) 名称类型索引:00 08(指向常量池#8,即out字段的名称和类型)
常量池[
7] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 09(指向常量池#9,即"java/lang/System"字符串)
常量池[
8] - CONSTANT_NameAndType 标记字节:0C(表示名称和类型) 名称索引:00 0A(指向常量池#10,即"out") 描述符索引:00 0B(指向常量池#11,即"Ljava/io/PrintStream;"
常量池[
9] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 10(表示长度16) 字符串内容:"java/lang/System"的UTF8编码
常量池[
10] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 03(表示长度3) 字符串内容:"out"的UTF8编码
常量池[
11] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 15(表示长度21) 字符串内容:"Ljava/io/PrintStream;"的UTF8编码
常量池[
12] - CONSTANT_String 标记字节:08(表示字符串常量) 字符串索引:00 0D(指向常量池#13,即"Hello World"字符串)
常量池[
13] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0B(表示长度11) 字符串内容:"Hello World"的UTF8编码
常量池[
14] - CONSTANT_Methodref 标记字节:0A(表示方法引用) 类索引:00 0F(指向常量池#15,即PrintStream类) 名称类型索引:00 10(指向常量池#16,即println方法的名称和类型)
常量池[
15] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 11(指向常量池#17,即"java/io/PrintStream"字符串)
常量池[
16] - CONSTANT_NameAndType 标记字节:0C(表示名称和类型) 名称索引:00 12(指向常量池#18,即"println") 描述符索引:00 13(指向常量池#19,即"(Ljava/lang/String;)V"
常量池[
17] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 13(表示长度19) 字符串内容:"java/io/PrintStream"的UTF8编码
常量池[
18] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 07(表示长度7) 字符串内容:"println"的UTF8编码
常量池[
19] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 15(表示长度21) 字符串内容:"(Ljava/lang/String;)V"的UTF8编码
常量池[
20] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 15(指向常量池#21,即"com/example/HelloWorld"字符串)
常量池[
21] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 16(表示长度22) 字符串内容:"com/example/HelloWorld"的UTF8编码
常量池[
22] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"Code"的UTF8编码
常量池[
23] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0F(表示长度15) 字符串内容:"LineNumberTable"的UTF8编码
常量池[
24] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 12(表示长度18) 字符串内容:"LocalVariableTable"的UTF8编码
常量池[
25] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"this"的UTF8编码
常量池[
26] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 18(表示长度24) 字符串内容:"Lcom/example/HelloWorld;"的UTF8编码
常量池[
27] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"main"的UTF8编码
常量池[
28] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 16(表示长度22) 字符串内容:"([Ljava/lang/String;)V"的UTF8编码
常量池[
29] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"args"的UTF8编码
常量池[
30] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 13(表示长度19) 字符串内容:"[Ljava/lang/String;"的UTF8编码
常量池[
31] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0A(表示长度10) 字符串内容:"SourceFile"的UTF8编码
常量池[
32] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0F(表示长度15) 字符串内容:"HelloWorld.java"的UTF8编码

 

什么是属性,这里的class的属性类似与exe的节区的概念,所有内容都是由属性构成的。code属性表示代码区域对应exe的text区域

不同位置可能的属性:

  • 类属性:SourceFile, InnerClasses, Deprecated等
  • 字段属性:ConstantValue, Synthetic, Deprecated等
  • 方法属性:Code, Exceptions, Deprecated等

 

方法1

 

访问标志
值:0x0001    表示这是一个public的构造方法

方法名索引
值:5    指向常量池#5,内容:"<init>",这是构造方法的特殊名称

方法描述符索引
值:6    指向常量池#6 内容:"()V"
描述符解析:
():表示无参数
V:表示返回值是void

属性计数
值:1
表示这个构造方法有1个属性(Code属性)

Code属性详细信息: 
a. 属性名索引
值:23    指向常量池#23    内容:"Code"

b. 属性长度
值:47    表示Code属性的内容占47字节

c. 最大栈深度 
值:1    表示操作数栈最多需要1个空间

d. 局部变量表大小
值:1
表示需要1个局部变量空间(用于this引用)

e. 代码长度
值:5h
表示方法的字节码指令占5个字节

第一条指令:
字节码:2A (aload_0)
含义:将第0个局部变量(this引用)加载到操作数栈顶
作用:准备调用父类构造方法时需要的this引用

第二条指令:
字节码:B7 (invokespecial)
操作数:00 01(指向常量池#1)
含义:调用父类的构造方法
具体调用:java/lang/Object.()V

第三条指令:
字节码:B1 (return)
含义:从构造方法返回
作用:完成构造方法的执行

方法的字节码 子属性计数 :
类型:struct attribute_info
值:2
含义:表示有两个属性表项
作用:告诉JVM需要解析的属性数量

LineNumberTable属性 :
属性名索引
值:24
含义:指向常量池中的"LineNumberTable"字符串
作用:标识这是行号表属性

属性长度
值:6
含义:属性信息的长度(字节)
作用:确定属性数据的范围

表长度
类型:u2
值:1
含义:行号表中的条目数
作用:说明有多少行号映射关系

行号表
start_pc:
值:0
含义:字节码的偏移位置
作用:指示代码开始位置

line_number:
值:3
含义:源代码的行号
作用:对应源文件中的行号

LocalVariableTable属性:
属性名索引
值:25
含义:指向常量池中的"LocalVariableTable"字符串
作用:标识这是局部变量表属性

属性长度
值:12
含义:属性信息的长度(字节)
作用:确定属性数据的范围

表长度
值:1
含义:局部变量表中的条目数
作用:说明有多少局部变量

LocalVariableTable项:
start_pc:
值:0
含义:变量的作用域起始位置
作用:标记变量开始有效的位置

length:
值:5
含义:变量的作用域长度
作用:确定变量的有效范围

name_index:
值:26
含义:指向变量名的常量池索引
作用:获取变量的名称this

descriptor_index:
值:27
含义:指向变量类型描述符的常量池索引
作用:确定变量的类型Lcom/example/HelloWorld;

index:
值:0
含义:变量在局部变量表中的槽位
作用:定位变量的存储位置

 

方法2同上忽略 

 

 javap反汇编

C:\Users\21558\CascadeProjects\class_demo>javap -v "C:\Users\21558\Documents\WeChat Files\wxid_nwercn4twf1c22\FileStorage\File\2025-02\HelloWorld.class"
Classfile /C:/Users/21558/Documents/WeChat Files/wxid_nwercn4twf1c22/FileStorage/File/2025-02/HelloWorld.class
  Last modified 2025年1月19日; size 557 bytes
  SHA-256 checksum a4c83c79ac32ed56d145ff91391e36f338b37eb95ebab2a4d4192795afee16d0
  Compiled from "HelloWorld.java"
public class com.example.HelloWorld
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #21                         // com/example/HelloWorld
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Class              #10            // java/lang/System
   #9 = NameAndType        #11:#12        // out:Ljava/io/PrintStream;
  #10 = Utf8               java/lang/System
  #11 = Utf8               out
  #12 = Utf8               Ljava/io/PrintStream;
  #13 = String             #14            // Hello World
  #14 = Utf8               Hello World
  #15 = Methodref          #16.#17        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #16 = Class              #18            // java/io/PrintStream
  #17 = NameAndType        #19:#20        // println:(Ljava/lang/String;)V
  #18 = Utf8               java/io/PrintStream
  #19 = Utf8               println
  #20 = Utf8               (Ljava/lang/String;)V
  #21 = Class              #22            // com/example/HelloWorld
  #22 = Utf8               com/example/HelloWorld
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               LocalVariableTable
  #26 = Utf8               this
  #27 = Utf8               Lcom/example/HelloWorld;
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               SourceFile
  #33 = Utf8               HelloWorld.java
{
  public com.example.HelloWorld();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String Hello World
         5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

C:\Users\21558\CascadeProjects\class_demo>javap -p "C:\Users\21558\Documents\WeChat Files\wxid_nwercn4twf1c22\FileStorage\File\2025-02\HelloWorld.class"
Compiled from "HelloWorld.java"
public class com.example.HelloWorld {
  public com.example.HelloWorld();
  public static void main(java.lang.String[]);
}

 

 smali反汇编

.class public Lcom/example/HelloWorld;
.super Ljava/lang/Object;
.source "HelloWorld.java"


# direct methods
.method public constructor <init>()V
    .registers 1

    .line 3
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

.method public static main([Ljava/lang/String;)V
    .registers 3

    .line 5
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v1, "Hello World"

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 6
    return-void
.end method

 

 

用ida反汇编

; +-------------------------------------------------------------------------+
; |      这个文件由 IDA (交互式反汇编器) 生成                               |
; |           版权所有 (c) 2023 Hex-Rays, <support@hex-rays.com>            |
; +-------------------------------------------------------------------------+

; 文件校验信息
; Input SHA256 : A4C83C79AC32ED56D145FF91391E36F338B37EB95EBAB2A4D4192795AFEE16D0
; Input MD5    : FBC27DC5D80422D99FACC4CA045323F6
; Input CRC32  : 0E1D6D53

; 文件信息
; 文件名称   : C:\Users\21558\Pictures\jar2\HelloWorld.class
; 文件格式   : JavaVM Class File (JDK 1.8版本)

; 字节码版本:Java 8 (52.0)
.bytecode 52.0
.source "HelloWorld.java"

; 导入的类和方法原型声明
;.import java.lang.Object                      ; 导入Object类,HelloWorld类的父类
;  java.lang.Object.<init>()void               ; 导入Object类的构造方法
;.import java.lang.System                      ; 导入System类
;  java.lang.System.out java.io.PrintStream    ; 导入System.out静态字段
;.import java.io.PrintStream                   ; 导入PrintStream类
;  java.io.PrintStream.println(java.lang.String)void  ; 导入println方法

; 类声明部分
.class public com.example.HelloWorld           ; 声明公共类 HelloWorld
.super java.lang.Object                        ; 声明父类为 Object

; 构造方法
.method public <init>                          ; 默认构造函数
  .limit stack 1                               ; 操作数栈最大深度为1
  .limit locals 1                              ; 局部变量表大小为1
met001_begin:                                  
  .line 3
    aload_0                                    ; 加载this引用到操作数栈
    invokespecial java.lang.Object.<init>()void  ; 调用父类Object的构造方法
    return                                     ; 返回
met001_end:



;met001_slot000 ; DATA XREF: <init>↑r
.var 0 is this com.example.HelloWorld from met001_begin to met001_end
.end method


  1. met001_slot000 是局部变量表中的第一个槽位(slot)的标签名称
  2. .var 0 表示这是局部变量表中索引为0的变量
  3. is this 表明这个变量是当前类的实例引用(this引用)
  4. com.example.HelloWorld 指定了变量的类型
  5. from met001_begin to met001_end 定义了这个变量的作用域范围,从方法开始到方法结束
 
  • 每个方法都有自己的局部变量表
  • 局部变量表中的每个位置称为一个槽位(slot)
  • 对于实例方法,slot0 永远保存this引用
  • 方法的参数从slot1开始存放
  • 方法内声明的局部变量从参数之后的slot开始存放


; main方法 .method
public static main ; 主方法声明 .limit stack 2 ; 操作数栈最大深度为2 .limit locals 1 ; 局部变量表大小为1 met002_begin: .line 5 getstatic java.lang.System.out java.io.PrintStream ; 获取System.out静态字段 ldc "Hello World" ; 将字符串常量"Hello World"压入栈 invokevirtual java.io.PrintStream.println(java.lang.String)void ; 调用println方法 .line 6 return ; 返回 met002_end: .end method

 

posted on 2025-01-19 15:20  GKLBB  阅读(17)  评论(0)    收藏  举报