Java Agent 内存马攻防

本文详细介绍了Java Agent的使用,包括premain和agentmain两种方式,以及如何通过VirtualMachine、Instrumentation和Transform API来实现字节码修改。还探讨了如何利用Java Agent注入和检测内存马,以及内存马的免杀策略,如破坏检测Agent的加载和阻止Agent的加载。最后,文章提到了网络安全学习路线和相关资源。
摘要由CSDN通过智能技术生成

前言

在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的
Api,用于监控、收集性能信息、诊断问题等。通过 java.lang.instrument 实现的工具我们称之为 Java Agent ,Java
Agent 能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法等。

Java Agent的使用方式有两种(图源先知社区):

  • premain方法,在JVM启动前加载。

image-20220107135410448.png

  • agentmain方法,在JVM启动后加载。

image-20220107135443296.png

在实际环境中,目标的JVM通常都是已经启动的状态,无法预先加载premain。相比之下,孰轻孰重已不言而喻。

premain和agentmain函数声明如下,方法名相同情况下,拥有Instrumentation inst参数的方法优先级更高:

public static void agentmain(String agentArgs, Instrumentation inst) {
    ...
}

public static void agentmain(String agentArgs) {
    ...
}

public static void premain(String agentArgs, Instrumentation inst) {
    ...
}

public static void premain(String agentArgs) {
    ...
}

JVM 会优先加载带Instrumentation签名的方法,加载成功则忽略第二种。如果第一种没有,则加载第二种方法。

  • 第一个参数String agentArgs就是Java agent的参数。

  • Inst是一个java.lang.instrument.Instrumentation的实例,可以用来类定义的转换和操作等等。

premain 方式

JVM启动时 会先执行premain方法,大部分类加载都会通过该方法,注意: 是大部分,不是所有 。遗漏的主要是系统类,因为很多系统类先于
agent 执行,而用户类的加载肯定是会被拦截的。也就是说, 这个方法是在 main 方法启动前拦截大部分类的加载活动
,既然可以拦截类的加载,就可以结合第三方的字节码编译工具,比如ASM,javassist,cglib等来改写实现类。

1)创建应用程序hello.jar

package com.nsfocus.test

public class hello {
    public static void main (String[] args) {
        System.out.println("hello world");
    }
}

将com.nsfocus.test.hello打包成hello.jar后单独执行java -jar hello.jar

image-20220120155044236.png

2)创建premain方式的Agent

package com.nsfocus.test

import java.lang.instrument.Instrumentation;

public class PreDemo {
    public static void premain(String args, Instrumentation inst) throws Exception{
        for (int i = 0; i < 10; i++) {
            System.out.println("I'm premain agent");
        }
    }
}

此时项目如果打包成jar包执行,则会因绝少入口main而报错(Java默认main为入口)。故需自定义一个MANIFEST.MF文件,用于指明premain的入口:

Manifest-Version: 1.0
Premain-Class: com.nsfocus.test.PreDemo

注:最后一行是 空行,不能省略 。以下是MANIFEST.MF的其他选项:

Premain-Class: 包含 premain 方法的类(类的全路径名)
Agent-Class: 包含 agentmain 方法的类(类的全路径名)
Boot-Class-Path: 设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
Can-Redefine-Classes: true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)

3)使用premain进行注入

java -javaagent:PreDemo.jar -jar hello.jar

image-20220120155425949.png

agentmain 方式

写一个agentmainpremain差不多,只需要在META-INF/MANIFEST.MF中加入Agent-Class:即可。

agent:

package com.nsfocus.test

import java.lang.instrument.Instrumentation;

public class AgentDemo {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        for (int i = 0; i < 10; i++) {
            System.out.println("I'm agentmain agent");
        }
    }
}

META-INF/MANIFEST.MF:

Manifest-Version: 1.0
Agent-Class: com.nsfocus.agent.AgentDemo
Can-Retransform-Classes: true
Can-Redefine-Classes: true

