首页 > Java > java教程 > SpringBoot怎么通过自定义classloader加密保护class文件

SpringBoot怎么通过自定义classloader加密保护class文件

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
发布: 2023-05-11 21:07:04
转载
1588 人浏览过

背景

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

SpringBoot怎么通过自定义classloader加密保护class文件

maven插件加密

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

@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);

       }

   }

登录后复制

注意事项

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

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

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

插件使用配置如图所示

SpringBoot怎么通过自定义classloader加密保护class文件

自定义classloader

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

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;

        }

    }

}

登录后复制

隐藏classloader

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

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

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

1

2

3

4

5

6

7

8

9

10

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

}

登录后复制

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

pom文件增加动态编译依赖

1

2

3

4

5

<dependency>

    <groupId>org.codehaus.groovy</groupId>

    <artifactId>groovy-all</artifactId>

    <version>2.4.13</version>

</dependency>

登录后复制

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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

    }

}

登录后复制

被保护class手动加壳

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

1

2

3

4

5

6

7

8

9

10

@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);

    }

}

登录后复制

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

1

2

3

4

5

<dependency>

    <groupId>com.esotericsoftware</groupId>

    <artifactId>reflectasm</artifactId>

    <version>1.11.0</version>

</dependency>

登录后复制

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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;

    }

}

登录后复制

以上是SpringBoot怎么通过自定义classloader加密保护class文件的详细内容。更多信息请关注PHP中文网其他相关文章!

相关标签:
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板