ShokaX

Java内存马3:内存马查杀

发布于 字数统计 12.6k 字 阅读时长 42 分钟

Java内存马3:内存马查杀

发布于 字数统计 12,559 阅读时长 63 分钟

前言

文章其实早就写好了,但前段时间run到了甲方,巨忙,一直没空整理发出来。= =

PS:个人学习见解,有错的地方,大佬们尽管喷。

这里针对Tomcat服务器中的内存马进行了查杀。主要是因为Springboot项目通常是一个jar包启动的,这种情况下我们没办法类似Tomcat使用JSP来执行任意代码卸载内存马。

对于jar包的想要杀掉内存马其实可以通过打反序列化漏洞链子把修复代码打进去,但其实就是相当于漏洞的反向利用了,但这要求应急人员对内存马和java反序列化漏洞有较深的理解。

而这就不得不提一个致命的问题了:

①服务能重启吗?

->能->直接重启解决。

-不能->服务很重要->你敢打反序列链子进去么?打崩了怎么办?->不敢打,没法重启->GG

因此就目前看来jar包Web想要杀掉内存马最简单的办法就是重启了,如果不允许重启的话,就只能在Waf方面做手脚,把内存马的映射地址给拦截掉之类的。

因此出于上述问题,其实针对内存马的查杀更多的应用场景还是Tomcat服务器,jar包的通常重启完事了,不能重启的想必也不敢打链子进去。

因此这里我们是针对Tomcat下的内存马做查杀。

工具介绍

arthas(仅排查)

arthas主要就是通过人工分析内存,查看内存中的类,分析其代码逻辑,来确定是不是真正的内存马。

https://arthas.aliyun.com/doc

规则类工具自动化提取排查Copagent(仅排查,只适用于jdk1.8)

基于Alibaba Arthas编写的copagent项目,分析JVM中所有的Class,根据危险注解和类名等信息dump可疑的组件,结合人工反编译后进行分析。

copagent 使用 javaagent 技术获得了全部的类,判断其包名、实现类名、接口名、注解来提取关键类,并根据类是否在磁盘上有资源链接对象、类中是否包含恶意行为关键字来判断其是否为内存马。

https://github.com/LandGrey/copagent

java-memshell-scanner(可查可杀,推荐)

(jsp文件,不能用在jar包服务)

通过名称、对应的Class是否存在来判断是否是内存马
优点在于一个简单的JSP文件即可结合人工审查(类名和ClassLoader等信息)对内存马进行查杀,也可以对有风险的Class进行dump后反编译分析。

c0ny大佬的是三年前的祖传代码,只能查杀servlet-api类型的,因此我这边在c0ny大佬的基础上更新了这版:

新增tomcat-vlaue、websocket、timer、ugrade、executorshell内存马的查杀逻辑,并给上一些tips,帮助不太懂内存马的同学更好的进行分析。

GitHub - ruyueattention/java-memshell-scanner: 通过jsp脚本扫描并查杀Tomcat内存马,当前支持Servlet-api、Tomcat-Value、Timer、Websocket 、Upgrade 、ExecutorShell内存马的查杀逻辑。

工具attach不上目标java进程的问题

如果我们的工具attach不上目标java进程的话,可能原因有:

①权限不对:切换到和目标java进程相同权限运行工具。

②java版本不对:使用和目标java进程相同的java版本运行工具。

③/tmp/.java_pid{pid}文件被删了。(这种情况下,只能重启java服务)

PS:在冰蝎中有一个防检测功能后,勾选上后并且为linux系统的话,会删除掉java socket文件,导致后续其他的jar包都无法注入。也就是说我们的arthas也无法注入,自然也就无法调试了。

image-20230309153331933

image-20230309153341662

这种时候我们只能重启java服务,或者使用gdb导出 进程快照(coredump)再转成Java heap dump来简单分析。

Java 无法 attach 到目标进程, 使用 core dump 转换为 Java heap dump - 见识 动脑 品质 - 生活的美好 (tianxiaohui.com)

