Java基础:初探JNI安全
最后更新时间:
文章总字数:
预计阅读时间:
Java基础:初探JNI安全
Java语言是基于C语言实现的,Java底层的很多API都是通过JNI(Java Native Interface)
来实现的。通过JNI
接口C/C++
和Java
可以互相调用(存在跨平台问题)。Java可以通过JNI调用来弥补语言自身的不足(代码安全性、内存操作等)。
本文属于初探,主要以本地命令执行为例讲解如何构建动态链接库供Java调用。
参考文章:https://blog.csdn.net/daiyi666/article/details/131163063
JNI-定义native方法
首先创建一个java项目,并在一个类中先定义将来要调用的native方法。
如上代码,就是使用native关键字定义一个类似接口的方法,关键字native通常表示要调用C或C++的结构。
JNI-生成类头文件
先在终端使用javac编译CommandExecution.java为.class文件,然后使用javah来生成类头文件(等下要在C++项目中引用该头文件),具体命令如下:
1 | javac com/anbai/sec/cmd/CommandExecution.java |
注意这里一定要是完整的软件包路径,自然要求终端的路径在软件包最外层,与com同级,否则会显示找不到类文件。
另外,需要注意JDK版本:
JDK10移除了javah
,需要改为javac
加-h
参数的方式生产头文件,如果您的JDK版本正好>=10
,那么使用如下方式可以同时编译并生成头文件。
javac -cp . com/anbai/sec/cmd/CommandExecution.java -h com/anbai/sec/cmd/
可以看到这个头文件命名是有强制性的,包含了完整包名和类名,点开看看里面:
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
可以看到里面有一个Java_com_anbai_sec_cmd_CommandExecution_exec的声明,等一下就需要在cpp文件中实现这个函数,这三个参数分别是JNI环境变量对象,java调用的类和参数的入参类型。
JNI-基础数据类型
可以看到上一段代码中jstring代表参数的入参类型,这个jstring就是JNI定义的数据类型,其与Java是需要转换的,不能直接用,也不能直接把JNI和C,CPP的类型直接返回Java。
jstring转char*:env->GetStringUTFChars(str, &jsCopy)
char*转jstring: env->NewStringUTF("Hello...")
字符串资源释放: env->ReleaseStringUTFChars(javaString, p);
JNI-编写C/C++本地命令执行实现
上文中我们已经编写好了头文件,下一步就是具体实现代码。
首先打开clion,新建一个项目。注意选择共享库,而不是可执行文件,不然后面构建的时候报错。
创建项目之后需要加入的文件:刚刚生成的头文件,jni.h和jni_md.h(这两个文件可以在java的安装路径include文件夹里面找到,当然也可以全局搜索找),然后我们创建一个和头文件同名的com_anbai_sec_cmd_CommandExecution.cpp
,开始实现具体代码。
具体代码实现如下:
1 |
|
写完之后可以点击clion上面的那个像锤子一样的按钮(对文件进行构建),成功在cmake-build-debug中得到构建完成的动态链接库libexec.dll(这个名字不重要,可以自行更改),这个就是java调用原生语言方法的关键。
JNI-使用动态链接库调用函数
调用System.load
方法加载刚刚得到的libexec.dll
动态链接库,System.load
的参数是动态链接库的绝对路径,这里先做一个简单的测试调用:
可以看到这里成功执行了命令,而我们本软件包内的CommandExecution.java是没有实现exec方法的,只是做了声明,可以得知成功load了动态链接库,并访问到了我们刚刚写的cpp文件内的函数。
这里为了方便,test代码直接放在了一开始声明方法的类下面,从而可以直接new一个对象,正常情况下需要先使用字节码注册类,然后通过反射获取exec方法。
1 | package com.anbai.sec.cmd; |
总流程就是注册类,然后动态链接,最后反射调用方法。
总结
本文中我们学习了如何通过JNI调用动态链接库实现本地命令执行功能,我们应该深入的认识到通过编写native方法我们可以做几乎任何事(比如不使用Java自带的FileInputStream
API读文件、不使用forkAndExec
执行系统命令等)。JNI如此强大的功能也带来了很多安全隐患,我们以后再慢慢细说。
另外,这个JNI这种跨平台跨语言的调用方式今天还是第一次见,在配置环境和进行测试的时候出了不少小问题,所幸一下午之后还是解决了,对JNI运行方式的理解也加深了不少,看技术文章的时候一定要自己多动手操作,再多想想,不能干看也不能照抄。