Table of Contents
背景
maven插件加密
注意事项
自定义classloader
隐藏classloader
被保护class手动加壳
Home Java javaTutorial How SpringBoot encrypts and protects class files through custom classloader

How SpringBoot encrypts and protects class files through custom classloader

May 11, 2023 pm 09:07 PM
class springboot classloader

背景

最近针对公司框架进行关键业务代码进行加密处理,防止通过jd-gui等反编译工具能够轻松还原工程代码,相关混淆方案配置使用比较复杂且针对springboot项目问题较多,所以针对class文件加密再通过自定义的classloder进行解密加载,此方案并不是绝对安全,只是加大反编译的困难程度,防君子不防小人,整体加密保护流程图如下图所示

How SpringBoot encrypts and protects class files through custom classloader

maven插件加密

使用自定义maven插件对编译后指定的class文件进行加密,加密后的class文件拷贝到指定路径,这里是保存到resource/coreclass下,删除源class文件,加密使用的是简单的DES对称加密

 @Parameter(name = "protectClassNames", defaultValue = "")
 private List<String> protectClassNames;
 @Parameter(name = "noCompileClassNames", defaultValue = "")
 private List<String> noCompileClassNames;
 private List<String> protectClassNameList = new ArrayList<>();
 private void protectCore(File root) throws IOException {
        if (root.isDirectory()) {
            for (File file : root.listFiles()) {
                protectCore(file);
            }
        }
        String className = root.getName().replace(".class", "");
        if (root.getName().endsWith(".class")) {
            //class筛选
            boolean flag = false;
            if (protectClassNames!=null && protectClassNames.size()>0) {
                for (String item : protectClassNames) {
                    if (className.equals(item)) {
                        flag = true;
                    }
                }
            }
            if(noCompileClassNames.contains(className)){
                boolean deleteResult = root.delete();
                if(!deleteResult){
                    System.gc();
                    deleteResult = root.delete();
                }
                System.out.println("【noCompile-deleteResult】:" + deleteResult);
            }
            if (flag && !protectClassNameList.contains(className)) {
                protectClassNameList.add(className);
                System.out.println("【protectCore】:" + className);
                FileOutputStream fos = null;
                try {
                    final byte[] instrumentBytes = doProtectCore(root);
                    //加密后的class文件保存路径
                    String folderPath = output.getAbsolutePath() + "\\" + "classes";
                    File  folder = new File(folderPath);
                    if(!folder.exists()){
                        folder.mkdir();
                    }
                    folderPath = output.getAbsolutePath() + "\\" + "classes"+ "\\" + "coreclass" ;
                    folder = new File(folderPath);
                    if(!folder.exists()){
                        folder.mkdir();
                    }
                    String filePath = output.getAbsolutePath() + "\\" + "classes" + "\\" + "coreclass" + "\\" + className + ".class";
                    System.out.println("【filePath】:" + filePath);
                    File protectFile = new File(filePath);
                    if (protectFile.exists()) {
                        protectFile.delete();
                    }
                    protectFile.createNewFile();
                    fos = new FileOutputStream(protectFile);
                    fos.write(instrumentBytes);
                    fos.flush();
                } catch (MojoExecutionException e) {
                    System.out.println("【protectCore-exception】:" + className);
                    e.printStackTrace();
                } finally {
                    if (fos != null) {
                        fos.close();
                    }
                    if(root.exists()){
                        boolean deleteResult = root.delete();
                        if(!deleteResult){
                            System.gc();
                            deleteResult = root.delete();
                        }
                        System.out.println("【protectCore-deleteResult】:" + deleteResult);
                    }
                }
            }
        }
    }
    private byte[] doProtectCore(File clsFile) throws MojoExecutionException {
        try {
            FileInputStream inputStream = new FileInputStream(clsFile);
            byte[] content = ProtectUtil.encrypt(inputStream);
            inputStream.close();
            return content;
        } catch (Exception e) {
            throw new MojoExecutionException("doProtectCore error", e);
        }
    }
Copy after login

注意事项

1.加密后的文件也是class文件,为了防止在递归查找中重复加密,需要对已经加密后的class名称记录防止重复

2.在删除源文件时可能出现编译占用的情况,执行System.gc()后方可删除

3.针对自定义插件的列表形式的configuration节点可以使用List来映射

插件使用配置如图所示

How SpringBoot encrypts and protects class files through custom classloader

自定义classloader

