Java agent for Java Security

Java agent for Java Security

0x00 Preface

I found that many technologies will use Java agent to implement it. It is said that these cracking of rasp, memory horse (one way) and idea are based on Java agent. Let's take a look at the subtleties of this technology.

0x01 Java agent mechanism

At jdk1 Since version 5, Java has added the functions of instrumentation (Java agent API) and JVMTI (JVM tool interface), which can enable the JVM to load a class file and modify its bytecode, or reload the loaded bytecode. Java agent can realize bytecode stake insertion, dynamic tracking analysis, etc.

Java AGET running mode

In mode 1, you can only specify the agent file to be loaded before startup, while in mode 2, you can dynamically inject the agent into the JVM according to the process ID after the Java program runs.

0x02 Java agent concept

Java agent is a parameter of a command in Java. The parameter content can specify a jar package with certain specifications

The premain method mentioned above will be called before running the main method, that is, the premain method in the premain class class in the jar package specified by - javaagent will be loaded before running the main method. In fact, Java agent is essentially a Java class, but ordinary Java classes take the main method as the entry point of the program, while Java agent takes premain (agent mode) and agentmain (attach mode) as the entry point of the agent program.

If you need to modify the bytecode of a class that has been loaded by the JVM, you also need to set it in manifest Add can retransform classes: true or can redefine classes: true in MF.

Let's look at the command parameters first

Command parameters:

-agentlib:<libname>[=<选项>] 加载本机代理库 <libname>,例如 -agentlib:hprof
	另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<选项>]
	按完整路径名加载本机代理库
-javaagent:<jarpath>[=<选项>]
	加载 Java 编程语言代理,请参阅 java.lang.instrument

Java Lang.instrument provides services that allow Java programming language agents to monitor programs running on the JVM. The monitoring mechanism is to modify the bytecode of the method. When starting the JVM, start an agent by indicating the agent class and its agent options.

The proxy class must implement a public static premain method, which is similar to the main application entry point in principle, and there are certain requirements in front of the premain method. The signature must meet the following two formats:

public static void premain(String agentArgs,Instrumentation inst)
    
public static void premain(String agentArgs)

The JVM will preferentially load the methods with instrumentation signature. If the loading is successful, the second method will be ignored. If the first method does not exist, the second method will be loaded. This logic is in sun instrument. Implemented in the instrumentationimpl class, you can audit the code

Example:

public static void premain(String agentArgs,Instrumentation inst);

Parameter details:

-javaagent:jarpath[=options]
	jarpath 是指向代理程序 JAR 文件的路径。options 是代理选项。此开关可以在同一命令行上多次使用,从而创建多个代理程序。多个代	理程序可以使用同一 jarpath。代理 JAR 文件必须符合 JAR 文件规范。下面的清单属性是针对代理 JAR 文件定义的:
Premain-Class
	代理类。即包含 premain 方法的类。此属性是必需的,如果它不存在,JVM 将中止。注:这是类名,而不是文件名或路径。
Boot-Class-Path
	由引导类加载器搜索的路径列表。路径表示目录或库(在许多平台上通常作为 jar 或 zip 库被引用)。查找类的特定于平台的机制出现故障之后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件的语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。此属性是可选的。
Can-redefine-Classes
	布尔值(true 或 false,与大小写无关)。能够重定义此代理所需的类。值如果不是 true,则被认为是 false。此属性是可选的,默认值为 false。
代理 JAR 文件附加到类路径之后。

There is a rt.jar package in the JDK, and there is a Java. Jar package Lang. instrument package, which provides the function of dynamically modifying class types in the system during Java runtime. But the key is Java agent. It can re receive external requests at run time and make a modification to the class type.

There are two important interfaces: Instrumentation and classfiletransformer

Instrumentation interface

Let's first look at the contents of the instrumentation interface

Looking at the figure above, this is Java lang.instrument. Some methods in instrumentation. Refer to a figure in javasec, which describes a function of various methods

java. lang.instrument. Instrumentation is used to monitor Java APIs running in the JVM. The following functions can be realized by using this class:

The corresponding methods of each major implementation function have been shown here.

Classfiletransformer interface

java. lang.instrument. Classfiletransformer is a proxy interface for converting class files. After obtaining the instrumentation object, we can add a custom class file converter through the addtransformer method.

In the example, we use addtransformer to register a custom transformer to the Java agent. When a new class is loaded by the JVM, the JVM will automatically call back the transform method of our custom transformer class and pass in the transform information of the class (class name, class loader, class bytecode, etc.). We can decide whether to modify the class bytecode according to the passed in class information, After modifying the bytecode, we will return the new class bytecode to the JVM, and the JVM will verify whether the class and the corresponding modification are legal. If the class loading requirements are met, the JVM will load our modified class bytecode.