查杀思路

首先,我们需要分析常见的内存马存在的一些可疑的特征。通常情况下更多的是使用以下特征去判断:

1.继承可能实现Webshell接口,例如Servlet,Filter,Listener,Interceptor

• javax.servlet.http.HttpServlet

•org.springframework.web.servlet.handler.AbstractHandlerMapping

• javax.servlet.Filter

• javax.servlet.Servlet

• javax.servlet.ServletRequestListener

•…

2.名字:内存马名可能包含shell等关键字,并且常见的包名如下

• net.rebeyond.

• com.metasploit.

3.高危classloader加载:查看classloader是不是Templates或bcel等

一般来说,正常的Servlet-api等组件都是由中间件的WebappClassLoader加载的。

而反序列化漏洞喜欢利用TemplatesImpl和bcel执行任意代码。所以这些class往往就是以下这两个:

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader

com.sun.org.apache.bcel.internal.util.ClassLoader

而由JSP加载的内存马对应的ClassLoader则为ClassLoader为org.apache.jasper.servlet.JasperLoader

4.对应的ClassLoader路径下没有class文件。

查杀例子

在tomcat中的内存马主要有以下类型:

Servlet-api、java agent、 tomcate value、timer、webscket、Executor、Upgrade

基本上我们使用java-memshell-scanner就能完成查杀内存马的操作了。但如果有特殊需求,可以搭配arthars去使用。

PS:冰蝎的内存马策略是javaagent,而哥斯拉则是Servlet-API类型

Servlet-api类型(代表:哥斯拉)

PS:也可以用cop.jar,直接跑就行,最终会输出结果和对应的class文件,然后根据危险等级,结合人工分析去确定是否为内存马。

这种类型的内存马最多,实现起来也是最简单的。其中的典型就是哥斯拉和我们平时一些利用工具,他们都是这种类型的内存马。

这里我们使用java-memshell-scanner去扫描,可以发现,扫到了很多没有class文件的内存马。(图中展示的是哥斯拉注入的内存马)

主要特征:可以看到,这种由工具生成的内存马它们的特征还是很明显的。classloader都是org.apache.coyote.xxx ,并且磁盘上没有对应的class文件,就能够很好的和正常的Servlet进行区分。

image-20230309153744507

显然使用工具注入的内存马有着不可避免的一些特征,但如果攻击者通过其他手段来注入内存马呢?

比如下面:攻击者通过上传JSP文件来注入内存马。可以看到,同是Servlet-api类型的内存马,它和哥斯拉有个区别就是磁盘中存在class文件。

image-20230309154626722

这种情况下,就不太好区分了,我们主要还是去看它的classload,可以看到,这里我们通过JSP来注入内存马,它的classload是org.apache.jasper.servlet.JasperLoader,而正常情况下的Servlet-api是由中间件的WebappClassLoader来加载的。

因此classload不是WebappClassLoader的Servlet-api就很有可能是内存马,但想要盖棺定论最好还是把内存dump下来,手动分析。

具体分析代码可以尝试dump他的内存下来分析。

如果dump不下来的话,可以使用arthars来搞。

查看 JVM 已加载的类信息,筛选Servlet类,其中我们内存马如下:

sc *.Servlet

image-20230309153842352

使用jad命令反编译这个可疑类

jad org.apache.coyote.ObjectReader

我们把它copy出来分析,其实就是哥斯拉的马子,到这里我们就能够确认这个是内存马了。

PS

dump出来本地用IDEA反编译分析

dump -d /home/momo/Desktop/MemSHell/copagent org.apache.coyote.ObjectReader

image-20230309153909622

image-20230309153916976

到这里已经可以确定是内存马了,接着我们还可以看看是谁把它加载到了JVM。

sc -d org.apache.coyote.ObjectReader

其中classLoad就是加载它的类:org.apache.jsp.godzilla_jsp

image-20230309154215759

