前言

出发点是Java Agent内存马的自动分析与查杀,实际上其他内存马都可以通过这种方式查杀

本文主要的难点主要是以下三个,我会在文中逐个解答

  1. 如何dumpJVM真正的当前的字节码
  2. 如何解决由于LAMBDA表达式导致非法字节码无法分析的问题
  3. 如何对字节码进行分析以确定某个类是内存马

背景

对于Java内存马的攻防一直没有停止,是Java安全领域的重点

回顾TomcatSpring内存马:FilterController等都需要注册新的组件

针对于需要注册新组件的内存马查杀起来比较容易:

例如c0ny1师傅的java-memshell-scanner项目,利用了Tomcat API删除添加的组件。优点在于一个简单的JSP文件即可查看所有的组件信息,结合人工审查(类名和ClassLoader等信息)对内存马进行查杀,也可以对有风险的Class进行dump后反编译分析

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

但实战中,可能并不是以上这种注册新组件的内存马

例如师傅们常用的冰蝎内存马,是Java Agent内存马。以下这段是冰蝎内存马一段代码,简单分析后可以发现冰蝎内存马是利用Java Agent注入到javax.servlet.http.HttpServletservice方法中,这是JavaEE的规范,理论上部署在Tomcat的都要符合这个规范,简单来理解这是Tomcat处理请求最先且总是经过的地方,在该类加入内存马的逻辑,可以保证稳定触发

类似的逻辑,可以使用Java Agent将内存马注入org.apache.catalina.core.ApplicationFilterChain类中,该类位于Filter链头部,也就是说经过Tomcat的请求都会交经过该类的doFilter方法处理,所以在该方法中加入内存马逻辑,也是一种稳定触发的方式(据说这是老版本冰蝎内存马的方式)

还可以对类似的类进行注入,例如org.springframework.web.servlet.DispatcherServlet类,针对于Spring框架的底层进行注入。或者一些巧妙的思路,比如注入Tomcat自带的Filter之一org.apache.tomcat.websocket.server.WsFilter类,这也是Java Agent内存马可以做到的

上文简单地介绍了各种内存马的利用方式与普通内存马的查杀,之所以最后介绍Java Agent内存马的查杀,是因为比较困难。宽字节安全的师傅提出查杀思路:基于javaAgent内存马检测查杀指南

引用文章讲到Java Agent内存马检测的难点:

调用retransformClass方法的时候参数中的字节码并不是调用redefineClass后被修改的类的字节码。对于冰蝎来讲,根本无法获取被冰蝎修改后的字节码。我们自己写Java Agent清除内存马的时候,同样也是无法获取到被redefineClass修改后的字节码,只能获取到被retransformClass修改后的字节码。通过JavaassistASM工具获取到类的字节码,也只是读取磁盘上响应类的字节码,而不是JVM中的字节码

宽字节安全的师傅找到了一种检测手段:sa-jdi.jar

借用公众号师傅的图片,这是一个GUI工具,可以查看JVM中所有已加载的类。区别在于这里获取到的是真正的当前的字节码,而不是获取到原始的,本地的字节码,所以是可以查看被Java Agent调用redefineClass后被修改的类的字节码。进一步可以dump下来认为存在风险的类然后反编译人工审核

介绍

以上是背景,接下来介绍我做了些什么,能够实现怎样的效果

不难看出,以上内存马查杀手段都是半自动结合人工审核的方式,当检测出内存马后

是否可以找到一种方式,做到一条龙式服务:

  • 检测(同时支持普通内存马和Java Agent内存马的检测)
  • 分析(如何确定该类是内存马,仅根据恶意类名和注解等信息不完善)
  • 查杀(当确定内存马存在,如何自动地删除内存马并恢复正常业务逻辑)

大致看来,实现起来似乎不难,然而实际中遇到了很多坑,接下来我会逐个介绍

【→>所有资源关注我,查看“资料”获取<←】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

SA-JDI分析

我尝试通过Java Agent技术来获取当前的字节码,发现如师傅所说拿不到被修改的字节码

所以为了可以检测Agent马需要从sa-jdi.jar本身入手,想办法dump得到当前字节码(这样不止可以分析被修改了字节码的Agent马也可以分析普通类型的内存马)

注意到其中一个类:sun.jvm.hotspot.tools.jcore.ClassDump并通过查资料发现该类功能正是dump当前的Class(根据类名也可猜测出)其中的main方法提供一个dump class的命令行工具

