使用jvmti dll |so 加密java class jar包

使用到的的项目

需要加密的Jar,主要是里面的一个Test2.class需要加密

java-ext2-jar-0.0.1-SNAPSHOT.jar

需要使用加密jar的项目

java-jvmti-loader-0.0.1-SNAPSHOT.jar

 

调用DLL中的加密方法进行jar加密的项目

java-jni-call

 

dll代码 

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <jni_md.h>
#include <jni.h>
#include <jvmti.h>
#include <string.h>
#include <stdio.h>
#include "com_example_javajnicall_JniCall.h"



using namespace std;



void encode(char *str ,int len)
{
    unsigned int m = strlen(str);
    
    printf("wait encode len %d arg len %d\n",m ,len);


    for (int i = 0; i < len; i++) {
        str[i] = str[i] + k;
    }


    /*for (int i = 0; i < m; i++)
    {
        str[i] = str[i] + k;
    }*/
}

void decode(char *str,int len)
{
    unsigned int m = strlen(str);
    for (int i = 0; i < len; i++) {
        str[i] = str[i] - k;
    }



    /*for (int i = 0; i < m; i++)
    {
        str[i] = str[i] - k;
    }*/
}

extern"C" JNIEXPORT jbyteArray JNICALL Java_com_example_javajnicall_JniCall_encrypt(JNIEnv * env, jclass cla, jbyteArray text,jint len)
{
    printf("call it!");
    char* dst = (char*)env->GetByteArrayElements(text, 0);


    encode(dst, len);
    //env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst);
    env->SetByteArrayRegion(text, 0, len, (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;
    // com/example/javajvmtiloader/scope/Test2
    if (name && strncmp(name, "com/far/demo/Test2", 38) == 0 )
    {

        printf("find it  len:%d\n", class_data_len);
        for (int i = 0; i < class_data_len; i++){
            _data[i] = class_data[i];
        }
        decode((char*)_data, class_data_len);
        
    }else {
        for (int i = 0; i < class_data_len; i++){
            _data[i] = class_data[i];
        }
    }


}

JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
    return JNI_OK;
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm) {

}

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;
    }

    printf("jvmti oj8k\n");

    return JNI_OK;
}
View Code

 

com_example_javajnicall_JniCall.h

 这个头文件是用javac生成的,实际上这个DLL可以分成两个,我偷懒搞一成一个了

一个用于加密(Java Jni调用),一个用于jvm加载时解密(java agent),对于给jvm加载的dll 包含解密方法即可

关于jni和javac 生成jni头文件可以看

https://blog.csdn.net/qq_40318498/article/details/98330665

 

调用dll中的加密方法的java项目

package com.example.javajnicall;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

public class JniCall {

    static {

        String dllPath = "E:\\AAAA_CODE\\vsproject\\run-jar-app\\x64\\Debug\\run-jar-app.dll";
        System.load(dllPath);

    }

    public static void init() {
        System.out.println("初始化");
    }

    public static final int getProcessID() {
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        System.out.println(runtimeMXBean.getName());
        return Integer.valueOf(runtimeMXBean.getName().split("@")[0])
                .intValue();
    }


    public static void enJava() throws IOException {
        BufferedReader br = null;
        String file = "E:\\AAAA_CODE\\new-eclipse-workspace\\demoall\\java-demo\\java-ext2-jar\\target\\classes\\com\\far\\demo\\Test2.class";
        String fileEncode = "E:\\AAAA_CODE\\new-eclipse-workspace\\demoall\\java-demo\\java-ext2-jar\\target\\classes\\com\\far\\demo\\Test2-en.class";
        try {


            FileInputStream fileInputStream = new FileInputStream(file);
            byte[] fInput = new byte[1];


            int readf;

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            FileOutputStream outputStream = new FileOutputStream(fileEncode);
            while ((readf = fileInputStream.read(fInput)) != -1) {

                byteArrayOutputStream.write(fInput);
            }
            byte[] datas = byteArrayOutputStream.toByteArray();
            datas = JniCall.encrypt(datas, datas.length);
            outputStream.write(datas);

        } catch (IOException e) {
            br.close();
            e.printStackTrace();
        }

    }


    public native static byte[] encrypt(byte[] text, int len);


}
View Code

 

 

 

执行

java -agentpath:E:\AAAA_CODE\vsproject\run-jar-app\x64\Debug\run-jar-app.dll -cpjava-jvmti-loader-0.0.1-SNAPSHOT.jar com.example.javajvmtiloader.JavaJvmtiLoaderApplication

 agentpath 需要完整的DLL路径

agenlib也可以加载DLL jvmti官方文档有介绍

 

 

坑点

Springboot的jar包格式导致不触发jvmti的fIleLoad事件

springboot项目jar包结构

 

正常的Jar包结构

 

 

要想触发jvmti中的fileLoad的事件,必须是正常的jar包格式

