Skip to content

Commit dad9053

Browse files
committed
Add controller check algorithm
1 parent c30ecfc commit dad9053

File tree

6 files changed

+163
-25
lines changed

6 files changed

+163
-25
lines changed

IDEA.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
还可发展的方向(欢迎提交Pr):
2+
3+
- 目前暂未支持Interceptor、Filter、Servlet等内存马检测算法。
4+
- 目前可以支持Attach Agent,但是还未实现Self Attach JVM的方式运行。
5+
- 目前Sink函数只有Runtime.exec,还可以添加其他恶意函数进行检测。
6+
- 添加Agent启动参数,用于控制是否自动删除内存马/是否开启DumpClass功能

README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,52 @@
22
Java Agent memory horse scanner combined with Call Graph modus
33

44

5+
![Platforms](https://img.shields.io/badge/Platforms-JavaAgent-brightgreen.svg)
6+
![Java version](https://img.shields.io/badge/Java-8-green.svg)
7+
![License](https://img.shields.io/badge/License-Mozilla-green.svg)
8+
9+
10+
### About
11+
12+
MemoryShellHunter是一款结合动态Building Call Graph的内存马Scanner/Killer工具。支持Agent和Attach方式启动检测,弥补常规内存马检测工具需要人工验证WebSocket内存马的问题。
13+
14+
MemoryShellHunter项目使用了逆拓扑算法精确捕获恶意方法的调用行为,可弥补SpringBoot内存马查杀的难点和WebSocket新型内存马无法从Class文件是否落地上进行判断。有着性能影响低于一般的RASP检测、属于轻量级Agent、对业务代码侵入性小等特点。
15+
16+
17+
518

619
### How to used
720

821
```bash
922
java -javaagent:./MemoryShellHunter.jar -jar SpringBootRunner.jar
1023
```
1124

25+
```java
26+
VirtualMachine vmObj = VirtualMachine.attach(targetJvmPid);//targetJvmPid为目标JVM的进程ID
27+
vmObj.loadAgent(agentJarPath, cfg); // agentJarPath为MemoryShellHunter jar包的路径,cfg为传递给agent的参数
28+
```
29+
1230

1331

1432
### Supported middleware
1533

34+
1.1 Version:
35+
36+
- Add Controller memory shell check algorithm
37+
1638
1.0 Version:
1739

18-
- WebSocket Memory Shell
40+
- Add WebSocket memory shell check/delete algorithm
1941

2042

2143

2244
### Show results
2345

46+
##### WebSocket Memory Shell Test Report
47+
2448
![1666788512005](./images/renderings.png)
2549

50+
51+
52+
##### Controller Memory Shell Test Report
53+
![controller](./images/controller.png)

images/controller.png

83.3 KB
Loading

src/main/java/com/websocket/findMemShell/SearchCallsThread.java

+67-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.websocket.findMemShell;
22

3+
import java.net.URL;
34
import java.util.ArrayList;
45
import java.util.List;
56
import java.util.Map;
67
import java.util.Stack;
78

9+
import com.websocket.findMemShell.checkAndDel.getControllerResult;
10+
import com.websocket.findMemShell.checkAndDel.getWsConfigResult;
11+
812
public class SearchCallsThread extends Thread{
913
Map<String,List<String>> discoveredCalls;
1014
String sinkMethod = "java/lang/Runtime#exec";
@@ -16,34 +20,75 @@ public SearchCallsThread(Map<String,List<String>> discoveredCalls) {
1620
this.discoveredCalls = discoveredCalls;
1721
}
1822

23+
public void checkWsConfig(ConfigPath cp) {
24+
//System.out.println("WsConfig Class: \n"+cp.getClassName().replaceAll("\\.", "/")+"#onMessage"+"\n");
25+
26+
if(discoveredCalls.containsKey(cp.getClassName().replaceAll("\\.", "/")+"#onMessage")) {
27+
List<String> list = discoveredCalls.get(cp.getClassName().replaceAll("\\.", "/")+"#onMessage");
28+
for(String str : list) {
29+
if(dfsSearchSink(str)) {
30+
stack.push(str);
31+
stack.push(cp.getClassName().replaceAll("\\.", "/")+"#onMessage");
32+
StringBuilder sb = new StringBuilder();
33+
while(!stack.empty()) {
34+
sb.append("->");
35+
sb.append(stack.pop());
36+
}
37+
System.out.println("CallEdge: "+sb.toString());
38+
if(getWsConfigResult.deleteConfig(cp.getPath())) {
39+
System.out.println("Delete Class "+cp.getPath()+" Succeed");
40+
}else {
41+
System.out.println("Delete Class "+cp.getPath()+" Failed");
42+
}
43+
break;
44+
}
45+
}
46+
}
47+
}
48+
49+
public void checkControllerPath(ConfigPath cp) {
50+
// //内存马常规检测方式
51+
// String className = cp.getClassName().split("#")[0];
52+
// String classNamePath = className.replace(".", "/") + ".class";
53+
// URL is = App.servletContext.getClass().getClassLoader().getResource(classNamePath);
54+
// if (is == null) {
55+
// return "在磁盘上没有对应class文件,可能是内存马";
56+
// } else {
57+
// return is.getPath();
58+
// }
59+
60+
//System.out.println("Controller Class: \n"+cp.getClassName().replaceAll("\\.", "/"));
61+
62+
if(discoveredCalls.containsKey(cp.getClassName().replaceAll("\\.", "/"))) {
63+
List<String> list = discoveredCalls.get(cp.getClassName().replaceAll("\\.", "/"));
64+
for(String str : list) {
65+
if(dfsSearchSink(str)) {
66+
stack.push(str);
67+
stack.push(cp.getClassName().replaceAll("\\.", "/"));
68+
StringBuilder sb = new StringBuilder();
69+
while(!stack.empty()) {
70+
sb.append("->");
71+
sb.append(stack.pop());
72+
}
73+
System.out.println("Controller CallEdge: "+sb.toString());
74+
break;
75+
}
76+
}
77+
}
78+
}
79+
1980
@Override
2081
public void run() {
2182
while(true) {
2283
List<ConfigPath> result = getWsConfigResult.getWsConfig();
84+
getControllerResult.getControllerMemShell(result);
2385
if(result != null && result.size() != 0) {
2486
for(ConfigPath cp : result) {
25-
System.out.println("WsConfig Class: \n"+cp.getClassName().replaceAll("\\.", "/")+"#onMessage"+"\n");
26-
27-
if(discoveredCalls.containsKey(cp.getClassName().replaceAll("\\.", "/")+"#onMessage")) {
28-
List<String> list = discoveredCalls.get(cp.getClassName().replaceAll("\\.", "/")+"#onMessage");
29-
for(String str : list) {
30-
if(dfsSearchSink(str)) {
31-
stack.push(str);
32-
stack.push(cp.getClassName().replaceAll("\\.", "/")+"#onMessage");
33-
StringBuilder sb = new StringBuilder();
34-
while(!stack.empty()) {
35-
sb.append("->");
36-
sb.append(stack.pop());
37-
}
38-
System.out.println("CallEdge: "+sb.toString());
39-
if(getWsConfigResult.deleteConfig(cp.getPath())) {
40-
System.out.println("Delete Class "+cp.getPath()+" Succeed");
41-
}else {
42-
System.out.println("Delete Class "+cp.getPath()+" Failed");
43-
}
44-
break;
45-
}
46-
}
87+
if(!cp.getClassName().contains("#")) {
88+
checkWsConfig(cp); //Check WebSocket Memory Shell
89+
}else {
90+
//Normal Memory Shell Checked
91+
checkControllerPath(cp);
4792
}
4893
}
4994
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.websocket.findMemShell.checkAndDel;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.Method;
5+
import java.util.ArrayList;
6+
import java.util.Collection;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Set;
10+
11+
import com.websocket.findMemShell.App;
12+
import com.websocket.findMemShell.ConfigPath;
13+
14+
public class getControllerResult {
15+
public static void getControllerMemShell(List<ConfigPath> classList) {
16+
try {
17+
Object servletContext = App.servletContext;
18+
if(servletContext == null) {
19+
return;
20+
}
21+
Method getAttribute = servletContext.getClass().getClassLoader().loadClass("org.apache.catalina.core.ApplicationContextFacade").getDeclaredMethod("getAttribute", String.class);
22+
Object context = getAttribute.invoke(servletContext, "org.springframework.web.context.WebApplicationContext.ROOT");
23+
Method getBean = servletContext.getClass().getClassLoader().loadClass("org.springframework.beans.factory.BeanFactory").getDeclaredMethod("getBean", Class.class);
24+
Class requestMappingHandlerMapping = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
25+
Object mappingHandlerMapping = getBean.invoke(context, requestMappingHandlerMapping);
26+
27+
Field mappingRegistryField = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredField("mappingRegistry");
28+
mappingRegistryField.setAccessible(true);
29+
Object mappingRegistry = mappingRegistryField.get(mappingHandlerMapping);
30+
Method getRegistrations = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredMethod("getRegistrations");
31+
getRegistrations.setAccessible(true);
32+
Map<?, ?> registry = (Map<?, ?>) getRegistrations.invoke(mappingRegistry);
33+
34+
Method getMapping = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistration").getDeclaredMethod("getMapping");
35+
getMapping.setAccessible(true);
36+
Method getPatternsCondition = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.mvc.method.RequestMappingInfo").getDeclaredMethod("getPatternsCondition");
37+
getPatternsCondition.setAccessible(true);
38+
Method getPatterns = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.mvc.condition.PatternsRequestCondition").getDeclaredMethod("getPatterns");
39+
getPatterns.setAccessible(true);
40+
Method getHandlerMethod = servletContext.getClass().getClassLoader().loadClass("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistration").getDeclaredMethod("getHandlerMethod");
41+
getHandlerMethod.setAccessible(true);
42+
Collection<?> registryValues = registry.values();
43+
for(Object value : registryValues) {
44+
Object mapping = getMapping.invoke(value);
45+
Object patternsCondition = getPatternsCondition.invoke(mapping);
46+
Set<String> patterns = (Set<String>) getPatterns.invoke(patternsCondition);
47+
String path = patterns.iterator().next();
48+
String methodName = getHandlerMethod.invoke(value).toString().split("\\(")[0];
49+
50+
ConfigPath cp = new ConfigPath(path,methodName);
51+
classList.add(cp);
52+
}
53+
}catch(Exception e) {
54+
e.printStackTrace();
55+
}
56+
}
57+
}

src/main/java/com/websocket/findMemShell/getWsConfigResult.java src/main/java/com/websocket/findMemShell/checkAndDel/getWsConfigResult.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.websocket.findMemShell;
1+
package com.websocket.findMemShell.checkAndDel;
22

33
import java.lang.reflect.Field;
44
import java.lang.reflect.Method;
@@ -8,6 +8,9 @@
88
import java.util.Set;
99
import java.util.concurrent.ConcurrentHashMap;
1010

11+
import com.websocket.findMemShell.App;
12+
import com.websocket.findMemShell.ConfigPath;
13+
1114
public class getWsConfigResult {
1215

1316
public static boolean deleteConfig(String className) {
@@ -53,7 +56,6 @@ public static List<ConfigPath> getWsConfig() {
5356

5457
// 遍历configExactMatchMap, 打印所有注册的 websocket 服务
5558
Set<String> keyset = configExactMatchMap.keySet();
56-
StringBuilder sb = new StringBuilder();
5759
for (String key : keyset) {
5860
System.out.println("configExactMatchMap key:" + key);
5961
Object object = servletContext.getClass().getClassLoader().loadClass("org.apache.tomcat.websocket.server.WsServerContainer").getDeclaredMethod("findMapping", String.class).invoke(wsServerContainer, key);

0 commit comments

Comments
 (0)