# 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/

JNI

您可以使用 IDE 或者 vim 完成动态链接库的编写,如果您使用 MacOS + CLion 可能需要把 #include <jni.h> 改成 #include "jni.h" ,不改也没关系,编译的时候带上库地址就行了。

头文件命名强制性

javah 生成的头文件中的函数命名方式是有非常强制性的约束的,如 Java_com_anbai_sec_cmd_CommandExecution_execJava_ 是固定的前缀,而 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 的头文件目录。以下是解决这个问题的步骤:

  1. 确认 JAVA_HOME 环境变量: 确保 JAVA_HOME 环境变量已正确设置,并指向你的 JDK 安装目录。你可以通过以下命令检查:

    echo $JAVA_HOME

    如果 JAVA_HOME 没有正确设置,可以在 ~/.bash_profile (或 ~/.zshrc ,取决于你使用的 shell)中添加以下行,并重新加载配置文件:

    export JAVA_HOME=$(/usr/libexec/java_home)
    source ~/.bash_profile
  2. 确认包含路径正确: 确认你在 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
  3. 检查 JDK 安装: 确认你的 JDK 安装目录中确实存在 jni.h 文件。你可以通过以下命令检查:

    ls $JAVA_HOME/include/jni.h

    如果文件不存在,可能是 JDK 安装不完整,建议重新安装 JDK。

  4. 完整示例: 以下是一个完整的示例,假设 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();
        }
    }
}

JNI调用

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

尘落 微信支付

微信支付

尘落 支付宝

支付宝

尘落 贝宝

贝宝