1. 项目概述:Agenst是什么,以及它解决了什么问题
在渗透测试和红队评估的后期,我们常常会遇到一个棘手的情况:已经通过某种方式获取了目标服务器的访问权限,但为了维持访问、避免被管理员发现并清除,我们需要一种隐蔽且持久化的后门。传统的Webshell文件落地,很容易被安全软件或人工巡检发现。这时,“内存马”技术就成为了一个关键选择。它直接在目标Java应用的内存中动态注册一个恶意的Servlet、Filter或Controller,不向磁盘写入任何文件,极大地提升了隐蔽性。然而,手动构造和注入内存马,需要对Java Instrumentation API、字节码操作(如ASM、Javassist)以及目标Web容器的内部机制有深入理解,过程繁琐且容易出错。
Agenst的出现,正是为了解决这个痛点。它是一个基于Java Agent技术的一键化内存马注入工具。你可以把它理解为一个“内存马发射器”。它的核心价值在于,将复杂的内存马注入过程封装成一个简单的JAR包,使用者无需关心底层字节码如何编织、如何挂载到目标JVM,只需一条命令,就能将冰蝎、Neo-reg等主流Webshell的内存马版本注入到正在运行的Java Web服务中。这极大地降低了红队人员、安全研究员的工具使用门槛,提升了后渗透阶段的效率。当然,我必须强调,这个工具仅应用于获得明确授权的安全测试、企业内部攻防演练或教育研究场景,任何未经授权的使用都是非法且不道德的。
2. Agenst的核心原理深度拆解
要理解Agenst如何工作,我们需要深入两层:Java Agent的机制,以及内存马在Web容器中的存活原理。Agenst巧妙地将两者结合,实现了“无文件、动态、持久”的驻留。
2.1 Java Agent:JVM的“手术刀”
Java Agent是Java SE 5引入的一个强大特性,它允许开发人员在JVM启动时(通过-javaagent参数)或运行时(通过Attach API)将一个特定的JAR包加载到目标JVM中。这个JAR包中的代码,可以访问并修改正在运行的Java应用程序的字节码。你可以把它想象成给运行中的JVM进程做“微创手术”的能力。
Agenst主要利用了“运行时Attach”机制。它内部会调用com.sun.tools.attach.VirtualMachine类(这就是为什么需要attach.dll或libattach.so的原因,这些是JVM工具接口的本地库),连接到目标JVM进程。连接成功后,它会将自己的Agent JAR(即agenst.jar本身)加载到目标JVM中。一旦Agent被加载,其agentmain方法会被调用,这是整个注入过程的起点。
注意:Attach API的成功调用,通常要求执行Agenst的用户与运行目标Java进程的用户是同一个,或者具有足够的权限(如root)。这也是为什么工具文档提示“扔到服务器上”运行,因为通常你已经获得了该服务器的shell权限。
2.2 内存马:在内存中“寄生”
内存马本身是一段恶意的Java字节码,它的目标是将自己注册为Web容器(如Tomcat、Spring Boot内嵌容器、Jetty等)的一部分。常见的内存马类型有:
- Filter型内存马:注册一个恶意的Filter到所有URL路径上,所有请求都会经过它,从而实现对请求的拦截和控制。
- Servlet型内存马:注册一个恶意的Servlet到特定路径,直接处理该路径的HTTP请求。
- Controller型内存马:针对Spring MVC框架,动态注册一个恶意的Controller。
Agenst支持的内存马(如冰蝎、Neo-reg)本质上是将这些Webshell的后台逻辑,以Filter或Servlet的形式实现,并生成对应的字节码。工具内部预置或动态加载这些字节码。
2.3 Agenst的工作流程:从注入到访问
结合以上两点,Agenst的完整工作流程可以拆解为以下步骤:
- 定位与附着:Agenst进程启动,通过Attach API连接到本机(或远程,但Agenst通常用于本地)指定的Java进程(默认可能是当前JVM或自动寻找Web服务进程)。
- 加载Agent:将agenst.jar作为Agent加载到目标JVM。此时,Agent的
agentmain方法获得执行权。 - 字节码转换:在
agentmain方法中,Agenst会注册一个ClassFileTransformer。这个转换器会拦截JVM后续的类加载行为。Agenst的策略通常是:寻找Web容器中处理请求的核心类(例如Tomcat的StandardContext、Spring的RequestMappingHandlerMapping),当这些类被加载时,通过字节码操作技术(如ASM),向这些类的关键方法(如addFilter、registerMapping)中插入一段调用逻辑。这段逻辑的作用是,在Web容器初始化或请求映射时,动态地将内存马组件(Filter/Servlet)注册进去。 - 触发类重载:为了让注入的代码生效,Agenst可能需要触发目标类的重新加载。一种常见技巧是,利用反射获取到当前Web应用的
ServletContext或ApplicationContext,然后直接调用其添加Filter或Servlet的方法。这种方式更为直接,无需等待类加载拦截。 - 建立Web访问入口:内存马被成功注册后,就会响应特定的HTTP路径(如默认的
/agenstR)。当攻击者(或测试者)通过浏览器或客户端(如冰蝎客户端)访问该路径时,请求就会被内存马捕获,从而建立起一个加密的、交互式的Webshell通道。 - 提供管理接口:Agenst自身也会启动一个简单的HTTP服务(或复用现有服务),用于接收自定义内存马的上传(
/agenstE路径),实现动态加载自定义Payload的功能。
这个流程的核心在于,所有操作都在内存中完成。目标服务器的磁盘上只有原始的Web应用文件,没有任何新增的恶意类文件,因此常规的文件监控和静态扫描很难发现。
3. Agenst的实战使用与参数详解
了解了原理,我们来看如何具体使用Agenst。根据README,它的使用看似简单,但每个参数背后都有其安全考量和实战意义。
3.1 基础环境准备与启动
首先,你需要一个已经取得shell权限的Linux或Windows服务器,上面运行着Java Web应用(如Tomcat、Spring Boot Jar)。
- 上传工具:将
agenst.jar以及对应操作系统所需的本地库(libattach.sofor Linux,attach.dllfor Windows)上传到目标服务器的一个可执行目录。通常,lib-*文件夹里已经提供了这些库。 - 检查Java环境:确保服务器上有JDK而不仅仅是JRE,因为Attach API相关类(
tools.jar)通常在JDK中。运行java -version确认。 - 基础启动:进入存放jar包的目录,执行最基本的命令:
这条命令会尝试将Agent附加到当前JVM(或自动寻找合适的Java进程),并使用所有默认配置。java -jar agenst.jar
常见启动问题排查:
- 报错:找不到或无法加载主类:确认使用的是
java -jar命令,并且jar包完整未损坏。 - 报错:找不到attach库:这是最常见的问题。你需要将
libattach.so(Linux)或attach.dll(Windows)放在Java的库加载路径下。最简单的方法是将它们复制到与agenst.jar相同的目录。因为Agenst的代码可能会默认从当前目录加载这些本地库。 - 附加进程失败:可能原因有:1) 目标Java进程的用户与当前执行Agenst的用户不同;2) 目标进程是
root启动的,而你是普通用户。尝试使用sudo提权执行,或者先切换到对应进程的用户(如tomcat用户)。
3.2 核心功能参数解析
Agenst提供了几个关键参数来增强隐蔽性和对抗性。
3.2.1 自定义校验机制 (-a=)
这个功能是为了防止内存马路径被他人意外访问或扫描器“捡走”。它通过检查HTTP请求头来实现简易的认证。
java -jar agenst.jar -a=Referer:123456- 参数格式:
-a=HeaderName:HeaderValue。注意,根据文档,请求头名称和值在比较时可能会被转换为小写,所以你在客户端访问时,使用referer: 123456或Referer: 123456均可。 - 工作原理:当设置此参数后,Agenst在注册内存马时,会为内存马的处理逻辑增加一个前置检查。所有发往内存马路径(如
/agenstR)的请求,都必须包含指定的请求头和正确的值,否则将返回404或其他错误响应,伪装成路径不存在。 - 实战意义:这相当于给你的后门加了一把“钥匙”。自动化扫描器或蓝队人员即使发现了可疑路径,如果没有正确的请求头,也无法触发内存马,从而避免了暴露。你可以使用一些不常见但合法的请求头,如
X-Client-ID、X-Token等。
3.2.2 自定义注入路径 (-u=,-uR=,-uE=,-uN=)
默认路径/agenstR、/agenstE、/agenstN特征明显,容易被安全设备或人工检查的访问日志发现。自定义路径功能就是为了解决这个问题。
全局路径替换 (
-u=):java -jar agenst.jar -u=api_v1_user执行后,所有内存马的路径前缀将从
agenst替换为api_v1_user。即:- 冰蝎路径变为:
/api_v1_userR - 自定义内存马加载器路径变为:
/api_v1_userE - Neo-reg路径变为:
/api_v1_userN - 重要提示:文档提到“注入的filter等不沿用此规则”。这意味着,虽然访问路径变了,但内存马在容器内部注册的Filter或Servlet的名称(内部标识)可能还是原来的,这有助于避免与现有业务组件冲突。
- 冰蝎路径变为:
独立路径定义 (
-uR=,-uE=,-uN=):java -jar agenst.jar -uR=health_check -uN=proxy_endpoint这种方式更灵活,可以为每种内存马单独设置路径。例如,你可以将冰蝎路径伪装成一个健康检查接口
/health_check,将Neo-reg伪装成一个代理端点/proxy_endpoint。
3.2.3 组合使用示例
在实际的渗透测试中,为了最大化隐蔽性,通常会组合使用这些参数:
java -jar agenst.jar -a=Authorization:Basic_dGVzdDp0ZXN0 -u=actuator/health这条命令会:
- 设置一个校验请求头:
Authorization: Basic dGVzdDp0ZXN0(一个Base64编码的测试凭据)。 - 将所有内存马路径前缀改为
actuator/health,使得冰蝎路径看起来像/actuator/healthR。
这样,蓝队人员即使看到/actuator/healthR这样的请求,也会先入为主地认为它是Spring Boot Actuator的某个健康检查变体,而请求如果没有携带正确的Authorization头,则无法得到任何响应,极大地增加了排查难度。
4. 支持的内存马类型与客户端连接
Agenst预置了三种内存马,对应不同的Webshell客户端。
4.1 冰蝎(Behinder)内存马 (/agenstR或自定义路径)
冰蝎是目前最流行的加密Webshell管理工具之一,以其通信加密、流量隐蔽著称。
- 默认路径:
/agenstR(使用-uR=自定义) - 默认密码:
rebeyond - 连接方式:
- 在冰蝎4.x客户端中,新建一个Shell。
- URL地址填写:
http://目标IP:端口/自定义路径R(例如http://192.168.1.100:8080/health_check)。 - 密码填写:
rebeyond(如果服务端启动时未修改默认密码)。 - 选择正确的脚本类型(通常是
JSP,但内存马版本可能适配为Java,具体需看Agenst实现,一般选默认或JSP即可)。 - 点击连接。如果配置了请求头校验,需要在冰蝎的“请求头”设置中添加对应的Header。
实操心得:冰蝎内存马的稳定性很高,但在一些特定中间件或高版本JDK环境下可能会遇到兼容性问题。连接成功后,建议先使用
env、whoami等基础命令测试功能是否正常。如果执行命令无回显,可能是内存马注入的类或方法在当前环境中不适用,这时可以尝试使用自定义内存马功能。
4.2 Neo-reg代理内存马 (/agenstN或自定义路径)
Neo-reg是一个基于HTTP协议的Socks5代理工具,常用于在渗透测试中建立内网代理通道。
- 默认路径:
/agenstN(使用-uN=自定义) - 默认密码:
donttouch - 连接方式:
- 在攻击机上,使用Neo-reg的Python客户端。
- 执行命令:
python3 neoreg.py -k donttouch -u http://目标IP:端口/自定义路径N - 客户端会启动一个本地的Socks5代理端口(默认1080)。之后,你就可以配置你的渗透工具(如Proxifier、Nmap的
--proxy选项)通过这个代理来访问目标内网。
Neo-reg内存马的工作原理:它注入的内存马实际上是一个HTTP隧道处理器。当客户端连接后,所有的Socks5协议流量都会被封装在HTTP请求中,通过这个特定的路径进行传输。由于流量走的是正常的HTTP/HTTPS端口,因此可以绕过很多基于端口和协议的内网出口限制。
4.3 自定义内存马加载器 (/agenstE或自定义路径)
这是Agenst最强大的功能,它为你提供了一个“通用插座”,可以动态加载任何由你生成的Java内存马字节码。
- 默认路径:
/agenstE(使用-uE=自定义) - 使用方式:使用
POST方法访问该路径,并将内存马的字节码文件(通常是.class文件)进行Base64编码后,放在请求体(Body)中发送。 - 生成内存马:文档推荐使用
JMG(Java Memory Gun)等工具来生成所需的内存马字节码。你需要先使用这类工具生成一个二进制的.class文件。 - 注入流程:
base64编码你的.class文件:cat your_mem_shell.class | base64(Linux) 或 使用在线工具。- 使用
curl或Burp Suite等工具发送POST请求:
其中curl -X POST http://目标IP:端口/agenstE -H "Content-Type: text/plain" --data-binary @base64_payload.txtbase64_payload.txt文件内容就是上一步得到的Base64字符串。 - 如果注入成功,服务器会返回成功响应。然后,你就可以根据你生成的内存马类型(如Filter型、Servlet型),访问对应的路径来使用它。
这个功能的实战价值:当预置的内存马因为环境原因(如JDK版本、中间件类型、安全防护软件)失效时,你可以利用这个接口,上传一个为当前环境量身定制、或能绕过特定防护的内存马。这大大提升了工具的适应性和生存能力。
5. 编译、部署与高级对抗考量
5.1 交叉编译与定制化
Agenst项目提供了Gradle构建脚本,允许你从源码编译,这对于免杀和定制化非常重要。
- 环境准备:克隆项目源码,确保本地安装了JDK和Gradle。
- 修改与定制:你可以阅读
src/main/java下的源码。例如,你可以修改默认的密码、预置内存马的字节码、或者增加新的内存马类型。但请注意,这需要较强的Java和字节码知识。 - 交叉编译:项目
build.gradle中已经配置了交叉编译选项。你可以通过取消注释或修改相关行,来指定编译目标平台(Linux或Windows)。编译后,在build/libs/目录下会生成对应的jar包。记得将对应平台的libattach.so或attach.dll与jar包一起分发。
重要安全提示:直接使用公开仓库的二进制工具在实战中风险极高,因为其哈希值或特征可能已被收录到安全设备的病毒库或威胁情报中。对工具进行源码级的轻微修改(如改类名、方法名、字符串常量),然后重新编译,是绕过静态特征检测的一种基本手段。
5.2 部署策略与隐蔽性强化
仅仅运行Agenst还不够,如何让它存活得更久,是红队需要思考的。
- 进程隐藏:
java -jar agenst.jar命令会启动一个明显的Java进程。可以考虑使用nohup、screen或systemd服务将其后台化,并赋予一个具有迷惑性的进程名(通过启动脚本包装)。 - 文件隐藏:将
agenst.jar和本地库文件放在隐蔽的目录,如/dev/shm(内存文件系统,重启消失但运行时隐蔽)、/lib/.lib64/等非常规路径。并修改文件的时间戳(touch -t)使其与系统文件保持一致。 - 网络隐蔽:
- 使用合法路径:充分利用
-u=参数,将路径伪装成业务接口、健康检查端点、API文档路径等。 - 启用校验:务必使用
-a=参数添加请求头校验,这是防止扫描和误访问的第一道防线。 - HTTPS适配:如果目标网站启用了HTTPS,你的内存马路径同样可以通过HTTPS访问,这有助于混合在正常加密流量中。
- 使用合法路径:充分利用
- 清理痕迹:Agenst本身可能会在目标JVM中留下一些特殊的类加载器或Agent记录。在授权测试结束后,应使用工具提供的或手动编写卸载功能进行清理,避免留下持久化的Agent影响业务。
5.3 与防御技术的对抗
随着内存马技术的普及,蓝队的检测手段也在升级。Agenst的使用者需要了解可能的检测点:
- Agent检测:安全Agent或RASP(运行时应用自保护)可以监控
VirtualMachine.attach的调用和InstrumentationAPI的使用。高强度的防护环境可能会拦截此类操作。 - 异常类/Filter检测:内存马注入的类通常不在应用的原始类路径中,其类加载器可能是自定义的。安全产品可以通过扫描JVM中已加载的类,查找可疑的类名、方法名或继承关系。
- 行为检测:内存马通常具有执行命令、文件读写等敏感操作。RASP可以通过Hook关键API(如
Runtime.exec,ProcessBuilder.start)来检测并阻断恶意行为。 - 流量特征检测:尽管冰蝎等工具流量已加密,但其通信模式(如固定长度的周期性请求、特定的响应结构)仍可能被高级威胁检测系统(NDR)识别。
应对思路:
- 轻量化:自定义内存马时,尽量精简功能,避免直接调用敏感API,可以采用反射、JNI等更隐蔽的方式。
- 伪装:让内存马的行为更像一个正常业务组件,例如,在命令执行前检查请求是否来源于特定的、伪装过的管理后台IP。
- 动态化:不要长期使用固定的路径和密码,定期更换。
6. 常见问题、排查与防御视角
6.1 使用者常见问题排查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
运行java -jar后无任何输出或立即退出 | 1. 目标JVM进程未找到或附加失败。 2. 缺少 attach.dll/libattach.so。 | 1. 使用jps命令查看目标Java进程PID,尝试指定PID(如果Agenst支持)。2. 确认本地库文件与jar包在同一目录,且权限正确。 |
| 工具显示注入成功,但无法访问内存马路径 | 1. 路径错误或自定义路径未生效。 2. 请求头校验未通过。 3. 内存马注入的Web容器类型不匹配(如给Jetty注入了Tomcat的内存马)。 4. 目标应用存在安全过滤器拦截了请求。 | 1. 仔细检查启动命令和访问URL。 2. 使用Burp Suite等工具拦截请求,确认请求头正确添加。 3. 尝试使用自定义内存马功能,上传针对当前容器类型生成的内存马。 4. 尝试访问一个不存在的路径,对比响应,判断请求是否到达应用。 |
| 冰蝎/Neo-reg客户端连接超时或失败 | 1. 网络不通或防火墙拦截。 2. 内存马本身存在bug或兼容性问题。 3. 客户端配置错误(密码、脚本类型)。 | 1. 使用curl或浏览器直接访问路径,看是否有HTTP响应(即使是404/403)。2. 换用自定义内存马加载器,上传一个最简单的测试Servlet来验证注入通道是否畅通。 3. 核对客户端配置,特别是密码和路径。 |
| 注入后导致目标Web服务崩溃或异常 | 1. 字节码修改冲突,破坏了原有类的逻辑。 2. 注入的内存马存在资源竞争或死锁。 3. 与目标JVM中已有的Agent冲突。 | 1. 这是最严重的情况。立即重启目标Web服务以恢复。 2. 在测试环境充分验证后再用于生产环境测试。 3. 考虑使用更稳定的注入点或减少注入的功能。 |
6.2 从防御者(蓝队)视角看检测
了解攻击工具,才能更好地防御。作为防御方,可以从以下层面检测Agenst或类似工具:
主机层面:
- 进程监控:监控异常
java -jar命令的执行,特别是参数中带有-javaagent或涉及tools.jar的进程。 - 文件监控:监控
/tmp、/dev/shm等临时目录或非标准目录下创建的jar、.so、.dll文件。 - 网络连接监控:发现Java进程存在异常的外联或监听端口(虽然内存马不新开端口,但Agent加载过程可能有网络行为)。
- 进程监控:监控异常
JVM层面:
- Agent检测:使用
jcmd <PID> VM.command_line或ManagementFactory.getRuntimeMXBean().getInputArguments()查看JVM启动参数,检查是否有未知的Agent被加载。对于运行时Attach,可以尝试定期扫描java.lang.instrument.Instrumentation实例中已注册的ClassFileTransformer。 - 类审计:定期Dump JVM中已加载的类列表,与基准快照对比,查找未知的、名称可疑的类(如包含
shell、filter、agent、memshell等关键词的类)。
- Agent检测:使用
应用层面(RASP):
- Filter/Servlet清单检查:通过
ServletContext的API获取所有已注册的Filter和Servlet,与web.xml或注解声明进行比对,发现动态注册的恶意组件。 - 关键API Hook:对
ClassLoader.defineClass、ServletContext.addFilter、RequestMappingHandlerMapping.registerMapping等关键方法进行Hook,记录并告警非预期的调用。
- Filter/Servlet清单检查:通过
流量层面:
- 访问日志分析:监控访问日志,寻找对可疑路径(如包含
agenst、shell、proxy等)的访问,特别是那些返回状态码异常(如404后突然200)的路径。 - 请求头异常:虽然Agenst支持自定义校验头,但固定的头键值对本身也可能成为特征。可以关注那些携带不常见请求头且访问特定路径的请求。
- 访问日志分析:监控访问日志,寻找对可疑路径(如包含
Agenst这类工具的出现,标志着攻防对抗已经深入到运行时内存和字节码层面。对于攻击方,它提供了高效隐蔽的持久化能力;对于防御方,则提出了更高的实时检测和运行时保护要求。无论是出于提升攻击技巧还是加固防御体系的目的,深入理解其原理和实现,都至关重要。在实际操作中,务必牢记法律与道德的边界,仅在合法授权的范围内使用这些知识和技术。