使用 JVMTI 实现 jar 包字节码加密

由于 Java 属于解释型语言,在 class 文件被 JVM 加载之前,可以很容易的将其反编译,得到源码。对比网上提供的很多方法,比如使用混淆器或是自定义类加载器,都是基于Java层面的,一样可以被反编译。最后,终于找到一种更有效的解决方案:使用 JVMTI 实现 jar 包字节码加密。

 

JVMTI 简介

JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口,可以探查JVM内部状态,并控制JVM应用程序的执行。可实现的功能包括但不限于:调试、监控、线程分析、覆盖率分析工具等。

 

实现思路

JVMTI 能够监听class加载事件,因此我们可以使用一套加密算法,对即将发布的 jar 包进行字节码加密,然后在 JVM 加载这些类之前再解密。由于这部分代码最终会以动态库(.dll, .so 文件)的形式发布出去,不容易被破解,因此对源代码可以达到较好的保护效果。

 

实现步骤

打开 com_seaboat_bytecode_ByteCodeEncryptor.cpp ,编写具体的加解密算法,并设指定哪些类需要解密

#include <iostream>

#include "com_seaboat_bytecode_ByteCodeEncryptor.h"
#include "jni.h"
#include <jvmti.h>
#include <jni_md.h>


void encode(char *str)
{
	unsigned int m = strlen(str);
	for (int i = 0; i < m; i++)
	{
		//str[i] = ((str[i] - 97)*k) - ((str[i] - 97)*k) / q*q + 97;
		str[i] = str[i] + 1;
	}

}

void decode(char *str)
{
	unsigned int m = strlen(str);
	//int k2 = (q + 1) / k;
	for (int i = 0; i < m; i++)
	{
		//str[i] = ((str[i] - 97)*k2) - ((str[i] - 97)*k2) / q*q + 97;
		str[i] = str[i] - 1;
	}
}


extern"C" JNIEXPORT jbyteArray JNICALL
Java_com_seaboat_bytecode_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;

    //指定要解密的类,此处将会对 cn.zzp 包下面所有的类进行解密
	if (name&&strncmp(name, "cn/zzp/", 6) == 0) {

		for (int i = 0; i < class_data_len; i++)
		{
			_data[i] = class_data[i];
		}
		printf("%s\n","INFO: decode class... \n");
		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;
	//Create the JVM TI environment(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;
}

 

编译生成加解密所需要的动态库

cl /EHsc -LD com_seaboat_bytecode_ByteCodeEncryptor.cpp -FeByteCodeEncryptor.dll

注:在这里我使用 Visual Studio 完成编译,期间报错:找不到 jvmti.h,进入 jdk 所在目录,将 bin/ 以及 bin/win32/ 目录下对应的文件放入 Visual Studio 安装目录中的 bin/include/ 下即可解决。

 

将生成的动态库文件 FeByteCodeEncryptor.dll 加入系统环境变量中,有时需要重启系统才能生效。

 

用 Java 对即将发布的 jar 包加密,得到加密之后的jar包 helloworld_encrypted.jar

package com.seaboat.bytecode;

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;

public class ByteCodeEncryptor {
//  加载库(com_seaboat_bytecode_ByteCodeEncryptor.cpp),向 JVM 注册本地方法
  static{
    System.loadLibrary("ByteCodeEncryptor");
  }
  
  public native static byte[] encrypt(byte[] text);   //表示这个方法的具体实现在本地方法中

