Behinder NoFileAgent内存马防检测浅析
Inject
(Behinder内存马attach思路已经在rebeyond本人的文章中解释的很清楚了
先看Behinder no file agent注入,其实是一个以本来上线的jsp shell做通信的过程,需要多步通信,简单看一下
直接进Behinder源码定位到net.rebeyond.behinder.ui.controller.MainController#injectAgentNoFile方法
看关键位置,首先是开头调用shellService.getMemShellTargetClass方法
上面方法跟进其实是调用net.rebeyond.behinder.core.ShellService#getMemShellTargetClass方法,跟进如下,通过自己写的一个调用payload方法的接口,传送payload的class字节码去目标jvm中执行,并且传入相应的参数action: get,这边parseCommonAction向下跟进其实就是一个与shell通信的过程
传送的payload class为net.rebeyond.behinder.payload.java.MemShell,执行action为get所以我们重点看一下这部分代码。一共定义了三个目标类分别是weblogic的ServletStubImpl、以及jakarta和javax的HttpServlet,调用getResourceAsStream方法尝试在目标项目目录中获取三者之一的class字节码,取到任意一个即写入临时Map classObj变量中,后面通过response形式返回给Behinder客户端
也就是说Behinder在处理寻找欲修改字节码的目标类时,是在已上线的shell基础上,客户端通过http通信的形式拿到目标环境中的字节码
其实拿回来之后大概可以猜到怎么处理的,无非就是javassist或者asm把shellcode写入方法中再组装到payload中,我们跟进看一下
回到injectAgentNoFile方法继续向下看,从请求中拿到类名和字节码之后做了下面这部分处理,确实是利用javassist进行恶意代码植入操作,调用CtMethod在目标类中植入shellcode
shellcode是写死的,在Constant类中可以找到
之后injectAgentNoFileMemShell方法,最后会进入MemShell类根据目标平台来植入无文件马
至此就是Behinder内存马植入的一个过程,接下来来分析一下他的防检测
Anti
上面分析到最后调用到ShellService的injectAgentNoFileMemShell方法,传入了一个Boolean类型的参数isAntiAgent,用来确定是否采用防检测,跟进看一下
Linux最终回跟踪到如下位置,直接删除了/tmp/.java_pid{pid}文件
(windows并没有
由于在linux中,socket通信是通过文件的形式(Linux一切皆文件),这个文件是Java attach api的一部分,用于jvm之间socket通信,分析agent attach过程就会知道这一点
可以发现正是通过创建了一个socket然后通过创建的/.java_pid$PID的形式来建立与目标JVM的通信(这边调试是MAC所以文件目录不太相同)
所以Behinder把这个文件删了就是阻止后续的JVM进程间socket通信,而attach是依赖于socket通信的
这样做可以阻止一般的通过attach agent检测工具,下面以alibaba的arthas分析一下,看一下一般jvm工具的attach流程
从arthas起点包开始分析,进入到arthas-boot,直接看主逻辑Bootstrap#main重点部分
获取pid部分如下,调用ProcessUtils#select方法调出所有正在运行jvm的pid
select方法中获取所有的jvm进程号是通过这个listProcessByJps,顾名思义调用jdk自带的jps工具来拿到jvm的pid和进程名等信息,select后面的代码就是选择process的一些操作,保存用户选择的jvm pid等信息到map中
拿到pid后回到Bootstrap#main方法中,在attach前会尝试检查是否有已经开放arthas client端
之后就是对当前arthas环境目录的获取,然后关键代码如下,将获得的pid以及attach的一些参数传入startArthasCore方法,这里就从arthas-boot包转向了刚才获取(构建)的arthas主目录(home)中的arthas-core.jar进行attach pid的操作
startArthasCore中通过创建新进程并且直接命令传参形式执行java命令来触发arthas-core.jar包
然后跟进到arthas-core.jar中,就是通过tools.jar中的VirtualMachine来attach的流程
将arthas-agent.jar load进去
基本流程基本理清了,但是操作arthas attach之后即使删除/tmp/.java_pid仍然可以attach,因为获取pid之后发现进程中正在运行已经挂在jvm上的arthas client,经过上述分析可以知道arthas会直接去连接那个client,端口默认3658
后续操作简单来讲就是通过arthas-core中server部署一个服务端(审计ArthasBootStrap#bind方法),然后arthas-client作为客户端去连接,形成一个隧道,之后的连接都是通过这个隧道,所以arthas只要连接一次即使删除/tmp/.java_pid文件也可以再次挂载
所以说Behinder的防检测针对一个attach过的jvm是有效的,并且已经挂载好的并不会受影响
现在有一个疑问,这个/tmp/.java_pid$PID这个文件是何时生成的呢,为什么说它是attach api的一部分,我们跟进VirtualMachin#attach方法,持续跟进到LinuxVirtualMachine的构造方法中,注意到下面这部分代码
在attach时会尝试获取/tmp/.java_pid这个文件的位置通过findSocketFile方法,首次是获取不到的,所以进入if中,他会生成临时文件/proc/$PID/cwd/.attach_pid$PID文件,并且调用sendQuitTo native方法给jvm发一个quit信号来开启attach机制,开启时会自动创建这个/tmp/.java_pid$PID文件并监听,回到java层再尝试寻找这个文件并返回
进入native层看一下jvm中attach listener部分的代码,简单看一下init方法,整个是一个socket创建过程,通过snprinf创建socket要绑定的文件路径,也就是我们之前提到的/tmp/.java_pid$PID,然后通过就是创建socket并bind,任何步骤失败会直接返回-1,整体其实就是创建了一个unix domain socket来监听后续的attach