preface

The starting point is automatic analysis and killing of Java Agent memory horses. In fact, other memory horses can be searched in this way

The main difficulties in this paper are the following three, and I will answer them one by one in this paper

  1. How todumpOut of theJVMIn theThe realCurrent bytecode
  2. How to solve the problemLAMBDAExpression resultIllegal bytecodeProblems that cannot be analyzed
  3. How do you analyze bytecode to determine if a class is a memory horse

background

The attack and defense of Java memory horse has not stopped, which is the focus of the Java security field

Review Tomcat or Spring memory horses: New components such as Filter and Controller need to be registered

Memory marchings are easier for those that need to register new components:

For example, master C0NY1’s Java-Memshell-scanner project uses the Tomcat API to remove added components. The advantage is that a simple JSP file can view all the component information, combined with manual review (Class name and ClassLoader information, etc.) to kill the memory horse, also can dump the risky classes after decompilation analysis

Or LandGrey, based on the CopAgent project written by Alibaba Arthas, analyzes all classes in the JVM, dumps suspicious components based on hazardous annotations and Class names, and analyzes them with manual decompilation

But in real life, it may not be the memory horse that registers the new component above

For example, the common ice Scorpion memory horse is Java Agent memory horse. This is ice, scorpion memory a piece of code, a simple analysis of ice were the memory can be found after the horse is the use of Java Agent injected into the javax.mail. Servlet. HTTP. HttpServlet service method, this is the specification, JavaEE This is the first and always the place where Tomcat processes requests. Adding memory horse logic to this class can ensure stable triggering

Similar logic, you can use the Java Agent injected memory horse org. Apache. Catalina. Core. ApplicationFilterChain class, the class is located in the Filter chain head, That is to say, all requests that go through Tomcat will be processed by doFilter method of this class, so adding memory horse logic to this method is also a way of stable triggering (it is said that this is the way of the old version of Ice Scorpion memory horse).

Can also similar classes for injection, such as org. Springframework. Web. Servlet. The DispatcherServlet class, on the bottom of the Spring framework for injection. Or some clever ideas, such as injection Tomcat’s own org. One of the Filter. The apache Tomcat. Websocket. Server WsFilter class, this is also a Java Agent memory mark to do it

The above briefly introduces the utilization of various memory horses and common memory horse search, the reason why the last introduction of Java Agent memory horse search, because it is difficult. The master of wide-byte security proposed the idea of checking and killing: based on javaAgent memory horse detection and killing guide

The article refers to the difficulties of Java Agent memory horse detection:

The bytecode in the argument to the retransformClass method is not the bytecode of the class that was modified after the redefineClass call. In the case of Ice Scorpion, there is no way to get the bytecode modified by ice Scorpion. When we write the Java Agent to clear the memory horse, we cannot obtain the bytecode modified by redefineClass, but can only obtain the bytecode modified by retransformClass. The bytecodes of the class obtained through ASM tools such as Javaassist are only read from the bytecodes of the response class on disk, not the bytecodes in the JVM

Wide-byte security gurus have found a way to detect it: sa-jdi.jar

This is a GUI tool that allows you to view all loaded classes in the JVM. The difference here is that you get the actual current bytecode, not the original, local bytecode, so you can see the bytecode of the class that was modified after the Java Agent called redefineClass. Further, you can dump the classes considered risky and decompile them for manual audit

introduce

So that’s the background, what did I do, what did I achieve

It is not difficult to see that the above means of memory check and kill are semi-automatic combined with manual audit, when the detection of memory horse

Can we find a way to do a one-stop service:

  • Detection (both support ordinary memory horse andJava AgentMemory horse detection)
  • Analysis (how to determine that the class is a memory horse, based only on information such as malicious class names and annotations is incomplete)
  • Kill (how to automatically remove memory horses and restore normal business logic when it is confirmed that memory horses exist)

In general, it doesn’t seem difficult to implement, but in practice there are a lot of pitfalls, which I’ll cover one by one

1, Network security learning route 2, electronic books (white hat) 3, security factory internal video 4, 100 SRC documents 5, common security comprehensive questions 6, CTF competition classic topic analysis 7, the complete kit 8, emergency response notes

SA – JDI analysis

I tried to obtain the current bytecode through Java Agent technology, but found that I could not get the modified bytecode as the teacher said

Therefore, in order to detect Agent horses, we need to start from sa-jdi.jar itself and try to dump the current bytecode (so that not only Agent horses with modified bytecode can be analyzed, but also common types of memory horses).