springboot是把用户写的类单独放到boot-inf目录下,springboot自己去加载,所以触发不了fileLoad事件!

通过maven将springboot打成普通的jar包

<build>
    <resources>
        <resource>
            <!--指定mapping下的所有xml文件打包在jar中-->
            <targetPath>${project.build.directory}/classes</targetPath>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>mapping/*.xml</include>
            </includes>
        </resource>
        <resource>
            <!--resources下一级的所有.xml .properties文件复制到config目录下-->
            <targetPath>${project.build.directory}/config</targetPath>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>**.xml</include>
                <include>**.yml</include>
            </includes>
        </resource>
    </resources>
    <plugins>
        <!--maven-dependency插件,将项目所有依赖包放到lib目录下-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <type>jar</type>
                        <includeTypes>jar</includeTypes>
                        <outputDirectory>
                            ${project.build.directory}/lib
                        </outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <classesDirectory>target/classes/</classesDirectory>
                <archive>
                    <!--生成的jar中,不要包含pom.xml和pom.properties这两个文件-->
                    <addMavenDescriptor>false</addMavenDescriptor>
                    <manifest>
                        <mainClass>com.example.javajvmtiloader.JavaJvmtiLoaderApplication</mainClass>
                        <!-- 打包时 MANIFEST.MF文件不记录的时间戳版本 -->
                        <useUniqueVersions>false</useUniqueVersions>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                    </manifest>
                    <manifestEntries>
                        <!--jar中的MANIFEST.MF文件ClassPath需要添加config目录才能读取到配置文件-->
                        <Class-Path>config/</Class-Path>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
View Code

 

 

修改后

 

 

该打包方式会把一些依赖的jar包单独放到一个lib目录下,我们只需要把加密的jar放到lib目录下,jvm就会自动加载并触发jvmti的fileLoad事件了,注意,加密的jar 也是用的普通的jar包结构

 

 

 

 

 

 

 

win下编译的dll 不需要dllMain函数

开始一直以为需要dllmain函数,其实不需要!

 

想单独加密一个java文件,结果有两个class?

这是因为用了内部类或者lamda方法

 

找不到DLL

Could not find agent library run-jar-app.dll in absolute path, with error: Can't find dependent libraries

这个问题很诡异 在同事A,B电脑上 一个能跑 一个不能 能跑的同事的电脑上我做了以下操作

复制dll到c windows system32 

regsvr32 DLL 完整路径 (但报错了)

之后莫名其妙可以运行了 (但是否和上面两个操作有关 还不确定...)

最后无奈甚至查看了DLL是否还依赖了其他什么DLL(用的一个DLL依赖查看工具) 结果还是没看出个所以然...

 

JAVA代码编译报错(用于调用DLL中的加密方法的项目java-jin-call)

报错 :程序包org.springframework.boot不存在

把下面的勾选起来

 

在Linux上编译

名词

AC :vs2017+win

BC:clion+win+linux虚拟机 交叉编译

为了能使用同一套代码,我最终分成AC和BC进行编译,实际cmake可以解决多端编译,配置好win下的clang或者msc编译器就行,但我本地的环境复杂会出问题,所以直接分为两个,so就用BC dll就用AC

 

Gcc版本 (高点也无所谓)

 

 

 

 

 

BC环境下编译

会在linux下生成临时的编译目录在tmp下,生成的SO文件也在里面 (注意:生成的文件在Linux 在linux)

 

另外会一直找不到jvmti相关的头文件,直接把linux上复制到项目内就行

 

 

改了下代码

//不同平台编译时,需要改此处
#define LINUX
#ifdef LINUX
#elif
#include "pch.h"
#include <jni_md.h>
#include <jni.h>
#include <jvmti.h>
#endif
#include <iostream>
#include <linux/jni_md.h>
#include <linux/jni.h>
#include <linux/jvmti.h>
#include <string.h>
#include <stdio.h>
#include "com_example_javajnicall_JniCall.h"

Cmake

cmake_minimum_required(VERSION 3.5.1)
project(run_jar_app)

set(CMAKE_CXX_STANDARD 14)


include_directories(run-jar-app)
include_directories(run-jar-app/Debug)
include_directories(run-jar-app/x64)
include_directories(run-jar-app/x64/Debug)
include_directories(run-jar-app/x64/Release)

if (CMAKE_SYSTEM_NAME MATCHES "linux")
    message("run linux plf....")

else()
    include_directories(D:/AAAA_WORK/java/Java1.8.0-jdk-jre/jdk1.8.0_161/include/win32)
    include_directories(D:/AAAA_WORK/java/Java1.8.0-jdk-jre/jdk1.8.0_161/include)
endif ()


SET(PROJECT_SRC run-jar-app/dllmain.cpp)
ADD_LIBRARY(run-jar-app SHARED ${PROJECT_SRC})

 

调式

 

posted @ 2022-10-28 14:14  方东信  阅读(964)  评论(0编辑  收藏  举报