JNI安全基础:Java与C/C++本地命令执行详解
本文详细介绍了JNI(Java Native Interface)的安全基础,通过实际案例讲解如何利用JNI实现Java与C/C++的本地命令执行,并探讨其潜在的安全风险。
- Java安全
- 网络安全
JNI安全基础
Java语言是基于C语言实现的,Java底层的很多API都是通过JNI(Java Native Interface)来实现的。通过JNI接口C/C++和Java可以互相调用(存在跨平台问题)。Java可以通过JNI调用来弥补语言自身的不足(代码安全性、内存操作等)。这个看似非常炫酷的特性其实自JDK1.1开始就有了,但是我们不得不去考虑JNI调用带来的一系列的安全问题!
本章节仍以本地命令执行为例讲解如何构建动态链接库供Java调用,也许很多人是第一次接触这个概念会比较陌生但是如果你了学习过C/C++或者Android NDK那么本章节就会非常的简单了。
JNI-定义native方法
首先在Java中如果想要调用native方法那么需要在类中先定义一个native方法。
CommandExecution.java演示
package com.anbai.sec.cmd;
/**
* 本地命令执行类
* Creator: yz
* Date: 2019/12/6
*/
public class CommandExecution {
public static native String exec(String cmd);
}
如上示例代码,我们需要使用native关键字定义一个类似于接口的方法就行了,是不是感觉非常简单?
JNI-生成类头文件
如上,我们已经编写好了CommandExecution.java,现在我们需要编译并生成c语言头文件。
完整的步骤如下:
javac -cp . src/main/java/org/chenluo/servlet/CommandExecution.java -h src/main/java/org/chenluo/servlet/

您可以使用IDE或者vim完成动态链接库的编写,如果您使用MacOS+CLion可能需要把#include <jni.h>改成#include "jni.h",不改也没关系,编译的时候带上库地址就行了。
头文件命名强制性
javah生成的头文件中的函数命名方式是有非常强制性的约束的,如Java_com_anbai_sec_cmd_CommandExecution_exec中Java_是固定的前缀,而com_anbai_sec_cmd_CommandExecution也就代表着Java的完整包名称:com.anbai.sec.cmd.CommandExecution,_exec自然是表示的方法名称了。(JNIEnv *, jclass, jstring)表示分别是JNI环境变量对象、java调用的类对象、参数入参类型。
JNI-基础数据类型
需要特别注意的是Java和JNI定义的类型是需要转换的,不能直接使用Java里的类型,也不能直接将JNI、C/C++的类型直接返回给Java。
参考如下类型对照表:
| Java类型 | JNI类型 | C/C++类型 | 大小 |
|---|---|---|---|
| Boolean | Jblloean | unsigned char | 无符号8位 |
| Byte | Jbyte | char | 有符号8位 |
| Char | Jchar | unsigned short | 无符号16位 |
| Short | Jshort | short | 有符号16位 |
| Int | Jint | int | 有符号32位 |
| Long | Jlong | long long | 有符号64位 |
| Float | Jfloat | float | 32位 |
| Double | Jdouble | double | 64位 |
jstring转char*:env->GetStringUTFChars(str, &jsCopy)
char*转jstring: env->NewStringUTF("Hello...")
字符串资源释放: env->ReleaseStringUTFChars(javaString, p);
其他知识点参考:jni中java与原生代码通信规则
JNI-编写C/C++本地命令执行实现
如上,我们已经生成好了头文件,接下来我们需要使用C/C++编写函数的最终实现代码。
com_anbai_sec_cmd_CommandExecution.cpp示例:
#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <string>
#include "org_chenluo_servlet_CommandExecution.h"
using namespace std;
JNIEXPORT jstring JNICALL Java_org_chenluo_servlet_CommandExecution_exec
(JNIEnv *env, jclass jclass, jstring str) {
if (str != NULL) {
jboolean jsCopy;
// 将 jstring 参数转换为 char 指针
const char *cmd = env->GetStringUTFChars(str, &jsCopy);
// 使用 popen 函数执行系统命令
FILE *fd = popen(cmd, "r");
if (fd != NULL) {
// 返回结果字符串
string result;
// 定义字符串数组
char buf[128];
// 读取 popen 函数的执行结果
while (fgets(buf, sizeof(buf), fd) != NULL) {
// 拼接读取到的结果到 result
result += buf;
}
// 关闭 popen
pclose(fd);
// 释放 jstring 字符串
env->ReleaseStringUTFChars(str, cmd);
// 返回命令执行结果给 Java
return env->NewStringUTF(result.c_str());
}
env->ReleaseStringUTFChars(str, cmd);
}
return env->NewStringUTF("Command execution failed or returned empty result.");
}
编辑器编写好cpp文件。
首先切换到我们的C目录:cd src/main/java/org/chenluo/serlvet然后使用g++命令编译成动态链接库,前提是您需要提前装好编译环境如:gcc/g++。
错误提示 jni.h 文件没有找到,通常是由于编译器找不到 JDK 的头文件目录。以下是解决这个问题的步骤:
-
确认 JAVA_HOME 环境变量: 确保
JAVA_HOME环境变量已正确设置,并指向你的 JDK 安装目录。你可以通过以下命令检查:echo $JAVA_HOME如果
JAVA_HOME没有正确设置,可以在~/.bash_profile(或~/.zshrc,取决于你使用的 shell)中添加以下行,并重新加载配置文件:export JAVA_HOME=$(/usr/libexec/java_home) source ~/.bash_profile -
确认包含路径正确: 确认你在
g++命令中指定的-I选项路径正确。$JAVA_HOME/include和$JAVA_HOME/include/darwin是包含jni.h文件的目录。重新运行以下命令:g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libcmd.jnilib org_chenluo_servlet_CommandExecution.cpp -
检查 JDK 安装: 确认你的 JDK 安装目录中确实存在
jni.h文件。你可以通过以下命令检查:ls $JAVA_HOME/include/jni.h如果文件不存在,可能是 JDK 安装不完整,建议重新安装 JDK。
-
完整示例: 以下是一个完整的示例,假设
org_chenluo_servlet_CommandExecution.cpp文件和org_chenluo_servlet_CommandExecution.h文件都在当前目录中:g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libcmd.jnilib org_chenluo_servlet_CommandExecution.cpp
CommandExecutionTest示例:
package org.chenluo.servlet;
public class CommandExecutionTest {
public static void main(String[] args) {
String cmd = "ifconfig"; // 需要执行的命令
try {
// 调用 native 方法
String content = CommandExecution.exec(cmd);
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
}

留言讨论
0 条留言
正在加载留言...