Noticed that one of the Class: sun JVM. Hotspot. View jcore. ClassDump and found that the function is through checking data dump the current Class (according to the name of the Class also can guess) one of the main method to provide the dump Class command line tools

So I thought of some methods, with the code to achieve the function of the command line tool, and can set a 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 ();Copy the code

The Filter mentioned above is used to determine which classes need to be dumped (too much dumping can cause performance problems).

public class NameFilter implements ClassFilter { @Override public boolean canInclude ( InstanceKlass instanceKlass ) { String klassName = instanceKlass . getName (). asString (); Dump if (blacklist. contains (klassName)) {return true; } // Dump for (String k: constant.keyword) {if (klassname.contains (k)) {return true; } } return false ; }}Copy the code

The above contains the blacklist and keywords of the class:

  • Blacklist: Java Agent memory horse will usually Hook where neededdumpCome down and analyze it
  • Keyword: class name if presentmemshellandshellSuch keywords that may be ordinary memory horse, need to be analyzed
public class Constant {
    // BLACKLIST (Analysis Target)
    // CLASS_NAME#METHOD_NAME
    public static List < String > blackList = new ArrayList <>();
    // SHELL KEYWORD
    public 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" );
    }
} 
Copy the code

In addition, if you want to add JDK/lib dependencies to your Maven project, special configuration is required

 <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> 
Copy the code

System Scope dependencies are not included by default when packaged as tool JARS, so special handling is required

 <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> 
Copy the code

Write the assembly.xml file

<! < <outputDirectory> / </outputDirectory> <unpack> true </unpack> <scope> system </scope> </dependencySet> </dependencySets>Copy the code

You can then use code to determine which classes to dump based on the blacklist and keywords

I encountered a minor problem in my testing that is worth sharing: The HttpServlet was properly dump-able but the ApplicationFilterChain class was not found. This is due to lazy loading of SpringBoot, requiring a manual request for an interface

Resolve illegal bytecode

Then I ran into a big pit where bytecodes dumped through the SA-jDI library were illegal

The following error is reported when analyzing the ApplicationFilterChain class

At first I suspected that I was using the latest ASM framework: 9.2

When ClassReader does not report an error, it does not report an error when ClassReader is demoted to 7.0

After comparison, it is found that the following situation

No error version is reported

After a bit of analysis, it turns out that the ApplicationFilterChain class contains LAMBDA

This class is not the only one that could contain a LAMBDA

It was found that the bytecode obtained through SA-JDI was illegal bytecode in the presence of LAMBDA and could not be analyzed

If you still want to analyze at this point, there are only two options:

  • Parse CLASS files by yourself (put the cart before the horse)
  • Rewrite ASM source code to skipLAMBDA

Based on Java basics, LAMBDA is related to the INVOKEDYNAMIC instruction, so I changed the ASM code

(Here does not explain why it is changed, it is determined after many debugging)

org/objectweb/asm/ClassReader#274

bootstrapMethodOffsets = null ; 
Copy the code

org/objectweb/asm/ClassReader#2456

case Opcodes . INVOKEDYNAMIC :
  {
    return ;
  } 
Copy the code

After changing the source code, you can normally analyze the illegal bytecode. At present, there is no major problem and it can be analyzed normally, but it is not sure whether there are some hidden dangers and bugs in such modification. Anyway, we can continue for now

Analysis bytecode

Analysis of bytecode doesn’t need to go too far, as most of the possible memory drives are implemented by Runtime.exec or classLoader.defineclass, and analysis for both cases is sufficient for the vast majority of cases

The following code reads the bytecode of dump and analyzes all methods for both cases

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 analysis
        cr = new ClassReader ( bytes );
        cv = new ShellClassVisitor ( api , results );
        cr . accept ( cv , parsingOptions );
        // classloader defineClass analysis
        cr = 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 ());
} 
Copy the code

For runtime. exec type analysis, it is easiest to determine if the method exists in all the methods in the dumped bytecode (theoretically there will be false positives, but this method cannot exist in the blacklist class, the keyword class itself is suspect, so there is nothing wrong with doing this).

@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 ); }Copy the code

But this case does not apply to the Ice Scorpion reflex call classLoader.defineclass

The code is not long, but the corresponding bytecode is complex

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

Corresponding bytecode

LDC Ljava/lang/ClassLoader; .class // focus on LDC "defineClass" // focus on 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; // Focus on 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; / / focus on POPCopy the code