于是我想了一些办法,用代码实现了命令行工具的功能,并可以设置一个Filter

ClassDump classDump = new ClassDump ();
// my filter
classDump . setClassFilter ( filter );
classDump . setOutputDirectory ( "out" );
// protected start method
Class <?> toolClass = Class . forName ( "sun.jvm.hotspot.tools.Tool" );
Method method = toolClass . getDeclaredMethod ( "start" , String []. class );
method . setAccessible ( true );
// jvm pid
String [] params = new String []{ String . valueOf ( pid )};
try {method . invoke ( classDump , ( Object ) params );
} catch ( Exception ignored ) {logger . error ( "unknown error" );return ;
}
logger . info ( "dump class finish" );
// detach
Field field = toolClass . getDeclaredField ( "agent" );
field . setAccessible ( true );
HotSpotAgent agent = ( HotSpotAgent ) field . get ( classDump );
agent . detach ();

上文提到设置一个Filter是用于确定需要对哪些类进行dump操作(dump过多会导致性能等问题)

public class NameFilter implements ClassFilter  {@Overridepublic boolean canInclude ( InstanceKlass instanceKlass )   {String klassName = instanceKlass . getName (). asString ();// 在黑名单中的类需要dumpif ( blackList . contains ( klassName )) {return true ;}// 包含了关键字的类也需要dumpfor ( String k : Constant . keyword ) {if ( klassName . contains ( k )) {return true ;}}return false ;}
}

以上包含了类的黑名单和关键字:

  • 黑名单:Java Agent内存马通常会Hook的地方,需要dump下来进行分析
  • 关键字:类名如果出现memshellshell等关键字认为可能是普通内存马,需要分析
public class Constant {// BLACKLIST (Analysis Target)// CLASS_NAME#METHOD_NAMEpublic static List < String > blackList = new ArrayList <>();// SHELL KEYWORDpublic static List < String > keyword = new ArrayList <>();static {blackList . add ( "javax/servlet/http/HttpServlet#service" );blackList . add ( "org/apache/catalina/core/ApplicationFilterChain#doFilter" );blackList . add ( "org/springframework/web/servlet/DispatcherServlet#doService" );blackList . add ( "org/apache/tomcat/websocket/server/WsFilter#doFilter" );keyword . add ( "shell" );keyword . add ( "memshell" );keyword . add ( "agentshell" );keyword . add ( "exploit" );keyword . add ( "payload" );keyword . add ( "rebeyond" );keyword . add ( "metasploit" );}
}

另外如果想在Maven项目中加入JDK/lib下的依赖,需要特殊配置

 <dependency><groupId> sun.jvm.hotspot </groupId><artifactId> sa-jdi </artifactId><version> jdk-8 </version><scope> system </scope><systemPath> ${env.JAVA_HOME}/lib/sa-jdi.jar </systemPath>
</dependency>

在打包成工具Jar包时默认情况下不会加入system scope的依赖,所以需要特殊处理

 <artifactId> maven-assembly-plugin </artifactId>
<configuration><appendAssemblyId> false </appendAssemblyId><descriptors><descriptor> assembly.xml </descriptor></descriptors><archive><manifest><mainClass> org.sec.Main </mainClass></manifest></archive>
</configuration>

编写assembly.xml文件

 <!-- 省略部分 -->
<dependencySets><dependencySet><outputDirectory> / </outputDirectory><unpack> true </unpack><scope> system </scope></dependencySet>
</dependencySets>

接着就可以通过代码的方式,根据黑名单和关键字来确定需要dump哪些类然后进行dump操作了

我在测试中遇到一个小问题,值得分享:HttpServlet是正常可以dump的但是ApplicationFilterChain类没有找到。这是因为SpringBoot的懒加载问题,需要手动请求下某个接口就可以了

解决非法字节码

接下来我遇到了一个比较大的坑,通过sa-jdidump下来的字节码是非法的

在对ApplicationFilterChain类分析的时候,会报如下的错

起初我怀疑是自己用了最新版ASM框架:9.2

于是逐渐降级,发现降级到7.0后不再报错,但ClassReader不报错,在分析时候会报错

经过对比,发现是以下的情况

不报错版本

稍微分析了下,发现是ApplicationFilterChain类包含了LAMBDA

不止这个类,不少的类都有可能会包含LAMBDA

发现通过sa-jdi获取的字节码在存在LAMBDA的情况下是非法字节码,无法进行分析

这时候如果还想进行分析,只有两个选择:

  • 自己解析CLASS文件做分析(本末倒置)
  • 改写ASM源码使跳过LAMBDA

根据Java基础知识可以得知:LAMBDAINVOKEDYNAMIC指令相关,于是我改了ASM的代码

(这里不解释为什么这么改了,是经过多次调试确定的)

org/objectweb/asm/ClassReader#274

bootstrapMethodOffsets = null ;

org/objectweb/asm/ClassReader#2456

case Opcodes . INVOKEDYNAMIC :{return ;}

改了源码后,就可以正常对非法字节码进行分析了。目前来看没有什么大问题,可以正常分析,但不确定这样的修改是否会存在一些隐患和BUG。总之目前能继续了

分析字节码

分析字节码并不需要太深入做,因为大部分可能出现的内存马都是Runtime.exec或冰蝎反射调ClassLoader.defineClass实现的,针对于这两种情况做分析,足以应对绝大多数情况

以下代码是读取dump的字节码并针对两种情况对所有方法分析

List < Result > results = new ArrayList <>();
int api = Opcodes . ASM9 ;
int parsingOptions = ClassReader . SKIP_DEBUG | ClassReader . SKIP_FRAMES ;
for ( String fileName : files ) {byte [] bytes = Files . readAllBytes ( Paths . get ( fileName ));if ( bytes . length == 0 ) {continue ;}ClassReader cr ;ClassVisitor cv ;try {// runtime exec analysiscr = new ClassReader ( bytes );cv = new ShellClassVisitor ( api , results );cr . accept ( cv , parsingOptions );// classloader defineClass analysiscr = new ClassReader ( bytes );cv = new DefineClassVisitor ( api , results );cr . accept ( cv , parsingOptions );} catch ( Exception ignored ) {}
}
for ( Result r : results ) {logger . info ( r . getKey () + " -> " + r . getTypeWord ());
}

对于Runtime.exec型的分析最为简单,仅判断已dump的字节码中所有方法中是否存在该方法的调用即可(理论上会存在误报,但黑名单类不可能存在该方法,关键字类本身就是可疑的,所以这样做并无不妥)

 @Override
public void visitMethodInsn ( int opcode , String owner , String name , String descriptor , boolean isInterface ) {boolean runtimeCondition = owner . equals ( "java/lang/Runtime" ) && name . equals ( "exec" ) &&descriptor . equals ( "(Ljava/lang/String;)Ljava/lang/Process;" );if ( runtimeCondition ) {Result result = new Result ();result . setKey ( this . owner );result . setType ( Result . RUNTIME_EXEC_TIME );results . add ( result );}super . visitMethodInsn ( opcode , owner , name , descriptor , isInterface );
}

但这种情况不适用于冰蝎反射调ClassLoader.defineClass

代码不长,但对应的字节码较复杂

Method m = ClassLoader . class . getDeclaredMethod ( "defineClass" , String . class ,  ByteBuffer . class ,  ProtectionDomain . class );
m . invoke ( null );

对应字节码

LDC Ljava/lang/ClassLoader;.class // 重点关注
LDC "defineClass" // 重点关注
ICONST_3
ANEWARRAY java/lang/Class
DUP
ICONST_0
LDC Ljava/lang/String;.class
AASTORE
DUP
ICONST_1
LDC Ljava/nio/ByteBuffer;.class
AASTORE
DUP
ICONST_2
LDC Ljava/security/ProtectionDomain;.class
AASTORE
INVOKEVIRTUAL java/lang/Class.getDeclaredMethod (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; // 重点关注
ASTORE 1
L1
LINENUMBER 11 L1
ALOAD 1
ACONST_NULL
ICONST_0
ANEWARRAY java/lang/Object
INVOKEVIRTUAL java/lang/reflect/Method.invoke (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; // 重点关注
POP

这种操作需要多个步骤,并不是简单的一个INVOKE那么简单,不特殊处理的话,由于反射和ClassLoader相关操作都算是比较常见的,有一定的误报可能

于是继续掏出栈帧分析大法,具体不再介绍,之前文章 已有详细解释

根据字节码,在defineClassLjava/lang/ClassLoader;通过LDC指令入栈之前,应该认为这是恶意操作,模拟JVM指令执行后应该在栈顶设置污点

 @Override
public void visitLdcInsn ( Object value ) {if ( value instanceof String ) {if ( value . equals ( "defineClass" )) {super . visitLdcInsn ( value );this . operandStack . set ( 0 , "LDC_STRING" );return ;}} else {if ( value . equals ( Type . getType ( "Ljava/lang/ClassLoader;" ))) {super . visitLdcInsn ( value );this . operandStack . set ( 0 , "LDC_CL" );return ;}}super . visitLdcInsn ( value );
}

后续主要是对于两个INVOKE进行分析

  • 如果getDeclaredMethod传入的是上文LDC处设置的污点,认为方法返回值也是污点,给栈顶的返回值设置REFLECTION_METHOD标志
  • 如果Method.invoke方法中的Method被标记了REFLECTION_METHOD则可以确定这是内存马
  • 开头一部分代码主要是根据方法参数的实际情况对参数在操作数栈中的索引位置进行确定,是一种动态和自动的确认方式,而不是直接根据经验或者调试写死索引,算是优雅写法
public void visitMethodInsn ( int opcode ,  String owner ,  String name ,  String descriptor ,  boolean isInterface ) {Type [] argTypes = Type . getArgumentTypes ( descriptor );if ( opcode != Opcodes . INVOKESTATIC ) {Type [] extendedArgTypes = new Type [ argTypes . length + 1 ];System . arraycopy ( argTypes , 0 , extendedArgTypes , 1 , argTypes . length );extendedArgTypes [ 0 ] = Type . getObjectType ( owner );argTypes = extendedArgTypes ;}boolean reflectionMethod = owner . equals ( "java/lang/Class" ) &&opcode == Opcodes . INVOKEVIRTUAL && name . equals ( "getDeclaredMethod" );boolean methodInvoke = owner . equals ( "java/lang/reflect/Method" ) &&opcode == Opcodes . INVOKEVIRTUAL && name . equals ( "invoke" );if ( reflectionMethod ) {int targetIndex = 0 ;for ( int i = 0 ; i < argTypes . length ; i ++) {if ( argTypes [ i ]. getClassName (). equals ( "java.lang.String" )) {targetIndex = i ;break ;}}if ( operandStack . get ( argTypes . length - targetIndex - 1 ). contains ( "LDC_STRING" )) {super . visitMethodInsn ( opcode , owner , name , descriptor , isInterface );operandStack . set ( TOP , "REFLECTION_METHOD" );return ;}}if ( methodInvoke ) {int targetIndex = 0 ;for ( int i = 0 ; i < argTypes . length ; i ++) {if ( argTypes [ i ]. getClassName (). equals ( "java.lang.reflect.Method" )) {targetIndex = i ;break ;}}if ( operandStack . get ( argTypes . length - targetIndex - 1 ). contains ( "REFLECTION_METHOD" )) {super . visitMethodInsn ( opcode , owner , name , descriptor , isInterface );Result result = new Result ();result . setKey ( owner );result . setType ( Result . CLASSLOADER_DEFINE );results . add ( result );return ;}}super . visitMethodInsn ( opcode , owner , name , descriptor , isInterface );
}

检测效果如下:

先写个内存马注入的Agent注入到HttpServlet中(关于这个不是文章重点)


然后跑起来我写的工具

  • 其中红色框内是注入的Agent内存马,可以分析出
  • 发现上面还有两个内存马结果,这是我模拟的普通内存马,直接写入到代码中做测试的

自动修复

接下来是内存马的修复,自行写一个Java Agent即可

暂时只处理ApplicationFilterChainHttpServlet的情况(也是最常见的情况)

public class RepairAgent {public static void agentmain ( String agentArgs ,  Instrumentation ins ) {ClassFileTransformer transformer = new RepairTransformer ();ins . addTransformer ( transformer , true );Class <?>[] classes = ins . getAllLoadedClasses ();for ( Class <?> clas : classes ) {if ( clas . getName (). equals ( "org.apache.catalina.core.ApplicationFilterChain" )|| clas . getName (). equals ( "javax.servlet.http.HttpServlet" )) {try {ins . retransformClasses ( clas );} catch ( Exception e ) {e . printStackTrace ();}}}}
}

处理的逻辑并不复杂

  • 由于ApplicationFilterChain中包含了LAMBDA所以我直接简化了代码,变成简单的一句internalDoFilter($1,$2)做修复(慎重选择,为什么这样做我将在总结里解释)
  • 修改方法的参数需要用$1 $2这样表示,不能写reqresp
  • 这里HttpServlet的情况稍复杂,其中有两个service方法,实际上对任何一个进行修改都可以导致内存马的效果,所以我要做的事情是恢复这两个方法,而不是只针对某一个
  • 注意任何非java.lang下的类都需要完整类名
public class RepairTransformer implements ClassFileTransformer {@Overridepublic byte [] transform ( ClassLoader loader ,String className ,Class <?> classBeingRedefined ,ProtectionDomain protectionDomain ,byte [] classfileBuffer ) {className = className . replace ( "/" , "." );ClassPool pool = ClassPool . getDefault ();if ( className . equals ( "org.apache.catalina.core.ApplicationFilterChain" )) {try {CtClass c = pool . getCtClass ( className );CtMethod m = c . getDeclaredMethod ( "doFilter" );m . setBody ( "{internalDoFilter($1,$2);}" );byte [] bytes = c . toBytecode ();c . detach ();return bytes ;} catch ( Exception e ) {e . printStackTrace ();}}if ( className . equals ( "javax.servlet.http.HttpServlet" )) {try {CtClass c = pool . getCtClass ( className );CtClass [] params = new CtClass []{pool . getCtClass ( "javax.servlet.ServletRequest" ),pool . getCtClass ( "javax.servlet.ServletResponse" ),};CtMethod m = c . getDeclaredMethod ( "service" , params );m . setBody ( "{" +"        javax.servlet.http.HttpServletRequest  request;\n" +"        javax.servlet.http.HttpServletResponse response;\n" +"\n" +"        try {\n" +"            request = (javax.servlet.http.HttpServletRequest) $1;\n" +"            response = (javax.servlet.http.HttpServletResponse) $2;\n" +"        } catch (ClassCastException e) {\n" +"            throw new javax.servlet.ServletException(lStrings.getString("http.non_http"));\n" +"        }\n" +"        service(request, response);" +"}" );CtClass [] paramsProtected = new CtClass []{pool . getCtClass ( "javax.servlet.http.HttpServletRequest" ),pool . getCtClass ( "javax.servlet.http.HttpServletResponse" ),};CtMethod mProtected = c . getDeclaredMethod ( "service" , paramsProtected );mProtected . setBody ( "{" +"String method = $1.getMethod();\n" +"\n" +"        if (method.equals(METHOD_GET)) {\n" +"            long lastModified = getLastModified($1);\n" +"            if (lastModified == -1) {\n" +"                doGet($1, $2);\n" +"            } else {\n" +"                long ifModifiedSince;\n" +"                try {\n" +"                    ifModifiedSince = $1.getDateHeader(HEADER_IFMODSINCE);\n" +"                } catch (IllegalArgumentException iae) {\n" +"                    ifModifiedSince = -1;\n" +"                }\n" +"                if (ifModifiedSince < (lastModified / 1000 * 1000)) {\n" +"                    maybeSetLastModified($2, lastModified);\n" +"                    doGet($1, $2);\n" +"                } else {\n" +"                    $2.setStatus(javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED);\n" +"                }\n" +"            }\n" +"\n" +"        } else if (method.equals(METHOD_HEAD)) {\n" +"            long lastModified = getLastModified($1);\n" +"            maybeSetLastModified($2, lastModified);\n" +"            doHead($1, $2);\n" +"\n" +"        } else if (method.equals(METHOD_POST)) {\n" +"            doPost($1, $2);\n" +"\n" +"        } else if (method.equals(METHOD_PUT)) {\n" +"            doPut($1, $2);\n" +"\n" +"        } else if (method.equals(METHOD_DELETE)) {\n" +"            doDelete($1, $2);\n" +"\n" +"        } else if (method.equals(METHOD_OPTIONS)) {\n" +"            doOptions($1, $2);\n" +"\n" +"        } else if (method.equals(METHOD_TRACE)) {\n" +"            doTrace($1, $2);\n" +"\n" +"        } else {\n" +"            String errMsg = lStrings.getString("http.method_not_implemented");\n" +"            Object[] errArgs = new Object[1];\n" +"            errArgs[0] = method;\n" +"            errMsg = java.text.MessageFormat.format(errMsg, errArgs);\n" +"\n" +"            $2.sendError(javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);\n" +"        }"+ "}" );byte [] bytes = c . toBytecode ();c . detach ();return bytes ;} catch ( Exception e ) {e . printStackTrace ();}}return new byte [ 0 ];}
}

当我们写好了Agent后,需要加入自动修复的逻辑

List < Result > results = Analysis . doAnalysis ( files );
if ( command . repair )   {RepairService . start ( results , pid );
}

如果分析出了结果,且用户选择了修复功能,才会进入修复逻辑(暂只修复这两个最常见的类)

public static void start ( List < Result >  resultList ,  int pid ) {logger . info ( "try repair agent memshell" );for ( Result result : resultList ) {String className = result . getKey (). replace ( "/" , "." );if ( className . equals ( "org.apache.catalina.core.ApplicationFilterChain" ) ||className . equals ( "javax/servlet/http/HttpServlet" )) {try {start ( pid );return ;} catch ( Exception ignored ) {}}}
}

修复的核心代码:把打包好的Agent拿过来,做一下AtachLoad将字节码替换为正常情况即可

public static void start ( int pid ) {try {String agent = Paths . get ( "RepairAgent.jar" ). toAbsolutePath (). toString ();VirtualMachine vm = VirtualMachine . attach ( String . valueOf ( pid ));logger . info ( "load agent..." );vm . loadAgent ( agent );logger . info ( "repair..." );vm . detach ();logger . info ( "detach agent..." );} catch ( Exception e ) {e . printStackTrace ();}
}

注意使用VirtualMachineAPI需要加入tools.jar,由于上文已经配置了打包插件,所以可以直接打入Jar包,使用时候java -jar xxx.jar --pid 000这样会比较方便

 <dependency><groupId> com.sun.tools </groupId><artifactId> tools </artifactId><version> jdk-8 </version><scope> system </scope><systemPath> ${env.JAVA_HOME}/lib/tools.jar </systemPath>
</dependency>

通过以上这些修复手段可以做到的效果:

  • 启动某SpringBoot应用
  • 通过Agent注入内存马,访问后内存马可用
  • 通过工具检测到内存马,尝试修改,使字节码被还原
  • 再次访问后内存马失效,不需要重启

总结

关于Dump字节码

经过我的一些测试,使用sa-jdi库不能保证dump所有的字节码,会出现莫名其妙的异常,猜测是某些字节码不允许被dump下来。但测试了常见TomcatSpringBoot等程序,发现基本没有问题

关于非法字节码

只要是包含LAMBDA的字节码都是非法字节码,无法正常处理,需要用修改源码后的ASM来做。这种方式终究不是完美的办法,是否存在能够dump下来合法字节码的方式呢(经过一些尝试没有找到办法)

关于检测

可以看到,字节码分析的过程比较简单,尤其是Runtime.exec的普通执行命令内存马,很容易绕过,但个人认为这已足够,因为之前的一些条件已经限制了分析的类是不可能包含Runtime.exec的黑名单类,且大多数用户都是脚本小子,使用免杀型内存马的可能性不大。大多数用户可能直接用了现成的工具,例如冰蝎型内存马的检测方式已完成,暂时来看这样做是足够的,没有必要加入各种免杀检测手段

关于查杀

使用Agent恢复字节码的修复方式理论上没有问题。但其中的ApplicationFilterChain类的doFilter方法中包含了LAMBDA和匿名内部类,这两者都是Javassist框架不支持的内容,可以用ASM来做,但可能难度较高

另外对于普通型内存马的修复,通过Agent技术只能覆盖方法体,不可以增加或删除方法。所以理论上可以根据方法的返回值类型,做返回NULL的处理进行修复

关于拓展

例如代码中我定义的黑名单和关键字,可以根据实战经验自行添加新的类,以实现更完善的效果。在查杀方面我做了最常见的两种,可以根据实际情况自行添加更多的逻辑

【网络安全】Agent内存马的自动分析与查杀相关推荐

  1. php的内存马的原理与查杀方法

    内存马原理 PHP内存马即PHP不死马,简单来说就是会写进PHP进程里,无限在指定目录中生成木马文件 生成过程 不死马.php → 上传到server → server执行文件 → server本地循 ...

  2. PHP内存马技术研究与查杀方法总结

    内存马 概念 内存马是无文件攻击的一种常用手段,随着攻防演练热度越来越高:攻防双方的博弈,流量分析.EDR等专业安全设备被蓝方广泛使用,传统的文件上传的webshll或以文件形式驻留的后门越来越容易被 ...

  3. 基于Java Agent内存马

    前言 前面说完最常见的基于Servlet-API型内存马,这里再提一下Java Agent内存马,像冰蝎,哥斯拉工具的内存马注入都是基于 agent 的,以后用到再分析 大的思路 第一种是通过perm ...

  4. 渗透测试-不死马的创建和查杀

    不死马的创建和查杀 文章目录 不死马的创建和查杀 0x00 创建 0x01 查看 0x02 查杀 root用户角度的查杀: 重启web服务. 其他用户的查杀: 0x00 创建 md5加密密码: 代码: ...

  5. java agent内存马学习

    文章目录 1. 什么是java agent 补充:一些重要的类 ClassFileTransformer VirtualMachine CtMethod 2. 利用premain函数实现java ag ...

  6. 安全狗发布网站后门查杀引擎“啸天”变形网马无处可匿

    2019独角兽企业重金招聘Python工程师标准>>> "啸天"--安全狗旗下产品网站安全狗新版本中加入的全新引擎,是安全狗研发团队历时数月精心打造的革命性产品, ...

  7. 内存马查杀copagent研究

    项目简介 copagent主要用于内存马查杀. 项目编译 1.生成agent.jar 2.将其拷贝进入cop的resources文件夹中 3.生成cop.jar 将刚才的agent.jar,拷贝到re ...

  8. 【应急响应】网站入侵篡改指南Webshell内存马查杀漏洞排查时间分析

    网站入侵篡改指南&Webshell内存马查杀&漏洞排查&时间分析 章节内容点: IIS&.NET-注入-基于时间配合日志分析 Apache&PHP-漏洞-基于漏 ...

  9. Java Agent 型内存马调试系列 (一)

    0x00 前言 在JDK1.5以后,引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控.收集性能信息.诊断问题,通过 java.lang.in ...

最新文章

  1. 小公司该如何吸引人才、留住人才?
  2. HTML 表格垂直对齐方式
  3. Java 使用List集合实现图书增删改查
  4. 53.垃圾回收算法的实现原理、启动Java垃圾回收、Java垃圾回收过程、垃圾回收中实例的终结、对象什么时候符合垃圾回收的条件、GC Scope 示例程序、GC OutOfMemoryError的示例
  5. complete checkbox in Fiori
  6. undefined reference to `vtable for XX::XX'
  7. No Identifier specified for entity的解决办法
  8. 阿里云环境中TLS/SSL握手失败的场景分析
  9. 错误代码0x800f0950怎么解决_解决win10安装net framework 3.5失败(错误代码 0x800F0950)...
  10. a as as big rat_干货!A Level数学大考攻略:冲刺A*的8大解题思路、8个失分大坑
  11. [JSOI2017]原力(分块+map(hash))
  12. 按3倍中误差去除粗差(C++)
  13. 软件测试工程师面试题汇总
  14. 【Golang开发面经】字节跳动(三轮技术面)
  15. Chrome插件:云音乐听歌识曲
  16. 天池小布助手对话短文本语义匹配-文本二分类实践(pytorch)
  17. JSP设置Excel表格换行_外企大公司的 Excel 表格,都是这样设置隐藏的
  18. javascript求质数(素数)和合数
  19. 学术论文等级与分类标准——JCR
  20. 查看dg主备库同步情况

热门文章

  1. easypoi教程_SpringBoot图文教程17—上手就会 RestTemplate 使用指南
  2. Interview:算法岗位面试—10.23下午—上海某科技公司算法岗位(偏机器学习算法,上市)技术面试之比赛积累、项目经验、个人未来发展
  3. ML之回归预测:利用13种机器学习算法对Boston(波士顿房价)数据集【13+1,506】进行回归预测(房价预测)+预测新数据得分
  4. AI:一个20年程序猿的学习资料大全—结构分析软件/办公软件/电气制造控制/高级语言编程/平面三维设计/视频编辑/FQ格式转换软件——只有你不想要的,没有你找不到的
  5. Crawler:爬虫之基于https+parse库实现爬取国内某知名招聘网上海、北京关于区块链职位的求职信息
  6. java_OA管理系统(一):Servlet总结案例仿网络聊天室
  7. UVA 1451 Average 数形结合
  8. lldb调试使用python脚本问题总结
  9. 第二十篇:定义一个整形变量
  10. java常用类解析十:Date类和Calendar类示例