m0b1u3/JavaMemHunter

GitHub: m0b1u3/JavaMemHunter

JavaMemHunter 是一款通过 Agent attach 附加到运行中 JVM 的 Java 内存马扫描、清理与验证工具,可检测并处置冰蝎、哥斯拉及各类 Tomcat/Spring 容器注入型 webshell。

Stars: 1 | Forks: 0

**English** | [中文](README.zh-CN.md) # JavaMemHunter [![Test](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/938ef7e355015734.svg)](https://github.com/m0b1u3/JavaMemHunter/actions/workflows/test.yml) [![Release](https://img.shields.io/badge/release-v1.0-blue.svg)](https://github.com/m0b1u3/JavaMemHunter/releases) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) [![JDK](https://img.shields.io/badge/JDK-8%2B-orange.svg)](#支持的环境) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) JavaMemHunter 附加到**运行中的 JVM**,查找内存级和基于文件的 webshell, 打印包含每个 shell 访问路径的简明终端摘要,并且可以通过带有支持回滚的 JSON 证据包,原子性地清理已确认的 shell。 ## 它能捕获什么 按你要查杀的 shell 进行分类(已通过实际样本验证): | Shell | 隐藏方式 | JavaMemHunter 如何发现它 | |-------------------------------|---------------------------------------------------------------------|---------------------------------------------------------| | **冰蝎 (Behinder) agent shell** | `redefineClasses` 篡改了 `HttpServlet.service` 字节码 | 字节码篡改比对磁盘 jar 包;提取注入的 URI/解密类 | | **哥斯拉 (Godzilla) filter shell** | 将 Jackson 类重命名为 `org.apache.coyote.*`,动态定义 | 伪装包检测(框架名 + 无 jar 来源) | | **JSP webshell** | 磁盘上的 `.jsp` 文件,编译为 `org.apache.jsp.*` | 类名反向映射到 `.jsp` 访问 URL | | **Tomcat Filter / Servlet / Listener / Valve** | 在运行时注册到容器中,没有类文件 | 容器注册表扫描 + 仅运行时/通配符启发式检测 | | **Spring Interceptor / Mapping** | 注入到 `AbstractHandlerMapping` / `RequestMappingHandlerMapping` 中 | Spring 运行时扫描 | ## 工作原理 - **容器注册表扫描** — 遍历 Tomcat `StandardContext` 的 filter/servlet/listener/valve 注册表以及 Spring handler mapping,寻找*已注册*的组件。 - **类加载扫描** — `ClassScanner` 遍历每一个已加载的类,寻找容器视图可能会遗漏的 web 组件 (基于文件的 JSP shell、依赖类)。 - **Agent 类型检测** — `AgentTypeScanner` 通过 ASM 方法体指纹,将关键类 (`HttpServlet.service`, valves, dispatchers)的内存字节码与其磁盘 jar 包进行比较, 捕获基于 `redefineClasses` 的 shell(冰蝎);从被篡改的常量池中提取注入的 访问路径 / 解密类字符串。 - **规则引擎评分** — 独立的规则相加得出总分 → `critical` / `high` / `suspicious` / `low`。亮点包括:`masqueraded-package`(框架名 + 空的 codeSource), 字节码恶意行为检查(`Runtime.exec` / `defineClass` / `Cipher.doFinal`),以及几个 误报抑制器。 - **噪声控制,在实时目标上验证为零误报**: 良性的 webapp 组件不会被报告;JVM 反射生成的类会被加入白名单; 来自不同扫描器的同类发现会被去重;哥斯拉 filter shell 注入的 Jackson *依赖*类会从 `critical` 降级(仍会在 `high` 中报告)。 - **访问路径标注** — 每一个列出的发现都显示了在哪里可以找到它:filter 的 `urlPatterns`,servlet 的 mappings,JSP 反向映射的 `.jsp` URL,或者是冰蝎 agent shell 注入的 URI。 - **原子清理 + 验证** — 一个 5 阶段计划(重新扫描 → 备份 → 替换 → 销毁 → 验证) 附带 JSON 证据包和回滚元数据。 ## 清理能力 | 发现类型 | 扫描 | 清理 | 方式 | |--------------|------|-------|-----------------------------------------------------------| | `tomcat-filter` | ✅ | ✅ | 原子性地复制替换 filterDefs / filterMaps / filterConfigs | | `tomcat-servlet` | ✅ | ✅ | 原子性地复制替换 children / servletMappings | | `tomcat-listener-{request,session,context,other}` | ✅ | ✅ | 复制替换 application(Event/Lifecycle)Listeners | | `tomcat-valve` | ✅ | ✅ | 重新链接 Pipeline 链 | | `spring-mapping` | ✅ | ✅ | 官方的 `unregisterMapping(info)` | | `spring-interceptor` | ✅ | ✅ | 跨 bean 复制替换 `adaptedInterceptors` | | `agent-bytecode-tampered` (冰蝎) | ✅ | — | 字节码被篡改;需重启 / 手动修复 | | `class-*` (JVM 类级别) | ✅ | — | 仅评分;JSP webshell 是一个需要从磁盘删除的 `.jsp` 文件 | 所有清理操作流程为:试运行(写出计划 + 证据)→ 在附加端输入确切的小写 `yes` 确认 → 确认(原子替换 + 验证 + 失败时回滚)→ 验证(独立重新扫描)。 ## 快速开始 ``` # 1. 构建 ./mvnw -DskipTests package # 2. 查找目标 PID jps -l # 3. 扫描(--output 可选;默认为 ./memhunter-scan-.json) java -jar attach/target/memhunter-attach.jar agent/target/memhunter-agent.jar scan ``` 简明的摘要会直接打印到你的终端上——只有 `critical` / `high` / `suspicious` 会被列出,`low` 仅计数但不显示: ``` [memhunter] scan summary (PID ): critical: 4 high: 9 suspicious: 0 low: 67 [critical] tomcat-filter org.apache.coyote.JavaType score=16 path=[/*] [critical] tomcat-filter org.apache.coyote.MapperFeature score=16 path=[/*] [critical] tomcat-filter org.apache.coyote.jsontype.impl.TypeSerializerBase score=16 path=[/*] [critical] tomcat-filter org.apache.coyote.deser.BeanDeserializerModifier score=16 path=[/*] [high] class-servlet org.apache.coyote.util.EnumValues score=8 (Jackson dependency class, downgraded) [high] class-servlet org.apache.jsp._jsp score=7 path=[/.jsp] [high] tomcat-servlet score=8 path=[/] [memhunter] full report: ./memhunter-scan-.json ``` ``` # 4. 试运行清理(生成一个计划,不作任何更改) java -jar attach/target/memhunter-attach.jar agent/target/memhunter-agent.jar \ clean --id --dry-run --evidence-dir . # 5. 确认清理(提示输入准确的 "yes") java -jar attach/target/memhunter-attach.jar agent/target/memhunter-agent.jar \ clean --id --confirm --evidence-dir . ``` ## 解读输出结果 **等级** — `critical` = 已激活、已注册的 shell(立即行动);`high` = webshell, `null`-class servlet shell,或注入的依赖类(需审查);`suspicious` = 较弱的 信号;`low` = 背景噪声(良性组件、JVM 类),仅计数。 **每个发现后面的路径/位置:** - `path=[...]` — 一个你可以在 WAF 拦截 / 在访问日志中搜索的**访问路径**: filter 的 `/*`,servlet 的 mapping,JSP 的 `.jsp` URL,或者冰蝎 agent shell 的 URI。 - `trigger=` / `pipeline=` — 由事件或 Pipeline 触发的 shell(listener / valve),有 **无 URL**;它会在任何请求时触发,因此没有单一的路径可以拦截。 - JSP `path=[/foo.jsp]` 指向一个**需要从磁盘删除的文件**——JSP webshell 是 基于文件的,而不是内存中的。 完整的 JSON 报告会保留**每一个**发现(包括 `low`)用于取证;终端 摘要是分诊视图。 ## CLI 选项 | 选项 | 命令 | 含义 | |--------|---------|---------| | `--output ` | scan | 报告路径(可选;默认为 `./memhunter-scan-.json`)。无论哪种方式,都会打印终端摘要并显示完整报告路径。路径不能包含空格。 | | `--baseline ` | scan | 先前的 ScanReport JSON;不在基线中的发现将获得 `baseline-new` (+4) | | `--whitelist ` | scan | 用户白名单,每行一个 `:`,`` ∈ {framework, business, agent, codesource} | | `--explain` | scan | 在每个发现中包含逐规则的 `ruleHits` | | `--id ` | clean / verify | 目标发现 ID(来自扫描报告) | | `--dry-run` | clean | 写入 `clean-plan.json` + 证据,不进行运行时更改 | | `--confirm` | clean | 读取试运行计划,在 stdin 上输入 `yes` 后执行 | | `--force` | clean | 跳过 score < 7 的门控;persisted 和 confirm 标志必须一致 | | `--evidence-dir ` | clean / verify | 证据目录根(默认:当前目录) | ### 白名单文件示例 ``` business:com.mycompany.app. framework:com.acme.shared. agent:com.custom.tracer codesource:/opt/myapp/ ``` ### 清理证据目录结构 ``` /evidence// ├── finding.json # the original Finding (score / level / reasons) ├── clean-plan.json # written by dry-run; 4 fields strictly checked at confirm ├── before-snapshot.json # reflective snapshot of the Tomcat/Spring internals ├── clean-result.json # written after confirm (success / rolledBack / verifiedDisappeared / executedSteps) └── verify-result.json # written by the independent verify command ``` ## 支持的环境 | 组件 | 已验证版本 | |-------------|-------------------------------------------------------------------------| | JDK | 目标 JVM 8 / 11 / 17 / 21(agent 为 JDK 8 字节码);JDK 17 需要 `--add-opens`(见下文) | | Tomcat | 9.x(包含独立的 9.0.94,手动),10.x(通过 Spring Boot 3.2) | | Spring Boot | 2.7.x, 3.2.x | | 操作系统 | Linux (CI), Windows 11 (手动) | | Shells | 冰蝎 agent shell, 哥斯拉 filter shell, JSP webshell (实时, 手动)| ## 已知局限性 - **JDK 17+ 需要在目标 JVM 上使用 `--add-opens`。** 该 agent 通过反射遍历 Thread/field 图, 以定位 Tomcat 的 `StandardEngine`。除非目标以以下参数启动,否则 JDK 9 模块封装会阻止此操作: java --add-opens=java.base/java.lang=ALL-UNNAMED \ --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \ --add-opens=java.base/java.util=ALL-UNNAMED \ --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \ -jar your-app.jar 如果没有这些标志,扫描器将回退到精度较低的类加载模式,并且 清理器无法运行。JDK 8 没有模块系统,不需要标志。 - **`--output` 路径不能包含空格** — attach→agent 参数管道会按 空格分割;带有空格的路径会被明确的错误拒绝。 - **antiAgent(attach 通道关闭)** — 关闭 JVM attach 通道的 shell 会使 *所有*基于 attach 的工具失效,包括这个工具。要对抗它需要 premain 模式(已推迟)。 - **Windows + JDK 17 NIO Selector bug** — 使用 JDK 8 运行目标,或者在 Linux 上运行。 - **Spring Bean 清理** — 超出范围(回滚复杂性太高,无法保证安全)。 - **Spring Boot 1.x, Tomcat 7 / 8.5 / 11** — 不在测试矩阵中。 ## 文档 - [中文文档 (Chinese README)](README.zh-CN.md) - [设计说明 — 完整版本历史和架构 (中文)](java_memshell_scanner_design.md) - [贡献指南](CONTRIBUTING.md) ## 路线图 (1.0 之后) - HTML / Markdown 报告 - Container / Kubernetes 适配 - 采用 premain 模式对抗 antiAgent attach 通道关闭 ## 许可证 Apache License 2.0 — 见 [LICENSE](LICENSE)。
标签:DNS 反向解析, JS文件枚举, JVM Agent, Webshell检测, 内存马查杀, 域名枚举, 库, 应急响应