This operation requires multiple steps and is not as simple as a single INVOKE. Without special treatment, reflection and classloader-related operations are relatively common, so there is a certain possibility of false positives

So continue to take out stack frame analysis method, specific no longer introduced, before the article has detailed explanation

DefineClass and Ljava/lang/ClassLoader according to bytecode; This should be considered malicious until an LDC instruction is pushed, and a blot should be placed at the top of the stack after an emulated JVM instruction is executed

 @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 );
} 
Copy the code

The following analysis focuses on two Invokes

  • ifgetDeclaredMethodThe incoming is aboveLDCThe method return value is also a stain and is set to the return value at the top of the stackREFLECTION_METHODmark
  • ifMethod.invokeIn the methodMethodMarked theREFLECTION_METHODIt can be determined that this is a memory horse
  • The first part of the code is mainly based on the actual situation of the method parameters in the operand stack index position to determine, is a dynamic and automatic way to confirm, rather than directly based on experience or debugging write dead index, is an elegant way to write
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 ); }Copy the code

The detection results are as follows:

Write a memory horse Agent to inject into the HttpServlet (this is not the focus of the article)

Then ran up the tool I wrote

  • Where the red box inside is injectedAgentMemory horse, can be analyzed
  • There are also two memory horse results, this is my simulation of ordinary memory horse, directly written into the code to do the test

Auto repair

Next is the memory horse repair, write a Java Agent can be

Only the ApplicationFilterChain and HttpServlet cases (which are the most common) are handled for now

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 (); } } } } }Copy the code

 

The logic of processing is not complicated

  • Due to theApplicationFilterChainIncluded in theLAMBDASo I just simplified the code to a simple sentenceinternalDoFilter($1,$2)Make fixes (choose carefully, I’ll explain why in the summary)
  • To modify the parameters of the method$1, $2I can’t write it like thisreqandresp
  • hereHttpServletThe situation is slightly more complicated, and there are two of themserviceMethod, in fact any modification can cause memory horse effect, so what I want to do is restore both methods, not just one
  • Pay attention to any nonjava.langAll classes below need full class names
public class RepairTransformer implements ClassFileTransformer { @Override public 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 ]; }}Copy the code

After we have written the Agent, we need to add automatic repair logic

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

If the analysis results are found and the user selects the repair function, the repair logic will be entered (only the two most common classes will be repaired for now)

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 ) {
            }
        }
    }
} 
Copy the code

Fix the core code: take the packaged Agent, do Atach and Load to replace the bytecode as normal

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 (); }}Copy the code

Jar –pid 000 — java-jar xxx.jar –pid 000 — 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> 
Copy the code

The effect that can be achieved through the above repair means:

  • Start a SpringBoot application
  • throughAgentInject memory horse, memory horse is available after access
  • Memory horse detected by tool, try to modify, so that the bytecode is restored
  • The memory horse becomes invalid after the second access. No restart is required

conclusion

About Dump bytecode

In some of my tests, using the SA-JDI library does not guarantee that all bytecodes will be dumped. However, common Tomcat and SpringBoot programs have been tested and found basically no problems

About illegal bytecode

Any bytecode containing LAMBDA is illegal bytecode and cannot be processed normally. You need to use ASM after modifying the source code to do so. This method is not perfect after all, is there a way to dump the legal bytecode (after some attempts failed to find a way)?

About the test

As can be seen, the bytecode analysis process is relatively simple, especially runtime. exec’s ordinary execution command memory, which is easy to bypass, but I think this is enough, because some previous conditions have limited the analysis of the class cannot contain Runtime.exec’s blacklist class, and most users are script boys. The possibility of using a no-kill memory horse is unlikely. Most users may directly use off-the-shelf tools, such as the ice Scorpion memory horse detection method has been completed, for the time being, it is enough to do so, there is no need to add a variety of no-kill detection methods

About killing

The fix using Agent to recover bytecode is theoretically fine. However, the ApplicationFilterChain class has LAMBDA and anonymous inner classes in its doFilter method, both of which are not supported by the Javassist framework and can be done with ASM, though it may be more difficult

In addition, for the repair of ordinary memory horse, Agent technology can only cover method body, can not add or delete method. Therefore, it is theoretically possible to fix the method by returning NULL based on its return value type

About expanding

For example, I defined the blacklist and keywords in the code, you can add new classes according to the actual experience, in order to achieve a better effect. I’ve done two of the most common ones for killing, and can add more logic as needed