创建CustomClassLoader继承自ClassLoader,重写findClass方法只处理装载加密后的class文件,其他class交有默认加载器处理,需要注意的是默认处理不能调用super.finclass方法,在idea调试没问题,打成jar包运行就会报加密的class中的依赖class无法加载(ClassNoDefException/ClassNotFoundException),这里使用的是当前线程的上下文的类加载器就没有问题(Thread.currentThread().getContextClassLoader())

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clz = findLoadedClass(name);
        //先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
        if (clz != null) {
            return clz;
        }
        String[] classNameList = name.split("\\.");
        String classFileName = classNameList[classNameList.length - 1];
        if (classFileName.endsWith("MethodAccess") || !classFileName.endsWith("CoreUtil")) {
            return Thread.currentThread().getContextClassLoader().loadClass(name);
        }
        ClassLoader parent = this.getParent();
        try {
            //委派给父类加载
            clz = parent.loadClass(name);
        } catch (Exception e) {
            //log.warn("parent load class fail:"+ e.getMessage(),e);
        }
        if (clz != null) {
            return clz;
        } else {
            byte[] classData = null;
            ClassPathResource classPathResource = new ClassPathResource("coreclass/" + classFileName + ".class");
            InputStream is = null;
            try {
                is = classPathResource.getInputStream();
                classData = DESEncryptUtil.decryptFromByteV2(FileUtil.convertStreamToByte(is), "xxxxxxx");
            } catch (Exception e) {
                e.printStackTrace();
                throw new ProtectClassLoadException("getClassData error");
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                clz = defineClass(name, classData, 0, classData.length);
            }
            return clz;
        }
    }
}
Copy after login

隐藏classloader

classloader加密class文件处理方案的漏洞在于自定义类加载器是完全暴露的,只需进行分析解密流程就能获取到原始class文件,所以我们需要对classloder的内容进行隐藏

1.把classloader的源文件在编译期间进行删除(maven自定义插件实现)

