Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How does SpringBoot protect class files with custom classloader encryption

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)05/31 Report--

Today, I would like to share with you how SpringBoot protects class files through custom classloader encryption. The content is detailed and the logic is clear. I believe most people still know too much about this knowledge, so share this article for your reference. I hope you can get something after reading this article.

Background

Recently, the key business code of the company framework is encrypted to prevent the engineering code from being easily restored through decompilation tools such as jd-gui. The configuration of the relevant confusion scheme is more complex and there are many problems for the springboot project, so it is not absolutely safe to encrypt the class file and then decrypt and load it through the custom classloder. It just increases the difficulty of decompilation to prevent the gentleman but not the villain. The overall encryption protection flow chart is shown below.

Maven plug-in encryption

Use a custom maven plug-in to encrypt the class file specified after compilation, and copy the encrypted class file to the specified path. Here, save it to resource/coreclass and delete the source class file. The encryption uses simple DES symmetric encryption.

@ Parameter (name = "protectClassNames", defaultValue = "") private List protectClassNames; @ Parameter (name = "noCompileClassNames", defaultValue = "") private List noCompileClassNames; private List 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 filter boolean flag = false If (protectClassNamesinvalid 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); / / path to save encrypted class files 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);}} considerations

1. The encrypted file is also a class file. In order to prevent repeated encryption in recursive lookup, it is necessary to prevent duplicate class name records that have been encrypted.

two。 Compilation usage may occur when deleting the source file, and it can be deleted only after System.gc () is executed.

3. Configuration nodes in the form of a list of custom plug-ins can be mapped using List

The plug-in usage configuration is shown in the figure

Custom classloader

The creation of CustomClassLoader inherits from ClassLoader. The rewrite findClass method only deals with loading encrypted class files, and other class is handled by default loader. It should be noted that the default processing cannot call the super.finclass method. Debugging in idea is fine. If you run as a jar package, you will report that the dependent class in the encrypted class cannot be loaded (ClassNoDefException/ClassNotFoundException). Here, there is no problem using the classloader in the context of the current thread (Thread.currentThread (). GetContextClassLoader ()).

Public class CustomClassLoader extends ClassLoader {@ Override protected Class findClass (String name) throws ClassNotFoundException {Class clz = findLoadedClass (name); / / check to see if this class has been loaded. If it is already loaded, the loaded class is returned directly. If not, the new class is loaded. 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 {/ / delegate the parent class to load 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;}} hide classloader

The loophole of the classloader encryption class file processing scheme is that the custom class loader is completely exposed, and the original class file can be obtained by analyzing and decrypting the process, so we need to hide the contents of the classloder.

1. Delete the source files of classloader during compilation (maven custom plug-in implementation)

two。 Split the content of classloder after base64 encoding to find multiple system startup injection points to write to the loader.key file (the path and file name written during the split need to be encrypted by base64 to avoid global search), for example

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. Dynamically compile the content (string) of classloder through GroovyClassLoader to obtain the object and delete the loader.key file

Pom files add dynamic compilation dependencies

Org.codehaus.groovy groovy-all 2.4.13

Get the contents of the file and compile the code as follows (write / read pay attention to utf-8 handling to prevent garbled code)

Public class CustomCompile {private static Object Compile (String source) {Object instance = null; try {/ / Compiler CompilerConfiguration config = new CompilerConfiguration (); config.setSourceEncoding ("UTF-8"); / / set the parent ClassLoader of the GroovyClassLoader as the loader of the current thread (default) GroovyClassLoader groovyClassLoader = new GroovyClassLoader (Thread.currentThread (). GetContextClassLoader (), config) Class clazz = groovyClassLoader.parseClass (source); / / create instance 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);} protected class is manually shelled

Because the relevant class files that need to be encrypted are loaded through customerclassloder, the displayed class type cannot be obtained, so our actual business class can only be called through reflective methods, such as the business tool class LicenseUtil, the encrypted class is LicenseCoreUtil, and we need to reflect the call in the method of LicenseUtil, the method in LicenseCoreUtil, for example

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

In order to avoid the performance loss of reflection calls as the number of calls increases, a third-party plug-in reflectasm,pom is used to increase dependencies.

Com.esotericsoftware reflectasm 1.11.0

Reflectasm uses the MethodAccess quick positioning method and calls it at the bytecode level. The code for CoreLoader is as follows

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 }} above is all the content of the article "how SpringBoot protects class files through custom classloader encryption". Thank you for reading! I believe you will gain a lot after reading this article. The editor will update different knowledge for you every day. If you want to learn more knowledge, please pay attention to the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report