初识——JavaAgent的宿命

 

前言

在各大HW轮番对国内Web安全生态进行考核之后,安全设备大量普及.而基于Web的一句话木马显得力不从心,于是在Shiro反序列化的RCE大潮之际,无落地文件的内存马登上历史舞台,内存马实现之一的技术便是JavaAgent,于是下文便对JavaAgent进行学习,为后面文章埋下伏笔。

 

JavaAgent简介

Java Agent 直译过来叫做 Java 代理,还有另一种称呼叫做 Java 探针。首先说 Java Agent 是一个 jar 包,只不过这个 jar 包不能独立运行,它需要依附到我们的目标 JVM 进程中。

Agent 是在 Java 虚拟机启动之时加载的,这个加载处于虚拟机初始化的早期,在这个时间点上:
1.所有的 Java 类都未被初始化;
2.所有的对象实例都未被创建;
3.因而,没有任何 Java 代码被执行;

Javaagent是java命令的一个参数。参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:
1.agent的这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。
2.Premain-Class 指定的那个类必须实现 premain() 方法。

 

JavaAgent的应用场景

各个 Java IDE 的调试功能,例如 eclipse、IntelliJ ;
热部署功能,例如 JRebel、XRebel、 spring-loaded;
各种线上诊断工具,例如 Btrace、Greys,还有阿里的 Arthas;
各种性能分析工具,例如 Visual VM、JConsole 等;

 

代码实现Agent类

1.创建JavaMaven项目

2.在Maven依赖中添加

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>
    </dependencies>

3.创建一个agent类,声明agent入口函数

public class MyCustomAgent {
    /**
     * jvm 参数形式启动,运行此方法
     * @param agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst){
        System.out.println("premain");
        customLogic(inst);
    }

    /**
     * 动态 attach 方式启动,运行此方法
     * @param agentArgs
     * @param inst
     */
    public static void agentmain(String agentArgs, Instrumentation inst){
        System.out.println("agentmain");
        customLogic(inst);
    }

    /**
     * 打印所有已加载的类名称
     * 修改字节码
     * @param inst
     */
    private static void customLogic(Instrumentation inst){
        inst.addTransformer(new MyTransformer(), true);
        Class[] classes = inst.getAllLoadedClasses();
        for(Class cls :classes){
            System.out.println(cls.getName());
        }
    }
}

4.创建一个自定义的方法,对被代理的Java类进行处理

public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("Loading Class :"+ className);
        if (!"Test".equals(className)){
            return classfileBuffer;
        }
        CtClass cl = null;
        try {
            ClassPool classPool = ClassPool.getDefault();
            cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod ctMethod = cl.getDeclaredMethod("test");
            System.out.println("Get Method Name :"+ ctMethod.getName());

            ctMethod.insertBefore("System.out.println(\"before the method \");");
            ctMethod.insertAfter("System.out.println(\"after the method \");");

            byte[] transformed = cl.toBytecode();
            return transformed;
        }catch (Exception e){
            e.printStackTrace();
        }
        return classfileBuffer;
    }
}
 ClassPool classPool = ClassPool.getDefault();
            cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod ctMethod = cl.getDeclaredMethod("test");
            System.out.println("Get Method Name :"+ ctMethod.getName());

            ctMethod.insertBefore("System.out.println(\"before the method \");");
            ctMethod.insertAfter("System.out.println(\"after the method \");");

此代码的含义是获取被加强类,使用反射的方式获取方法名称为test()的函数,然后使用ctMethod.insertBefore方法在调用前执行内容,ctMethod.insertAfter方法在调用后执行内容

5.在resources下创建META-INF/MANIFEST.MF文件

Manifest-Version: 1.0
Created-By: 雁不过衡阳
Agent-Class: MyCustomAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: MyCustomAgent

6.最后 Java Agent 是以 jar 包的形式存在,所以最后一步就是将上面的内容打到一个 jar 包里。

在 pom 文件中加入以下配置

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </plugin>
    </plugins>
</build>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>

此配置是将上文创建的配置文件位置指定于此

7.使用Maven打包项目

mvn assembly:assembly

8.生成结束后会在target目录下生成一个xx-1.0-SNAPSHOT-jar-with-dependencies

 

代码实现被代理类

1.创建新项目

2.创建测试类和测试方法,也就是被加强的类

public class Test {
    public void test(){
        System.out.println("执行测试方法");
    }
}

3.创建被加强的入口类

public class App {
    public static void main(String[] args) {
        System.out.println("********main*******");
        new Test().test();
    }
}
new Test().test();

创建Test类并调用test方法

4.配置加强类的启动JVM参数

-javaagent:E:\IdeaProject\Java-agent\agent\target\agent-1.0-SNAPSHOT-jar-with-dependencies.jar
-javaagent:上文打包的agent.jar路径

5.运行 加强类

因为在agent类中定义了使用反射对被加强类的test方法进行的前后置增强,所有在调用test方法之前调用增强打印,在调用test方法之后调用增强打印。做到了对被加强类的控制。

 

总结

此文实现了JavaAgent的使用方法,以Demo的形式深入理解Agent的AOP模式,在接下来的文章中将会分析内存马的实现等内容,助君安全之路一往无前.

(完)