Java 安全之Java Agent

Java安全之JavaAgent0x00前言在前面发现很多技术都会去采用JavaAgent该技术去做实现,比分说RASP和内存马(其中一种方式)、包括IDEA的这些破解都是基于JavaA

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

Java 安全之Java Agent

0x00 前言

在前面发现很多技术都会去采用Java Agent该技术去做实现,比分说RASP和内存马(其中一种方式)、包括IDEA的这些破解都是基于Java Agent去做实现。下面来领略该技术的微妙所在。

0x01 Java Agent 机制

在JDK1.5版本开始,Java增加了Instrumentation(Java Agent API)JVMTI(JVM Tool Interface)功能,该功能可以实现JVM再加载某个class文件对其字节码进行修改,也可以对已经加载的字节码进行一个重新的加载。Java Agent可以去实现字节码插桩、动态跟踪分析等。

Java Aget运行模式

  1. 启动Java程序的时候添加-javaagent(Instrumentation API实现方式)-agentpath/-agentlib(JVMTI的实现方式)参数

  2. 在1.6版本新增了attach(附加方式)方式,可以对运行中的Java进程插入Agent

方式一中只能在启动前去指定需要加载的Agent文件,而方式二可以在Java程序运行后根据进程ID进行动态注入Agent到JVM里面去。

0x02 Java Agent 概念

Java Agent是一个Java里面命令的参数该参数内容可以指定一个jar包,该jar包内容有一定的规范

  1. jar包中的MANIFEST.MF 文件必须指定 Premain-Class 项
  2. Premain-Class 指定的那个类必须实现 premain() 方法

上面说到的这个premain方法会在运行main方法前被调用,也就是说在运行main方法前会去加载-javaagent指定的jar包里面的Premain-Class类中的premain方法。那么其实Java agent本质上就是一个Java的类,但是普通的Java类是以main方法作为程序入口点,而Java Agent则将premain(Agent模式)和agentmain(Attach模式)作为了Agent程序的入口。

如果需要修改已经被JVM加载过的类的字节码,那么还需要设置在MANIFEST.MF中添加Can-Retransform-Classes: trueCan-Redefine-Classes: true

先来看看命令参数

命令参数:

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

上面说到的 java.lang.instrument 提供允许 Java 编程语言代理监测运行在 JVM 上的程序的服务。监测的机制是对方法的字节码的修改,在启动 JVM 时,通过指示代理类 及其代理选项 启动一个代理程序。

该代理类必须实现公共的静态 premain 方法,该方法原理上类似于 main 应用程序入口点,并且premain 方法的前面也会有一定的要求,签名必须满足一下两种格式:

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

JVM会去优先加载带 Instrumentation 签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。这个逻辑在sun.instrument.InstrumentationImpl 类中实现,可以来审计一下该代码

Java 安全之Java Agent

例:

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

参数详细说明:

-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 文件附加到类路径之后。

在JDK里面有个rt.jar包中存在一个java.lang.instrument的包,这个包提供了Java运行时,动态修改系统中的Class类型的功能。但最关键的还是javaagent 。它可以在运行时重新接收外部请求,对class类型进行一个修改。

这里面有2个重要的接口 InstrumentationClassFileTransformer

Instrumentation接口

先来看看Instrumentation接口中的内容

Java 安全之Java Agent

Java 安全之Java Agent

来看到上图,这是java.lang.instrument.Instrumentation中的一些方法。借鉴一下javasec里面的一张图,该图片描述了各种方法的一个作用

java.lang.instrument.Instrumentation的作用是用来监测运行在JVM中的Java API,利用该类可以实现如下功能:

  1. 动态添加或移除自定义的ClassFileTransformeraddTransformer/removeTransformer),JVM会在类加载时调用Agent中注册的ClassFileTransformer
  2. 动态修改classpathappendToBootstrapClassLoaderSearchappendToSystemClassLoaderSearch),将Agent程序添加到BootstrapClassLoaderSystemClassLoaderSearch(对应的是ClassLoader类的getSystemClassLoader方法,默认是sun.misc.Launcher$AppClassLoader)中搜索;
  3. 动态获取所有JVM已加载的类(getAllLoadedClasses);
  4. 动态获取某个类加载器已实例化的所有类(getInitiatedClasses)。
  5. 重定义某个已加载的类的字节码(redefineClasses)。
  6. 动态设置JNI前缀(setNativeMethodPrefix),可以实现Hook native方法。
  7. 重新加载某个已经被JVM加载过的类字节码retransformClasses)。

