Use ptrace to instrument java bytecode
字数 1234 2025-08-04 00:38:02

使用ptrace插桩Java字节码技术详解

1. 前言

Java原生提供了两种官方接口用于插桩、调试和控制程序执行:

1.1 Java Instrumentation API

Java Agent是Java SE 5引入的机制,用于在JVM启动时或运行时加载代理:

  • 启动时加载:实现premain方法

    public static void premain(String agentArgs, Instrumentation inst);
    public static void premain(String agentArgs);
    
  • 运行时加载:实现agentmain方法

    public static void agentmain(String agentArgs, Instrumentation inst);
    public static void agentmain(String agentArgs);
    

Agent需要打包成jar,并在Manifest中指定:

Premain-Class: class
Agent-Class: class

加载Agent的代码示例:

VirtualMachine virtualMachine = VirtualMachine.attach(targetVM);
virtualMachine.loadAgent("agent.jar", "params");

1.2 Instrumentation接口功能

public interface Instrumentation {
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
    void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException;
    long getObjectSize(Object objectToSize);
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);
    Class[] getAllLoadedClasses();
}

使用javassist修改方法的示例:

public static void agentmain(String args, Instrumentation inst) throws Exception {
    ClassPool classPool = ClassPool.getDefault();
    CtClass ctClass = classPool.get(clazz.getName());
    CtMethod method = ctClass.getDeclaredMethod("methodName");
    method.insertBefore("{ System.out.println(\"Wheeeeee!\"); }");
    byte[] bytecode = ctClass.toBytecode();
    ClassDefinition definition = new ClassDefinition(Class.forName(clazz.getName()), bytecode);
    inst.redefineClasses(definition);
}

1.3 JVMTI

JVMTI是更底层的C/C++接口,Java Instrumentation API基于此实现。开发JVMTI agent需要:

  • 包含jvmti.h头文件
  • 实现必要的函数:
    JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *options, void *reserved);
    JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm);
    

2. JNI基础

JNI(Java Native Interface)允许Java代码调用本地代码:

JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj) {
    /* 实现本地方法 */
}

JNIEnv指针提供了与JVM交互的所有必要函数。例如调用System.out.println:

jclass syscls = env->FindClass("java/lang/System");
jfieldID fid = env->GetStaticFieldID(syscls, "out", "Ljava/io/PrintStream;");
jobject out = env->GetStaticObjectField(syscls, fid);
jclass pscls = env->FindClass("java/io/PrintStream");
jmethodID mid = env->GetMethodID(pscls, "println", "(Ljava/lang/String;)V");
jstring str = env->NewStringUTF("you are hacked");
env->CallVoidMethod(out, mid, str);

3. 使用ptrace插桩Java字节码

3.1 核心思路

  1. 通过ptrace获取JVM进程控制权
  2. 获取JNI环境上下文(JNIEnv指针)
  3. 修改Java方法字节码或将其转换为native方法

3.2 关键步骤

  1. 获取JavaVM实例

    JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);
    
  2. 获取JNIEnv和jvmtiEnv

    vm->GetEnv((void **)&_jni_env, JNI_VERSION_1_8);
    vm->GetEnv((void **)&_jvmti_env, JVMTI_VERSION_1_2);
    
  3. 方法Hook技术

    • 将方法修饰符改为native
    • 为方法提供native实现
    • 直接修改方法字节码

3.3 JVMMethod结构

struct JVMMethod {
    MethodInternal * _method;
    // 其他方法...
    unsigned long native_function_addr() const;
    void native_function_addr(unsigned long v);
    // ...
};

4. taycan-sdk使用指南

4.1 环境要求

  • Linux Kernel > 3.2
  • GLIBC > 2.15
  • OpenJDK/OracleJDK 1.8 64bit

4.2 开发步骤

  1. 定义继承JavaNativeBase的类
  2. 在构造函数中使用JNI/JVMTI API
  3. 定义替换函数
  4. 调用hookJvmMethod进行方法替换
  5. 静态实例化类

4.3 示例1:Hook非native方法

目标:Hook TestJni.circle()方法

#include "include/java_native_base.h"
#include "include/jvm_method.h"

class example : JavaNativeBase {
public:
    example();
    ~example(){};
};

static example _e;

JNIEXPORT void JNICALL hook_circle(JNIEnv* env, jobject thiz) {
    // 实现hook逻辑
}

example::example() {
    JNIEnv * env = getEnv();
    jclass clazz = env->FindClass("TestJni");
    jmethodID method_circle = env->GetMethodID(clazz, "circle", "()V");
    hookJvmMethod((JVMMethod *)method_circle, (unsigned long)hook_circle);
}

