百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Java 安全之 JNI:深入解析与实战应用

haoteby 2025-07-27 20:04 18 浏览

一、JNI 简介

JNI(Java Native Interface)是一种强大的接口技术,它允许 Java 程序与本地代码(如 C 或 C++)进行互操作。借助 JNI,Java 程序能够调用本地代码,从而优化性能和功能,突破 Java 在某些场景下内存管理和执行效率的瓶颈。开发者可以在 Java 应用中集成底层操作系统功能或使用已有的高效本地库,提升应用的执行速度和访问硬件资源的能力。

二、JNI 基本知识

(一)本地库生命周期

(二)Native 方法命名规范

动态链接器依据特定规则查找 Java 本地方法对应的 Native 函数,方法名遵循以下规则:



  • 函数名带有 Java_ 前缀。
  • 后跟以下划线分隔的完整类名,类的包名分隔符 . 以 _ 代替。
  • 后跟函数名。
  • 对于重载的本地方法,使用双下划线 __ 分隔参数签名。



命名结构为:Java_ {全限定类名}_ {方法名},例如:
Java_com_example_Encryptor_encryptData。对于重载方法,会追加双下划线 __ 和参数签名缩写,如
encrypt__Ljava_lang_String_2。

(三)Native 方法参数

  • 第一个参数是 JNI 接口指针,JNIEnv *。
  • 第二个参数是 Java 对象,具体取决于当前方法是静态方法还是实例方法。若是静态方法,则表示类对象;若是实例方法,则表示实例对象。
  • 其余参数与定义本地方法时的参数一一对应。

以下是 Java 方法声明和对应的 C++ 函数实现示例:

public native void process(
    int count,          // -> jint
    String data,        // -> jstring
    byte[] buffer       // -> jbyteArray
);
JNIEXPORT void JNICALL Java_Processor_process(
    JNIEnv *env,        // JNI环境指针
    jobject obj,        // 调用对象实例
    jint count,         // 对应Java int
    jstring jstr,       // 对应Java String
    jbyteArray jarray   // 对应Java byte[]
) { 
    /* 实现代码 */ 
}

(四)JNI 数据类型

(五)类型签名

