jni

简介

Java语言是基于C语言实现的,Java底层的很多API都是通过JNI(Java Native Interface)来实现的。通过JNI接口C/C++Java可以互相调用(存在跨平台问题)。Java可以通过JNI调用来弥补语言自身的不足(代码安全性、内存操作等)。这个看似非常炫酷的特性其实自JDK1.1开始就有了,但是我们不得不去考虑JNI调用带来的一系列的安全问题!

jni-定义native 方法

只需要添加一个native 关键字

1
2
3
4
5
6
package org.example;

public class JNILearn{
public static native String Exec(String cmd);
}

jni-生成类头文件

已经编写好了java 文件,现在尝试将这个Java 文件编译成c 语言的头文件

javac -cp . org/example/JNILearn.java

javac -cp . org/example/JNILearn.java -h org/example

此时头文件就在 org/example

生成的文件特点

image-20250524194301362

头文件的命名强制性

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与原生代码通信规则

编写逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <string>
#include "org_example_JNILearn.h"

using namespace std;

JNIEXPORT jstring

JNICALL
Java_com_anbai_sec_cmd_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);

// 返回命令执行结果给Java
return env->NewStringUTF(result.c_str());
}
}

return NULL;
}
1
g++ -fPIC -I"$JAVA_8U181_HOME/include" -I"$JAVA_8U181_HOME/include/darwin" -shared -o libcmd.jnilib org_example_JNILearn.cpp 

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package org.example;

import java.io.File;
import java.lang.reflect.Method;

/**
* Creator: yz
* Date: 2019/12/8
*/
public class JniExec {

private static final String COMMAND_CLASS_NAME = "org.example.JNILearn";

/**
* JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
* 只有一个public static native String exec(String cmd);的方法
*/
private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 61, 0, 15,
10, 0, 2, 0, 3, 7, 0, 4, 12, 0,
5, 0, 6, 1, 0, 16, 106, 97, 118, 97,
47, 108, 97, 110, 103, 47, 79, 98, 106, 101,
99, 116, 1, 0, 6, 60, 105, 110, 105, 116,
62, 1, 0, 3, 40, 41, 86, 7, 0, 8,
1, 0, 20, 111, 114, 103, 47, 101, 120, 97,
109, 112, 108, 101, 47, 74, 78, 73, 76, 101,
97, 114, 110, 1, 0, 4, 67, 111, 100, 101,
1, 0, 15, 76, 105, 110, 101, 78, 117, 109,
98, 101, 114, 84, 97, 98, 108, 101, 1, 0,
4, 69, 120, 101, 99, 1, 0, 38, 40, 76,
106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
83, 116, 114, 105, 110, 103, 59, 41, 76, 106,
97, 118, 97, 47, 108, 97, 110, 103, 47, 83,
116, 114, 105, 110, 103, 59, 1, 0, 10, 83,
111, 117, 114, 99, 101, 70, 105, 108, 101, 1,
0, 13, 74, 78, 73, 76, 101, 97, 114, 110,
46, 106, 97, 118, 97, 0, 33, 0, 7, 0,
2, 0, 0, 0, 0, 0, 2, 0, 1, 0,
5, 0, 6, 0, 1, 0, 9, 0, 0, 0,
29, 0, 1, 0, 1, 0, 0, 0, 5, 42,
-73, 0, 1, -79, 0, 0, 0, 1, 0, 10,
0, 0, 0, 6, 0, 1, 0, 0, 0, 3,
1, 9, 0, 11, 0, 12, 0, 0, 0, 1,
0, 13, 0, 0, 0, 2, 0, 14,
};

public static void main(String[] args) {
String cmd = "ifconfig";// 定于需要执行的cmd

try {
ClassLoader loader = new ClassLoader(JNILearn.class.getClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch (ClassNotFoundException e) {
return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
}
}
};

// 测试时候换成自己编译好的lib路径
File libPath = new File(".............libcmd.jnilib"); // 绝对路径

// load命令执行类
Class commandClass = loader.loadClass("org.example.JNILearn");

// 可以用System.load也加载lib也可以用反射ClassLoader加载,如果loadLibrary0
// 也被拦截了可以换java.lang.ClassLoader$NativeLibrary类的load方法。
// System.load("/Users/yz/IdeaProjects/javaweb-sec/javaweb-sec-source/javase/src/main/java/com/anbai/sec/cmd/libcmd.jnilib/libcmd.jnilib");
Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
loadLibrary0Method.setAccessible(true);
loadLibrary0Method.invoke(loader, commandClass, libPath);

String content = (String) commandClass.getMethod("Exec", String.class).invoke(null, cmd);
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}

}

COMMAND_CLASS_NAME 是最开始写的.java 的名字

COMMAND_CLASS_BYTES 是COMMAND_CLASS_NAME 对应的class 文件

一下的方法可以获取这个bytes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package cn.au9u5t;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {

public static void main(String[] args) {
// 填入你的.class文件路径
String filePath = "/Users/august/code/java/JavaSecLearn/src/main/java/org/example/JNILearn.class";

try {
byte[] classBytes = readClassFile(filePath);
printByteArray(classBytes);
} catch (IOException e) {
System.err.println("读取类文件时出错: " + e.getMessage());
}
}

/**
* 从指定路径读取.class文件并返回字节数组
*/
public static byte[] readClassFile(String filePath) throws IOException {
return Files.readAllBytes(Paths.get(filePath));
}

/**
* 将字节数组打印成与你提供的格式相似的形式
*/
public static void printByteArray(byte[] bytes) {
System.out.println("private static final byte[] COMMAND_CLASS_BYTES = new byte[]{");

int lineLength = 0;
for (int i = 0; i < bytes.length; i++) {
System.out.printf("%4d,", bytes[i]);
lineLength++;

if (lineLength >= 10) {
System.out.println();
lineLength = 0;
}
}

System.out.println("\n};");
}
}

jni
https://tsy244.github.io/2025/05/24/java安全/jni/
Author
August Rosenberg
Posted on
May 24, 2025
Licensed under