这里已经表明各大实现功能所对应的方法了。

ClassFileTransformer接口

java.lang.instrument.ClassFileTransformer是一个转换类文件的代理接口,我们可以在获取到Instrumentation对象后通过addTransformer方法添加自定义类文件转换器。

示例中我们使用了addTransformer注册了一个我们自定义的TransformerJava Agent,当有新的类被JVM加载时JVM会自动回调用我们自定义的Transformer类的transform方法,传入该类的transform信息(类名、类加载器、类字节码等),我们可以根据传入的类信息决定是否需要修改类字节码,修改完字节码后我们将新的类字节码返回给JVMJVM会验证类和相应的修改是否合法,如果符合类加载要求JVM会加载我们修改后的类字节码。

查看一下该接口

Java 安全之Java Agent

该接口中有只有一个transform方法,里面的参数内容对应的信息分别是:

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

重写transform方法注意事项:

  1. ClassLoader如果是被Bootstrap ClassLoader(引导类加载器)所加载那么loader参数的值是空。
  2. 修改类字节码时需要特别注意插入的代码在对应的ClassLoader中可以正确的获取到,否则会报ClassNotFoundException,比如修改java.io.FileInputStream(该类由Bootstrap ClassLoader加载)时插入了我们检测代码,那么我们将必须保证FileInputStream能够获取到我们的检测代码类。
  3. JVM类名的书写方式路径方式:java/lang/String而不是我们常用的类名方式:java.lang.String
  4. 类字节必须符合JVM校验要求,如果无法验证类字节码会导致JVM崩溃或者VerifyError(类验证错误)
  5. 如果修改的是retransform类(修改已被JVM加载的类),修改后的类字节码不得新增方法修改方法参数类成员变量
  6. addTransformer时如果没有传入retransform参数(默认是false)就算MANIFEST.MF中配置了Can-Redefine-Classes: true而且手动调用了retransformClasses方法也一样无法retransform
  7. 卸载transform时需要使用创建时的Instrumentation实例。

0x03 Java Agent 技术实现

上面说的都是一些概念性的问题,现在去做一个Java agent的实现

来看一下实现的大致几个步骤

  1. 定义一个 MANIFEST.MF 文件,必须包含 Premain-Class 选项,通常也会加入Can-Redefine-Classes 和 Can-Retransform-Classes 选项。
  2. 创建指定的Premain-Class类,并且里面包含premain 方法,方法逻辑由用户自己确定
  3. premain MANIFEST.MF文件打包成一个jar包
  4. 使用 -javaagent: jar参数包路径 启动要代理的方法。

完成以上步骤后,启动程序的时候会去执行premain 方法,当然这个肯定是优先于main方法执行的。但是不免会有一些系统类优先于javaagent进行执行。但是用户类这些肯定是会被javaagent给拦截下来的。这么这时候拦截下来后就可以进行一个重写类等操作,例如使用ASM、javassist,cglib等等来改写实现类。在实现里面需要去些2个项目,一个是javaAgent的类,一个是需要JavaAagent需要去代理的类。在mian方法执行前去执行的一些代码。

JVM运行前运行

创建一个Agent类,里面需要包含premain方法:

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类:

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];
    }
}

这里需要重写transform方法。也就是在加载的时候需要执行操作都会在该方法中进行实现。

SRC\META-INF\MANIFEST.MF文件中添加内容:

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

我这里用的是maven去做一个配置

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>

