学习背景

  1. Agent本来就在学习的清单上
  2. 更深入理解下草师傅说的代理模式
  3. study the world

AOP

AOP为Aspect Oriented Programming的缩写,意为: 面向切面编程,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
以下引用来自su18和天下大木头师傅的博客:

JDK 1.5 开始,Java新增了Instrumentation(Java Agent API)和JVMTI(JVM Tool Interface)功能,允许JVM在加载某个class文件之前对其字节码进行修改,同时也支持对已加载的class(类字节码)进行重新加载(Retransform)。

开发者可以在一个普通Java程序(带有main函数的Java类)运行时,通过–javaagent参数指定一个特定的jar文件(包含Instrumentation代理)来启动Instrumentation的代理程序。在类的字节码载入jvm前会调用ClassFileTransformer的transform方法,从而实现修改原类方法的功能,实现AOP。

通过java.lang.instrument实现的工具我们称之为Java Agent,Java Agent能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法,Agent内存马的实现就是利用了这一特性使其动态修改特定类的特定方法,将我们的恶意方法添加进去。

说白了Java Agent只是一个Java类而已,只不过普通的Java类是以main函数作为入口点的,Java Agent的入口点则是premain和agentmain

Java Agent 支持两种方式进行加载:

  • 实现 premain 方法,在启动时进行加载 (该特性在 jdk 1.5 之后才有)
  • 实现 agentmain 方法,在启动后进行加载 (该特性在 jdk 1.6 之后才有)

premain

首先创建一个premain的agent:

import java.lang.instrument.Instrumentation;

public class DemoTest {
    public static void premain(String agentArgs, Instrumentation inst) throws Exception{
        System.out.println(agentArgs);
        for(int i=0;i<5;i++){
            System.out.println("premain method is invoked!");
        }
    }
}

然后通过mvn或者mf文件打生成jar包:

Manifest-Version: 1.0
Premain-Class: DemoTest

利用javac生成class文件之后打包:jar cvfm agent.jar agent.mf DemoTest.class

然后再创建一个普通的类Hello.java和mf文件:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello,Java");
    }
}

mf文件:

Manifest-Version: 1.0
Main-Class: Hello

同样打包为jar: jar cvfm hello.jar hello.mf Hello.class

接下来运行的时候增加-javaagent:agent.jar: java -javaagent:agent.jar=Hello -jar hello.jar

agentmain

首先对这种方式有一个初步的认识,需要三个文件:

App.java

循环输出,模拟运行的应用

package agentmain.demo1;

import java.lang.management.ManagementFactory;

public class App {
    public static void main(String[] args) throws InterruptedException {
        while (true){
            String name = ManagementFactory.getRuntimeMXBean().getName();
            String pid = name.split("@")[0];
            System.out.println("Pid is:" + pid);
            for(int i=0;i<500;i++){
                System.out.println("App is Running!");
                Thread.sleep(2000);
            }
        }
    }
}

AgentDemo.java

实现agentmain功能,用于agent的功能实现

package agentmain.demo1;

import java.lang.instrument.Instrumentation;

public class AgentDemo {
    public static void agentmain(String agentArgs, Instrumentation inst){
        System.out.println("Surprise! I'm Agent Smith");
    }
}

Inject.java

通过VirtualMachine的loadAgent方式,把Agent注入正在运行的App里:

package agentmain.demo1;

import com.sun.tools.attach.VirtualMachine;

public class Inject {
    public static void main(String[] args) throws Exception {
        String pid = args[0];
        String jarName = args[1];

        VirtualMachine vm = VirtualMachine.attach(pid);
        vm.loadAgent(jarName);
        vm.detach();
    }
}

以上三个java文件通过IDEA打包为Jar文件,首先运行App.jar:

java -cp JavaAgentDemo.jar agentmain.demo1.App

然后使用Inject.jar注入进程:

java -cp JavaAgentDemo.jar agentmain.demo1.Inject 22932 /var/tmp/AgentDemo.jar

可以看到App.jar的输出被动态的改变了:

参考资料

⬆︎TOP