Take a look at this interface

There is only one transform method in the interface, and the corresponding information of the parameter content is:

ClassLoader loader              	定义要转换的类加载器;如果是引导加载器,则为 null
String   className           		加载的类名,如:java/lang/Runtime
Class<?> classBeingredefined 		如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
ProtectionDomain protectionDomain   要定义或重定义的类的保护域
byte[]  classfileBuffer     		类文件格式的输入字节缓冲区(不得修改)

Considerations for overriding the transform method:

0x03 Java agent technology implementation

All the above are conceptual problems. Now let's implement a Java agent

Let's take a look at the general steps of implementation

After completing the above steps, the premain method will be executed when starting the program. Of course, this must take precedence over the main method. However, some system classes inevitably take precedence over Java agent. However, these user classes will certainly be intercepted by Java agent. In this way, after being intercepted, you can rewrite the class, for example, use ASM, javassist, cglib, etc. to rewrite the implementation class. Two projects need to be implemented, one is the class of javaagent, and the other is the class of javaaagent. Some code to be executed before the mian method is executed.

Run before JVM runs

Create an agent class that contains the premain method:

package com.nice0e3;

import java.lang.instrument.Instrumentation;

public class Agent {
    public static void premain(String agentArgs,Instrumentation inst){
        System.out.println("agentArgs"+agentArgs);
        inst.addTransformer(new DefineTransformer(),true);//调用addTransformer添加一个Transformer
    }
}

Definetransformer class:

package com.nice0e3;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class DefineTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader,String className,Class<?> classBeingredefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("premain load class"+className); //打印加载的类
        return new byte[0];
    }
}

You need to override the transform method here. That is, all operations that need to be performed during loading will be implemented in this method.

SRC\Meta-INF\MANIFEST. Add content to MF file:

Manifest-Version: 1.0
Can-redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.nice0e3.Agent

I use Maven to make a configuration here

pom. xml:

  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <!--自动添加Meta-INF/MANIFEST.MF -->
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>com.nice0e3.Agent</Premain-Class>
                            <Agent-Class>com.nice0e3.Agent</Agent-Class>
                            <Can-redefine-Classes>true</Can-redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

After compiling into a jar package, create a project, configure and add the - javaagent parameter, - javaagent: out \ agent1-1.0-snapshot Jar cannot be followed by extra spaces.

Write a main method

package com.test;

import java.io.IOException;
import java.io.InputStream;

public class test {


    public static void main(String[] args) throws IOException {
        System.out.println("main");


    }

}

Here you can see that all classes loaded by the JVM are printed. The character main is printed before shutdown, and shutdown is loaded on the last side. Shutdown is also an important point, but it will not be repeated here.

As mentioned earlier, the transform method, that is, other operations that need to be performed during loading, will be implemented in this method. This is because the classfiletransformer will intercept system classes and class objects implemented by itself. If you need to rewrite a class, you can seize the class when intercepting and implement it with bytecode compiler.

Small case

Here's a small case

import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

/**
 * @author rickiyang
 * @date 2019-08-06
 * @Desc
 */