2.将classloder的内容进行base64编码后拆分内容寻找多个系统启动注入点写入到loader.key文件中(拆分时写入的路径和文件名需要进行base64加密避免全局搜索),例如

    private static void init() {
        String source = "dCA9IG5hbWUuc3BsaXQoIlxcLiIpOwogICAgICAgIFN0cmluZyBjbGFzc0ZpbGVOYW1lID0gY2xhc3NOYW1lTGlzdFtjbGFzc05hbWVMaXN0Lmxlbmd0aCAtIDFdOwogICAgICAgIGlmIChjbGFzc0ZpbGVOYW1lLmVuZHNXaXRoKCJNZXRob2RBY2Nlc3MiKSB8fCAhY2xhc3NGaWxlTmFtZS5lbmRzV2l0aCgiQ29yZVV0aWwiKSkgewogICAgICAgICAgICByZXR1cm4gVGhyZWFkLmN1cnJlbnRUaHJlYWQoKS5nZXRDb250ZXh0Q2xhc3NMb2FkZXIoKS5sb2FkQ2xhc3MobmFtZSk7CiAgICAgICAgfQogICAgICAgIENsYXNzTG9hZGVyIHBhcmVudCA9IHRoaXMuZ2V0UGFyZW50KCk7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgLy/lp5TmtL7nu5nniLbnsbvliqDovb0KICAgICAgICAgICAgY2x6ID0gcGFyZW50LmxvYWRDbGFzcyhuYW1lKTsKICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgewogICAgICAgICAgICAvL2xvZy53YXJuKCJwYXJlbnQgbG9hZCBjbGFzcyBmYWls77yaIisgZS5nZXRNZXNzYWdlKCksZSk7CiAgICAgICAgfQogICAgICAgIGlmIChjbHogIT0gbnVsbCkgewogICAgICAgICAgICByZXR1cm4gY2x6OwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGJ5dGVbXSBjbGFzc0RhdGEgPSBudWxsOwogICAgICAgICAgICBDbGFzc1BhdGhSZXNvdXJjZSBjbGFzc1BhdGhSZXNvdXJjZSA9IG5ldyBDbGFzc1BhdGhSZXNvdXJjZSgiY29yZWNsYXNzLyIgKyBjbGFzc0ZpbGVOYW1lICsgIi5jbGFzcyIpOwogICAgICAgICAgICBJbnB1dFN0cmVhbSBpcyA9IG51bGw7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBpcyA9IGNsYXNzUGF0aFJlc291cmNlLmdldElucHV0U3RyZWFtKCk7CiAgICAgICAgICAgICAgICBjbGFzc0RhdGEgPSBERVNFbmNyeXB0VXRpbC5kZWNyeXB0RnJvbUJ5dGVWMihGaWxlVXRpbC5jb252ZXJ0U3RyZWFtVG9CeXRlKGlzKSwgIlNGQkRiRzkxWkZoaFltTmtNVEl6TkE9PSIpOwogICAgICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgewogICAgICAgICAgICAgICAgZS5wcmludFN0YWNrVHJhY2UoKTsKICAgICAgICAgICAgICAgIHRocm93IG5ldyBQc";
        String filePath = "";
        try{
            filePath = new String(Base64.decodeBase64("dGVtcGZpbGVzL2R5bmFtaWNnZW5zZXJhdGUvbG9hZGVyLmtleQ=="),"utf-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        FileUtil.writeFile(filePath, source,true);
    }
Copy after login

3.通过GroovyClassLoader对classloder的内容(字符串)进行动态编译获取到对象,删除loader.key文件

pom文件增加动态编译依赖

        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.13</version>
        </dependency>
Copy after login

获取文件内容进行编译代码如下(写入/读取注意utf-8处理防止乱码)

public class CustomCompile {
    private static Object Compile(String source){
        Object instance = null;
        try{
            // 编译器
            CompilerConfiguration config = new CompilerConfiguration();
            config.setSourceEncoding("UTF-8");
            // 设置该GroovyClassLoader的父ClassLoader为当前线程的加载器(默认)
            GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);
            Class<?> clazz = groovyClassLoader.parseClass(source);
            // 创建实例
            instance = clazz.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }
    public static  ClassLoader getClassLoader(){
        String filePath = "tempfiles/dynamicgenserate/loader.key";
        String source = FileUtil.readFileContent(filePath);
        byte[] decodeByte = Base64.decodeBase64(source);
        String str = "";
        try{
            str = new String(decodeByte, "utf-8");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            FileUtil.deleteDirectory("tempfiles/dynamicgenserate/");
        }
        return (ClassLoader)Compile(str);
    }
}
Copy after login

被保护class手动加壳

因为相关需要加密的class文件都是通过customerclassloder加载的,获取不到显示的class类型,所以我们实际的业务类只能通过反射的方法进行调用,例如业务工具类LicenseUtil,加密后类为LicenseCoreUtil,我们在LicenseUtil的方法中需要反射调用,LicenseCoreUtil中的方法,例如

@Component
public class LicenseUtil {
    private String coreClassName = "com.haopan.frame.core.util.LicenseCoreUtil";
    public String getMachineCode() throws Exception {
        return (String) CoreLoader.getInstance().executeMethod(coreClassName, "getMachineCode");
    }
    public boolean checkLicense(boolean startCheck) {
        return (boolean)CoreLoader.getInstance().executeMethod(coreClassName, "checkLicense",startCheck);
    }
}
Copy after login

为了避免反射调用随着调用次数的增加损失较多的性能,使用了一个第三方的插件reflectasm,pom增加依赖

        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>reflectasm</artifactId>
            <version>1.11.0</version>
        </dependency>
Copy after login

reflectasm使用了MethodAccess快速定位方法并在字节码层面进行调用,CoreLoader的代码如下

public class CoreLoader {
    private ClassLoader classLoader;
    private CoreLoader() {
        classLoader = CustomCompile.getClassLoader();
    }
    private static class SingleInstace {
        private static final CoreLoader instance = new CoreLoader();
    }
    public static CoreLoader getInstance() {
        return SingleInstace.instance;
    }
    public Object executeMethod(String className,String methodName, Object... args) {
        Object result = null;
        try {
            Class clz = classLoader.loadClass(className);
            MethodAccess access = MethodAccess.get(clz);
            result = access.invoke(clz.newInstance(), methodName, args);
        } catch (Exception e) {
            e.printStackTrace();
            throw  new ProtectClassLoadException("executeMethod error");
        }
        return result;
    }
}
Copy after login

The above is the detailed content of How SpringBoot encrypts and protects class files through custom classloader. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
1 months ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

How Springboot integrates Jasypt to implement configuration file encryption How Springboot integrates Jasypt to implement configuration file encryption Jun 01, 2023 am 08:55 AM

Introduction to Jasypt Jasypt is a java library that allows a developer to add basic encryption functionality to his/her project with minimal effort and does not require a deep understanding of how encryption works. High security for one-way and two-way encryption. , standards-based encryption technology. Encrypt passwords, text, numbers, binaries... Suitable for integration into Spring-based applications, open API, for use with any JCE provider... Add the following dependency: com.github.ulisesbocchiojasypt-spring-boot-starter2. 1.1Jasypt benefits protect our system security. Even if the code is leaked, the data source can be guaranteed.

How to use Redis to implement distributed locks in SpringBoot How to use Redis to implement distributed locks in SpringBoot Jun 03, 2023 am 08:16 AM

1. Redis implements distributed lock principle and why distributed locks are needed. Before talking about distributed locks, it is necessary to explain why distributed locks are needed. The opposite of distributed locks is stand-alone locks. When we write multi-threaded programs, we avoid data problems caused by operating a shared variable at the same time. We usually use a lock to mutually exclude the shared variables to ensure the correctness of the shared variables. Its scope of use is in the same process. If there are multiple processes that need to operate a shared resource at the same time, how can they be mutually exclusive? Today's business applications are usually microservice architecture, which also means that one application will deploy multiple processes. If multiple processes need to modify the same row of records in MySQL, in order to avoid dirty data caused by out-of-order operations, distribution needs to be introduced at this time. The style is locked. Want to achieve points

How to solve the problem that springboot cannot access the file after reading it into a jar package How to solve the problem that springboot cannot access the file after reading it into a jar package Jun 03, 2023 pm 04:38 PM

Springboot reads the file, but cannot access the latest development after packaging it into a jar package. There is a situation where springboot cannot read the file after packaging it into a jar package. The reason is that after packaging, the virtual path of the file is invalid and can only be accessed through the stream. Read. The file is under resources publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

How to implement Springboot+Mybatis-plus without using SQL statements to add multiple tables How to implement Springboot+Mybatis-plus without using SQL statements to add multiple tables Jun 02, 2023 am 11:07 AM

When Springboot+Mybatis-plus does not use SQL statements to perform multi-table adding operations, the problems I encountered are decomposed by simulating thinking in the test environment: Create a BrandDTO object with parameters to simulate passing parameters to the background. We all know that it is extremely difficult to perform multi-table operations in Mybatis-plus. If you do not use tools such as Mybatis-plus-join, you can only configure the corresponding Mapper.xml file and configure The smelly and long ResultMap, and then write the corresponding sql statement. Although this method seems cumbersome, it is highly flexible and allows us to

Comparison and difference analysis between SpringBoot and SpringMVC Comparison and difference analysis between SpringBoot and SpringMVC Dec 29, 2023 am 11:02 AM

SpringBoot and SpringMVC are both commonly used frameworks in Java development, but there are some obvious differences between them. This article will explore the features and uses of these two frameworks and compare their differences. First, let's learn about SpringBoot. SpringBoot was developed by the Pivotal team to simplify the creation and deployment of applications based on the Spring framework. It provides a fast, lightweight way to build stand-alone, executable

How SpringBoot customizes Redis to implement cache serialization How SpringBoot customizes Redis to implement cache serialization Jun 03, 2023 am 11:32 AM

1. Customize RedisTemplate1.1, RedisAPI default serialization mechanism. The API-based Redis cache implementation uses the RedisTemplate template for data caching operations. Here, open the RedisTemplate class and view the source code information of the class. publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations, BeanClassLoaderAware{//Declare key, Various serialization methods of value, the initial value is empty @NullableprivateRedisSe

How to get the value in application.yml in springboot How to get the value in application.yml in springboot Jun 03, 2023 pm 06:43 PM

In projects, some configuration information is often needed. This information may have different configurations in the test environment and the production environment, and may need to be modified later based on actual business conditions. We cannot hard-code these configurations in the code. It is best to write them in the configuration file. For example, you can write this information in the application.yml file. So, how to get or use this address in the code? There are 2 methods. Method 1: We can get the value corresponding to the key in the configuration file (application.yml) through the ${key} annotated with @Value. This method is suitable for situations where there are relatively few microservices. Method 2: In actual projects, When business is complicated, logic

SpringBoot+Dubbo+Nacos development practical tutorial SpringBoot+Dubbo+Nacos development practical tutorial Aug 15, 2023 pm 04:49 PM

This article will write a detailed example to talk about the actual development of dubbo+nacos+Spring Boot. This article will not cover too much theoretical knowledge, but will write the simplest example to illustrate how dubbo can be integrated with nacos to quickly build a development environment.

See all articles