ARTICLE / 安全
Java 内存马应急检测实战:从手册到开源工具的全栈演进
摘要
内存马(Memory Shell)已成为攻陷 Java 应用后的首选持久化手段——它在 JVM 内存中动态注册恶意组件,磁盘无文件、进程无异常、杀软无感知,传统基于文件的检测体系完全失效。本文以实战视角完整呈现 Java 内存马的检测方法论、工具设计演进与取证闭环:从信号分级体系(A1-A5 强信号 + B1-B5 辅助信号)到开源工具 memshell-auditor 的落地,再到双程序防识别架构、类 Metasploit 特征库生态与 AI 增强分析。全部结论基于真实实验:用 java-memshell-generator(JMG)生成 7 种真实内存马载荷,注入 Tomcat 9/10 靶场逐一验证,检出率 100%。
一、引言:为什么内存马检测是应急响应的硬骨头
1.1 研究问题
在应急响应实践中,Java 应用被攻陷后(反序列化、表达式注入、文件上传等入口),攻击者最常植入的就是内存马。它具备三个让传统检测失效的特性:
- 磁盘无文件:恶意类通过
ClassLoader.defineClass直接注入 JVM 内存,磁盘上不存在任何恶意文件 - 进程无异常:恶意代码寄生在合法进程(Tomcat/Spring Boot)内,进程列表看起来完全正常
- 持久化免杀:攻击者拿到一次执行机会即可植入,且内存马随业务流量长期存活,杀软/EDR 无从感知
由此引出三个核心研究问题:
- RQ1:内存马有哪些类型与注入方式?最新技术动向是什么?
- RQ2:如何在运行时可靠检测内存马,且不依赖"已知特征"(能对抗未知变种)?
- RQ3:检测出内存马后,如何完成取证闭环(dump 恶意类、反编译核心代码、分析回连),并把特征反哺给社区?
1.2 相关工作
现有检测方案存在明显空白:
- 静态扫描(扫描 class/jar 文件):对内存马无效,因为根本没有文件
- 容器层扫描器(如 java-memshell-scanner):检测 Filter/Servlet 层,但对 Agent 型内存马、伪装类名(如
org.springframework.ServletRequestAujFilter)无效 - RASP 类产品:能拦截但误报高、部署重,不适合应急现场
差异化定位:memshell-auditor 通过 attach 方式(类 Arthas 但更聚焦),零依赖纯反射实现,覆盖容器层 + Agent 层 + 启发式行为检测,并内置取证闭环。
二、内存马技术全景
2.1 分类体系
| 类型 | 注入点 | 特性 | 典型工具 |
|---|---|---|---|
| Filter 型 | Servlet 容器过滤器 | 最主流,拦截所有请求 | 冰蝎/哥斯拉/蚁剑 |
| Servlet 型 | 动态注册 Servlet | 独立 URL 入口 | 哥斯拉 |
| Listener 型 | ServletRequestListener | 每次请求触发,隐蔽 | 冰蝎 |
| Valve 型 | Catalina Valve | 绕过 Filter 链检测 | 哥斯拉 |
| WebSocket 型 | Endpoint 动态注册 | 不走 HTTP Filter | 少见但更难发现 |
| Upgrade 型 | 协议处理器 | 绕过 Servlet 容器 | 高级对抗 |
| Agent 型 | Instrumentation transformer | 改写任意类字节码 | 无侵入 Agent |
| JSP 变体 | JSP 预编译 | 磁盘有文件,可静态查 | 传统 |
2.2 注入链路
典型攻击链:反序列化漏洞(入口)→ 表达式/反射执行(载荷投递)→ ClassLoader.defineClass(内存加载)→ 容器 API 注册(持久化)。JDK17+ 模块系统限制反射 defineClass 后,攻击者改用 MethodHandles.Lookup.defineClass(实测验证)。
2.3 2025-2026 最新技术动向(GitHub 实时情报)
- 生成器 MemShellParty(⭐1572)适配 JDK 6-21、Tomcat 5-11、Jetty/WebLogic/WebSphere/JBoss 及国产化中间件(TongWeb/BES/InforSuite/Apusic/Primeton)
- Agent 型内存马 ASM 轻量化:体积减 80%,无侵入注入
- 探测马自动识别服务类型,集成哥斯拉/冰蝎/蚁剑/Suo5/NeoreGeorg 隧道
- 检测端对抗升级:改 ClassLoader、隐藏 FilterConfig、ThreadLocal 存恶意逻辑、不死化持久化
- 类名伪装成常态:JMG 实测所有载荷类名伪装成 Spring/Log4j 框架类(见 §5.2)
三、检测方法论:信号分级体系
核心思路:不猜"内存马长什么样",而是检测"类做了什么不该做的事"。
3.1 强信号(A 系,一锤定音)
| 信号 | 检测项 | 判定 |
|---|---|---|
| A1 | 容器组件(Filter/Servlet/Listener/Valve)注册的类在磁盘无对应 class 文件 | 🔴 高度疑似 |
| A2 | 容器组件注册数量异常(对比基线) | 🔴 需复核 |
| A3 | 非系统 ClassLoader 加载恶意类 | 🟠 需复核 |
| A4 | -javaagent/agentlib/系统属性注入 + 可疑 ClassFileTransformer | 🔴 高度疑似 |
| A5 | 启动命令/环境变量异常 | 🔴 需复核 |
3.2 辅助信号(B 系,组合提升置信度)
| 信号 | 检测项 | 对抗性 |
|---|---|---|
| B1 | 类名特征(无包名/短随机名/大小写混淆) | 可被伪装绕过 |
| B2 | 类名含恶意关键字 | 可被伪装绕过 |
| B3-B5 | 行为模式辅助 | 组合评分 |
3.3 启发式行为检测(对抗未知变种)
从字节码可读字符串提取行为模式组合评分:命令执行(Runtime.exec/ProcessBuilder)+ 动态加载(defineClass)+ 载荷解密(Base64/AES/Cipher)+ 网络回连(Socket/硬编码 IP)+ WebShell 回显(getParameter + 响应流)。容器组件特征 + ≥2 个行为模式 → 判定可疑,类名伪装无效。
四、工具设计演进:memshell-auditor
4.1 v1.0:核心检测能力
零依赖纯反射实现(JDK 8 编译,兼容目标 JVM 8-21+),五大审计器:
| 模块 | 能力 |
|---|---|
| ContainerAuditor | Filter(filterDefs+filterConfigs 双路径)/Servlet/Listener/Valve 全链路审计 |
| AgentAuditor | 启动参数/Agent 型检测 |
| TransformerAuditor | ClassFileTransformer 审计 |
| ClassLoaderAuditor | ClassLoader 血缘 |
| ClassFeatureAuditor | defineClass + 磁盘无 class → A1 强信号 |
关键技术难点:容器上下文定位。WebappClassLoaderBase.getResources() 方法反射在部分版本返回 null,最终改用 resources 字段反射兜底成功拿到 StandardContext(坑 2)。
4.2 v1.1:取证闭环
检出不是终点——还要能 dump、反编译、分析回连:
- ClassDumper:字节码提取落盘(transformer + retransform 技巧,
addTransformer(tf, true)的 canRetransform 必须为 true,坑 4) - Decompiler:javap 反汇编提取恶意核心代码(命令执行/解密/网络方法)
- NetworkAnalyzer:进程 ESTABLISHED 外连分析 + 字节码字符串 IP 提取
- MemoryForensics:jmap 堆内存取证(跨平台)
实测:冰蝎内存马 dump 4160B 字节码,反编译展示核心逻辑:
4.3 v1.2:双程序防识别架构(核心对抗设计)
问题:固定名字的取证工具(memshell-auditor.jar)进程一出现,攻击者立刻知道被取证了,可隐藏内存马、销毁证据甚至反制。
方案:--gen-agent 生成混淆取证程序——每次随机文件名/包名/类名/字符串特征:
| 主程序(分析端) | 取证程序(现场端) | |
|---|---|---|
| 位置 | 分析者机器 | 目标系统现场 |
| 文件名 | memshell-auditor.jar | system-diag-2c4488.jar(随机) |
| 类名/包名 | com.memshellauditor.* | net.jvm.check.*(每次随机) |
| 特征字符串 | memshell/auditor | javacore/classcore(中性化) |
| 规则匹配 | ✅ | ✅(内置规则,离线可用) |
| AI 能力 | ✅ | ❌(AI 仅在主程序) |
技术实现:class 文件常量池重写器(ClassRewriter)——纯字节码级替换 Utf8 条目,不依赖 JDK 编译。三个关键坑:jar 重写时 MANIFEST.MF 重复(ZipException)、替换规则顺序导致包名二次替换(LinkedHashMap 保证顺序)、类名不能参与敏感词替换(this_class 不一致,坑 10-12)。
4.4 v1.3:全自动扫描(–scan)
现场取证人员不需要知道审计哪个 PID:自动枚举所有 Java 进程 → 可疑度评分排序(Web 容器 +100 优先、可疑关键字 +60、工具/守护 -30、自身 -1000 跳过)→ 逐个审计 → 汇总 HIGH 排行。
实测 9 进程环境:自动识别 4 个高危目标(HIGH=2/1/1/1)+ 1 个正常,全程无需人工指定 PID。
4.5 v2.0:特征库生态 + AI 增强
特征库(类 Metasploit):x7peeps/memshell-rules 仓库,CLI 在线更新:
AI 增强分析:OpenAI 通用兼容接口(base_url + api_key + model 三要素可配),兼容 OpenAI/DeepSeek/通义/Ollama/vLLM;可配可跳过,未配置时结尾引导三种配置方式,重跑自动带 AI 增强展示(恶意行为解读/回连判断/处置建议)。
特征提交闭环(众包反哺):--analyze 检测未命中规则的高危项 → 提示提交 → --submit --auto-commit 自动推送;push 失败降级生成 GitHub Issue(五大段描述:场景/检出详情/规则/复现/说明)+ 自动打开新建 Issue 页面。
五、实战验证:JMG 真实载荷 7/7 检出
5.1 实验设计
用 JMG v1.0.9 生成 7 种真实内存马(冰蝎 Filter/Listener/Listener2、哥斯拉 Filter/Valve、蚁剑 Filter、Suo5 Filter),注入 Tomcat 9(javax)与 Tomcat 10(jakarta)靶场,逐一验证检测能力。
5.2 实验结果
| 载荷 | 工具 | 伪装类名 | 检出 |
|---|---|---|---|
| 冰蝎 Filter | Behinder | org.springframework.ServletRequestAujFilter | ✅ HIGH |
| 哥斯拉 Filter | Godzilla | org.springframework.WhiteBlackListGbyfbdFilter | ✅ HIGH |
| 蚁剑 Filter | AntSword | org.springframework.AbstractMatcherVyjFilter | ✅ HIGH |
| Suo5 Filter | Suo5 | org.springframework.SessionKqvcFilter | ✅ HIGH |
| 冰蝎 Listener | Behinder | org.apache.logging.Log4jConfigEaeListener | ✅ HIGH |
| 哥斯拉 Valve | Godzilla | org.springframework.AbstractMatcherGbValve | ✅ HIGH |
| 冰蝎 Listener2 | Behinder | org.springframework.ContextLoaderDmasjListener | ✅ HIGH |
检出率 100%(7/7)。关键发现:所有载荷类名伪装成 Spring/Log4j 框架类——B1 类名特征检测完全失效,但 A1 磁盘无 class 强信号不受影响。这是工具的差异化护城河。
5.3 误报控制
- 磁盘类(BizFilter)→ 正常(INFO)
- Agent 自身类 → 动态自排除(ReflectUtil.isSelfClass 兼容混淆版)
- JDK/框架类 → 豁免
- 混淆取证程序 attach 后自身误报从 19 条清零到 0
六、版本管理产品设计
6.1 版本分离自动更新
| 维度 | 工具版本 | 策略版本 |
|---|---|---|
| 标识 | v2.2 | 1.1.0 |
| 更新命令 | –update(GitHub releases) | –rules update |
| 展示 | banner 署名 x7peeps | banner 显示策略版本 |
6.2 策略更新失败 → 版本推进
策略更新出问题时本地版本自动 bump(1.1.0 → 1.1.1),banner 显示 partial/failed 状态警告并建议重试——让"更新出问题"可被识别。
6.3 自定义规则保护(不误删)
官方规则存 rules/、用户自定义存 rules-custom/(origin 字段区分);更新只覆盖官方,自定义永不触碰。实测 CUSTOM-001 自定义规则经多次 update 均安全保留。
6.4 增量同步不整体替换
只更新官方 index 中的规则,只删除"官方目录中已从 index 移除"的规则,自定义目录完全不动。
七、踩坑大全(实战经验速查)
| # | 坑 | 根因 | 解决 |
|---|---|---|---|
| 1 | lsof 抓错进程连接 | macOS lsof -p 与 -iTCP 组合混入系统连接 | 外部 IP 过滤 + 代码字符串 IP 提取 |
| 2 | 容器上下文定位失败 | getResources() 方法反射返回 null | 改 resources 字段反射 |
| 3 | 同一 JVM 重复 attach 不生效 | agent 类被系统 ClassLoader 缓存 | 每次改代码必须重启目标进程 |
| 4 | retransform 不触发 | addTransformer 的 canRetransform 必须为 true | 传 true |
| 5 | JMG 混淆载荷 retransform 失败 | 激进 ASM 处理,JVM 拒绝重定义 | CodeSource=null 作证据 + jmap 兜底 |
| 6 | 混淆 jar 打包规则不生效 | 规则文件名与 id 不匹配 | 按 id 重命名 |
| 7 | 取证程序 CLI 暴露分析端 | Main-Class 指向错误 | 新增 ForensicMain 分离 |
| 8 | 自动提交污染官方索引 | index.json 整体覆盖 | 改 mergeIndex 合并 |
| 9 | class 常量池替换顺序错误 | HashMap 无序遍历 | LinkedHashMap 保证顺序 |
| 10 | 类名被敏感词替换 | this_class 与文件路径不一致 | 只替换包名前缀 |
八、局限性与未来方向
已知局限:
- JMG 混淆载荷(Suo5 等激进 ASM 处理)无法 retransform dump,依赖 jmap 堆 dump 兜底
- Agent 型内存马 transformer 枚举受 JDK 模块限制,内部字段探测 + 类特征间接检测
- 回连分析会混入本机其他服务连接(代理/远程控制),需结合威胁情报确认
- 运行时窗口:attach 只能看到当前已加载的类
未来方向:
- 并发扫描提速(资源允许时多线程审计)
- 总览报表(HTML 汇总所有进程结果)
- 国产中间件专项适配验证(TongWeb/BES)
- 规则库持续扩充(社区贡献)
- 与威胁情报 API 联动(回连地址自动查询)
九、结语
从信号分级方法论到开源工具全栈实现,本文完整呈现了 Java 内存马应急检测的实战路径。核心结论:检测内存马的关键不是"认出它",而是"发现异常"——A1 磁盘无 class 强信号、行为模式组合评分、双程序防识别、众包特征反哺,构成了可对抗未知变种的完整检测体系。工具开源在 github.com/x7peeps/memshell-auditor,特征库在 github.com/x7peeps/memshell-rules,欢迎社区贡献特征、共同成长。
附录:复现指南
环境
- macOS (Apple Silicon) / JDK 26 编译(–release 8)/ Maven
- Tomcat 9.0.89(javax)/ Tomcat 10.1.24(jakarta)
- JMG v1.0.9(源码构建)