继续追踪,找到代码路径,到web目录把godzilla.jsp给删了。

sc -d org.apache.jsp.godzilla_jsp

image-20230309154240825

agent类型内存马查杀(代表:冰蝎)

原理:利用instrument机制,在不增加新类和新方法的情况下,对现有类的执行逻辑进行修改实现的内存马。

这类内存马实现方式比较多,检测比较困难,copagent和memershell都无法监测冰蝎4注入的内存马。

因此这里我们用artars去分析,主要关注的其实是agent内存马常改的一些类

javax.servlet.http.HttpServlet

org.apache.tomcat.websocket.server.WsFilter

org.apache.catalina.core.ApplicationFilterChain

其中冰蝎内存马通过修改javax.servlet.http.HttpServlet#service方法,添加自己的处理逻辑,也就是内存马。

jad javax.servlet.http.HttpServlet

可以看到这个类被添加上了一些冰蝎服务端的一些逻辑,妥妥的内存马。

image-20230309154912176

当然如果arthars attach不上java进程,我们也可也导出coredump内存来看。(有这闲心,不如重启)

image-20230309155002599

PS:zhouyu内存马的策略

image-20230309155102021

杀掉agent马的话,我们其实就是修改还原这个被篡改的类的字节码。需要编写一个javaagent,然后注入到目标中间件即可。

image-20230309155247945

image-20230309155120996

此时再次连接内存马失败,并且后续的内存马都注入不上去。

image-20230309155155735

Tomcat-Value内存马

原理:Tomcat-Value通常是一个Java对象,其中包含用户的会话信息,例如用户名、密码、购物车、浏览历史记录等。

而Tomcat-Value内存马就是针对Tomcat-Value的内存马。攻击者可以通过反序列化或者代码执行在Tomcat容器中注入恶意的会话数据,使得Java Web应用程序在读取和处理会话数据时,执行恶意代码,从而实现攻击目的。

这里根据Tomcat-Value的实现,简单的写了个jsp来获取当前的Value。

image-20230309155311895

image-20230309155315752

删除其实就是调用Pipeline对象的removeValve方法即可,和注入是一个原理。

image-20230309155328068

Timer内存马

原理:对于Java环境下的 Timer 内存马,一般是通过恶意代码注入到 Timer 定时任务的回调函数中,从而在回调函数执行时触发恶意代码的执行,进而实现对服务器的攻击。一般是新建一个Timer定时任务,其中回调函数逻辑为获取当前Tomcat线程,从线程中找到http请求对象,获取其中的命令并执行。因此通常需要BP大量发包来触发。

这类内存马查找的话主要是找内存中的java.util.Timer类。

然后看到可疑的加载类就jad反编译,查看源码分析是否恶意。

image-20230309155350392

随便跟进一个Timer,可以看到,我们跟进的这个Timer执行了list.get(1)这个参数。而list又是由TimerShell_jsp这个类的getRequest方法得来的。

image-20230309155358601

接着跟进这个TimerShell_jsp类

image-20230309155405735

看下源码就可以知道这其实就是通过Tomcat线程,读取HTTP头的数据,传给前面的Timer执行getRuntime了。

image-20230309155414733

查杀的话,这种内存马是创建了Timer定时任务来做的。

所以我的想法就是找到存储了Timer定时任务的一个对象,想办法把内存马从这个对象中卸载出去。

这里跟了下流程找到相关逻辑,其实就是把这个TimerTask给取消掉就行了

大致思路就是遍历当前线程,找到TimerThread的类,然后通过反射去拿到目标对象,最后调用TimerTask类的cancle方法来取消这个TimerTask。

image-20230309155456506

同样的,在我写的java-memshell-scanner中已经集成。

image-20230309155514194

Websocket内存马查杀

原理:创建一个Websocket服务端,服务段逻辑为webshell逻辑。其实和Servlet差不多的。

其实和打入是一样的,找到wsServerContainer获取里面的websocket服务端,调用remove方法即可。

image-20230309155546055