编译成jar包后,再建立一个项目,配置加入-javaagent参数,-javaagent:out\Agent1-1.0-SNAPSHOT.jar后面不能有多余的空格。

Java 安全之Java Agent

编写一个main方法

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");


    }

}

Java 安全之Java Agent

这里可以看到打印了JVM加载的所有类。而main这个字符再Shutdown之前被打印了,最后面才去加载Shutdown这个也是比较重要的一个点,但是在这里不做赘述。

前面说过transform方法,也就是在加载的时候需要执行其他的操作都会在该方法中进行实现。这是因为ClassFileTransformer中会去拦截系统类和自己实现的类对象,如果需要对某个类进行改写,就可以在拦截的时候抓住这个类使用字节码编译工具去实现。

小案例

这里来复制一个小案例

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;
    }
}

这里是使用javassist去动态创建一个类,并且对java.util.DateconvertToAbbr方法去做一个改写使用setBody插入新的内容,然后转换成字节码进行返回。

JVM运行后运行

前面是使用在main方法运行之前,执行Instrument。而在JDK1.6以后新增的agentmain方法,可以实现在main方法执行以后进行插入执行。

该方法和前面的permain类似,需要定义一个agentmain方法的类。

public static void agentmain (String agentArgs, Instrumentation inst)

public static void agentmain (String agentArgs)

这个也是和前面的一样,有Instrumentation类型参数的运行优先级也是会比没有该参数的高。

在Java JDK6以后实现启动后加载Instrument的是Attach api。存在于com.sun.tools.attach里面有两个重要的类。

来查看一下该包中的内容,这里有两个比较重要的类,分别是VirtualMachineVirtualMachineDescriptor

Java 安全之Java Agent

VirtualMachine:

VirtualMachine可以来实现获取系统信息,内存dump、现成dump、类信息统计(例如JVM加载的类)。里面配备有几个方法LoadAgent,Attach 和 Detach 。下面来看看这几个方法的作用

Attach :从 JVM 上面解除一个代理等方法,可以实现的功能可以说非常之强大 。该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上

loadAgent:向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。

Detach:从 JVM 上面解除一个代理(agent)

手动获取Java程序进程

Attach模式需要知道我们运行的Java程序进程ID,通过Java虚拟机的进程注入方式实现可以将我们的Agent程序动态的注入到一个已在运行中的Java程序中。我们也可以使用自带的Jps -l命令去查看。

Java 安全之Java Agent

看到第一个16320进程估计就是IDEA的破解插件,使用的Java agent技术进行一个实现破解。

attach实现动态注入的原理如下:

VirtualMachine类的attach(pid)方法,便可以attach到一个运行中的java进程上,之后便可以通过loadAgent(agentJarPath)来将agent的jar包注入到对应的进程,然后对应的进程会调用agentmain方法。

Java 安全之Java Agent

代码自动获取Java程序进程

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());
        }
    }
}

Java 安全之Java Agent

有了进程ID后就可以使用Attach API注入Agent了。

动态注入Agent代码实现

编辑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>
                        <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类:

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类:

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 classfileBuffer;
    }
}

编译成jar包后,编写一个main方法来进行测试

main方法类:

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();

            }
        }

    }
}

执行结果:

Java 安全之Java Agent

Tips:

  1. 已加载的Java类是不会再被Agent处理的,这时候我们需要在Attach到目标进程后调用instrumentation.redefineClasses ,让JVM重新该Java类,这样我们就可以使用Agent机制修改该类的字节码了。
  2. premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
  3. 类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses()方法,此方法有以下限制:
    1. 新类和老类的父类必须相同;
    2. 新类和老类实现的接口数也要相同,并且是相同的接口;
    3. 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致;
    4. 新类和老类新增或删除的方法必须是private static/final修饰的;
    5. 可以修改方法体。

破解IDEA小案例

下面拿一个Javasec的里面的案例来做一个测试,复制该代码

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();
    }

}

这里是模拟了一个IDEA的检测激活功能。

执行如下

Java 安全之Java Agent

现在需要的就是将这个检测的激活的CrackLicenseTest这个类给HOOK掉。

