Java agent

5 篇文章 0 订阅

简介

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的特殊语法

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

  1. 为了在 Javaassist 中使用某些类,需要指定完整的包名:InputStream–>java.io.InputStream
  2. for循环等要用i++的形式:for(int i=0;i<len;i++)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值