最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用JVMTI實(shí)現(xiàn)SpringBoot的jar加密,防止反編譯

 更新時(shí)間:2023年08月31日 15:06:24   作者:完美明天cxp  
這篇文章主要介紹了使用JVMTI實(shí)現(xiàn)SpringBoot的jar加密,防止反編譯問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

1.背景

ToB項(xiàng)目私有化部署,攜帶有項(xiàng)目jar包,防止別人下載jar,反編譯出源碼

2.JVMTI解釋

JVMTI(Java Virtual Machine Tool Interface)即指 Java 虛擬機(jī)工具接口,它是一套由虛擬機(jī)直接提供的 native 接口,它處于整個(gè) JPDA(Java Platform Debugger Architecture) 體系的最底層,所有調(diào)試功能本質(zhì)上都需要通過(guò) JVMTI 來(lái)提供。

通過(guò)這些接口,開(kāi)發(fā)人員不僅調(diào)試在該虛擬機(jī)上運(yùn)行的 Java 程序,還能查看它們運(yùn)行的狀態(tài),設(shè)置回調(diào)函數(shù),控制某些環(huán)境變量,從而優(yōu)化程序性能。

3.使用JVMTI思路

在SpringBoot項(xiàng)目打包后,通過(guò)dll動(dòng)態(tài)鏈接庫(kù)(so共享對(duì)象)加密生成新的jar,在啟動(dòng)jar時(shí),讓jvm啟動(dòng)時(shí)調(diào)用jvmti提供的Agent_OnLoad方法,用c++去實(shí)現(xiàn),去解密對(duì)應(yīng)的class文件。

利用c++編譯型語(yǔ)言特性,無(wú)法反編譯出對(duì)應(yīng)的加密解密方法。

4.實(shí)現(xiàn)源碼

c++加密實(shí)現(xiàn)和jvm啟動(dòng)監(jiān)聽(tīng),需要jni.h,jvmti.h,jni_md.h三個(gè)頭文件,分別在java環(huán)境變量下include和include/linux(win32)下

// c++加密頭文件
#include <jni.h>
#ifndef _Included_com_cxp_demo_encrypt_ByteCodeEncryptor
#define _Included_com_cxp_demo_encrypt_ByteCodeEncryptor
#ifdef __cplusplus
extern "C"
{
#endif
    JNIEXPORT jbyteArray JNICALL Java_com_cxp_demo_encrypt_ByteCodeEncryptor_encrypt(JNIEnv *, jclass, jbyteArray);
#ifdef __cplusplus
}
#endif
#endif
#ifndef CONST_HEADER_H_  
#define CONST_HEADER_H_   
const int k = 4;
const int compare_length = 18;
const char* package_prefix= "com/cxp/demo";
#endif
// c++加密解密源碼
#include <iostream>
#include <jni.h>
#include <jvmti.h>
#include <jni_md.h>
#include "demo_bytecode_encryptor.h"
void encode(char *str)
{
    unsigned int m = strlen(str);
    for (int i = 0; i < m; i++)
    {
        str[i] = str[i] + k;
    }
}
void decode(char *str)
{
    unsigned int m = strlen(str);
    for (int i = 0; i < m; i++)
    {
        str[i] = str[i] - k;
    }
}
extern"C" JNIEXPORT jbyteArray JNICALL Java_com_cxp_demo_encrypt_ByteCodeEncryptor_encrypt(JNIEnv * env, jclass cla, jbyteArray text)
{
    char* dst = (char*)env->GetByteArrayElements(text, 0);
    encode(dst);
    env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst);
    return text;
}
void JNICALL ClassDecryptHook(
    jvmtiEnv *jvmti_env,
    JNIEnv* jni_env,
    jclass class_being_redefined,
    jobject loader,
    const char* name,
    jobject protection_domain,
    jint class_data_len,
    const unsigned char* class_data,
    jint* new_class_data_len,
    unsigned char** new_class_data
)
{
    *new_class_data_len = class_data_len;
    jvmti_env->Allocate(class_data_len, new_class_data);
    unsigned char* _data = *new_class_data;
    if (name && strncmp(name, package_prefix, compare_length) == 0 && strstr(name, "BySpringCGLIB") == NULL)
    {
        for (int i = 0; i < class_data_len; i++)
        {
            _data[i] = class_data[i];
        }
        decode((char*)_data);
    }
    else {
        for (int i = 0; i < class_data_len; i++)
        {
            _data[i] = class_data[i];
        }
    }
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
    jvmtiEnv *jvmti;
    jint ret = vm->GetEnv((void **)&jvmti, JVMTI_VERSION);
    if (JNI_OK != ret)
    {
        printf("ERROR: Unable to access JVMTI!\n");
        return ret;
    }
    jvmtiCapabilities capabilities;
    (void)memset(&capabilities, 0, sizeof(capabilities));
    capabilities.can_generate_all_class_hook_events = 1;
    capabilities.can_tag_objects = 1;
    capabilities.can_generate_object_free_events = 1;
    capabilities.can_get_source_file_name = 1;
    capabilities.can_get_line_numbers = 1;
    capabilities.can_generate_vm_object_alloc_events = 1;
    jvmtiError error = jvmti->AddCapabilities(&capabilities);
    if (JVMTI_ERROR_NONE != error)
    {
        printf("ERROR: Unable to AddCapabilities JVMTI!\n");
        return error;
    }
    jvmtiEventCallbacks callbacks;
    (void)memset(&callbacks, 0, sizeof(callbacks));
    callbacks.ClassFileLoadHook = &ClassDecryptHook;
    error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
    if (JVMTI_ERROR_NONE != error)
    {
        printf("ERROR: Unable to SetEventCallbacks JVMTI!\n");
        return error;
    }
    error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
    if (JVMTI_ERROR_NONE != error)
    {
        printf("ERROR: Unable to SetEventNotificationMode JVMTI!\n");
        return error;
    }
    return JNI_OK;
}

 java加密邏輯:

// JNI調(diào)用c++代碼
public class ByteCodeEncryptor {
    static {
        String currentPath = ByteCodeEncryptor.class.getResource("").getPath().split("SpringBootJarEncryptor.jar!")[0];
        if (currentPath.startsWith("file:")) {
            currentPath = currentPath.substring(5);
        }
        String dllPath;
        String os = System.getProperty("os.name");
        if (os.toLowerCase().startsWith("win")) {
            dllPath = currentPath + "SpringBootJarEncryptor.dll";
        } else {
            dllPath = currentPath + "SpringBootJarEncryptor.so";
        }
        System.load(dllPath);
    }
    public native static byte[] encrypt(byte[] text);
}
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
// 加密jar代碼
public class JarEncryptor {
    public static void encrypt(String fileName, String dstName) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            File srcFile = new File(fileName);
            File dstFile = new File(dstName);
            FileOutputStream dstFos = new FileOutputStream(dstFile);
            JarOutputStream dstJar = new JarOutputStream(dstFos);
            JarFile srcJar = new JarFile(srcFile);
            for (Enumeration<JarEntry> enumeration = srcJar.entries(); enumeration.hasMoreElements(); ) {
                JarEntry entry = enumeration.nextElement();
                InputStream is = srcJar.getInputStream(entry);
                int len;
                while ((len = is.read(buf, 0, buf.length)) != -1) {
                    bos.write(buf, 0, len);
                }
                byte[] bytes = bos.toByteArray();
                String name = entry.getName();
                if (name.startsWith("com/cxp/demo") && name.endsWith(".class")) {
                    try {
                        bytes = ByteCodeEncryptor.encrypt(bytes);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                JarEntry ne = new JarEntry(name);
                dstJar.putNextEntry(ne);
                dstJar.write(bytes);
                bos.reset();
            }
            srcJar.close();
            dstJar.close();
            dstFos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        if (args == null || args.length == 0) {
            System.out.println("please input parameter");
            return;
        }
        if (args[0].endsWith(".jar")) {
            JarEncryptor.encrypt(args[0], args[0].substring(0, args[0].lastIndexOf(".")) + "_encrypted.jar");
        } else {
            File file = new File(args[0]);
            if (file.isDirectory()) {
                String[] list = file.list();
                if (list != null) {
                    for (String jarFilePath : list) {
                        if (jarFilePath.endsWith(".jar")) {
                            JarEncryptor.encrypt(args[0] + "/" + jarFilePath, args[0] + "_encrypted/" + jarFilePath);
                        }
                    }
                }
            } else {
                System.out.println("this is not a folder or folder is empty");
            }
        }
    }
}

實(shí)現(xiàn)步驟:

1.先把c++打成dll(so)動(dòng)態(tài)鏈接庫(kù)

2.通過(guò)java調(diào)用dll文件,加密jar

3.啟動(dòng)加密后的jar,加上啟動(dòng)參數(shù)-agentpath:*.dll

gradle.build打包:

task copyJar(type: Copy) {
    delete "$buildDir/libs/lib"
    from configurations.runtime
    into "$buildDir/libs/lib"
}
jar {
    enabled = true
    dependsOn copyJar
    archivesBaseName = "app"
    archiveVersion = ""
    manifest {
        attributes 'Main-Class': "主類全限定名",
                'Class-Path': configurations.runtime.files.collect { "lib/$it.name" }.join(' ')
    }
}

