webshell的隐藏与检出一直以来都是攻防的焦点,随着内存马的研究与工具的公开,内存shell已然成为了webshell主流。
引用【9】中关于攻击的思路
- 基于Servlet规范的利用,动态注册Servlet规范中的组件,包括Servlet,Filter,Listener,这部分的公开文章比较多,比如:基于tomcat的内存 Webshell 无文件攻击技术(https://xz.aliyun.com/t/7388)。
- 基于特定框架的利用,框架一般对于Servlet又进行了一层封装,动态注册框架的路由,文章:基于内存 Webshell 的无文件攻击技术研究(https://www.anquanke.com/post/id/198886)
- 基于javaagent修改Servlet处理流程中的字节码,工具:memShell(https://github.com/rebeyond/memShell)
引用【8】中关于检测的思路
无论是以上哪种攻击方式,从防守方的角度来说,检测的方式都是通过java instrumentation机制,将检测jar包attach到tomcat jvm,检查加载到jvm中的类是否异常。整体检测思路为:
- 获取tomcat jvm中所有加载的类
- 遍历每个类,判断是否为风险类。我们把可能被攻击方新增/修改内存中的类,标记为风险类(比如实现了filter/servlet的类)
- 遍历风险类,检查是否为webshell:
- 检查高风险类的class文件是否存在;
- 反编译风险类字节码,检查java文件中包含恶意代码
有攻有防,而防御又引发攻击的升级,冰蝎最新版已经加入了反VirtualMachine.Attach功能。
冰蝎AntiAgent
AntiAgent的实现
先看看冰蝎的实现,具体在MemShell.java
1 | public void doAgentShell(boolean antiAgent) throws Exception { |
测试AntiAgent
1 | public class testAttach { |
- 找一个jvm进程,运行testAttach
- 删除对应.java_pidxx
- 再运行testAttach,会发现报错,socket连不上
可以看出,冰蝎通过删除.java_pid,实现了无法VirtualMachine.attach。原理如何?检测是否有绕过空间?
Java Attach机制
参考【1】中总结了Java 的3种动态Attach方法
- 继承Tool/HotSpotAgent.attach(采用Serviceability Agent,简称SA)
- VirtualMachine.attach(Attach到Attach Listener线程后执行有限命令)
- Perf.getPerf().attach(通过PerfData文件获取信息)
其中1会暂停进程,不适用于内存马的检测,3只是关注进程状态信息,主要用于性能分析,关注的类不全,无法用于检测异常类。
Java Instrumentation API
引用参考【10】,Java Instrumentation APi支持启动时/运行时Attach Agent
如果需要在目标JVM启动的同时加载Agent,需要在Agent中实现下面的方法:
1 | [1] public static void premain(String agentArgs, Instrumentation inst); |
如果希望在目标JVM运行时加载Agent,则需要实现下面的方法:
1 | [1] public static void agentmain(String agentArgs, Instrumentation inst); |
本文只关注运行时加载Agent,也就是VirtualMachine.attach。
VirtualMachine.attach
此流程图需要搭配参考【3】中的调试过程,分析得出
JVM 流程
- init, 等待SIGQUIT(也是SIGBREAK)
- 收到SINQUIT,attach_pid验证(这个不是很重要)
- 判断是否有对应java_pid socket文件,没有则创建
- 建立unix socket tcp server ,绑定java_pid socket通道
- 进入循环,响应attach client
- 新SIGQUIT,验证attach_pid(不重要), 进入5(重点,并不会重新建server)
在步骤5完成之后,删除掉java_pid socket文件,会怎样?可恢复吗?
- 即使删除socket通道,server不会失败重新尝试连接
- 即使新建相同name的unix socket通道,client也无法连接,所以不可恢复
UnixSocket
以下通过实验验证以上的两点结论
server.go
1 | import ( |
client.go
1 | import ( |
- 运行server
- 运行client,tcp连接ok,数据传输ok
- 删除java_pid
- 运行client,tcp连接失败
- nc -lkU java_pid,创建相同文件
- 运行client,仍然连接失败
JVM信号
如上,通过恢复java_pid重建连接的思路无法走通,那有其他的思路重新让jvm建立新的tcp server吗?
第一步时是通过SIGQUIT信号触发jvm init tcp server的,有没有其他信号可以重现呢?可惜通过参考【4】,并未发现有如此功能的信号。
小结
本文复现了冰蝎为防止常见内存马检测antiAgent的功能(不得不膜拜beyond大佬,对Java实在是熟悉),在此基础上分析其实现原理,得出无法绕过的悲情结论。
不过无法Attach本身就是一个强特征(虽然有些/tmp目录清理会导致误报),也使得antiAgent有利有弊,攻击者也需要斟酌而用。
水平有限,如有疏漏,恳请告知。
———–20210624补充————
参考【11】中带头大哥 补充了关于冰蝎3.0beta8版本windows的实现,AntiAttach原理是通过关闭sun.tools.attach.WindowsVirtualMachine pipe,带头大哥 通过分析WindowsVirtualMachine JNI的实现得出:
WindowsVirtualMachine是通过向目标JVM注入shellcode来执行我们与目标JVM的通信指令。Pipe只是一种通信手段,关闭pipe并不能影响我们向目标JVM加载javaagent。
大哥66的
在linux侧,转化为的问题是:UnixSocket文件删除之后如何恢复原有的server连接?本文只尝试了新建同名socket文件,或者还有其他方式?这块还是菜🐔,求知道的大佬们指点。