例如,Java 函数声明 long f (int n, String s, int[] arr); 的类型签名是 (ILjava/lang/String;[I)J。

(六)JNI 结构函数表

可参考 jni.h 头文件中的 JNINativeInterface 定义,这里简单介绍几个接口函数:

三、第一个 JNI 程序

(一)目标

  1. 实现 Java 调用 Native 函数。
  2. 实现 Native 函数调用 Java 实例函数。
  3. 实现 Java 调用 JNI 动态注册的 Native 函数。

(二)代码实现

Main.java

public class Main {
    static {
        System.loadLibrary("test");
    }

    private native int add(int a, int b);
    private native int sub(int a, int b);
    private native void foo();

    private void sayHi() {
        System.out.println("Hello World");
    }

    public static void main(String[] args) {
        Main mainObj = new Main();
        System.out.println(mainObj.add(1, 2)); 
        System.out.println(mainObj.sub(2, 1)); 
        mainObj.foo();
    }
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

project(test)
set(PRODUCT_NAME ${PROJECT_NAME})
set(CMAKE_CXX_STANDARD 17)

set(JAVA_HOME $ENV{JAVA_HOME})
include_directories(${JAVA_HOME}/include)
include_directories(${JAVA_HOME}/include/win32)
link_directories(${JAVA_HOME}/lib)

message(STATUS JAVA_HOME:${JAVA_HOME})

aux_source_directory(. SRC_LIST)

add_library(${PRODUCT_NAME} SHARED ${SRC_LIST})

使用 javah -jni Main 自动生成 Main 类的 JNI 头文件

Main.h

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

#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Main
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Main_add
  (JNIEnv *, jobject, jint, jint);

/*
* Class:     Main
* Method:    foo
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Main_foo
(JNIEnv*, jobject);


#ifdef __cplusplus
}
#endif
#endif

Main.cpp

#include "Main.h"

JNIEXPORT jint JNICALL Java_Main_add(JNIEnv* env, jobject obj, jint a, jint b)
{
  return  a + b;
}

JNIEXPORT void JNICALL Java_Main_foo(JNIEnv* env, jobject obj)
{
  jclass clazz = env->GetObjectClass(obj);
  jmethodID  sayHi = env->GetMethodID(clazz, "sayHi", "()V");
  env->CallVoidMethod(obj, sayHi);
}

JNIEXPORT jint JNICALL Main_sub(JNIEnv* env, jobject obj, jint a, jint b)
{
  printf("Java_Main_sub\n");
  return  a - b;
}

static const JNINativeMethod methods[] = {
    {"sub", "(II)I", (void*)Main_sub},
};

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
  printf("JNI_OnLoad\n");
  JNIEnv* env;
  if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) 
  {
    return JNI_ERR;
  }

  jclass mainClass = env->FindClass("Main");
  if (mainClass == NULL) 
  {
    return JNI_ERR;  
  }

  // 注册本地方法
  int numMethods = sizeof(methods) / sizeof(methods[0]);
  if (env->RegisterNatives( mainClass, methods, numMethods) < 0) 
  {
    return JNI_ERR; 
  }

  return JNI_VERSION_1_6;
}

(三)运行结果

JNI_OnLoad      // 加载库时调用JNI_OnLoad
3               // add(1, 2) 的结果 
Java_Main_sub   // 调用动态注册的sub方法
1               // sub(2, 1) 的结果
Hello World     // foo函数调用Java的sayHi方法

(四)执行流程解析

  1. 加载库:JVM 加载 test 库,触发 JNI_OnLoad 执行,动态注册 sub 方法。
  2. 调用 add:mainObj.add(1, 2) 调用 Java_Main_add 函数。
  3. 返回计算结果 3。
  4. 调用 sub:mainObj.sub(2, 1) 调用动态注册的 Main_sub 函数,打印 "Java_Main_sub"。
  5. 返回计算结果 1。
  6. Java_Main_foo 函数调用 Java 的 sayHi 方法。

四、代码安全加固策略

Java 字节码具有高度可读性和易反编译的特性,使得关键业务逻辑和敏感算法容易被逆向分析。通过 JNI 将核心功能迁移到 C/C++ 实现的本地库中,可以显著提高攻击者的逆向门槛:

  • 逆向难度提升:本地库的二进制代码比 Java 字节码更难反编译。
  • 调试障碍增加:需要专业的底层调试工具(如 IDA Pro、GDB)。
  • 分析成本激增:逆向工程师需要同时精通 Java 和底层汇编语言。
  • 攻击面转移:从 Java 层的逆向转向底层的二进制逆向。

此时,可以使用 Virbox Protector 成熟的代码保护工具进行加固。该工具提供多种保护方案(包括 Native 库代码保护和虚拟化保护 VME 等),通过组合使用能有效保护核心代码不被轻易泄露或分析。

通过以上对 JNI 的深入解析和实战示例,相信你对 JNI 有了更全面的了解,并且能够在实际项目中运用 JNI 技术提升应用性能和安全性。

相关推荐

一日一技:用Python程序将十进制转换为二进制

用Python程序将十进制转换为二进制通过将数字连续除以2并以相反顺序打印其余部分,将十进制数转换为二进制。在下面的程序中,我们将学习使用递归函数将十进制数转换为二进制数,代码如下:...

十进制转化成二进制你会吗?#数学思维

六年级奥赛起跑线:抽屉原理揭秘。同学们好,我是你们的奥耀老师。今天一起来学习奥赛起跑线第三讲二进制计数法。例一:把十进制五十三化成二进制数是多少?首先十进制就是满十进一,二进制就是满二进一。二进制每个...

二进制、十进制、八进制和十六进制,它们之间是如何转换的?

在学习进制时总会遇到多种进制转换的时候,学会它们之间的转换方法也是必须的,这里分享一下几种进制之间转换的方法,也分享两个好用的转换工具,使用它们能够大幅度的提升你的办公和学习效率,感兴趣的小伙伴记得点...

c语言-2进制转10进制_c语言 二进制转十进制

#include<stdio.h>intmain(){charch;inta=0;...

二进制、八进制、十进制和十六进制数制转换

一、数制1、什么是数制数制是计数进位的简称。也就是由低位向高位进位计数的方法。2、常用数制计算机中常用的数制有二进制、八进制、十进制和十六进制。...

二进制、十进制、八进制、十六进制间的相互转换函数

二进制、十进制、八进制、十六进制间的相互转换函数1、输入任意一个十进制的整数,将其分别转换为二进制、八进制、十六进制。2、程序代码如下:#include<iostream>usingna...

二进制、八进制、十进制和十六进制等常用数制及其相互转换

从大学开始系统的接触计算机专业,到现在已经过去十几年了,今天整理一下基础的进制转换,希望给还在上高中的表妹一个入门的引导,早日熟悉这个行业。一、二进制、八进制、十进制和十六进制是如何定义的?二进制是B...

二进制如何转换成十进制?_二进制如何转换成十进制例子图解

随着社会的发展,电器维修由继电器时代逐渐被PLC,变频器,触摸屏等工控时代所替代,特别是plc编程,其数据逻辑往往涉及到数制二进制,那么二进制到底是什么呢?它和十进制又有什么区别和联系呢?下面和朋友们...

二进制与十进制的相互转换_二进制和十进制之间转换

很多同学在刚开始接触计算机语言的时候,都会了解计算机的世界里面大多都是二进制来表达现实世界的任何事物的。当然现实世界的事务有很多很多,就拿最简单的数字,我们经常看到的数字大多都是十进制的形式,例如:我...

十进制如何转换为二进制,二进制如何转换为十进制

用十进制除以2,除的断的,商用0表示;除不断的,商用1表示余0时结束假如十进制用X表示,用十进制除以2,即x/2除以2后为整数的(除的断的),商用0表示;除以2除不断的,商用1表示除完后的商0或1...

十进制数如何转换为二进制数_十进制数如何转换为二进制数举例说明

我们经常听到十进制数和二进制数,电脑中也经常使用二进制数来进行计算,但是很多人却不清楚十进制数和二进制数是怎样进行转换的,下面就来看看,十进制数转换为二进制数的方法。正整数转二进制...

二进制转化为十进制,你会做吗?一起来试试吧

今天孩子问把二进制表示的110101改写成十进制数怎么做呀?,“二进制”简单来说就是“满二进一”,只用0和1共两个数字表示,同理我们平常接触到的“十进制”是“满十进一”,只用0-9共十个数字表示。如果...

Mac终于能正常打游戏了!苹果正逐渐淘汰Rosetta转译

Mac玩家苦转译久矣!WWDC2025苹果正式宣判Rosetta死刑,原生游戏时代终于杀到。Metal4光追和AI插帧技术直接掀桌,连Steam都连夜扛着ARM架构投诚了。看到《赛博朋克2077》...

怎么把视频的声音提出来转为音频?音频提取,11款工具实测搞定

想把视频里的声音单独保存为音频文件(MP3/AAC/WAV/FLAC)用于配音、播客、听课或二次剪辑?本文挑出10款常用工具,给出实测可复现的操作步骤、优缺点和场景推荐。1)转换猫mp3转换器(操作门...

6个mp4格式转换器测评:转换速度与质量并存!

MP4视频格式具有兼容性强、视频画质高清、文件体积较小、支持多种编码等特点,适用于网络媒体传播。如果大家想要将非MP4格式的视频转换成MP4的视频格式的话,可以使用MP4格式转换器更换格式。本文分别从...