 Dockerfile文件:

# 加密jar
RUN mkdir ./build/libs/lib_encrypted
RUN java -jar ./encrypt/SpringBootJarEncryptor.jar ./build/libs/app.jar
RUN java -jar ./encrypt/SpringBootJarEncryptor.jar ./build/libs/lib
RUN rm -r ./build/libs/lib
RUN rm ./build/libs/app.jar
RUN mv ./build/libs/lib_encrypted ./build/libs/lib
RUN mv ./build/libs/app_encrypted.jar ./build/libs/app.jar
# --- java ---
FROM java8
# 提取啟動(dòng)需要的資源
COPY --from=builder /build/libs/app.jar /application/app.jar
COPY --from=builder /build/libs/lib /application/lib
COPY --from=builder /encrypt /encrypt
COPY --from=builder /start.sh start.sh
ENTRYPOINT ["sh", "start.sh"]

啟動(dòng)命令:

java -agentpath:./SpringBootJarEncryptor.dll -jar app.jar

5.踩坑

java使用jni調(diào)用c++函數(shù),c++被調(diào)用的函數(shù)名要與java方法全限定名一致,如:com.cxp.demo.encrypt.ByteCodeEncryptor#encrypt=》Java_com_cxp_demo_encrypt_ByteCodeEncryptor_encrypt,注意java和c++數(shù)據(jù)類型的對(duì)應(yīng)關(guān)系

JVMTI只能作用Java原生啟動(dòng)類加載器,而SpringBoot有自己?jiǎn)?dòng)類加載器,讀取SpringBoot規(guī)定的原文件路徑(BOOT-INF/classes)和依賴路徑(BOOT-INF/lib),所以SpringBoot要打包成普通jar方式,bootjar是SpringBoot默認(rèn)的打包方式,改為jar打包方式無(wú)法打入依賴包,只能把依賴包打到同文件夾下,修改MANIFEST.MF文件的CLass-Path屬性,把依賴包添加進(jìn)去。可以看下SpringBoot的啟動(dòng)jar和普通jar區(qū)別。

c++在linux下打成so庫(kù),可以自行搜索下,推薦使用cmake

java引入dll動(dòng)態(tài)庫(kù),只能放在java的classpath下或絕對(duì)路徑

