依据《内存马应急排查手册 v2.0》实现的 只读 自动化排查程序。上机后一条命令即可完成 Java 进程 / Agent 排查、磁盘注入器扫描、配置篡改检查、网络连接分析、JVM 运行时检测、 日志分析与持久化排查,输出带风险等级的排查报告,替代手册中需逐条手工复制执行的几十条命令。
- 零第三方依赖:仅使用 Go 标准库,编译为单个静态二进制,天然适配 x86_64 / ARM64、 国产化环境与断网现场(拷贝二进制即可运行,无需装任何东西)。
- 只读安全:绝不删除文件、修改配置、杀进程或重启服务。清除操作按手册「四、内存马清除 操作」由人工在确认后执行——本工具只负责精准定位。
- 原生实现、少依赖外部命令:直接读
/proc、遍历文件系统、用archive/zip解析 jar、 解析/proc/net/tcp得到网络连接,不强依赖ps/find/grep/netstat/ss(这些在 应急现场可能被裁剪或被攻击者替换)。仅在做 JVM 运行时检测时可选调用 JDK 的jstack/jmap。
| 手册阶段 | 本工具实现 |
|---|---|
| 一 · Java 进程与启动参数 (1–5) | 遍历 /proc 找 Java 进程,检查 -javaagent/-agentpath/-Xbootclasspath、JAVA_TOOL_OPTIONS 等环境变量注入、(deleted) 文件句柄 |
| 二 · 磁盘注入器 (6–14) | 原生遍历应用目录,命中已知恶意类名 / 危险关键词的 JSP、非 lib 目录的可疑 .class、work/Catalina 编译产物、隐藏文件、Spring Boot fat jar 内部类 |
| 三 · 配置篡改 (15–19) | 解析 web.xml 的 filter/listener/servlet、server.xml 的 Valve、resin.xml,比对白名单与已知特征 |
| 四 · 网络连接 (20–23) | 纯 Go 解析 /proc/net/tcp[6] 并映射 socket→PID,标记非业务端口的外联/监听,识别内网代理隧道特征 |
| 五/六 · 运行时检测 (24–43) | 调用 JDK jstack/jmap(若存在)在堆栈/直方图中搜索已知恶意类与代理线程;Arthas 需交互式,给出操作指引 |
| 七 · 日志分析 (44–48) | 定位并分析 access log / catalina.out:WebSocket 升级请求、可疑 POST、类加载异常 |
| 八/九 · 持久化 (49–58) | crontab / cron.d / systemd / rc.local、启动脚本中的 -javaagent、authorized_keys、非标准目录 SUID |
| 四 · 内存马热卸载 (59–65) | 可选、需显式开启:attach 到运行中 JVM,交互确认后从内存移除恶意 Filter/Servlet/Listener 注册(手册 OGNL 热清除的自动化版) |
内置已知内存马特征库(冰蝎 EdwardsiidaeFilter、哥斯拉 PlasmodesmaFilter 及 5 个载荷组件、
代理马 Prepupa)与常见框架 Filter/Servlet 白名单(Spring / Shiro / Druid / Tomcat / Resin),
兼顾检出率与误报控制。
仓库已包含预编译的 agent/agent.jar,直接 go build 即可。若修改了 agent 源码,需用 JDK 重新构建:
sh agent/build.sh # 需要 javac/jar,产出 agent/agent.jar(供 go:embed 内嵌)
go build -o memcheck .
# 交叉编译(现场为 ARM64 时在本机编好拷过去)
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o memcheck-arm64 .
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o memcheck-amd64 .# 自动定位应用目录并全面排查(建议 root 运行以获得完整 /proc 与文件系统可见性)
sudo ./memcheck
# 指定应用根目录、只看近 7 天变更
sudo ./memcheck -root /opt/tomcat -days 7
# 输出 JSON 便于对接 SIEM / 自动化编排
sudo ./memcheck -json -o /tmp/memcheck.json
# 全盘扫描(慢)
sudo ./memcheck -full| 参数 | 说明 |
|---|---|
-root |
指定应用根目录,逗号分隔(默认自动定位;显式指定后以其为准) |
-full |
从 / 全盘扫描(慢) |
-days |
近期修改判定天数阈值(默认 30) |
-timeout |
磁盘扫描总超时(默认 3m) |
-json |
JSON 输出 |
-o |
报告写入文件 |
-no-jvm |
跳过 jstack/jmap 运行时检测 |
-no-net |
跳过网络排查 |
-no-color |
禁用终端着色 |
0:未见异常1:存在中/低危发现2:存在高危/严重发现
便于在编排/巡检脚本中直接判断。
对应手册第四节「内存马清除操作」的自动化版本:在不重启服务的前提下,从运行中的 JVM 内存里移除恶意组件的注册(Filter / Servlet / Listener),等价于手册 [62][63] 的 Arthas OGNL 热清除。
重要:反序列化/漏洞一次性注入的内存马只存在于 JVM 内存、磁盘无任何文件, 纯被动的磁盘/配置/日志检测无法发现它,只有 attach 进 JVM 枚举过滤器链才能看到。 因此 v1.4.0 起,默认运行即会对发现的 Java 进程做只读 attach 运行时枚举(不改动应用), 用
-no-attach可关闭。经真机验证可发现 Spring Boot 内嵌 Tomcat 中的哥斯拉等内存马。
# 默认:直接运行即 attach 只读枚举运行时过滤器链,按 codeSource 报告可疑内存马
sudo ./memcheck
# 纯被动检测,不接触任何 JVM
sudo ./memcheck -no-attach
# 自动卸载:枚举 + 挑出可疑项,逐个请求确认后热卸载
sudo ./memcheck -remove
# 定向:手工指定要卸载的类名,跳过确认(自动化场景)
sudo ./memcheck -remove-class com.summersec.x.GodzillaFilter -yes参数:
| 参数 | 说明 |
|---|---|
| (默认) | 发现 Java 进程时自动 attach 只读枚举运行时过滤器链,报告可疑内存马(不卸载、不改动应用) |
-no-attach |
关闭上述默认行为,回到纯被动(不接触 JVM)检测 |
-attach-scan |
即使用了 -no-attach 也强制做只读运行时枚举 |
-remove |
枚举 + 挑出可疑项,逐个请求确认后热卸载 |
-remove-class s |
手工指定类名(逗号分隔),隐含 -remove |
-yes |
跳过交互确认(谨慎使用;非交互式环境下不加 -yes 一律跳过卸载) |
检测原理(关键):agent 加载后会枚举运行中 JVM 里每个 Tomcat 上下文已注册的
全部 Filter/Servlet/Listener,并对每个组件解析其 codeSource 与类加载器,回写给主程序;
主程序用统一启发式判定可疑项——核心信号是 codeSource:
- 通过反序列化/
defineClass一次性注入的内存马,其类codeSource通常为空 → 判为可疑; - 正常业务/框架过滤器
codeSource指向其 jar → 不误伤; codeSource指向 JSP → JSP 注入器编译加载;- 叠加已知内存马特征库、框架白名单、无包名/生僻词+组件后缀/Lambda 伪装等启发式。
因此即使内存马类名、包名完全正常(如 com.company.web.InjectedFilter),只要它是内存注入
的、不在磁盘 jar 里,就能被发现并卸载——这正是应对 Shiro/反序列化等“磁盘无文件”一次性注入
内存马的关键。报告还会列出完整的已注册组件清单(等价 Arthas sc -d *Filter*),便于人工核对。
实现方式:卸载能力由一个自研 Java agent(agent/MemCheckAgent.java)提供,编译为
agent.jar 后通过 go:embed 静态内嵌进单一二进制;运行时释放到临时文件,用纯 Go 实现的
HotSpot attach 协议(无 cgo,.attach_pid + SIGQUIT + Unix socket)加载进目标 JVM。
上下文发现遍历所有已加载类的类加载器定位 webapp 类加载器,兼容独立 Tomcat 与 Spring Boot
内嵌 Tomcat;移除恶意 FilterDef/FilterMap/filterConfig 等注册项后回写结果。
整个过程不落地依赖 JDK、不需要 Arthas。
安全模型(务必了解):
- 默认关闭,必须显式
-remove才会有任何写操作;非交互环境不加-yes一律跳过。 - 只在内存中移除注册,不删除磁盘文件、不杀进程、不重启服务。
- 自动模式只推荐已确认命中的已知内存马类;其余可疑项仍只报告不处置。
- 热卸载不是终点:磁盘上的注入器 JSP / 篡改的 web.xml / 恶意
-javaagent若不清除, 重启或再次访问后内存马会复活。卸载后请务必按报告删除磁盘注入器与配置、并排查入侵入口。 - 容器场景:目标 JVM 若在独立命名空间(容器)中,需在该容器内运行本工具,程序会明确提示。
- 建议以 root 运行;非 root 时只能 attach 到同属主的 JVM。
支持范围:主要覆盖 Tomcat / Spring Boot 内嵌 Tomcat 的 Filter/Servlet/Listener 型内存马。 其它容器(Resin/Jetty/Undertow)或 Agent 型、Valve 型可能无法热卸载,此时程序会报告类已加载 但无注册项可卸载,提示重启清除并排查持久化。生产环境首次使用建议先在同版本测试环境验证。
报告按风险等级(严重/高危/中危/低危/信息)排序,每条包含证据(文件路径、大小、修改时间、
连接、命令等)与处置建议,并附手册「六、排查结果判断指南」的 codeSource 研判要点与
下一步人工清除指引。
仓库随附 arthas-boot.jar(Alibaba 开源的 Java 诊断工具,Apache-2.0),用于对 MemCheck
的运行时判定做人工复核,或在本工具未覆盖的场景(非 Tomcat 容器、复杂加载链)手工排查。
官网与文档:https://arthas.aliyun.com/
# 连接目标 JVM(列出所有 Java 进程,输入编号选择)
java -jar arthas-boot.jar
# 进入交互界面后常用命令:
sc -d *Filter* # 列出所有 Filter 类
sc -d com.xxx.EvilFilter # 查该类的 classLoader / codeSource(为空或指向 JSP 即可疑)
jad com.xxx.EvilFilter # 反编译确认是否恶意
thread # 查代理线程(Socket.connect 等特征)
arthas-boot.jar只是引导器,首次运行会联网下载完整 arthas 到~/.arthas/。 断网现场请改用官网的 arthas-bin 完整离线包,或预先填充~/.arthas/lib。
- 运行时检测(Arthas)最精准但需交互式操作,无法完全自动化;断网现场按上节用
arthas-boot.jar, 用sc -d查恶意类的codeSource/classLoader定位注入来源。 jstack/jmap通常需与目标 Java 进程 同一用户 才能连接,必要时sudo -u <appuser>运行。- 本工具用于 授权范围内 的应急响应与安全演练。