不同之处在于,这种方法不是在JVM启动前使用参数来指定的。官方为了实现启动后的加载,提供了Attach API。Attach API 很简单,只有 2
个主要的类,都在com.sun.tools.attach包里面。需要着重关注的是VitualMachine这个类,它用来与目标JVM建立连接,从而在启动后加载我们的agentmain。

:Linux、Windows等不同下的Attach API不尽相同,详见下文。

:总的来说,agentmain的实现也并不是很难理解。笔者将其简要概括为三个阶段:

连接(VirtualMachine) => 加载(Instrumentation) => 修改(Javassist),详见下文。

VirtualMachine

字面意义表示虚拟机,也就是Agent程序需要监控的目标JVM。它提供了获取系统信息、loadAgentAttachDetach等方法,可以实现的功能非常强大
。该类允许我们给attach方法传入一个JVM的pid,远程连接到目标JVM上
。代理类注入操作只是它众多功能中的一个,我们可以通过loadAgent方法向JVM注册一个代理程序Agent,在该Agent代理程序中将会得到一个Instrumentation实例。

VirtualMachine的用法:

// com.sun.tools.attach.VirtualMachine

// 下面的示例演示如何使用VirtualMachine:

        // attach to target VM
        VirtualMachine vm = VirtualMachine.attach("2177");
        // start management agent
        Properties props = new Properties();
        props.put("com.sun.management.jmxremote.port", "5000");
        vm.startManagementAgent(props);
        // detach
        vm.detach();

// 在此示例中,我们附加到由进程标识符2177标识的Java虚拟机。然后,使用提供的参数在目标进程中启动JMX管理代理。最后,客户端从目标VM分离。

attacher:

package com.nsfocus.attacher;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

public class AgentAttach {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        String id = args[0];
        String jarName = args[1];

        System.out.println("id ==> " + id);
        System.out.println("jarName ==> " + jarName);

        VirtualMachine virtualMachine = VirtualMachine.attach(id);
        virtualMachine.loadAgent(jarName);
        virtualMachine.detach();

        System.out.println("ends");
    }
}



Manifest-Version: 1.0
Main-Class: com.nsfocus.attacher.AgentAttach

过程非常简单:通过pid attach到目标JVM - > 加载agent -> 解除连接。

后话 :在windows下将该项目打包为attacher.jar后,复制到linux下执行报错:

Exception in thread "main" java.lang.UnsatisfiedLinkError: sun.tools.attach.WindowsAttachProvider.tempPath()Ljava/lang/String;
        at sun.tools.attach.WindowsAttachProvider.tempPath(Native Method)
        at sun.tools.attach.WindowsAttachProvider.isTempPathSecure(WindowsAttachProvider.java:74)
        at sun.tools.attach.WindowsAttachProvider.listVirtualMachines(WindowsAttachProvider.java:58)
        at com.sun.tools.attach.VirtualMachine.list(VirtualMachine.java:134)
        at sun.tools.jconsole.LocalVirtualMachine.getAttachableVMs(LocalVirtualMachine.java:151)
        at sun.tools.jconsole.LocalVirtualMachine.getAllVirtualMachines(LocalVirtualMachine.java:110)
        ...

前面已经提到了,不同的AttachProvider适用于不同的平台,即不同平台下的${JAVA_HOME}/lib/tools.jar有略微的差别:

[solaris] sun.tools.attach.SolarisAttachProvider
[windows] sun.tools.attach.WindowsAttachProvider
[linux]   sun.tools.attach.LinuxAttachProvider

image-20220120162000974.png

将项目拷贝到linux下再打包,或者将windows下的tools.jar替换为linux下的tools.jar均能解决这个问题。

Instrumentation

通过attacher连接到目JVM后,可以通过instrumentation类和目标JVM上的类进行交互,为动态修改字节码奠定基础。

官方文档:[java.lang.instrument (Java SE 9 & JDK 9 )
(oracle.com)](https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/package-
summary.html)

public interface Instrumentation {

    // 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。
    void addTransformer(ClassFileTransformer transformer);

    // 删除一个类转换器
    b
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值