简介
Java Agent可以用于注入, 在程序当中执行自定义代码, 常常用于Hook框架, Rasp等…
基本方法
premain
在程序启动时修改, 利用premain()
:
每当加载一个class文件时输出当前class文件名:
Demo.java:
import java.io.PrintStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class Demo{
public static void premain(String agentArgs, Instrumentation inst)
{
System.out.println("agentArgs : " + agentArgs);
inst.addTransformer(new DefineTransformer(), true);
}
}
DefineTransformer.java:
import java.lang.instrument.ClassFileTransformer;
public class DefineTransformer implements ClassFileTransformer{
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException{
System.out.println("premain load Class:" + className);
return classfileBuffer;
}
}
配置文件META-INF/MANIFEST.MF
:
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: Demo
然后在需要注入的java程序启动时添加启动参数:
-javaagent:Demo.jar=sir
agentmain
通过agentmain()
可以在程序运行时注入我们的自定义代码:
打印出当前程序已经加载了的类:
SufMainAgent.Java:
import java.io.PrintStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class SufMainAgent{
public static void agentmain(String args,Instrumentation inst){
Class<?>[] classes = inst.getAllLoadedClasses();
for(Class<?>[] cls:classes){
System.out.println(cls.getName());
}
System.out.println("Finished");
}
}
配置文件META-INF/MANIFEST.MF
:
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: SufMainAgent
打包后jar后, 需要通过VirtualMachine
来注入我们的jar包:
import com.sun.tools.attach.*;
import java.util.List;
public class Sir {
public static void main(String[] args) throws Exception {
//获取当前系统中所有 运行中的 虚拟机
System.out.println("start...");
String vmname = null;
String vmid = null;
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : list) {
vmname = vmd.displayName();
vmid = vmd.id();
System.out.println(vmid + " : " + vmname);
if(vmname.equals("org.apache.catalina.startup.Bootstrap")){ // 注入程序的名字
System.out.println("vmid: " + vmid);
VirtualMachine virtualMachine = VirtualMachine.attach(vmid);
virtualMachine.loadAgent("agent.jar"); // 打包好的agent.jar目录
virtualMachine.detach();
break;
}
}
System.out.println("detached....");
}
}
Instrument
在agent
中通过addTransformer
可以添加应该自定义的Transform
, 每当用新的类被load的时候就会被调用, 所以在premain
的demo中我们可以每当加载一个class时输出当前class名…
addTransformer()
增加一个Class文件的转换器,该转换器用于改变class二进制流的数据,参数canRetransform设置是否允许重新转换;
redefineClasses()
类加载之前,重新定义class文件,ClassDefinition表示一个类新的定义,如果在类加载之后,需要用retransformClasses方法重新定义;
retransformClasses()
在类加载之后,重新定义class, 相当于重新加载一下类可以调用到自定义的Transform
;
getAllLoadedClasses()
获取加载的所有类数组, 比较常用;
appendToBootstrapClassLoaderSearch()
添加jar文件到BootstrapClassLoader中;
appendToSystemClassLoaderSearch()
添加jar文件到system class loader;
Javassist的特殊语法
例子
Hook ProcessBuilder:
agent.java:
import java.io.PrintStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class Demo{
public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Exception
{
System.out.println("agentArgs : " + agentArgs);
inst.addTransformer(new Classsformer(), true);
}
}
ClassTransformer.java
public class ClassTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class redefinedClass, ProtectionDomain protDomain,
byte[] classfileBuffer) {
// Hook ProcessImpl
if (className.contains("ProcessImpl")) {
byte[] classfileBuffer;
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(className);
CtMethod[] methods = ctClass.getMethods();
String src =
"{" +
"String cmdarray[] = $1;" +
"System.out.print(\"cmd_args: \");" +
"for(int i=0; i<cmdarray.length; i++){" +
"System.out.print(cmdarray[i] + \" \");" +
"}" +
"System.out.println();" +
"StackTraceElement[] stack = Thread.currentThread().getStackTrace();" +
"for(int i=0;i<stack.length;i++){" +
"System.out.println(stack[i].getClassName()+\".\"+stack[i].getMethodName());" +
"}" +
"}";
for (CtMethod method : methods) {
// 找到start方法,并插入拦截代码
if (method.getName().equals("start")){
method.insertBefore(src);
break;
}
}
classfileBuffer = ctClass.toBytecode();
return classfileBuffer;
}
}
}
Dump Class
ClassDumpTransformer.java
public class ClassDumpTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class redefinedClass, ProtectionDomain protDomain,
byte[] classfileBuffer) {
if (className.contains("ProcessImpl")) {
dumpClass(dumpDir, className, classBytes);
}
}
public static void dumpClass(String dumpDir, String className, byte[] classBuf) {
try {
// create package directories if needed
className = className.replace("/", File.separator);
StringBuilder buf = new StringBuilder();
buf.append(dumpDir);
buf.append(File.separatorChar);
int index = className.lastIndexOf(File.separatorChar);
if (index != -1) {
buf.append(className, 0, index);
}
String dir = buf.toString();
new File(dir).mkdirs();
// write .class file
String fileName = dumpDir +
File.separator + className + ".class";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(classBuf);
fos.close();
}
}
Tips
- 为了在 Javaassist 中使用某些类,需要指定完整的包名:
InputStream
–>java.io.InputStream
for
循环等要用i++
的形式:for(int i=0;i<len;i++)