1. 项目概述:Agenst是什么,以及为什么我们需要关注它
如果你是一名负责Java应用安全或者红队渗透测试的工程师,那么“内存马”这个词对你来说一定不陌生。传统的Webshell需要写入磁盘文件,动静大、易被查杀。而内存马则直接将恶意代码驻留在目标应用的运行时内存中,无文件、无落地,隐蔽性极强。今天要聊的“Agenst”,以及网络上热门的同类工具如vagent,正是将Java Agent技术与内存马结合,实现自动化、无感注入的利器。简单来说,Agenst这类工具的核心,就是利用Java Agent的Instrumentation机制,在目标JVM进程启动时或运行时,动态修改其字节码,植入一个常驻内存的后门。
为什么这值得我们深入探讨?因为在实战攻防中,尤其是防守方(蓝队)视角,这种攻击手段的威胁等级非常高。攻击者一旦通过某种初始漏洞(比如反序列化、文件上传)获得执行权限,就可以通过加载一个恶意的Java Agent Jar包,悄无声息地在所有基于该JVM的Web应用(如Tomcat、Spring Boot)中植入后门。即使服务器重启,如果Agent被植入到了JDK/JRE的系统库路径或Tomcat的lib目录,后门依然会随应用自启动。这对于防守方的溯源、排查和清除工作构成了巨大挑战。理解Agenst的原理,不仅是为了攻击,更是为了能有效地防御和检测这类高级威胁。接下来,我将从一个实践者的角度,拆解其技术原理、多种实战注入手法,并分享在模拟环境中复现和研究的核心细节与避坑指南。
2. 核心原理深度拆解:Java Agent如何成为内存马的“特洛伊木马”
要理解Agenst,必须吃透两个核心概念:Java Agent和内存马。它们一个是“运输工具”,一个是“有效载荷”。
2.1 Java Agent的“合法”外衣与Instrumentation能力
Java Agent本身是一个完全合法的JVM特性,主要用于监控、诊断、热部署等。它通过一个premain(主程序运行前)或agentmain(主程序运行后)方法作为入口。其核心能力来自于java.lang.instrument.Instrumentation接口,这个接口提供了两大“魔法”:
- 类重定义(RedefineClasses):可以替换一个已加载类的字节码。
- 类转换(RetransformClasses):可以重新转换已加载的类,这通常通过
ClassFileTransformer来实现。
Agenst正是利用了ClassFileTransformer。它会注册一个自定义的转换器,当JVM加载某个特定的类(比如org.apache.catalina.core.ApplicationFilterChain,这是Tomcat处理请求过滤器的核心类)时,转换器会介入,对类的字节码进行修改,插入恶意逻辑。这个过程发生在内存中,修改后的字节码被JVM直接执行,原始磁盘上的.class文件完好无损。
注意:这里的选择很有讲究。
ApplicationFilterChain是Tomcat中每个请求必经的“关卡”,在这里插入代码,可以拦截所有HTTP请求,实现Webshell的功能。对于Spring Boot嵌入式容器,原理类似,但目标类可能是org.springframework.web.servlet.DispatcherServlet的相关组件。
2.2 内存马的“无文件”驻留艺术
内存马之所以难以察觉,是因为它放弃了传统的文件Webshell模式。其生命周期完全与宿主应用绑定:
- 无磁盘写入:恶意代码作为修改后的字节码存在于JVM的Metaspace(方法区)中。
- 动态注册:通过修改Filter、Servlet、Controller、Interceptor等Web组件的映射关系,动态添加一个恶意端点(如
/faviconb,/faviconc)。 - 加密通信:为了绕过WAF、IDS等网络层检测,内存马通常使用自定义的加密、编码(如Base64、异或、GZIP)甚至类似冰蝎的复杂流量混淆协议进行通信。
Agenst这类工具将上述两者结合:利用Java Agent作为稳定、隐蔽的注入载体,在JVM层面植入一个可以动态注册各种功能型内存马的后门框架。这个框架一旦植入,攻击者就可以通过向特定URL发送特定格式的请求,来动态加载冰蝎马、命令执行马、代理马等不同功能模块,实现“一次注入,多功能切换”。
2.3 Agenst与vagent的关联与实现猜想
根据提供的vagent项目资料,我们可以推断出Agenst类工具的一般实现架构:
- Agent入口类:包含
premain或agentmain方法。在这里获取Instrumentation实例,并注册关键的ClassFileTransformer。 - 核心转换器(ClassFileTransformer):这是工具的“心脏”。它判断当前加载的类是否为目标类(如
ApplicationFilterChain)。如果是,则使用ASM或Javassist等字节码操作库,修改其doFilter方法。在方法开头插入一段逻辑:检查当前请求的URI是否匹配某个特定模式(如/favicon*),如果是,则交给一个“内存马管理器”处理;否则,继续原有过滤器链。 - 内存马管理器与通信协议:这是一个在内存中存在的单例对象。它负责:
- 解析请求:根据URI路径后缀(
b代表冰蝎,c代表CMD等)路由到不同的处理器。 - 处理载荷:对POST过来的加密/编码数据进行解密、解压(如GZIP)、解码(Base64)。
- 执行与响应:调用对应功能(执行系统命令、执行JS代码、启动代理连接等),并将结果按约定协议加密后返回。
- 解析请求:根据URI路径后缀(
- 多种加载方式适配:为了适应不同的入侵场景,工具需要提供多种加载Agent的方式,正如vagent所示:命令行加载、Tomcat Lib后门、JDK/JRE替换、通过已上传的JAR文件动态加载等。
3. 实战环境搭建与工具准备
在深入研究之前,我们必须在一个安全的、隔离的环境中进行实验。绝对禁止在生产环境或任何未经授权的系统上进行测试。
3.1 实验环境配置
我推荐使用虚拟机或Docker来搭建一个干净的靶场环境。
- 操作系统:Ubuntu 20.04/22.04 LTS 或 CentOS 7。
- Java环境:JDK 8(这是目前企业中最主流的版本,兼容性最好)。确保
JAVA_HOME环境变量配置正确。 - Web服务器:Apache Tomcat 8.5.x。从官网下载核心版(
tar.gz)即可。 - 网络工具:
- Burp Suite Community/Professional:用于拦截、修改和重放HTTP请求,是测试注入过程的关键。
- Postman:用于发送复杂的POST请求,特别是发送GZIP压缩的二进制文件。
- 冰蝎(Behinder)、蚁剑(AntSword):用于连接和测试注入的内存马。请仅从官方或可信源获取,并仅在实验环境中使用。
- Neo-reGeorg、Gost:用于测试代理型内存马。
3.2 获取与理解工具包
以分析vagent为例,我们需要获取其JAR包。通常这类项目会提供编译好的Release。在实验环境中,我们可以通过Git克隆源码并自行编译,以更深入地理解其结构。
# 1. 克隆项目(假设项目可访问) git clone <vagent-git-repo-url> cd vagent # 2. 使用Maven编译打包 mvn clean package -DskipTests编译后,在target目录下会生成vagent.jar(大马)和可能的vagent-mini.jar(小马)。用压缩软件打开vagent.jar,你可以看到其内部结构:
META-INF/MANIFEST.MF:定义了Premain-Class或Agent-Class,这是Agent的入口。- 核心的.class文件:包含了字节码转换器和内存马逻辑。
- 依赖的库(如ASM)。
实操心得:自行编译能让你确认代码中没有夹带私货(虽然仍需审计),并且可以尝试修改一些特征(如默认的URL路径、密码),这对理解工具和后续的防御研究至关重要。不过,对于初次分析,直接使用Release包进行行为观察也是可以的。
4. 多种注入方式实战详解与原理剖析
vagent文档中列举了四种主要注入方式,每一种都对应不同的攻击场景和持久化级别。我们来逐一拆解。
4.1 方式一:命令行加载 - 最直接的运行时注入
命令:java -javaagent:vagent.jar -jar your-app.jar或直接java -jar vagent.jar(如果vagent的MANIFEST中指定了Main-Class)。
原理:通过JVM启动参数-javaagent指定Agent Jar包路径。JVM在启动应用主类之前,会先调用Agent Jar中MANIFEST.MF里指定的Premain-Class的premain方法。这是最标准、最“干净”的Agent加载方式,但前提是你能控制启动命令。
实战步骤与注意点:
- 将
vagent.jar放在与你的Web应用(比如一个demo.war)同一目录。 - 启动Tomcat(如果是嵌入式Spring Boot,则修改其启动脚本):
# 假设在Tomcat的bin目录下 export JAVA_OPTS="-javaagent:/path/to/vagent.jar" ./catalina.sh start - 观察Tomcat启动日志,通常不会有明显错误。访问一个正常的应用页面,确认应用能跑起来。
- 尝试连接内存马,例如使用冰蝎连接
http://your-target:8080/faviconb。
踩坑记录:这种方式注入的Agent,其生命周期与本次JVM进程绑定。如果Tomcat重启且启动命令中没有再次包含
-javaagent参数,则后门失效。因此,攻击者会想方设法修改启动脚本或系统服务文件(如systemdunit file)来实现持久化。
4.2 方式二:Tomcat Lib后门 - 优雅的持久化
操作:将vagent.jar重命名(例如tomcat-agent-core.jar),放入$CATALINA_HOME/lib/目录。
原理:Tomcat的通用类加载器(Common ClassLoader)会加载lib目录下的所有JAR包。如果这个JAR包是一个合法的Java Agent(即其MANIFEST.MF中包含Premain-Class),并且JVM启动时开启了-javaagent支持(默认开启),Tomcat在初始化时会自动加载并初始化这些Agent。这是一种非常隐蔽的持久化方式,因为lib目录下通常有很多第三方库,一个陌生的JAR包不易引起怀疑。
实战步骤:
- 获取目标Tomcat的
lib目录路径。 - 上传重命名后的
vagent.jar到该目录。需要具有相应的写权限。 - 重启Tomcat。这是关键步骤,因为Agent的
premain方法只在类加载初期被调用。 - 重启后,内存马即生效。
排查技巧:作为防守方,应定期检查Tomcat/lib目录下JAR包的签名、创建时间、MD5哈希值,并与官方发布版本或已知干净版本进行比对。查看Tomcat启动日志(catalina.out)中是否有关于加载Agent的信息(但攻击者可能会修改日志输出)。
4.3 方式三:JDK/JRE替换 - 系统级的“核弹”
操作:将vagent.jar重命名为charsets.jar,替换$JAVA_HOME/jre/lib/charsets.jar。
原理:这是影响范围最广、最持久的方法。charsets.jar是JRE的标准库文件,包含字符集转换类。JVM启动时必然会加载它。攻击者利用的是Java Agent的一个特性:如果JAR文件放在$JAVA_HOME/lib或$JAVA_HOME/jre/lib目录下,并且其MANIFEST中指定了Agent-Class,那么在某些条件下,它可以被自动加载,而无需显式指定-javaagent参数。文档中提到“发送特定的包可能触发”,这可能利用了JVM在懒加载或某些类初始化时触发Agent加载的机制,但这部分依赖于具体的JVM实现和版本,并不总是稳定。
实战与风险:
- 替换系统JRE目录下的文件需要极高的权限(通常是root)。
- 替换后,所有使用该JDK/JRE的Java进程,在下次启动时都有可能被植入后门,影响极其恶劣。
- 文档中提到的通过发送特定HTTP头(
Accept: text/html;charset=GBK)来触发,可能是在某些场景下触发了对charsets.jar中类的加载,从而激活了Agent。但这并非百分百可靠。
重要警告:这种方式破坏性极强,在实验环境中也要谨慎操作,因为会污染你的基础Java环境。建议使用Docker容器,测试完毕后直接销毁容器。
4.4 方式四:动态加载 - 无文件落地的终极攻击
这是最具技巧性的一种方式,适用于已经通过Webshell获得了代码执行权限,但无法上传文件或重启服务的情况。核心是利用URLClassLoader动态加载远程或临时写入的JAR字节码。
4.4.1 JSP/Java代码加载
提供的JSP代码片段是一个经典的“无文件”加载器。其步骤是:
- 接收载荷:通过HTTP POST请求,接收经过两次GZIP压缩的
vagent.jar文件二进制流。 - 临时写入:将二进制流写入临时目录的一个随机文件名文件中。
- 动态加载:创建一个
URLClassLoader,指向这个临时文件,然后加载并调用其中的org.apache.catalina.servlets.Attach.att()方法。这个方法内部应该实现了通过Instrumentation.attach()API动态加载Agent到当前JVM的逻辑。
为什么是两次GZIP?单次GZIP压缩可以减小体积。两次压缩可能是一种简单的混淆,也可能为了绕过某些基于内容特征的检测。在Burp中直接Paste from file处理这种双重压缩流可能有问题,所以文档推荐用Postman。
4.4.2 Base64变种与vagent-mini小马当请求参数需要放在URL中时,二进制文件不适合,于是有了Base64编码的版本。JSP代码会先尝试使用java.util.Base64(JDK8+),回退到sun.misc.BASE64Decoder。vagent-mini.jar是一个精简版,可能只包含了动态加载Agent的核心逻辑,体积更小,方便通过命令回显等方式直接写入。先注入小马,小马再去拉取完整的大马,这是一种分阶段攻击策略。
实战模拟:
- 将
vagent.jar进行两次GZIP压缩:cp vagent.jar temp.jar gzip -f temp.jar mv temp.jar.gz temp.jar gzip -f temp.jar # 最终得到 temp.jar.gz - 将得到的
temp.jar.gz用Postman,以二进制形式(binary)作为Body,POST到部署了上述JSP的页面。注意:需要连续发送两次相同的请求。这是因为JSP代码设计可能为了确保数据完整性。 - 如果成功,会返回一个成功的标识或空响应。之后即可用冰蝎等客户端连接内存马地址。
5. 内存马功能解析与连接测试
成功注入后,内存马会注册多个端点,提供不同的功能。
5.1 冰蝎内存马连接与流量分析
- 路径:
/faviconb - 密码:自定义协议(见原理部分代码)。冰蝎客户端需要配置对应的加密器。
- 连接步骤:
- 在冰蝎客户端添加Shell。
- URL填写
http://target:port/faviconb。 - 选择“自定义”加密器。
- 关键是要实现与内存马服务端完全一致的加密解密算法。你需要将vagent文档中提供的
Encrypt和DecryptJava方法,翻译成冰蝎客户端支持的语言(通常是Java或C#)并打包成Jar,放在冰蝎的plugins目录下。 - 选择你打包的加密器进行连接。
流量特征:通信数据会经过+1->GZIP->+1的混淆。在WAF看来,这像是经过压缩的乱码,很难匹配到传统的Webshell特征。这体现了内存马在对抗流量检测方面的优势。
5.2 命令执行与代码执行马
- CMD马:路径
/faviconc。POST两次Base64编码的系统命令,返回命令执行结果。这相当于一个无文件的Webshell。 - JS马:路径
/faviconjs。POST两次Base64编码的JavaScript代码(基于JVM的Nashorn或GraalVM引擎执行)。这提供了更大的灵活性。 - 蚁剑连接:文档提到蚁剑连接密码是
a。这意味着这个内存马也兼容蚁剑的默认连接协议,降低了攻击者的使用门槛。
5.3 代理型内存马 - 内网穿透的跳板
这是内存马的高级用法,将Web服务器变成一个SOCKS5代理或端口转发节点。
- Neo-reGeorg:路径
/faviconneo,密码page。使用Neo-reGeorg客户端连接时,需要加上--skip选项。它会在服务端建立一个HTTP隧道,用于代理流量。 - Suo5:路径
/faviconsuo,无密码。Suo5是另一款HTTP隧道工具。 - WebSocket代理:路径
/faviconws,使用Gost等支持WebSocket隧道的工具连接。WebSocket协议更接近于普通HTTP流量,隐蔽性更好。
这些代理马使得攻击者在植入后门后,可以直接利用该服务器作为跳板,对内网其他机器进行扫描和攻击,极大地扩展了攻击面。
6. 防御、检测与排查思路
了解了攻击,才能更好地防御。针对Java Agent内存马,防守方可以从多个层面构建防线。
6.1 预防阶段:加固与最小化权限
- JVM启动参数限制:
- 在非必要的情况下,考虑使用
-XX:-DisableAttachMechanism来禁用Attach机制(但这会影响一些合法的诊断工具)。 - 使用
-javaagent参数时,严格限定Agent Jar的路径和签名,只加载可信的Agent。
- 在非必要的情况下,考虑使用
- 文件系统与权限控制:
- 对Tomcat的
lib目录、JDK的jre/lib目录设置严格的写权限,只有安装和更新时由特定管理账户操作。 - 使用文件完整性监控(FIM)工具,监控这些关键目录下文件的创建、修改和删除。
- 对Tomcat的
- 应用运行权限:使用非root用户运行Tomcat/Java应用,遵循最小权限原则,防止攻击者替换系统级文件。
6.2 检测阶段:主动发现与监控
- JVM进程检测:
- 使用
jcmd <PID> VM.command_line查看JVM启动参数,检查是否有未知的-javaagent。 - 使用
jcmd <PID> VM.system_properties查看系统属性,攻击者可能会设置一些属性作为标记。 - 核心命令:
jcmd <PID> ManagementAgent.status和jcmd <PID> ManagementAgent.list可以列出已加载的Agent。定期在服务器上执行此命令进行巡检。
- 使用
- 网络流量与行为检测:
- 监控Web应用是否出现了非常规的URL路径,如
/faviconb、/faviconc等。可以使用RASP(运行时应用自保护)技术在应用内部监控Filter/Servlet的动态注册和可疑的URL访问模式。 - 分析HTTP请求/响应体。虽然流量被加密,但固定的URL路径、异常的POST请求长度和频率、以及响应内容的熵值(加密后数据随机性高)可以作为辅助检测指标。
- 监控Web应用是否出现了非常规的URL路径,如
- 内存扫描与RASP:
- 部署商业或开源的RASP产品,它们可以在应用内部拦截类加载、方法执行等行为,直接检测字节码的非法修改。
- 使用
Java Mission Control (JMC)或Async-Profiler等工具分析运行时内存中的类,寻找被修改的类或可疑的类加载器。
6.3 应急响应与排查
如果怀疑存在内存马,可以按以下步骤排查:
- 定位可疑进程:使用
netstat -antp或ss -antp结合ps aux,找到所有Java进程及其监听端口。 - 检查已加载Agent:对每一个Java进程PID,执行
jcmd <PID> ManagementAgent.list。 - 检查启动参数和类路径:使用
jcmd <PID> VM.command_line和jcmd <PID> VM.system_properties。 - 检查关键目录:立即检查
Tomcat/lib、JAVA_HOME/jre/lib下是否有可疑的、近段时间新增的JAR文件。 - 重启并验证:在业务允许的情况下,重启应用服务器。如果重启后可疑URL访问依然存在,则极可能存在文件级的持久化后门(如Tomcat Lib或JDK替换)。需要彻底清理环境,甚至重装JDK/Tomcat。
- 内存Dump分析(高级):在安全环境下,使用
jmap -dump:live,format=b,file=heap.bin <PID>导出堆内存。然后用MAT、JProfiler等工具分析,查找内存中存在的恶意类实例、奇怪的字符串常量(如密码、URL路径)等。
7. 总结与个人思考
研究Agenst这类工具的过程,是一次对JVM底层机制和攻防思维的深度训练。它清晰地展示了,在应用安全领域,攻击面远不止于SQL注入、XSS这些Web层漏洞。运行时环境本身,如果配置不当或缺乏监控,就会成为攻击者坚固的“堡垒”。
从攻击者视角看,这种技术组合拳(利用合法机制+无文件驻留+加密通信)极具威力。而从防御者视角,我们必须建立起纵深防御的体系:从操作系统权限、JVM安全配置,到应用运行时行为监控(RASP),再到网络流量的异常检测,每一层都不能缺失。
最后,一个很深的体会是:安全是一个持续对抗的过程。今天分析的内存马,明天可能就会有新的变种(比如利用LambdaMetafactory的动态代理、或者瞄准GraalVM原生镜像)。作为安全从业者,保持对底层技术的敬畏和持续学习的心态,理解每一行代码、每一个机制可能被如何滥用,是我们构筑有效防御的基石。在实验环境中亲手复现一遍这些攻击流程,你会对如何保护你的系统有完全不同的、更深刻的认识。