  public static void main(String[] args){
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      byte[] buf = new byte[1024];
      String fileName = "D:\\jarpath\\helloworld.jar";

      File srcFile = new File(fileName);
      File dstFile = new File(fileName.substring(0, fileName.indexOf("."))+"_encrypted.jar");
      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) {
          baos.write(buf, 0, len);
        }
        byte[] bytes = baos.toByteArray();
        String name = entry.getName();
        if(name.startsWith("cn/zzp/")){   //对 cn.zzp 包下面的 所有 class 文件加密
          try {
            bytes = ByteCodeEncryptor.encrypt(bytes);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        JarEntry ne = new JarEntry(name);
        dstJar.putNextEntry(ne);
        dstJar.write(bytes);
        baos.reset();
      }
      srcJar.close();
      dstJar.close();
      dstFos.close();
      System.out.println("encrypt finished");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

运行 jar 包,需要指定所依赖的动态库,以及 jar 包的入口(主方法所在的类)

java -agentlib:ByteCodeEncryptor -cp helloworld_encrypted.jar cn.zzp.HelloWorld

 

最终效果

加密前:

 

加密后:

 

另外,加密所需要的文件可以从这里获得

------------------------------------------------------------------------------------------------------------------------

参考资料

https://juejin.im/post/596de75a5188254b5e015767#heading-3

https://juejin.im/post/5b8c7f54e51d4538b63d3db8

https://blog.csdn.net/wangyangzhizhou/article/details/74931733

众所周知,Java编译后的Jar和Class文件,可以轻而易举的使用反编译工具(如JD-GUI)进行反编译,拿到源码。为了保护自己发布的Jar和Class文件,采用的方式大多是混淆方式,这种方式对于Class文件的加密是不彻底的,还是能够通过分析得出核心算法。本工具是采用jvmti方式对Class文件进行加密使用C++生成加密和解密库,先用加密库对Jar进行加密,将加密后的Jar及解密库文件发布出去,执行时候需要JVM引入解密库文件,解密后执行。c++的.dll文件和.so文件的破解难度是很大的,这就能有效的保护软件和代码的知识产权. 使用方法: 1.打开windows命令行(运行=>cmd=>回车),在命令行中 进入 EncryptJar目录 2.执行 java -jar encrypt.jar 3.输入h,然后回车,可以看到帮助菜单 4.输入3,然后按回车键,进入加入jar文件功能 5.输入要加密jar文件的路径 6.提示输入秘钥(key)的时候,直接回车,不要输入任何字符(否则后面classhook将不可解密加密后的jar) 7.输入目标路径(加密后的jar文件路径,此处要注意:jar文件名要保持相同,将加密后的文件保存到不同的目录) 8.将加密后的jar,替换原来的没有加密jar,与要发布的程序一起进行发布.(一般替换lib目录下对应的jar即可) 9.加密后的jar运行方法: windows下: 拷贝libClassHook.dll文件到程序的根目录(通常为要执行的jar程序的根目录) 使用以下命令启动程序: java -agentlib:libClassHook -jar xxxxxxxxxxx.jar 则在运行过程中会自动进行解密操作(解密过程是运行过程中用c++的dll进行解密的,可以有效防止破解class文件) 如果执行过程报错,可将程序根目录添加到环境变量path中去 Linux下: 拷贝libClassHook.so到程序的根目录(通常为要执行的jar程序的根目录) 使用以下命令启动程序: java -agentlib:ClassHook -jar xxxxxxxxxxx.jar (这里要删除掉lib,linux系统下会自动补全) 则在运行过程中会自动进行解密操作(解密过程是运行过程中用c++的dll进行解密的,可以有效防止破解class文件) 如果执行过程报错,可以在程序根目录下执行以下语句:export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH 或将libClassHook.so 拷贝到/usr/lib目录中去。 支持操作系统:加密请在windows64位系统并安装了64位jdk环境下进行。 需要解密运行的程序支持LINUX(64位)和windows(64位)安装了JDK1.8以上的系统。 测试程序: (t_lib目录下的jar为经过加密jar) java -agentlib:libClassHook -jar test.jar
### 回答1: JVMTI是Java虚拟机调试接口的缩写,它是Java平台提供的一组原生接口,可以帮助开发者在Java虚拟机层面参与程序的调试和分析过程。JVMTI可以为开发者提供很多方便的调试工具,比如断点、内存泄漏检查、垃圾回收统计信息等等。但是JVMTI本身并不提供加密jar的能力,因为它的主要作用是提供开发者在运行时操作虚拟机的接口,而不是加密jar。 如果您想对Java程序的jar进行加密或者保护,您可以考虑使用Java加密扩展(JCE)这个Java API。JCE可以帮助您实现加密通信、数字签名和安全密钥管理等功能。JCE的加密算法括DES、AES、RSA等多种标准算法,也支持其他自定义算法。使用JCE加密jar可以保护您的程序不被反编译或者恶意利用,同时也可以保护数据的机密性和完整性。 总之,JVMTI和JCE都是Java平台提供的重要接口,但是它们的作用不同,前者主要用于开发和调试,后者则是用于加密和保护数据安全的。如果您需要对Java程序进行加密保护,建议使用JCE这个Java API。 ### 回答2: JVMTI是Java虚拟机工具接口的缩写,是JDK 1.5加入的一项新功能,它提供一组API,可以在运行时监控、检测和管理Java虚拟机和Java应用程序。JVMTI可以被用于加密Java程序的JAR加密JAR的目的在于保护Java程序的源代码,防止别人拷贝或查看程序的源代码。使用JVMTI进行JAR加密的步骤如下: 1. 创建一个新JAR文件,可以使用Java工具命令创建。 2. 将需要加密JAR文件和一个密钥文件放入创建的新JAR文件中。 3. 使用JVMTI API,从Java VM中获取应用程序的字节码,然后使用密钥对其进行加密。 4. 加密后的字节码存入新的JAR文件中,并将原始JAR文件中的其他文件也复制到新的JAR文件中。 5. 加密后的JAR文件可以被用于发布Java程序。 JVMTI加密Jar可以确保Java程序源代码的安全性。虽然Java编译后会生成字节码,可以进行反编译,但加密后的字节码无法被反编译,从而保护程序的源代码不被泄露。 ### 回答3: JVMTI是Java虚拟机工具接口的缩写,它提供了访问Java虚拟机内部的调试和监控的功能。在Java开发过程中,为了保护Java代码的安全性,我们通常会使用加密技术来保护Java代码,而对于已经被加密的Java代码,我们需要使用JVMTI来进行反调试和监控。JVMTI可以访问Java虚拟机内部的数据结构,可以读写Java类的字节码、常量池、方法表和字段表。因此,我们可以使用JVMTI来解密加密的Java代码,并获取其源代码和调试信息。 对于加密jar,我们可以使用JVMTI工具来进行解密。首先,我们需要创建一个JVMTI的Agent程序,Agent是一种能够监控和修改Java应用程序的工具,它可以在程序运行时进行操作,可以读取和修改Java类的字节码,同时还可以获取应用程序内部的状态信息。然后,我们需要将Agent程序注入到Java虚拟机中,并使用JVMTI来获取加密jar字节码,并进行解密操作。解密完成之后,我们可以将解密后的字节码重新加载到虚拟机中,这样就可以获取到源代码和调试信息了。 总之,JVMTI是Java开发中非常重要的工具,它可以帮助我们进行调试和监控Java应用程序,同时还可以帮助我们解密加密的Java代码。使用JVMTI可以使我们更好地了解Java程序的运行机制,保护Java代码的安全性,并提高我们的开发效率。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值