Tomcat中实现一个Websocket服务端

tomcat中存在两种方式:一、ServerEndpoint注解方式。二、继承抽象类Endpoint方式。
这两种方式就好比Servlet的注解创建方式和继承创建方式一样。
显然的通过注解,就不需要更多配置就能实现一个简单的Websocket。

注解形式(建议)

Tomcat在启动时会默认通过 WsSci 内的 ServletContainerInitializer 初始化 Listener 和 servlet。
然后再扫描 classpath下带有 @ServerEndpoint注解的类进行 addEndpoint加入websocket服务。

image-20230424125217874

image-20230424125231746

继承抽象类Endpoint方式

继承抽象类 Endpoint方式比加注解 @ServerEndpoint方式更麻烦,主要是需要自己实现 MessageHandler和 ServerApplicationConfig。@ServerEndpoint的话都是使用默认的,原理上差不多,只是注解更自动化,更简洁。

image-20230424125319346

内存马注入

实现思路类比其他类型内存马,这里以JSP注入为例。

  • 获取ServletContext
  • 获取WebSocketContainer
  • 创建恶意的ServerEndpointConfig
  • 调用addEndpoint()

首先定义一个Websocket服务段。这里我用注解形式来创建。

image-20230424125421163

然后获取ServletContext并把websocket服务端绑定到指定url上。

image-20230424125521762

接着访问JSP,执行注入。此时,成功注入Websocket内存马。

image-20230424125541169

image-20230424125605601

查杀Websocket内存马

其实和打入是一样的,找到wsServerContainer获取里面的websocket服务段,调用remove方法即可。

image-20230424125709700

Upgrade内存马

这类内存马也是完全可以参考其实现,简单的实现查杀。

简单的通过反射去获取httpUpgradeProtocols,然后从中remove掉内存马即可。

image-20230309155602179

image-20230309155606986

还可以把类dump下来分析源码确定是否为恶意的。

image-20230309155619640

Executor内存马

Executor内存马的大致原理是:Tomcat中存在一个对象存储了线程池,这个线程池在请求响应周期中起到作用。因此我们如果自己定义一个恶意的线程池,其中execute执行webshell逻辑,并把它注册到这个对象,那就是一个内存马了。

实现

绕过检测之Executor内存马浅析(内存马系列篇五) - FreeBuf网络安全行业门户

但上面的代码有点问题:主要就是获取requests数据和获取NioEndpoint的问题。我改成如下代码:

image-20230309160834750

另外的问题就是request数据的获取了:nioChannels里的数据时而有值时而没值,也就是不太稳定,需要我们多次发包。

image-20230309160051137

如图,当前http是存在cmd

,但通过nioChannels却获取不到。

image-20230309160058093

文章Java Tomcat Executor/Processor 内存马 - Twings (aluvion.github.io)发现execute方法中的command这个传参中socketWrapper->socket->appReadBufHandler对象为Http11InputBuffer类,而这个类就恰好是request数据的对象,因此通过这个类,我们就能稳定的获取到传参。

因此我们完全可以用这个对象来获取传参,最终完成效果如下:

image-20230309160142619

正当我以为大功告成之际,很快,我就发现另一个致命的问题了——当前获取到的requests对象是上一个请求(或者前几次,跟线程数有关)的缓存数据,这问题就大了,这就意味着我们的响应数据可能会返回到别人的http会话中。所以还得重新去找存储了request数据的对象。

image-20230309160252619

通过文章Executor内存马的实现(二) - 先知社区 (aliyun.com)我找到了解决办法。但跟文章里说的点去找,发现是找不到attachment对象的,最终我是在selector->channelArray中找到了。

image-20230309160321202

随后就是跟着作者的思路复原即可,最终实现如下:

image-20230309160332009

查杀

查杀起来也简单,只要看Executor 是不是tomcat的类,然后把这个对象中存储的线程池还原成原来的就行。

image-20230309155856315

老样子。

image-20230309155930731