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 核心思路
- 通过ptrace获取JVM进程控制权
- 获取JNI环境上下文(JNIEnv指针)
- 修改Java方法字节码或将其转换为native方法
3.2 关键步骤
-
获取JavaVM实例:
JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *); -
获取JNIEnv和jvmtiEnv:
vm->GetEnv((void **)&_jni_env, JNI_VERSION_1_8); vm->GetEnv((void **)&_jvmti_env, JVMTI_VERSION_1_2); -
方法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 开发步骤
- 定义继承
JavaNativeBase的类 - 在构造函数中使用JNI/JVMTI API
- 定义替换函数
- 调用
hookJvmMethod进行方法替换 - 静态实例化类
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字节码插桩和运行时修改。
关键优势:
- 不受Java安全限制影响(如无法修改java.lang包的限制)
- 无需额外agent文件,直接内存修改
- 结合了JNI和JVMTI的所有能力
注意事项:
- 需要root权限或与目标JVM相同的用户权限
- 对JVM版本有依赖性
- 在生产环境使用需谨慎,可能影响系统稳定性