引包方式改為可以打出依賴包的方式,如compile,運(yùn)行時(shí)不需要的可以不用

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Spring菜鳥(niǎo)教你看源碼沖面試

    Spring菜鳥(niǎo)教你看源碼沖面試

    這篇文章主要介紹了Spring菜鳥(niǎo)教你看源碼沖面試,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • 使用IDEA查看jar包及jar包的正確打開(kāi)方式

    使用IDEA查看jar包及jar包的正確打開(kāi)方式

    IDEA 是把 jar 包添加為 Libraries,然后展開(kāi)后即可查看,因?yàn)槭蔷幾g后的 class 文件,所以注釋是沒(méi)有的,今天小編給大家介紹下使用IDEA查看jar包及jar包的正確打開(kāi)方式,感興趣的朋友一起看看吧
    2023-07-07
  • JVM垃圾收集器詳解

    JVM垃圾收集器詳解

    本文主要介紹了JVM垃圾收集器的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • java 使用idea將工程打成jar并創(chuàng)建成exe文件類型執(zhí)行的方法詳解

    java 使用idea將工程打成jar并創(chuàng)建成exe文件類型執(zhí)行的方法詳解

    這篇文章主要介紹了java 使用idea將工程打成jar并創(chuàng)建成exe文件類型執(zhí)行,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2020-09-09
  • Java多線程之鎖學(xué)習(xí)(增強(qiáng)版)

    Java多線程之鎖學(xué)習(xí)(增強(qiáng)版)

    這篇文章主要為大家詳細(xì)介紹了Java多線程中鎖的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們了解線程有一定幫助,需要的可以參考一下
    2023-02-02
  • 基于Spire.Doc for Java實(shí)現(xiàn)HTML轉(zhuǎn)PDF的操作方案

    基于Spire.Doc for Java實(shí)現(xiàn)HTML轉(zhuǎn)PDF的操作方案

    本文記錄了使用Spire.DocforJava將HTML內(nèi)容轉(zhuǎn)換為PDF文檔的過(guò)程,包括環(huán)境準(zhǔn)備、兩種常見(jiàn)的轉(zhuǎn)換場(chǎng)景、進(jìn)階設(shè)置以及注意事項(xiàng),適用于樣式復(fù)雜度相對(duì)可控、排版以文檔流為主的轉(zhuǎn)換需求,需要的朋友可以參考下
    2026-04-04
  • shiro與spring?security用自定義異常處理401錯(cuò)誤

    shiro與spring?security用自定義異常處理401錯(cuò)誤

    這篇文章主要介紹了shiro與spring?security用自定義異常處理401錯(cuò)誤,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java冒泡排序詳解(附有詳細(xì)代碼)

    Java冒泡排序詳解(附有詳細(xì)代碼)

    冒泡排序是一種簡(jiǎn)單的排序算法,通過(guò)相鄰元素的比較和交換來(lái)實(shí)現(xiàn),它雖然在效率上不如其他排序算法,但在某些特定場(chǎng)景下仍然有其應(yīng)用價(jià)值,這篇文章主要介紹了Java冒泡排序的相關(guān)資料,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下
    2025-04-04
  • springboot優(yōu)雅獲取前端參數(shù)的方法詳解

    springboot優(yōu)雅獲取前端參數(shù)的方法詳解

    現(xiàn)在的項(xiàng)目基本上都是前后端分離的項(xiàng)目,如何打通前后端,接收前端傳過(guò)來(lái)的參數(shù)呢,這篇文章小編就來(lái)和大家詳細(xì)介紹一下springboot如何優(yōu)雅的獲取前端參數(shù)吧
    2024-03-03
  • Jpa使用Page和Pageable分頁(yè)遇到的問(wèn)題及解決

    Jpa使用Page和Pageable分頁(yè)遇到的問(wèn)題及解決

    這篇文章主要介紹了Jpa使用Page和Pageable分頁(yè)遇到的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07

最新評(píng)論

潮安县| 洛隆县| 新源县| 龙井市| 凌海市| 织金县| 怀集县| 吉首市| 杭州市| 育儿| 通江县| 乐安县| 轮台县| 岳池县| 庆元县| 黑山县| 海口市| 囊谦县| 安义县| 三穗县| 沙田区| 建始县| 通城县| 新竹县| 招远市| 黄龙县| 方山县| 历史| 阿巴嘎旗| 乐山市| 盱眙县| 拉孜县| 高密市| 通渭县| 永靖县| 长乐市| 五莲县| 抚松县| 呼伦贝尔市| 东明县| 资中县|