public class MyClassTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(final ClassLoader loader,final String className,final Class<?> classBeingredefined,final ProtectionDomain protectionDomain,final byte[] classfileBuffer) {
        // 操作Date类
        if ("java/util/Date".equals(className)) {
            try {
                // 从ClassPool获得CtClass对象
                final ClassPool classPool = ClassPool.getDefault();
                final CtClass clazz = classPool.get("java.util.Date");
                CtMethod convertToAbbr = clazz.getDeclaredMethod("convertToAbbr");
                //这里对 java.util.Date.convertToAbbr() 方法进行了改写,在 return之前增加了一个 打印操作
                String methodBody = "{sb.append(Character.toUpperCase(name.charAt(0)));" +
                        "sb.append(name.charAt(1)).append(name.charAt(2));" +
                        "System.out.println(\"sb.toString()\");" +
                        "return sb;}";
                convertToAbbr.setBody(methodBody);

                // 返回字节码,并且detachCtClass对象
                byte[] byteCode = clazz.toBytecode();
                //detach的意思是将内存中曾经被javassist加载过的Date对象移除,如果下次有需要在内存中找不到会重新走javassist加载
                clazz.detach();
                return byteCode;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        // 如果返回null则字节码不会被修改
        return null;
    }
}

Here is to use javassist to dynamically create a class and util. The converttoabbr method of date is used to rewrite, insert new content using setbody, and then convert it into bytecode for return.

Run after JVM runs

Previously, execute the instrument before using the main method. And in jdk1 The agentmain method added after 6 can be inserted after the main method is executed.

This method is similar to the previous permain. You need to define a class of agentmain method.

public static void agentmain (String agentArgs,Instrumentation inst)

public static void agentmain (String agentArgs)

As before, the operation priority with instrumentation type parameter will be higher than that without this parameter.

After the implementation of Java JDK6, the attach API loads the instrument after startup. Exists on COM sun. tools. There are two important classes in attach.

Let's take a look at the contents of this package. There are two important classes, namely, virtualmachine and virtualmachinedescriptor

VirtualMachine:

Virtualmachine can obtain system information, such as memory dump, ready-made dump and class information statistics (such as classes loaded by JVM). It is equipped with several methods, loadagent, attach and detach. Let's take a look at the functions of these methods

Attach: the functions that can be realized by removing an agent from the JVM can be said to be very powerful. This class allows us to remotely connect to the JVM by passing in a JVM PID (process ID) to the attach method

Loadagent: register an agent program agent with the JVM, and an instrumentation instance will be obtained in the agent program of the agent. The instance can change the bytecode of class before class loading or reload after class loading. When calling the methods of the instrumentation instance, these methods are processed using the methods provided in the classfiletransformer interface.

Detach: detach an agent from the JVM

Get Java program process manually

Attach mode needs to know the process ID of the Java program we are running. Through the process injection of Java virtual machine, we can dynamically inject our agent program into a running Java program. We can also use the built-in JPS - L command to check.

It is estimated that the first 16320 process is the cracking plug-in of idea, which uses Java agent technology to crack.

The principle of dynamic injection by attach is as follows:

The attach (PID) method of the virtualmachine class can be attached to a running java process, and then the jar package of the agent can be injected into the corresponding process through the loadagent (agentjarpath), and then the corresponding process will call the agentmain method.

Code automatically gets Java program process

package com.nice0e3;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.util.List;

public class test {
    public static void main(String[] args) {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
            System.out.println(virtualMachineDescriptor+"\n"+virtualMachineDescriptor.id());
        }
    }
}

Once you have the process ID, you can inject the agent using the attach API.

Implementation of dynamic injection agent code

Edit POM XML file

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <archive>
                    <!--自动添加Meta-INF/MANIFEST.MF -->
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Agent-Class>com.nice0e3.Agent</Agent-Class>
                        <Can-redefine-Classes>true</Can-redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

Agent class:

package com.nice0e3;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class Agent {
    public static void agentmain(String agentArgs,Instrumentation instrumentation) {
        instrumentation.addTransformer(new DefineTransformer(),true);

    }
}

Definetransformer class:

package com.nice0e3;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class DefineTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader,byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("premain load class"+className);
        return classfileBuffer;
    }
}

After compiling into a jar package, write a main method to test

Main method class:

package com.test;

import com.sun.tools.attach.*;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;


public class test {


    public static void main(String[] args) throws IOException,AttachNotSupportedException,AgentLoadException,AgentInitializationException {

        System.out.println("main running");
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vir : list) {
            System.out.println(vir.displayName());//打印JVM加载类名
            if (vir.displayName().endsWith("com.test.test")){
                VirtualMachine attach = VirtualMachine.attach(vir.id());   //attach注入一个jvm id注入进去
                attach.loadAgent("out\\Agent1-1.0-SNAPSHOT.jar");//加载agent
                attach.detach();

            }
        }

    }
}

Execution results:

Tips:

Crack idea small case

Let's take a case in javasec to do a test and copy the code

package com.test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Creator: yz
 * Date: 2020/10/29
 */