下面来编写一下代码。

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, final Instrumentation inst) {
        loadAgent(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, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, 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;
            }
        };
    }

}

这个不知道为啥自己做的时候没有成功,贴一张成功的图过来。

Java 安全之Java Agent

补充一张原理图

.
Java 安全之Java Agent

参考文章

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

0x04 结尾

在中途中会遇到很多坑,比如tools.jar的jar包在windows下找不到,需要手工去Java jdk的lib目录下然后将该包手工进行添加进去。学习就是一个排坑的过程。假设用Java agent 需要在反序列化或者是直接打入内存马该怎么去实现?其实y4er师傅文中有提到过一些需要注意点和考虑到的点。这个后面再去做实现。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/119898.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 白盒测试技术_静态白盒测试

    白盒测试技术_静态白盒测试覆盖率 它是度量测试完整性的一个工具,通常可以分为逻辑覆盖和功能覆盖。覆盖率=(被执行到的项数/总项数)*100%逻辑覆盖 逻辑覆盖是以程序内部的逻辑结构为基础设计测试用例的技术,属于白盒测试。      被测试模块的流程图语句覆盖 设计若干测试用例,运行被测程序,使每个可执行语句至少执行一次。 语句覆盖率=被评价到的语句数量/

  • Graphics2D 绘制图形

    Java语言在Graphics类提供绘制各种基本的几何图形的基础上,扩展Graphics类提供一个Graphics2D类,它拥用更强大的二维图形处理能力,提供、坐标转换、颜色管理以及文字布局等更精确的控制。绘图属性Graphics2D定义了几种方法,用于添加或改变图形的状态属性。可以通过设定和修改状态属性,指定画笔宽度和画笔的连接方式;设定平移、旋转、缩放或修剪变换图形;以及设定填充图

  • 网页视频下载方法[通俗易懂]

    问题有时候我们在做PPT或者撰写一些报告、案例的时候,需要一些视频作为素材,网上搜到后,想下载却比较麻烦,有的在专业视频网站上,有的在新闻网站上,有的在机构网站上,有的在社交媒体上,有没有简便、快速、可行的视频下载方法,并且不需要付费或者安装额外软件呢。下面说明几种方法,基本可以涵盖绝大多数情况。解决办法非专业视频网站上的视频以下两种办法需要使用谷歌浏览器Chrome电脑版打开视频所在的网页,右键——>审查元素——>点击左上角的小箭头——>在页面中选中视频界面——>在审查

  • QXDM打印高通sensor 日志问题总结

    QXDM打印高通sensor 日志问题总结在使用QXDM打印高通sensor日志的时候,经常会发现有些赋予已经权限很高的log居然打印不出来,这就个代码的追踪带来了一系列困难,鉴于此,我研究了一下高通中log打印问题,给大家今后的使用带来一些经验。在高通的关于日志的头文件定义中,许多日志是默认不打开的,研究代码:#if(BUILD_DRAGON_BOARD)&&(DEBUG_DATA)#defineLSM6DSM_DATA_M

  • redis过期策略六种(java的内存回收机制)

    Redis缓存作为提高系统性能最好的方式相信大家对其一定不陌生,各位作为秃头老码农不仅需要掌握Redis的基础用法还得了解Redis的相关原理,比如Redis过期策略和内存淘汰机制。大家都知道,Redis缓存使用的是内存资源,虽然缓存服务器会配置比较高的内存资源,但如果对于Redis中的缓存数据我们不管不顾,内存资源总有耗尽的时候,这时缓存服务器就无法再对外提供服务了。我们要用有限的服务器资源支撑…

  • Android telephony_android获取真实时间

    Android telephony_android获取真实时间做一波获取手机卡LET的信息操作。看了一波源码写出来的一些东西首先需要的一些权限(危险权限动态获取一下,之前的里面有):<uses-permissionandroid:name=”android.permission.INTERNET”/><uses-permissionandroid:name=”android.permission.ACCESS_WIFI_ST…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号