编译后使用soloader注入:

./soloader <pid> /path/to/libmy.so

4.4 示例2:注入Tomcat内存马

Hook ApplicationFilterChain.internalDoFilter方法:

void example::inject_mem_shell() {
    JNIEnv * env = getJNIEnv();
    jvmtiEnv * jvmti_env = getJVMTIEnv();
    
    _applicationFilterChain_clazz = jvmti_find_class("org.apache.catalina.core.ApplicationFilterChain");
    jmethodID internalDoFilter = env->GetMethodID(_applicationFilterChain_clazz, "internalDoFilter", "(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V");
    
    hookJvmMethod((JVMMethod *)internalDoFilter, (unsigned long)hook_internalFilter);
}

JNIEXPORT void JNICALL hook_internalFilter(JNIEnv * env, jobject thiz, jobject req, jobject rsp) {
    // 内存马逻辑
    if(检查参数) {
        // 执行命令并返回结果
        return;
    }
    
    // 正常调用原方法
    jfieldID fid = env->GetFieldID(_applicationFilterChain_clazz, "servlet", "Ljavax/servlet/Servlet;");
    jobject servlet = env->GetObjectField(thiz, fid);
    jmethodID mid = env->GetMethodID(_servlet_clazz, "service", "(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V");
    env->CallVoidMethod(servlet, mid, req, rsp);
}

5. 结语

ptrace技术提供了比官方API更底层的Java方法修改能力,可以绕过Java安全限制,实现更灵活的方法Hook。taycan-sdk封装了这些技术,使得开发人员可以更方便地进行Java字节码插桩和运行时修改。

关键优势:

  1. 不受Java安全限制影响(如无法修改java.lang包的限制)
  2. 无需额外agent文件,直接内存修改
  3. 结合了JNI和JVMTI的所有能力

注意事项:

  1. 需要root权限或与目标JVM相同的用户权限
  2. 对JVM版本有依赖性
  3. 在生产环境使用需谨慎,可能影响系统稳定性
使用ptrace插桩Java字节码技术详解 1. 前言 Java原生提供了两种官方接口用于插桩、调试和控制程序执行: 1.1 Java Instrumentation API Java Agent是Java SE 5引入的机制,用于在JVM启动时或运行时加载代理: 启动时加载 :实现 premain 方法 运行时加载 :实现 agentmain 方法 Agent需要打包成jar,并在Manifest中指定: 加载Agent的代码示例: 1.2 Instrumentation接口功能 使用javassist修改方法的示例: 1.3 JVMTI JVMTI是更底层的C/C++接口,Java Instrumentation API基于此实现。开发JVMTI agent需要: 包含 jvmti.h 头文件 实现必要的函数: 2. JNI基础 JNI(Java Native Interface)允许Java代码调用本地代码: JNIEnv指针提供了与JVM交互的所有必要函数。例如调用System.out.println: 3. 使用ptrace插桩Java字节码 3.1 核心思路 通过ptrace获取JVM进程控制权 获取JNI环境上下文(JNIEnv指针) 修改Java方法字节码或将其转换为native方法 3.2 关键步骤 获取JavaVM实例 : 获取JNIEnv和jvmtiEnv : 方法Hook技术 : 将方法修饰符改为native 为方法提供native实现 直接修改方法字节码 3.3 JVMMethod结构 4. taycan-sdk使用指南 4.1 环境要求 Linux Kernel > 3.2 GLIBC > 2.15 OpenJDK/OracleJDK 1.8 64bit 4.2 开发步骤 定义继承 JavaNativeBase 的类 在构造函数中使用JNI/JVMTI API 定义替换函数 调用 hookJvmMethod 进行方法替换 静态实例化类 4.3 示例1:Hook非native方法 目标:Hook TestJni.circle() 方法 编译后使用soloader注入: 4.4 示例2:注入Tomcat内存马 Hook ApplicationFilterChain.internalDoFilter 方法: 5. 结语 ptrace技术提供了比官方API更底层的Java方法修改能力,可以绕过Java安全限制,实现更灵活的方法Hook。taycan-sdk封装了这些技术,使得开发人员可以更方便地进行Java字节码插桩和运行时修改。 关键优势: 不受Java安全限制影响(如无法修改java.lang包的限制) 无需额外agent文件,直接内存修改 结合了JNI和JVMTI的所有能力 注意事项: 需要root权限或与目标JVM相同的用户权限 对JVM版本有依赖性 在生产环境使用需谨慎,可能影响系统稳定性