public class CrackLicenseTest {

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static boolean checkExpiry(String expireDate) {
        try {
            Date date = DATE_FORMAT.parse(expireDate);

            // 检测当前系统时间早于License授权截至时间
            if (new Date().before(date)) {
                return false;
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return true;
    }

    public static void main(String[] args) {
        // 设置一个已经过期的License时间
        final String expireDate = "2020-10-01 00:00:00";

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        String time = "[" + DATE_FORMAT.format(new Date()) + "] ";

                        // 检测license是否已经过期
                        if (checkExpiry(expireDate)) {
                            System.err.println(time + "您的授权已过期,请重新购买授权!");
                        } else {
                            System.out.println(time + "您的授权正常,截止时间为:" + expireDate);
                        }

                        // sleep 1秒
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

}

Here is a simulation of the detection activation function of an idea.

The implementation is as follows

What you need now is to give the detected activated cracklicensetest class to hook.

Let's write the code.

package com.nice0e3;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.List;

/**
 * Creator: yz
 * Date: 2020/1/2
 */
public class CrackLicenseAgent {

    /**
     * 需要被Hook的类
     */
    private static final String HOOK_CLASS = "com.anbai.sec.agent.CrackLicenseTest";

    /**
     * Java Agent模式入口
     *
     * @param args 命令参数
     * @param inst Instrumentation
     */
    public static void premain(String args,final Instrumentation inst) {
        loadAgent(args,inst);
    }

    /**
     * Java Attach模式入口
     *
     * @param args 命令参数
     * @param inst Instrumentation
     */
    public static void agentmain(String args,inst);
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            List<VirtualMachineDescriptor> list = VirtualMachine.list();

            for (VirtualMachineDescriptor desc : list) {
                System.out.println("进程ID:" + desc.id() + ",进程名称:" + desc.displayName());
            }

            return;
        }

        // Java进程ID
        String pid = args[0];

        try {
            // 注入到JVM虚拟机进程
            VirtualMachine vm = VirtualMachine.attach(pid);

            // 获取当前Agent的jar包路径
            URL agentURL = CrackLicenseAgent.class.getProtectionDomain().getCodeSource().getLocation();
            String agentPath = new File(agentURL.toURI()).getAbsolutePath();

            // 注入Agent到目标JVM
            vm.loadAgent(agentPath);
            vm.detach();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载Agent
     *
     * @param arg  命令参数
     * @param inst Instrumentation
     */
    private static void loadAgent(String arg,final Instrumentation inst) {
        // 创建ClassFileTransformer对象
        ClassFileTransformer classFileTransformer = createClassFileTransformer();

        // 添加自定义的Transformer,第二个参数true表示是否允许Agent Retransform,
        // 需配合MANIFEST.MF中的Can-Retransform-Classes: true配置
        inst.addTransformer(classFileTransformer,true);

        // 获取所有已经被JVM加载的类对象
        Class[] loadedClass = inst.getAllLoadedClasses();

        for (Class clazz : loadedClass) {
            String className = clazz.getName();

            if (inst.isModifiableClass(clazz)) {
                // 使用Agent重新加载HelloWorld类的字节码
                if (className.equals(HOOK_CLASS)) {
                    try {
                        inst.retransformClasses(clazz);
                    } catch (UnmodifiableClassException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static ClassFileTransformer createClassFileTransformer() {
        return new ClassFileTransformer() {

            /**
             * 类文件转换方法,重写transform方法可获取到待加载的类相关信息
             *
             * @param loader              定义要转换的类加载器;如果是引导加载器,则为 null
             * @param className           类名,如:java/lang/Runtime
             * @param classBeingredefined 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
             * @param protectionDomain    要定义或重定义的类的保护域
             * @param classfileBuffer     类文件格式的输入字节缓冲区(不得修改)
             * @return 字节码byte数组。
             */
            @Override
            public byte[] transform(ClassLoader loader,byte[] classfileBuffer) {

                // 将目录路径替换成java类名
                className = className.replace("/",".");

                // 只处理com.anbai.sec.agent.CrackLicenseTest类的字节码
                if (className.equals(HOOK_CLASS)) {
                    try {
                        ClassPool classPool = ClassPool.getDefault();

                        // 使用javassist将类二进制解析成CtClass对象
                        CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

                        // 使用CtClass对象获取checkExpiry方法,类似于Java反射机制的clazz.getDeclaredMethod(xxx)
                        CtMethod ctMethod = ctClass.getDeclaredMethod(
                                "checkExpiry",new CtClass[]{classPool.getCtClass("java.lang.String")}
                        );

                        // 在checkExpiry方法执行前插入输出License到期时间代码
                        ctMethod.insertBefore("System.out.println(\"License到期时间:\" + $1);");

                        // 修改checkExpiry方法的返回值,将授权过期改为未过期
                        ctMethod.insertAfter("return false;");

                        // 修改后的类字节码
                        classfileBuffer = ctClass.toBytecode();
                        File classFilePath = new File(new File(System.getProperty("user.dir"),"src\\main\\java\\com\\nice0e3\\"),"CrackLicenseTest.class");

                        // 写入修改后的字节码到class文件
                        FileOutputStream fos = new FileOutputStream(classFilePath);
                        fos.write(classfileBuffer);
                        fos.flush();
                        fos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                return classfileBuffer;
            }
        };
    }

}

I don't know why I didn't succeed when I did it. Post a picture of success.

Add a schematic diagram

.

Reference articles

https://www.cnblogs.com/rickiyang/p/11368932.html
https://javasec.org/javase/JavaAgent/JavaAgent.html
https://y4er.com/post/javaagent-tomcat-memshell/

0x04 end

You will encounter many pits on the way, such as tools The jar package of jar cannot be found under windows. You need to manually go to the Lib directory of Java JDK and add the package manually. Learning is a pit draining process. Suppose that the Java agent needs to be deserialized or directly entered into memory, how to implement it? In fact y4er, master Wen mentioned some points that need attention and consideration. This will be implemented later.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>