├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── pom.xml └── scuti-core ├── .gitignore ├── configuration.json ├── dictionaries ├── IiIIiiiIIIiIiI.txt ├── glitchy.txt ├── keywords.txt ├── lLLLLllLLlLLlLL.txt └── random.txt ├── pom.xml └── src └── main ├── java └── tk │ └── netindev │ └── scuti │ └── core │ ├── Scuti.java │ ├── configuration │ ├── Configuration.java │ ├── Option.java │ ├── io │ │ ├── Parser.java │ │ └── Writer.java │ └── option │ │ ├── obfuscation │ │ ├── ClassEncrypt.java │ │ ├── ControlFlow.java │ │ ├── HideCode.java │ │ ├── InvokeDynamic.java │ │ ├── MiscellaneousObfuscation.java │ │ ├── NumberObfuscation.java │ │ ├── RenameMembers.java │ │ ├── ResourceEncrypt.java │ │ ├── ShuffleMembers.java │ │ └── StringEncryption.java │ │ └── shrinking │ │ ├── InnerClasses.java │ │ └── UnusedMembers.java │ ├── dictionary │ ├── Dictionary.java │ ├── Types.java │ └── type │ │ ├── Alphabet.java │ │ ├── Custom.java │ │ ├── Number.java │ │ └── Randomized.java │ ├── exception │ └── ClassNotFoundException.java │ ├── rewrite │ ├── CustomWriter.java │ └── Hierarchy.java │ ├── transform │ ├── Transformer.java │ ├── Transformers.java │ ├── obfuscation │ │ ├── ClassEncryptTransformer.java │ │ ├── ControlFlowTransformer.java │ │ ├── HideCodeTransformer.java │ │ ├── InvokeDynamicTransformer.java │ │ ├── MiscellaneousObfuscationTransformer.java │ │ ├── NumberObfuscationTransformer.java │ │ ├── RenameMembersTransformer.java │ │ ├── ResourceEncryptTransformer.java │ │ ├── ShuffleMembersTransformer.java │ │ └── StringEncryptionTransformer.java │ ├── optimization │ │ ├── ConstantTransformer.java │ │ ├── DeadCodeTransformer.java │ │ ├── LoopTransformer.java │ │ ├── NoOperationTransformer.java │ │ ├── PeepholeTransformer.java │ │ └── RedundantTransformer.java │ └── shrinking │ │ ├── InnerClassTransformer.java │ │ └── UnusedMemberTransformer.java │ └── util │ ├── ASMUtil.java │ ├── RandomUtil.java │ ├── StringUtil.java │ └── Util.java └── resources └── simplelogger.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | *.ctxt 4 | target/ 5 | dependency-reduced-pom.xml 6 | .setup/ 7 | bin/ 8 | .classpath 9 | .project 10 | *.iml 11 | .idea/ 12 | .settings/ 13 | out/ 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: true 3 | dist: trusty 4 | jdk: 5 | - oraclejdk8 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scuti [![Build Status](https://travis-ci.org/netindev/scuti.svg?branch=master)](https://travis-ci.org/netindev/scuti) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c2f3c51e05ae4422bb869c141959b12d)](https://www.codacy.com/manual/netindev/scuti?utm_source=github.com&utm_medium=referral&utm_content=netindev/scuti&utm_campaign=Badge_Grade) 2 | 3 | ## Download 4 | * Binary releases: https://github.com/netindev/scuti/releases 5 | * Git tree: https://github.com/netindev/scuti.git 6 | 7 | ## Build 8 | * Install [Maven](https://maven.apache.org/download.html) 9 | * Go to: `..\scuti` and execute `mvn clean install` 10 | 11 | ## How to use: 12 | 13 | ```java -jar scuti.jar configuration.json``` 14 | 15 | ## Options: 16 | 17 | ### Obfuscation: 18 | 19 | | Module | Description | 20 | | --- | --- | 21 | | Class Encrypt | Encrypt all classes, creates a custom class loader and load all them in memory | 22 | | Control Flow | Creates randoms conditionals and put them inside the method | 23 | | Hide Code | Hide all classes and members | 24 | | Invoke Dynamic | Replace invokestatic and invokevirtual with dynamics | 25 | | Miscellaneous Obfuscation | Miscellaneous obfuscation, varargs, local variable renaming, etc | 26 | | Number Obfuscation | Split numbers into operations | 27 | | Rename Members | Rename classes, methods and fields | 28 | | Shuffle Members | Shuffles all class members | 29 | | String Encryption | Encrypt strings (that's obvious lol) | 30 | 31 | ### Optimization: 32 | 33 | | Module | Description | 34 | | --- | --- | 35 | | Dead Code | Remove unused code | 36 | | No Operation | Clean all no operations | 37 | 38 | ### Shrinking: 39 | 40 | | Module | Description | 41 | | --- | --- | 42 | | Inner Class | Remove inner classes | 43 | | Unused Member | Remove all unused classes, methods and fields | 44 | 45 | ## Contribute 46 | 47 | Any contribution is welcome, just create a pr. 48 | 49 | ## Contacts 50 | * [email](mailto:contact@netindev.tk) 51 | * [twitter](https://twitter.com/netindev) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | tk.netindev.scuti 4 | scuti 5 | 0.0.1-SNAPSHOT 6 | pom 7 | 8 | scuti-core 9 | 10 | 11 | -------------------------------------------------------------------------------- /scuti-core/.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | *.ctxt 4 | target/ 5 | dependency-reduced-pom.xml 6 | .setup/ 7 | bin/ 8 | .classpath 9 | .project 10 | *.iml 11 | .idea/ 12 | .settings/ 13 | out/ 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar -------------------------------------------------------------------------------- /scuti-core/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": "input.jar", 3 | "output": "output.jar", 4 | 5 | "libraries": [ 6 | "path/to/rt.jar" 7 | ], 8 | 9 | "corrupt_output_stream": true, 10 | "corrupt_class_names": false, 11 | 12 | "obfuscation": { 13 | "class_encrypt": { 14 | "enable_transformer": false, 15 | 16 | "loader_name": "ScutiTemplate", 17 | "main_class": "tk/netindev/scuti/core/Scuti", 18 | 19 | "string_key": 315, 20 | "class_key": 945 21 | }, 22 | 23 | "control_flow": { 24 | "enable_transformer": true, 25 | 26 | "heavy_try_catch": true, 27 | "goto_flooding": true 28 | }, 29 | 30 | "hide_code": { 31 | "enable_transformer": true 32 | }, 33 | 34 | "invoke_dynamic": { 35 | "enable_transformer": true 36 | }, 37 | 38 | "miscellaneous_obfuscation": { 39 | "enable_transformer": true, 40 | 41 | "invalid_annotation": true, 42 | 43 | "massive_source": true, 44 | "massive_signature": true, 45 | 46 | "push_transient": true, 47 | "push_varargs": true, 48 | 49 | "duplicate_variables": true, 50 | 51 | "variable_descriptor": true, 52 | 53 | "variable_descriptor_list": [ 54 | "Z", "C", "B", "S", "I", "F", "J", "D", 55 | "Ljava/lang/Integer;", 56 | "Ljava/lang/String;" 57 | ], 58 | 59 | "random_exceptions": true 60 | }, 61 | 62 | "number_obfuscation": { 63 | "enable_transformer": true, 64 | 65 | "execute_twice": true 66 | }, 67 | 68 | "rename_members": { 69 | "enable_transformer": true, 70 | 71 | "remove_packages": true, 72 | 73 | "rename_classes": true, 74 | "rename_methods": true, 75 | "rename_fields": true, 76 | 77 | "internal_dictionary": "alphabet", 78 | 79 | "keep_classes": [ 80 | "tk/netindev/scuti/core/Scuti" 81 | ], 82 | "keep_methods": [ 83 | "z/y/x/Class/methodName" 84 | ], 85 | "keep_fields": [ 86 | "z/y/x/Class/fieldName" 87 | ], 88 | 89 | "packages_dictionary": "dictionaries/glitchy.txt", 90 | "classes_dictionary": "dictionaries/glitchy.txt", 91 | "methods_dictionary": "dictionaries/glitchy.txt", 92 | "fields_dictionary": "dictionaries/glitchy.txt" 93 | }, 94 | 95 | "shuffle_members": { 96 | "enable_transformer": true 97 | }, 98 | 99 | "string_encryption": { 100 | "enable_transformer": true, 101 | 102 | "type": "strong" 103 | } 104 | }, 105 | 106 | "optimization": { 107 | "dead_code": { 108 | "enable_transformer": true 109 | }, 110 | 111 | "no_operation": { 112 | "enable_transformer": true 113 | }, 114 | 115 | "loop": { 116 | "enable_transformer": false 117 | }, 118 | 119 | "peephole": { 120 | "enable_transformer": false 121 | }, 122 | 123 | "redudant": { 124 | "enable_transformer": false 125 | } 126 | }, 127 | 128 | "shrinking": { 129 | "unused_members": { 130 | "enable_transformer": true, 131 | 132 | "remove_classes": true, 133 | "remove_methods": true, 134 | "remove_fields": true, 135 | 136 | "keep_classes": [ 137 | "z/y/x/Class" 138 | ] 139 | }, 140 | 141 | "inner_class": { 142 | "enable_transformer": true 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /scuti-core/dictionaries/keywords.txt: -------------------------------------------------------------------------------- 1 | do 2 | if 3 | for 4 | int 5 | new 6 | try 7 | byte 8 | case 9 | char 10 | else 11 | goto 12 | long 13 | this 14 | void 15 | break 16 | catch 17 | class 18 | const 19 | final 20 | float 21 | short 22 | super 23 | throw 24 | while 25 | double 26 | import 27 | native 28 | public 29 | return 30 | static 31 | switch 32 | throws 33 | boolean 34 | default 35 | extends 36 | finally 37 | package 38 | private 39 | abstract 40 | continue 41 | strictfp 42 | volatile 43 | interface 44 | protected 45 | transient 46 | implements 47 | instanceof 48 | synchronized -------------------------------------------------------------------------------- /scuti-core/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 1.8 7 | 1.8 8 | 9 | 10 | tk.netindev.scuti 11 | scuti 12 | 0.0.1-SNAPSHOT 13 | 14 | 15 | 16 | org.ow2.asm 17 | asm 18 | 7.1 19 | 20 | 21 | org.ow2.asm 22 | asm-tree 23 | 7.1 24 | 25 | 26 | org.ow2.asm 27 | asm-commons 28 | 7.1 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | 1.7.5 34 | 35 | 36 | org.slf4j 37 | slf4j-simple 38 | 1.7.21 39 | 40 | 41 | com.eclipsesource.minimal-json 42 | minimal-json 43 | 0.9.2 44 | 45 | 46 | scuti-core 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-jar-plugin 52 | 53 | 54 | 55 | true 56 | tk.netindev.scuti.core.Scuti 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-shade-plugin 64 | 3.2.0 65 | 66 | 67 | package 68 | 69 | shade 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/Scuti.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.InputStream; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | import java.util.jar.Attributes; 15 | import java.util.jar.Attributes.Name; 16 | import java.util.jar.JarEntry; 17 | import java.util.jar.JarFile; 18 | import java.util.jar.JarOutputStream; 19 | import java.util.jar.Manifest; 20 | import java.util.stream.Stream; 21 | import java.util.zip.ZipEntry; 22 | import java.util.zip.ZipOutputStream; 23 | 24 | import org.objectweb.asm.ClassReader; 25 | import org.objectweb.asm.ClassWriter; 26 | import org.objectweb.asm.tree.ClassNode; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import tk.netindev.scuti.core.configuration.Configuration; 31 | import tk.netindev.scuti.core.configuration.io.Parser; 32 | import tk.netindev.scuti.core.exception.ClassNotFoundException; 33 | import tk.netindev.scuti.core.rewrite.CustomWriter; 34 | import tk.netindev.scuti.core.rewrite.Hierarchy; 35 | import tk.netindev.scuti.core.transform.Transformer; 36 | import tk.netindev.scuti.core.transform.Transformers.Obfuscation; 37 | import tk.netindev.scuti.core.transform.Transformers.Optimization; 38 | import tk.netindev.scuti.core.transform.Transformers.Shrinking; 39 | import tk.netindev.scuti.core.util.StringUtil; 40 | import tk.netindev.scuti.core.util.Util; 41 | 42 | /** 43 | * 44 | * @author netindev 45 | * 46 | */ 47 | public class Scuti { 48 | 49 | private static final Logger LOGGER = LoggerFactory.getLogger(Scuti.class.getClass()); 50 | private static final double PACKAGE_VERSION = 1.4D; 51 | 52 | private ZipOutputStream outputStream; 53 | 54 | private final long START_TIME; 55 | 56 | private final ExecutorService executorService = Executors 57 | .newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 58 | 59 | private final Configuration configuration; 60 | 61 | final Map classes, dependencies; 62 | final List> ordered; 63 | 64 | Hierarchy hierarchy; 65 | 66 | public static void main(final String[] args) { 67 | System.out.println("Scuti java obfuscator, written by netindev, V: " + Scuti.PACKAGE_VERSION); 68 | if (args == null || args.length == 0) { 69 | System.err.println( 70 | "You need to inform your configuration file in options section.\nCorrect usage: \"java -jar scuti.jar configuration.json\"."); 71 | } else { 72 | final File file = new File(args[0]); 73 | if (file == null || !file.isFile() || !file.canRead()) { 74 | System.err.println("Your configuration file \"" + file.getPath() + "\" isn't accessible."); 75 | } 76 | LOGGER.info("Initialize.."); 77 | try { 78 | LOGGER.info(" Parsing configuration \"" + file.getPath() + "\""); 79 | new Scuti(new Parser(file)); 80 | } catch (final Exception e) { 81 | LOGGER.error("Initialization exception occurred, message: " + e.getMessage()); 82 | } 83 | } 84 | } 85 | 86 | public Scuti(final Configuration configuration) throws Exception { 87 | this.START_TIME = System.nanoTime(); 88 | this.configuration = Objects.requireNonNull(configuration); 89 | this.init(); 90 | } 91 | 92 | private void init() throws Exception { 93 | final File input = this.configuration.getInput(); 94 | if (!input.canRead() || !input.isFile()) { 95 | throw new Exception("Your input file \"" + input.getName() + "\" isn't accessible."); 96 | } 97 | for (final File file : this.configuration.getDependencies()) { 98 | if (!file.exists() || !file.canRead()) { 99 | throw new Exception("The classpath: \"" + file.getAbsolutePath() + "\" isn't accessible"); 100 | } 101 | } 102 | if (this.configuration.getTransformers().isEmpty()) { 103 | throw new Exception("No transformer available"); 104 | } else { 105 | this.order(); 106 | } 107 | final JarFile jarFile = new JarFile(input); 108 | if (jarFile.getManifest() == null) { 109 | this.outputStream = new JarOutputStream(new FileOutputStream(this.configuration.getOutput()), null); 110 | } else { 111 | final Manifest manifest = jarFile.getManifest(); 112 | final Attributes attributes = manifest.getMainAttributes(); 113 | if (this.configuration.getTransformers().contains(Obfuscation.CLASS_ENCRYPT_TRANSFORMER)) { 114 | attributes.put(Name.MAIN_CLASS, this.configuration.getClassEncrypt().getLoaderName()); 115 | } 116 | this.outputStream = new JarOutputStream(new FileOutputStream(this.configuration.getOutput()), manifest); 117 | } 118 | jarFile.close(); 119 | this.executorService.execute(new Runner()); 120 | } 121 | 122 | private void walk(final File file, final boolean dependency) throws Exception { 123 | final JarFile jarFile = new JarFile(file); 124 | if (dependency) { 125 | jarFile.stream().filter(entries -> entries.getName().endsWith(".class") && !entries.isDirectory()) 126 | .forEach(entry -> { 127 | try (InputStream stream = jarFile.getInputStream(entry)) { 128 | final ClassReader classReader = new ClassReader(stream); 129 | final ClassNode classNode = new ClassNode(); 130 | classReader.accept(classNode, 0x7 /* FRAMES | DEBUG | CODE */); 131 | this.dependencies.put(classNode.name, classNode); 132 | } catch (final Exception e) { 133 | LOGGER.error("Error on entry: " + entry.getName() + " with exception: " + e.getMessage()); 134 | } 135 | }); 136 | jarFile.close(); 137 | } else { 138 | jarFile.stream().filter(entries -> !entries.isDirectory()).forEach(entry -> { 139 | try (InputStream stream = jarFile.getInputStream(entry)) { 140 | if (entry.getName().endsWith(".class") && entry.getSize() != 0x0) { 141 | final ClassReader classReader = new ClassReader(stream); 142 | final ClassNode classNode = new ClassNode(); 143 | classReader.accept(classNode, 0x6 /* FRAMES | DEBUG */); 144 | this.classes.put(classNode.name, classNode); 145 | } else { 146 | if (!entry.getName().contains("META-INF/MANIFEST.MF")) { 147 | this.outputStream.putNextEntry(new ZipEntry(entry.getName())); 148 | this.outputStream.write(Util.toByteArray(stream)); 149 | } 150 | } 151 | } catch (final Exception e) { 152 | LOGGER.error("Error on entry: " + entry.getName() + " with exception: " + e.getMessage()); 153 | } 154 | }); 155 | jarFile.close(); 156 | } 157 | } 158 | 159 | private void dump() throws Exception { 160 | LOGGER.info(" Dumping into output \"" + Scuti.this.configuration.getOutput().getName() + "\""); 161 | if (this.configuration.corruptCRC32()) { 162 | LOGGER.info(" Corrupting \"" + this.configuration.getOutput().getName() + "\""); 163 | Util.corruptCRC32(this.outputStream); 164 | } 165 | this.classes.values().forEach(classNode -> { 166 | ClassWriter classWriter = new CustomWriter(this.hierarchy, ClassWriter.COMPUTE_FRAMES); 167 | try { 168 | classNode.accept(classWriter); 169 | } catch (final Exception e) { 170 | if (e.getMessage() != null) { 171 | if (e instanceof ClassNotFoundException) { 172 | LOGGER.warn(e.getMessage() + "\" could not be found while writing \"" + classNode.name 173 | + "\". Forcing COMPUTE_MAXS"); 174 | } else if (e.getMessage().contains("JSR/RET")) { 175 | LOGGER.warn("JSR/RET found on: \"" + classNode.name + "\". Forcing COMPUTE_MAXS"); 176 | } 177 | classWriter = new CustomWriter(this.hierarchy, ClassWriter.COMPUTE_MAXS); 178 | classNode.accept(classWriter); 179 | } else { 180 | LOGGER.error("Unknown error while accept: " + classNode.name + ", ignoring..."); 181 | } 182 | } 183 | this.write(classNode.name, classWriter.toByteArray()); 184 | }); 185 | LOGGER.info("Closing output stream.."); 186 | this.outputStream.close(); 187 | LOGGER.info("Done in " + String.format("%.2f", (System.nanoTime() - this.START_TIME) / 1e9) 188 | + " seconds, output file size: " + StringUtil.getFileSize(this.configuration.getOutput().length())); 189 | this.executorService.shutdown(); 190 | } 191 | 192 | /** 193 | * 194 | * @author netindev 195 | * 196 | */ 197 | final class Runner extends Thread { 198 | 199 | private final Logger logger = LoggerFactory.getLogger(Runner.class.getClass()); 200 | 201 | @Override 202 | public void run() { 203 | try { 204 | this.logger.info("Walk.."); 205 | Scuti.this.configuration.getDependencies().forEach(file -> { 206 | this.logger.info(" Walking into dependency \"" + file.getName() + "\", size: " 207 | + StringUtil.getFileSize(file.length())); 208 | try { 209 | Scuti.this.walk(file, true); 210 | } catch (final Exception e) { 211 | e.printStackTrace(); 212 | } 213 | }); 214 | this.logger.info(" Walking into input \"" + Scuti.this.configuration.getInput().getName() + "\", size: " 215 | + StringUtil.getFileSize(Scuti.this.configuration.getInput().length())); 216 | try { 217 | Scuti.this.walk(Scuti.this.configuration.getInput(), false); 218 | } catch (final Exception e) { 219 | e.printStackTrace(); 220 | } 221 | this.logger.info("Transform.."); 222 | for (final Class clazz : Scuti.this.ordered) { 223 | clazz.getConstructor(Configuration.class, Map.class, Map.class) 224 | .newInstance(Scuti.this.configuration, Scuti.this.classes, Scuti.this.dependencies) 225 | .transform(); 226 | } 227 | LOGGER.info("Hierarchy.."); 228 | Scuti.this.classes.values().forEach(classNode -> Scuti.this.hierarchy.buildHierarchy(classNode, null)); 229 | this.logger.info("Write.."); 230 | Scuti.this.dump(); 231 | } catch (final Exception e) { 232 | this.logger.error("Unknown error while executing main thread"); 233 | e.printStackTrace(); 234 | if (e.getMessage() != null) { 235 | this.logger.error("Exception message: " + e.getMessage()); 236 | } else { 237 | this.logger.error("Stack trace: ", e); 238 | } 239 | this.logger.error("Exiting..."); 240 | Scuti.this.executorService.shutdown(); 241 | } 242 | } 243 | 244 | } 245 | 246 | void order() { 247 | if (this.configuration.getTransformers().contains(Obfuscation.INVOKE_DYNAMIC_TRANSFORMER) 248 | && this.configuration.getTransformers().contains(Obfuscation.CLASS_ENCRYPT_TRANSFORMER)) { 249 | throw new RuntimeException( 250 | "Encrypt and Invoke Dynamic doesn't work together, please select just ONE of them."); 251 | } 252 | 253 | /* order transformers */ 254 | final Class[] transformers = { Obfuscation.CLASS_ENCRYPT_TRANSFORMER, 255 | 256 | Shrinking.INNER_CLASS_TRANSFORMER, Shrinking.UNUSED_MEMBER_TRANSFORMER, 257 | 258 | Optimization.NO_OPERATION_TRANSFORMER, Optimization.DEAD_CODE_TRANSFORMER, 259 | 260 | Obfuscation.RENAME_MEMBERS_TRANSFORMER, Obfuscation.STRING_ENCRYPTION_TRANSFORMER, 261 | Obfuscation.INVOKE_DYNAMIC_TRANSFORMER, Obfuscation.HIDE_CODE_TRANSFORMER, 262 | Obfuscation.MISCELLANEOUS_OBFUSCATION_TRANSFORMER, Obfuscation.NUMBER_OBFUSCATION_TRANSFORMER, 263 | Obfuscation.CONTROL_FLOW_TRANSFORMER, Obfuscation.SHUFFLE_MEMBERS_TRANSFORMER }; 264 | 265 | Stream.of(transformers).filter(transformer -> this.configuration.getTransformers().contains(transformer)) 266 | .forEach(transformer -> this.ordered.add((Class) transformer)); 267 | } 268 | 269 | void write(final String className, final byte[] bytecode) { 270 | try { 271 | if (this.configuration.getTransformers().contains(Obfuscation.CLASS_ENCRYPT_TRANSFORMER) 272 | && !className.equals(this.configuration.getClassEncrypt().getLoaderName())) { 273 | this.outputStream.putNextEntry( 274 | new JarEntry(Util.xor(className, this.configuration.getClassEncrypt().getStringKey()))); 275 | this.outputStream.write(Util.xor(bytecode, this.configuration.getClassEncrypt().getClassKey())); 276 | } else { 277 | this.outputStream.putNextEntry(new JarEntry(className.concat(".class" + (this.configuration.corruptNames() ? "/" : "")))); 278 | this.outputStream.write(bytecode); 279 | } 280 | } catch (final Exception e) { 281 | LOGGER.error("Error while writing " + className + " with exception: " + e.getMessage()); 282 | } 283 | } 284 | 285 | /* init */ { 286 | this.classes = Collections.synchronizedMap(new HashMap<>()); 287 | this.dependencies = Collections.synchronizedMap(new HashMap<>()); 288 | this.ordered = Collections.synchronizedList(new ArrayList<>()); 289 | 290 | this.hierarchy = new Hierarchy(this.classes, this.dependencies); 291 | } 292 | 293 | } 294 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/Configuration.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import tk.netindev.scuti.core.configuration.option.obfuscation.ClassEncrypt; 8 | import tk.netindev.scuti.core.configuration.option.obfuscation.MiscellaneousObfuscation; 9 | import tk.netindev.scuti.core.configuration.option.obfuscation.NumberObfuscation; 10 | import tk.netindev.scuti.core.configuration.option.obfuscation.RenameMembers; 11 | import tk.netindev.scuti.core.configuration.option.obfuscation.StringEncryption; 12 | import tk.netindev.scuti.core.configuration.option.shrinking.UnusedMembers; 13 | import tk.netindev.scuti.core.transform.Transformer; 14 | 15 | /** 16 | * 17 | * @author netindev 18 | * 19 | */ 20 | public interface Configuration { 21 | 22 | File getInput(); 23 | 24 | File getOutput(); 25 | 26 | List getDependencies(); 27 | 28 | Set> getTransformers(); 29 | 30 | boolean corruptCRC32(); 31 | 32 | boolean corruptNames(); 33 | 34 | ClassEncrypt getClassEncrypt(); 35 | 36 | MiscellaneousObfuscation getMiscellaneousObfuscation(); 37 | 38 | NumberObfuscation getNumberObfuscation(); 39 | 40 | RenameMembers getRenameMembers(); 41 | 42 | StringEncryption getStringEncryption(); 43 | 44 | UnusedMembers getUnusedMembers(); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/Option.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration; 2 | 3 | /** 4 | * 5 | * @author netindev 6 | * 7 | */ 8 | public abstract class Option { 9 | 10 | /* define option classes */ 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/io/Parser.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.io; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.util.ArrayList; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | import com.eclipsesource.json.JsonArray; 12 | import com.eclipsesource.json.JsonObject; 13 | 14 | import tk.netindev.scuti.core.configuration.Configuration; 15 | import tk.netindev.scuti.core.configuration.option.obfuscation.ClassEncrypt; 16 | import tk.netindev.scuti.core.configuration.option.obfuscation.MiscellaneousObfuscation; 17 | import tk.netindev.scuti.core.configuration.option.obfuscation.NumberObfuscation; 18 | import tk.netindev.scuti.core.configuration.option.obfuscation.RenameMembers; 19 | import tk.netindev.scuti.core.configuration.option.obfuscation.StringEncryption; 20 | import tk.netindev.scuti.core.configuration.option.obfuscation.StringEncryption.EncryptionType; 21 | import tk.netindev.scuti.core.configuration.option.shrinking.UnusedMembers; 22 | import tk.netindev.scuti.core.dictionary.Types; 23 | import tk.netindev.scuti.core.transform.Transformer; 24 | import tk.netindev.scuti.core.transform.Transformers.Obfuscation; 25 | import tk.netindev.scuti.core.transform.Transformers.Optimization; 26 | import tk.netindev.scuti.core.transform.Transformers.Shrinking; 27 | 28 | /** 29 | * 30 | * @author netindev 31 | * 32 | */ 33 | public class Parser implements Configuration { 34 | 35 | File input, output; 36 | Set> transformers; 37 | List dependencies; 38 | 39 | boolean corruptNames, corruptCRC32; 40 | 41 | /* transformers */ 42 | ClassEncrypt classEncrypt; 43 | RenameMembers renameMembers; 44 | MiscellaneousObfuscation miscellaneousObfuscation; 45 | NumberObfuscation numberObfuscation; 46 | StringEncryption stringEncryption; 47 | 48 | UnusedMembers unusedMembers; 49 | 50 | public Parser(final File file) throws Exception { 51 | this.parse(file); 52 | } 53 | 54 | void parse(final File file) throws Exception { 55 | JsonObject.readFrom(new FileReader(file)).forEach(generalMember -> { 56 | JsonObject jsonObject = null; 57 | if (generalMember.getValue().isObject()) { 58 | jsonObject = generalMember.getValue().asObject(); 59 | } 60 | switch (generalMember.getName()) { 61 | case "input": 62 | this.input = new File(generalMember.getValue().asString()); 63 | return; 64 | case "output": 65 | this.output = new File(generalMember.getValue().asString()); 66 | return; 67 | case "libraries": 68 | final JsonArray jsonArray = generalMember.getValue().asArray(); 69 | jsonArray.forEach(value -> { 70 | this.dependencies.add(new File(value.asString())); 71 | }); 72 | return; 73 | case "corrupt_output_stream": 74 | this.corruptCRC32 = generalMember.getValue().asBoolean(); 75 | return; 76 | case "corrupt_class_names": 77 | this.corruptNames = generalMember.getValue().asBoolean(); 78 | return; 79 | case "shrinking": 80 | jsonObject.forEach(shrinkingTable -> { 81 | switch (shrinkingTable.getName()) { 82 | case "unused_members": 83 | shrinkingTable.getValue().asObject().forEach(unusedTable -> { 84 | if (unusedTable.getName().equals("enable_transformer") 85 | && unusedTable.getValue().asBoolean()) { 86 | this.transformers.add(Shrinking.UNUSED_MEMBER_TRANSFORMER); 87 | } else if (unusedTable.getName().equals("remove_classes")) { 88 | this.unusedMembers.setClasses(unusedTable.getValue().asBoolean()); 89 | } else if (unusedTable.getName().equals("remove_methods")) { 90 | this.unusedMembers.setMethods(unusedTable.getValue().asBoolean()); 91 | } else if (unusedTable.getName().equals("remove_fields")) { 92 | this.unusedMembers.setFields(unusedTable.getValue().asBoolean()); 93 | } else if (unusedTable.getName().equals("keep_classes")) { 94 | this.unusedMembers.setKeepClasses(unusedTable.getValue().asArray().values().stream() 95 | .map(value -> value.asString()).collect(Collectors.toList())); 96 | } 97 | }); 98 | break; 99 | case "inner_class": 100 | shrinkingTable.getValue().asObject().forEach(unusedTable -> { 101 | if (unusedTable.getName().equals("enable_transformer") 102 | && unusedTable.getValue().asBoolean()) { 103 | this.transformers.add(Shrinking.INNER_CLASS_TRANSFORMER); 104 | } 105 | }); 106 | break; 107 | } 108 | }); 109 | case "optimization": 110 | jsonObject.forEach(optimizationTable -> { 111 | switch (optimizationTable.getName()) { 112 | case "dead_code": 113 | optimizationTable.getValue().asObject().forEach(nopTable -> { 114 | if (nopTable.getName().equals("enable_transformer") && nopTable.getValue().asBoolean()) { 115 | this.transformers.add(Optimization.DEAD_CODE_TRANSFORMER); 116 | } 117 | }); 118 | case "no_operation": 119 | optimizationTable.getValue().asObject().forEach(nopTable -> { 120 | if (nopTable.getName().equals("enable_transformer") && nopTable.getValue().asBoolean()) { 121 | this.transformers.add(Optimization.NO_OPERATION_TRANSFORMER); 122 | } 123 | }); 124 | } 125 | }); 126 | break; 127 | case "obfuscation": 128 | jsonObject.forEach(obfuscationTable -> { 129 | switch (obfuscationTable.getName()) { 130 | case "class_encrypt": 131 | obfuscationTable.getValue().asObject().forEach(encryptTable -> { 132 | if (encryptTable.getName().equals("enable_transformer") 133 | && encryptTable.getValue().asBoolean()) { 134 | this.transformers.add(Obfuscation.CLASS_ENCRYPT_TRANSFORMER); 135 | } else if (encryptTable.getName().equals("loader_name")) { 136 | this.classEncrypt.setLoaderName(encryptTable.getValue().asString()); 137 | } else if (encryptTable.getName().equals("main_class")) { 138 | this.classEncrypt.setMainClass(encryptTable.getValue().asString()); 139 | } else if (encryptTable.getName().equals("string_key")) { 140 | this.classEncrypt.setStringKey(encryptTable.getValue().asInt()); 141 | } else if (encryptTable.getName().equals("class_key")) { 142 | this.classEncrypt.setClassKey(encryptTable.getValue().asInt()); 143 | } 144 | }); 145 | break; 146 | case "control_flow": 147 | obfuscationTable.getValue().asObject().forEach(flowTable -> { 148 | if (flowTable.getName().equals("enable_transformer") && flowTable.getValue().asBoolean()) { 149 | this.transformers.add(Obfuscation.CONTROL_FLOW_TRANSFORMER); 150 | } 151 | }); 152 | break; 153 | case "hide_code": 154 | obfuscationTable.getValue().asObject().forEach(hideTable -> { 155 | if (hideTable.getName().equals("enable_transformer") && hideTable.getValue().asBoolean()) { 156 | this.transformers.add(Obfuscation.HIDE_CODE_TRANSFORMER); 157 | } 158 | }); 159 | break; 160 | case "invoke_dynamic": 161 | obfuscationTable.getValue().asObject().forEach(indyTable -> { 162 | if (indyTable.getName().equals("enable_transformer") && indyTable.getValue().asBoolean()) { 163 | this.transformers.add(Obfuscation.INVOKE_DYNAMIC_TRANSFORMER); 164 | } 165 | }); 166 | break; 167 | case "miscellaneous": 168 | obfuscationTable.getValue().asObject().forEach(miscellaneousTable -> { 169 | if (miscellaneousTable.getName().equals("enable_transformer") 170 | && miscellaneousTable.getValue().asBoolean()) { 171 | this.transformers.add(Obfuscation.MISCELLANEOUS_OBFUSCATION_TRANSFORMER); 172 | } else if (miscellaneousTable.getName().equals("massive_signature")) { 173 | this.miscellaneousObfuscation 174 | .setMassiveSignature(miscellaneousTable.getValue().asBoolean()); 175 | } else if (miscellaneousTable.getName().equals("massive_source")) { 176 | this.miscellaneousObfuscation 177 | .setMassiveSource(miscellaneousTable.getValue().asBoolean()); 178 | } else if (miscellaneousTable.getName().equals("push_transient")) { 179 | this.miscellaneousObfuscation 180 | .setPushTransient(miscellaneousTable.getValue().asBoolean()); 181 | } else if (miscellaneousTable.getName().equals("push_varargs")) { 182 | this.miscellaneousObfuscation.setPushVarargs(miscellaneousTable.getValue().asBoolean()); 183 | } else if (miscellaneousTable.getName().equals("variable_descriptor")) { 184 | this.miscellaneousObfuscation 185 | .setVariableDescritor(miscellaneousTable.getValue().asBoolean()); 186 | } else if (miscellaneousTable.getName().equals("variable_descriptor_list")) { 187 | this.miscellaneousObfuscation 188 | .setVariableDescriptorList(miscellaneousTable.getValue().asArray().values() 189 | .stream().map(value -> value.asString()).collect(Collectors.toList())); 190 | } else if (miscellaneousTable.getName().equals("invalid_annotation")) { 191 | this.miscellaneousObfuscation 192 | .setInvalidAnnotation(miscellaneousTable.getValue().asBoolean()); 193 | } else if (miscellaneousTable.getName().equals("duplicate_variables")) { 194 | this.miscellaneousObfuscation 195 | .setDuplicateVariables(miscellaneousTable.getValue().asBoolean()); 196 | } else if (miscellaneousTable.getName().equals("random_exceptions")) { 197 | this.miscellaneousObfuscation 198 | .setRandomExceptions(miscellaneousTable.getValue().asBoolean()); 199 | } 200 | }); 201 | break; 202 | case "number_obfuscation": 203 | obfuscationTable.getValue().asObject().forEach(numberTable -> { 204 | if (numberTable.getName().equals("enable_transformer") 205 | && numberTable.getValue().asBoolean()) { 206 | this.transformers.add(Obfuscation.NUMBER_OBFUSCATION_TRANSFORMER); 207 | } else if (numberTable.getName().equals("execute_twice")) { 208 | this.numberObfuscation.setExecuteTwice(numberTable.getValue().asBoolean()); 209 | } 210 | }); 211 | break; 212 | case "rename_members": 213 | obfuscationTable.getValue().asObject().forEach(renamerTable -> { 214 | if (renamerTable.getName().equals("enable_transformer") 215 | && renamerTable.getValue().asBoolean()) { 216 | this.transformers.add(Obfuscation.RENAME_MEMBERS_TRANSFORMER); 217 | } else if (renamerTable.getName().equals("rename_classes")) { 218 | this.renameMembers.setRenameClasses(renamerTable.getValue().asBoolean()); 219 | } else if (renamerTable.getName().equals("rename_methods")) { 220 | this.renameMembers.setRenameMethods(renamerTable.getValue().asBoolean()); 221 | } else if (renamerTable.getName().equals("rename_fields")) { 222 | this.renameMembers.setRenameFields(renamerTable.getValue().asBoolean()); 223 | } else if (renamerTable.getName().equals("remove_packages")) { 224 | this.renameMembers.setRemovePackages(renamerTable.getValue().asBoolean()); 225 | } else if (renamerTable.getName().equals("internal_dictionary")) { 226 | for (final Types types : Types.values()) { 227 | if (renamerTable.getValue().asString().toUpperCase().equals(types.toString())) { 228 | this.renameMembers.setRandomize(types); 229 | } 230 | } 231 | } else if (renamerTable.getName().equals("keep_classes")) { 232 | this.renameMembers.setExcludeClasses(renamerTable.getValue().asArray().values().stream() 233 | .map(value -> value.asString()).collect(Collectors.toList())); 234 | } else if (renamerTable.getName().equals("keep_methods")) { 235 | this.renameMembers.setExcludeMethods(renamerTable.getValue().asArray().values().stream() 236 | .map(value -> value.asString()).collect(Collectors.toList())); 237 | } else if (renamerTable.getName().equals("keep_fields")) { 238 | this.renameMembers.setExcludeFields(renamerTable.getValue().asArray().values().stream() 239 | .map(value -> value.asString()).collect(Collectors.toList())); 240 | } else if (renamerTable.getName().equals("packages_dictionary")) { 241 | this.renameMembers.setPackagesDictionary(new File(renamerTable.getValue().asString())); 242 | } else if (renamerTable.getName().equals("classes_dictionary")) { 243 | this.renameMembers.setClassesDictionary(new File(renamerTable.getValue().asString())); 244 | } else if (renamerTable.getName().equals("methods_dictionary")) { 245 | this.renameMembers.setMethodsDictionary(new File(renamerTable.getValue().asString())); 246 | } else if (renamerTable.getName().equals("fields_dictionary")) { 247 | this.renameMembers.setFieldsDictionary(new File(renamerTable.getValue().asString())); 248 | } 249 | }); 250 | break; 251 | case "shuffle_members": 252 | obfuscationTable.getValue().asObject().forEach(shuffleMemberTable -> { 253 | if (shuffleMemberTable.getName().equals("enable_transformer") 254 | && shuffleMemberTable.getValue().asBoolean()) { 255 | this.transformers.add(Obfuscation.SHUFFLE_MEMBERS_TRANSFORMER); 256 | } 257 | }); 258 | break; 259 | case "string_encryption": 260 | obfuscationTable.getValue().asObject().forEach(stringTable -> { 261 | if (stringTable.getName().equals("enable_transformer") 262 | && stringTable.getValue().asBoolean()) { 263 | this.transformers.add(Obfuscation.STRING_ENCRYPTION_TRANSFORMER); 264 | } else if (stringTable.getName().equals("type")) { 265 | for (final EncryptionType type : EncryptionType.values()) { 266 | if (stringTable.getValue().asString().toUpperCase().equals(type.toString())) { 267 | this.stringEncryption.setEncryptionType(type); 268 | } 269 | } 270 | } 271 | }); 272 | break; 273 | } 274 | 275 | }); 276 | } 277 | }); 278 | } 279 | 280 | @Override 281 | public File getInput() { 282 | return this.input; 283 | } 284 | 285 | @Override 286 | public File getOutput() { 287 | return this.output; 288 | } 289 | 290 | @Override 291 | public List getDependencies() { 292 | return this.dependencies; 293 | } 294 | 295 | @Override 296 | public Set> getTransformers() { 297 | return this.transformers; 298 | } 299 | 300 | @Override 301 | public boolean corruptCRC32() { 302 | return this.corruptCRC32; 303 | } 304 | 305 | @Override 306 | public boolean corruptNames() { 307 | return this.corruptNames; 308 | } 309 | 310 | @Override 311 | public ClassEncrypt getClassEncrypt() { 312 | return this.classEncrypt; 313 | } 314 | 315 | @Override 316 | public RenameMembers getRenameMembers() { 317 | return this.renameMembers; 318 | } 319 | 320 | @Override 321 | public MiscellaneousObfuscation getMiscellaneousObfuscation() { 322 | return this.miscellaneousObfuscation; 323 | } 324 | 325 | @Override 326 | public NumberObfuscation getNumberObfuscation() { 327 | return this.numberObfuscation; 328 | } 329 | 330 | @Override 331 | public StringEncryption getStringEncryption() { 332 | return this.stringEncryption; 333 | } 334 | 335 | @Override 336 | public UnusedMembers getUnusedMembers() { 337 | return this.unusedMembers; 338 | } 339 | 340 | /* init */ { 341 | this.input = null; 342 | this.output = null; 343 | 344 | this.transformers = new HashSet<>(); 345 | this.dependencies = new ArrayList<>(); 346 | 347 | this.classEncrypt = new ClassEncrypt(); 348 | this.miscellaneousObfuscation = new MiscellaneousObfuscation(); 349 | this.numberObfuscation = new NumberObfuscation(); 350 | this.renameMembers = new RenameMembers(); 351 | this.stringEncryption = new StringEncryption(); 352 | 353 | this.unusedMembers = new UnusedMembers(); 354 | } 355 | 356 | } 357 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/io/Writer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.io; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | 6 | import com.eclipsesource.json.JsonArray; 7 | import com.eclipsesource.json.JsonObject; 8 | 9 | import tk.netindev.scuti.core.configuration.Configuration; 10 | import tk.netindev.scuti.core.transform.Transformers.Obfuscation; 11 | 12 | /** 13 | * 14 | * @author netindev 15 | * 16 | */ 17 | public class Writer { 18 | 19 | public Writer(final File file, final Configuration configution) throws Exception { 20 | this.write(file, configution); 21 | } 22 | 23 | void write(final File file, final Configuration configuration) throws Exception { 24 | final JsonObject object = new JsonObject(); 25 | object.add("input", configuration.getInput().getAbsolutePath()); 26 | object.add("output", configuration.getOutput().getAbsolutePath()); 27 | 28 | final JsonArray libraries = new JsonArray(); 29 | configuration.getDependencies().forEach(library -> libraries.add(library.getAbsolutePath())); 30 | object.add("libraries", libraries); 31 | 32 | final JsonObject obfuscation = new JsonObject(); 33 | object.add("obfusacation", obfuscation); 34 | 35 | final JsonObject encrypt = new JsonObject(); 36 | encrypt.add("enable", configuration.getTransformers().contains(Obfuscation.CLASS_ENCRYPT_TRANSFORMER)); 37 | encrypt.add("loader_name", configuration.getClassEncrypt().getLoaderName()); 38 | encrypt.add("main_class", configuration.getClassEncrypt().getMainClass()); 39 | encrypt.add("string_key", configuration.getClassEncrypt().getStringKey()); 40 | encrypt.add("class_key", configuration.getClassEncrypt().getClassKey()); 41 | 42 | obfuscation.add("encrypt", encrypt); 43 | 44 | final JsonObject flow = new JsonObject(); 45 | flow.add("enable", configuration.getTransformers().contains(Obfuscation.CONTROL_FLOW_TRANSFORMER)); 46 | 47 | obfuscation.add("flow", flow); 48 | 49 | final JsonObject hide = new JsonObject(); 50 | hide.add("enable", configuration.getTransformers().contains(Obfuscation.HIDE_CODE_TRANSFORMER)); 51 | 52 | object.add("obfuscation", obfuscation); 53 | 54 | final FileWriter writer = new FileWriter(file); 55 | object.writeTo(writer); 56 | writer.close(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/ClassEncrypt.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | import tk.netindev.scuti.core.util.RandomUtil; 5 | 6 | /** 7 | * 8 | * @author netindev 9 | * 10 | */ 11 | public class ClassEncrypt extends Option { 12 | 13 | private String loaderName, mainClass; 14 | private int stringKey, classKey; 15 | 16 | public String getLoaderName() { 17 | return this.loaderName; 18 | } 19 | 20 | public void setLoaderName(final String loaderName) { 21 | this.loaderName = loaderName; 22 | } 23 | 24 | public String getMainClass() { 25 | return this.mainClass; 26 | } 27 | 28 | public void setMainClass(final String mainClass) { 29 | this.mainClass = mainClass; 30 | } 31 | 32 | public int getStringKey() { 33 | return this.stringKey; 34 | } 35 | 36 | public void setStringKey(final int stringKey) { 37 | this.stringKey = stringKey; 38 | } 39 | 40 | public int getClassKey() { 41 | return this.classKey; 42 | } 43 | 44 | public void setClassKey(final int classKey) { 45 | this.classKey = classKey; 46 | } 47 | 48 | /* default config */ { 49 | this.setLoaderName("ScutiTemplate"); 50 | this.setMainClass("MainClass"); 51 | this.setStringKey(RandomUtil.getRandom(20, 40)); 52 | this.setClassKey(RandomUtil.getRandom(20, 40)); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/ControlFlow.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class ControlFlow extends Option { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/HideCode.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class HideCode extends Option { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/InvokeDynamic.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class InvokeDynamic extends Option { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/MiscellaneousObfuscation.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import tk.netindev.scuti.core.configuration.Option; 7 | 8 | /** 9 | * 10 | * @author netindev 11 | * 12 | */ 13 | public class MiscellaneousObfuscation extends Option { 14 | 15 | private List variableDescriptorList; 16 | 17 | private boolean invalidAnnotation, massiveSource, massiveSignature, pushTransient, pushVarargs, variableDescritor, 18 | duplicateVariables, randomExceptions; 19 | 20 | public List getVariableDescriptorList() { 21 | return this.variableDescriptorList; 22 | } 23 | 24 | public void setVariableDescriptorList(final List variableDescriptorList) { 25 | this.variableDescriptorList = variableDescriptorList; 26 | } 27 | 28 | public boolean isInvalidAnnotation() { 29 | return this.invalidAnnotation; 30 | } 31 | 32 | public void setInvalidAnnotation(final boolean invalidAnnotation) { 33 | this.invalidAnnotation = invalidAnnotation; 34 | } 35 | 36 | public boolean isMassiveSource() { 37 | return this.massiveSource; 38 | } 39 | 40 | public void setMassiveSource(final boolean massiveSource) { 41 | this.massiveSource = massiveSource; 42 | } 43 | 44 | public boolean isMassiveSignature() { 45 | return this.massiveSignature; 46 | } 47 | 48 | public void setMassiveSignature(final boolean massiveSignature) { 49 | this.massiveSignature = massiveSignature; 50 | } 51 | 52 | public boolean isPushTransient() { 53 | return this.pushTransient; 54 | } 55 | 56 | public void setPushTransient(final boolean pushTransient) { 57 | this.pushTransient = pushTransient; 58 | } 59 | 60 | public boolean isPushVarargs() { 61 | return this.pushVarargs; 62 | } 63 | 64 | public void setPushVarargs(final boolean pushVarargs) { 65 | this.pushVarargs = pushVarargs; 66 | } 67 | 68 | public boolean isVariableDescritor() { 69 | return this.variableDescritor; 70 | } 71 | 72 | public void setVariableDescritor(final boolean variableDescritor) { 73 | this.variableDescritor = variableDescritor; 74 | } 75 | 76 | public boolean isDuplicateVariables() { 77 | return this.duplicateVariables; 78 | } 79 | 80 | public void setDuplicateVariables(final boolean duplicateVariables) { 81 | this.duplicateVariables = duplicateVariables; 82 | } 83 | 84 | public boolean isRandomExceptions() { 85 | return this.randomExceptions; 86 | } 87 | 88 | public void setRandomExceptions(final boolean randomExceptions) { 89 | this.randomExceptions = randomExceptions; 90 | } 91 | 92 | /* default config */ { 93 | this.setVariableDescriptorList(new ArrayList<>()); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/NumberObfuscation.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class NumberObfuscation extends Option { 11 | 12 | private boolean executeTwice; 13 | 14 | public boolean isExecuteTwice() { 15 | return this.executeTwice; 16 | } 17 | 18 | public void setExecuteTwice(final boolean executeTwice) { 19 | this.executeTwice = executeTwice; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/RenameMembers.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import tk.netindev.scuti.core.configuration.Option; 8 | import tk.netindev.scuti.core.dictionary.Types; 9 | 10 | /** 11 | * 12 | * @author netindev 13 | * 14 | */ 15 | public class RenameMembers extends Option { 16 | 17 | private List excludeClasses, excludeMethods, excludeFields; 18 | private File packagesDictionary, classesDictionary, methodsDictionary, fieldsDictionary; 19 | private boolean renameClasses, renameMethods, renameFields, removePackages; 20 | private Types randomize; 21 | 22 | public List getExcludeClasses() { 23 | return this.excludeClasses; 24 | } 25 | 26 | public void setExcludeClasses(final List excludeClasses) { 27 | this.excludeClasses = excludeClasses; 28 | } 29 | 30 | public List getExcludeMethods() { 31 | return this.excludeMethods; 32 | } 33 | 34 | public void setExcludeMethods(final List excludeMethods) { 35 | this.excludeMethods = excludeMethods; 36 | } 37 | 38 | public List getExcludeFields() { 39 | return this.excludeFields; 40 | } 41 | 42 | public void setExcludeFields(final List excludeFields) { 43 | this.excludeFields = excludeFields; 44 | } 45 | 46 | public File getPackagesDictionary() { 47 | return this.packagesDictionary; 48 | } 49 | 50 | public void setPackagesDictionary(final File packagesDictionary) { 51 | this.packagesDictionary = packagesDictionary; 52 | } 53 | 54 | public File getClassesDictionary() { 55 | return this.classesDictionary; 56 | } 57 | 58 | public void setClassesDictionary(final File classesDictionary) { 59 | this.classesDictionary = classesDictionary; 60 | } 61 | 62 | public File getMethodsDictionary() { 63 | return this.methodsDictionary; 64 | } 65 | 66 | public void setMethodsDictionary(final File methodsDictionary) { 67 | this.methodsDictionary = methodsDictionary; 68 | } 69 | 70 | public File getFieldsDictionary() { 71 | return this.fieldsDictionary; 72 | } 73 | 74 | public void setFieldsDictionary(final File fieldsDictionary) { 75 | this.fieldsDictionary = fieldsDictionary; 76 | } 77 | 78 | public boolean isRenameClasses() { 79 | return this.renameClasses; 80 | } 81 | 82 | public void setRenameClasses(final boolean renameClasses) { 83 | this.renameClasses = renameClasses; 84 | } 85 | 86 | public boolean isRenameMethods() { 87 | return this.renameMethods; 88 | } 89 | 90 | public void setRenameMethods(final boolean renameMethods) { 91 | this.renameMethods = renameMethods; 92 | } 93 | 94 | public boolean isRenameFields() { 95 | return this.renameFields; 96 | } 97 | 98 | public void setRenameFields(final boolean renameFields) { 99 | this.renameFields = renameFields; 100 | } 101 | 102 | public boolean isRemovePackages() { 103 | return this.removePackages; 104 | } 105 | 106 | public void setRemovePackages(final boolean removePackages) { 107 | this.removePackages = removePackages; 108 | } 109 | 110 | public Types getRandomize() { 111 | return this.randomize; 112 | } 113 | 114 | public void setRandomize(final Types randomize) { 115 | this.randomize = randomize; 116 | } 117 | 118 | /* default config */ { 119 | this.setRandomize(Types.ALPHABET); 120 | 121 | this.setExcludeClasses(new ArrayList<>()); 122 | this.setExcludeMethods(new ArrayList<>()); 123 | this.setExcludeFields(new ArrayList<>()); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/ResourceEncrypt.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class ResourceEncrypt extends Option { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/ShuffleMembers.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class ShuffleMembers extends Option { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/obfuscation/StringEncryption.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.obfuscation; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class StringEncryption extends Option { 11 | 12 | private EncryptionType encryptionType; 13 | 14 | public EncryptionType getEncryptionType() { 15 | return this.encryptionType; 16 | } 17 | 18 | public void setEncryptionType(final EncryptionType encryptionType) { 19 | this.encryptionType = encryptionType; 20 | } 21 | 22 | public enum EncryptionType { 23 | FAST, STRONG 24 | } 25 | 26 | /* default config */ { 27 | this.setEncryptionType(EncryptionType.FAST); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/shrinking/InnerClasses.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.shrinking; 2 | 3 | import tk.netindev.scuti.core.configuration.Option; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class InnerClasses extends Option { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/configuration/option/shrinking/UnusedMembers.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.configuration.option.shrinking; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import tk.netindev.scuti.core.configuration.Option; 7 | 8 | /** 9 | * 10 | * @author netindev 11 | * 12 | */ 13 | public class UnusedMembers extends Option { 14 | 15 | private List keepClasses; 16 | private boolean classes, methods, fields; 17 | 18 | public List getKeepClasses() { 19 | return this.keepClasses; 20 | } 21 | 22 | public void setKeepClasses(final List keepClasses) { 23 | this.keepClasses = keepClasses; 24 | } 25 | 26 | public boolean isClasses() { 27 | return this.classes; 28 | } 29 | 30 | public void setClasses(final boolean classes) { 31 | this.classes = classes; 32 | } 33 | 34 | public boolean isMethods() { 35 | return this.methods; 36 | } 37 | 38 | public void setMethods(final boolean methods) { 39 | this.methods = methods; 40 | } 41 | 42 | public boolean isFields() { 43 | return this.fields; 44 | } 45 | 46 | public void setFields(final boolean fields) { 47 | this.fields = fields; 48 | } 49 | 50 | /* init */ { 51 | this.setKeepClasses(new ArrayList<>()); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/dictionary/Dictionary.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.dictionary; 2 | 3 | /** 4 | * 5 | * @author netindev 6 | * 7 | */ 8 | public interface Dictionary { 9 | 10 | String next(); 11 | 12 | void reset(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/dictionary/Types.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.dictionary; 2 | 3 | import tk.netindev.scuti.core.configuration.Configuration; 4 | import tk.netindev.scuti.core.dictionary.type.Alphabet; 5 | import tk.netindev.scuti.core.dictionary.type.Custom; 6 | import tk.netindev.scuti.core.dictionary.type.Number; 7 | import tk.netindev.scuti.core.dictionary.type.Randomized; 8 | 9 | /** 10 | * 11 | * @author netindev 12 | * 13 | */ 14 | public enum Types { 15 | 16 | ALPHABET, NUMBER, RANDOMIZED, CUSTOM; 17 | 18 | public static enum CustomDictionary { 19 | CLASSES, FIELDS, METHODS, PACKAGES 20 | } 21 | 22 | public static Dictionary getDictionary(final Configuration configuration, final CustomDictionary customDictionary) { 23 | switch (configuration.getRenameMembers().getRandomize()) { 24 | case ALPHABET: 25 | return new Alphabet(); 26 | case NUMBER: 27 | return new Number(); 28 | case RANDOMIZED: 29 | return new Randomized(); 30 | case CUSTOM: 31 | switch (customDictionary) { 32 | case CLASSES: 33 | return new Custom(configuration.getRenameMembers().getClassesDictionary()); 34 | case FIELDS: 35 | return new Custom(configuration.getRenameMembers().getFieldsDictionary()); 36 | case METHODS: 37 | return new Custom(configuration.getRenameMembers().getMethodsDictionary()); 38 | case PACKAGES: 39 | return new Custom(configuration.getRenameMembers().getPackagesDictionary()); 40 | } 41 | default: 42 | return new Alphabet(); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/dictionary/type/Alphabet.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.dictionary.type; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import tk.netindev.scuti.core.dictionary.Dictionary; 7 | 8 | /** 9 | * 10 | * @author netindev 11 | * 12 | */ 13 | public class Alphabet implements Dictionary { 14 | 15 | private final List cachedMixedCaseNames = new ArrayList<>(); 16 | private int index = 0; 17 | 18 | @Override 19 | public void reset() { 20 | this.index = 0; 21 | } 22 | 23 | @Override 24 | public String next() { 25 | return this.name(this.index++); 26 | } 27 | 28 | private String name(final int index) { 29 | final List cachedNames = this.cachedMixedCaseNames; 30 | if (index < cachedNames.size()) { 31 | return cachedNames.get(index); 32 | } 33 | final String name = this.newName(index); 34 | cachedNames.add(index, name); 35 | return name; 36 | } 37 | 38 | private String newName(final int index) { 39 | final int totalCharacterCount = 2 * 26; 40 | final int baseIndex = index / totalCharacterCount; 41 | final int offset = index % totalCharacterCount; 42 | final char newChar = this.charAt(offset); 43 | return baseIndex == 0 ? new String(new char[] { newChar }) : this.name(baseIndex - 1) + newChar; 44 | } 45 | 46 | private char charAt(final int index) { 47 | return (char) ((index < 26 ? 'a' - 0 : 'A' - 26) + index); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/dictionary/type/Custom.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.dictionary.type; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.InputStreamReader; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import tk.netindev.scuti.core.dictionary.Dictionary; 11 | 12 | /** 13 | * 14 | * @author netindev 15 | * 16 | */ 17 | public class Custom implements Dictionary { 18 | 19 | private final List lines; 20 | private final Alphabet alphabet; 21 | 22 | private final String[] types = { "[", "]", ";", ".", "(", ")" }; 23 | 24 | private int count; 25 | 26 | public Custom(final File file) { 27 | try { 28 | final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); 29 | String string; 30 | while ((string = reader.readLine()) != null) { 31 | if (!string.isEmpty() && this.isTypeSafe(string) && string.length() < Short.MAX_VALUE) { 32 | this.lines.add(string); 33 | } 34 | } 35 | reader.close(); 36 | } catch (final Exception e) { 37 | throw new RuntimeException("Error while trying to read: \"" + file + "\" file"); 38 | } 39 | } 40 | 41 | @Override 42 | public String next() { 43 | return this.lines.size() != this.count ? this.lines.get(this.count++) : this.alphabet.next(); 44 | } 45 | 46 | @Override 47 | public void reset() { 48 | this.count = 0; 49 | this.alphabet.reset(); 50 | } 51 | 52 | boolean isTypeSafe(final String string) { 53 | for (final String type : this.types) { 54 | if (string.contains(type)) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | /* init */ { 62 | this.lines = new ArrayList<>(); 63 | this.alphabet = new Alphabet(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/dictionary/type/Number.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.dictionary.type; 2 | 3 | import tk.netindev.scuti.core.dictionary.Dictionary; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class Number implements Dictionary { 11 | 12 | private int count; 13 | 14 | @Override 15 | public String next() { 16 | return String.valueOf(this.count++); 17 | } 18 | 19 | @Override 20 | public void reset() { 21 | this.count = 0; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/dictionary/type/Randomized.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.dictionary.type; 2 | 3 | import java.util.HashSet; 4 | import java.util.Random; 5 | import java.util.Set; 6 | 7 | import tk.netindev.scuti.core.dictionary.Dictionary; 8 | 9 | /** 10 | * 11 | * @author netindev 12 | * 13 | */ 14 | public class Randomized implements Dictionary { 15 | 16 | private final char[] character = { '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', 17 | '\u2008', '\u2009', '\u200A', '\u200B', '\u200C', '\u200D', '\u200E', '\u200F' }; 18 | 19 | private final Set set; 20 | private final Alphabet alphabet; 21 | 22 | @Override 23 | public String next() { 24 | final Random random = new Random(); 25 | final StringBuilder builder = new StringBuilder(5); 26 | for (int i = 0; i < 6; i++) { 27 | builder.append(this.character[random.nextInt(this.character.length)]); 28 | } 29 | if (this.set.contains(builder.toString())) { 30 | return this.alphabet.next(); 31 | } 32 | this.set.add(builder.toString()); 33 | return builder.toString(); 34 | } 35 | 36 | @Override 37 | public void reset() { 38 | /* no sense */ 39 | } 40 | 41 | /* init */ { 42 | this.set = new HashSet<>(); 43 | this.alphabet = new Alphabet(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/exception/ClassNotFoundException.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.exception; 2 | 3 | /** 4 | * 5 | * @author netindev 6 | * 7 | */ 8 | public class ClassNotFoundException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | public ClassNotFoundException(final String message) { 13 | super(message); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/rewrite/CustomWriter.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.rewrite; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.ArrayDeque; 5 | import java.util.Deque; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | import org.objectweb.asm.ClassWriter; 10 | import org.objectweb.asm.tree.ClassNode; 11 | 12 | import tk.netindev.scuti.core.exception.ClassNotFoundException; 13 | import tk.netindev.scuti.core.rewrite.Hierarchy.Tree; 14 | 15 | /** 16 | * 17 | * @author netindev 18 | * 19 | */ 20 | public class CustomWriter extends ClassWriter { 21 | 22 | private final Hierarchy hierarchy; 23 | 24 | public CustomWriter(final Hierarchy hierarchy, final int flags) { 25 | super(flags); 26 | this.hierarchy = hierarchy; 27 | } 28 | 29 | @Override 30 | protected String getCommonSuperClass(final String firstType, final String secondType) { 31 | if ("java/lang/Object".equals(firstType) || "java/lang/Object".equals(secondType)) { 32 | return "java/lang/Object"; 33 | } 34 | final String first = this.deriveCommonSuperName(firstType, secondType); 35 | final String second = this.deriveCommonSuperName(secondType, firstType); 36 | if (!"java/lang/Object".equals(first)) { 37 | return first; 38 | } 39 | if (!"java/lang/Object".equals(second)) { 40 | return second; 41 | } 42 | return this.getCommonSuperClass(this.hierarchy.getClassNode(firstType).superName, 43 | this.hierarchy.getClassNode(secondType).superName); 44 | } 45 | 46 | private String deriveCommonSuperName(final String firstType, final String secondType) { 47 | ClassNode first = this.hierarchy.getClassNode(firstType); 48 | final ClassNode second = this.hierarchy.getClassNode(secondType); 49 | if (this.isAssignableFrom(firstType, secondType)) { 50 | return firstType; 51 | } else if (this.isAssignableFrom(secondType, firstType)) { 52 | return secondType; 53 | } else if (Modifier.isInterface(first.access) || Modifier.isInterface(second.access)) { 54 | return "java/lang/Object"; 55 | } else { 56 | String string; 57 | do { 58 | string = first.superName; 59 | first = this.hierarchy.getClassNode(string); 60 | } while (!this.isAssignableFrom(string, secondType)); 61 | return string; 62 | } 63 | } 64 | 65 | public boolean isAssignableFrom(final String firstType, final String secondType) { 66 | if ("java/lang/Object".equals(firstType)) { 67 | return true; 68 | } 69 | if (firstType.equals(secondType)) { 70 | return true; 71 | } 72 | final Tree firstTree = this.hierarchy.getTree(firstType); 73 | if (firstTree == null) { 74 | throw new ClassNotFoundException("Could not find " + firstType + " in the built class hierarchy"); 75 | } 76 | final Set set = new HashSet<>(); 77 | final Deque deque = new ArrayDeque<>(firstTree.getSubClasses()); 78 | while (!deque.isEmpty()) { 79 | final String string = deque.poll(); 80 | if (set.add(string)) { 81 | final Tree tree = this.hierarchy.getTree(string); 82 | deque.addAll(tree.getSubClasses()); 83 | } 84 | } 85 | return set.contains(secondType); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/rewrite/Hierarchy.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.rewrite; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | import org.objectweb.asm.tree.ClassNode; 10 | 11 | import tk.netindev.scuti.core.exception.ClassNotFoundException; 12 | 13 | /** 14 | * 15 | * @author netindev 16 | * 17 | */ 18 | public class Hierarchy { 19 | 20 | private final Map classes, dependencies; 21 | private final Map hierarchy; 22 | 23 | public Hierarchy(final Map classes, final Map dependencies) { 24 | this.classes = Collections.synchronizedMap(classes); 25 | this.dependencies = Collections.synchronizedMap(dependencies); 26 | } 27 | 28 | public ClassNode getClassNode(final String string) { 29 | if (!this.classes.containsKey(string)) { 30 | if (!this.dependencies.containsKey(string)) { 31 | throw new ClassNotFoundException("Missing class \"" + string + "\", please update your dependencies"); 32 | } else { 33 | return this.dependencies.get(string); 34 | } 35 | } else { 36 | return this.classes.get(string); 37 | } 38 | } 39 | 40 | public Tree getTree(final String string) { 41 | if (!this.hierarchy.containsKey(string)) { 42 | final ClassNode classNode = this.getClassNode(string); 43 | this.buildHierarchy(classNode, null); 44 | } 45 | return this.hierarchy.get(string); 46 | } 47 | 48 | public void buildHierarchy(final ClassNode classNode, final ClassNode subNode) { 49 | if (this.hierarchy.get(classNode.name) == null) { 50 | final Tree hierarchy = new Tree(classNode); 51 | if (classNode.superName != null) { 52 | hierarchy.getParentClasses().add(classNode.superName); 53 | this.buildHierarchy(this.getClassNode(classNode.superName), classNode); 54 | } 55 | if (classNode.interfaces != null) { 56 | classNode.interfaces.forEach(interfaces -> { 57 | hierarchy.getParentClasses().add(interfaces); 58 | this.buildHierarchy(this.getClassNode(interfaces), classNode); 59 | }); 60 | } 61 | this.hierarchy.put(classNode.name, hierarchy); 62 | } 63 | if (subNode != null) { 64 | this.hierarchy.get(classNode.name).getSubClasses().add(subNode.name); 65 | } 66 | } 67 | 68 | /** 69 | * 70 | * @author netindev 71 | * 72 | */ 73 | public class Tree { 74 | 75 | private final ClassNode classNode; 76 | 77 | private final Set parentClasses = new HashSet<>(); 78 | private final Set subClasses = new HashSet<>(); 79 | 80 | public Tree(final ClassNode classNode) { 81 | this.classNode = classNode; 82 | } 83 | 84 | public ClassNode getClassNode() { 85 | return this.classNode; 86 | } 87 | 88 | public Set getParentClasses() { 89 | return this.parentClasses; 90 | } 91 | 92 | public Set getSubClasses() { 93 | return this.subClasses; 94 | } 95 | 96 | } 97 | 98 | /* init */ { 99 | this.hierarchy = new HashMap<>(); 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/Transformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | 6 | import org.objectweb.asm.tree.ClassNode; 7 | 8 | import tk.netindev.scuti.core.configuration.Configuration; 9 | 10 | /** 11 | * 12 | * @author netindev 13 | * 14 | */ 15 | public abstract class Transformer { 16 | 17 | private final Configuration configuration; 18 | private final Map classes, dependencies; 19 | 20 | public Transformer(final Configuration configuration, final Map classes, 21 | final Map dependencies) { 22 | this.configuration = configuration; 23 | this.classes = Collections.synchronizedMap(classes); 24 | this.dependencies = Collections.synchronizedMap(dependencies); 25 | } 26 | 27 | protected Configuration getConfiguration() { 28 | return this.configuration; 29 | } 30 | 31 | protected Map getClasses() { 32 | return this.classes; 33 | } 34 | 35 | protected Map getDependencies() { 36 | return this.dependencies; 37 | } 38 | 39 | public abstract void transform(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/Transformers.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform; 2 | 3 | import tk.netindev.scuti.core.transform.obfuscation.ControlFlowTransformer; 4 | import tk.netindev.scuti.core.transform.obfuscation.ClassEncryptTransformer; 5 | import tk.netindev.scuti.core.transform.obfuscation.HideCodeTransformer; 6 | import tk.netindev.scuti.core.transform.obfuscation.InvokeDynamicTransformer; 7 | import tk.netindev.scuti.core.transform.obfuscation.MiscellaneousObfuscationTransformer; 8 | import tk.netindev.scuti.core.transform.obfuscation.NumberObfuscationTransformer; 9 | import tk.netindev.scuti.core.transform.obfuscation.RenameMembersTransformer; 10 | import tk.netindev.scuti.core.transform.obfuscation.ShuffleMembersTransformer; 11 | import tk.netindev.scuti.core.transform.obfuscation.StringEncryptionTransformer; 12 | import tk.netindev.scuti.core.transform.optimization.ConstantTransformer; 13 | import tk.netindev.scuti.core.transform.optimization.DeadCodeTransformer; 14 | import tk.netindev.scuti.core.transform.optimization.LoopTransformer; 15 | import tk.netindev.scuti.core.transform.optimization.NoOperationTransformer; 16 | import tk.netindev.scuti.core.transform.optimization.PeepholeTransformer; 17 | import tk.netindev.scuti.core.transform.optimization.RedundantTransformer; 18 | import tk.netindev.scuti.core.transform.shrinking.InnerClassTransformer; 19 | import tk.netindev.scuti.core.transform.shrinking.UnusedMemberTransformer; 20 | 21 | /** 22 | * 23 | * @author netindev 24 | * 25 | */ 26 | public interface Transformers { 27 | 28 | /** 29 | * 30 | * @author netindev 31 | * 32 | */ 33 | public static final class Obfuscation { 34 | public static final Class CLASS_ENCRYPT_TRANSFORMER = ClassEncryptTransformer.class; 35 | public static final Class CONTROL_FLOW_TRANSFORMER = ControlFlowTransformer.class; 36 | public static final Class HIDE_CODE_TRANSFORMER = HideCodeTransformer.class; 37 | public static final Class INVOKE_DYNAMIC_TRANSFORMER = InvokeDynamicTransformer.class; 38 | public static final Class MISCELLANEOUS_OBFUSCATION_TRANSFORMER = MiscellaneousObfuscationTransformer.class; 39 | public static final Class NUMBER_OBFUSCATION_TRANSFORMER = NumberObfuscationTransformer.class; 40 | public static final Class RENAME_MEMBERS_TRANSFORMER = RenameMembersTransformer.class; 41 | public static final Class SHUFFLE_MEMBERS_TRANSFORMER = ShuffleMembersTransformer.class; 42 | public static final Class STRING_ENCRYPTION_TRANSFORMER = StringEncryptionTransformer.class; 43 | } 44 | 45 | /** 46 | * 47 | * @author netindev 48 | * 49 | */ 50 | public static final class Optimization { 51 | public static final Class CONSTANT_TRANSFORMER = ConstantTransformer.class; 52 | public static final Class DEAD_CODE_TRANSFORMER = DeadCodeTransformer.class; 53 | public static final Class LOOP_TRANSFORMER = LoopTransformer.class; 54 | public static final Class PEEPHOLE_TRANSFORMER = PeepholeTransformer.class; 55 | public static final Class NO_OPERATION_TRANSFORMER = NoOperationTransformer.class; 56 | public static final Class REDUNDANT_TRANSFORMER = RedundantTransformer.class; 57 | } 58 | 59 | /** 60 | * 61 | * @author netindev 62 | * 63 | */ 64 | public static final class Shrinking { 65 | public static final Class INNER_CLASS_TRANSFORMER = InnerClassTransformer.class; 66 | public static final Class UNUSED_MEMBER_TRANSFORMER = UnusedMemberTransformer.class; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/ClassEncryptTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.Opcodes; 7 | import org.objectweb.asm.Type; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.objectweb.asm.tree.MethodNode; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import tk.netindev.scuti.core.configuration.Configuration; 14 | import tk.netindev.scuti.core.transform.Transformer; 15 | import tk.netindev.scuti.core.util.ASMUtil; 16 | import tk.netindev.scuti.core.util.RandomUtil; 17 | 18 | /** 19 | * 20 | * @author netindev 21 | * 22 | */ 23 | public class ClassEncryptTransformer extends Transformer { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(ClassEncryptTransformer.class.getName()); 26 | 27 | private String loaderName; 28 | private int stringDecryptorKey, classDecryptorKey; 29 | 30 | public ClassEncryptTransformer(final Configuration configuration, final Map classes, 31 | final Map dependencies) { 32 | super(configuration, classes, dependencies); 33 | LOGGER.info(" Class Encrypt Transformer ->"); 34 | } 35 | 36 | @Override 37 | public void transform() { 38 | LOGGER.info( 39 | " - Creating decrypter class \"" + this.getConfiguration().getClassEncrypt().getLoaderName() + "\""); 40 | this.loaderName = this.getConfiguration().getClassEncrypt().getLoaderName(); 41 | this.stringDecryptorKey = this.getConfiguration().getClassEncrypt().getStringKey(); 42 | this.classDecryptorKey = this.getConfiguration().getClassEncrypt().getClassKey(); 43 | if (this.stringDecryptorKey >= Short.MAX_VALUE) { 44 | this.stringDecryptorKey = RandomUtil.getRandom(Short.MAX_VALUE - 1); 45 | } 46 | if (this.classDecryptorKey >= Short.MAX_VALUE) { 47 | this.classDecryptorKey = RandomUtil.getRandom(Short.MAX_VALUE - 1); 48 | } 49 | this.getConfiguration().getRenameMembers().getExcludeClasses().add(this.loaderName); 50 | final String mainClass = this.getConfiguration().getClassEncrypt().getMainClass().replaceAll("/", "."); 51 | final ClassNode classNode = new ClassNode(); 52 | classNode.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, this.loaderName, null, 53 | "java/lang/ClassLoader", null); 54 | classNode.methods.add(this.createInit()); 55 | classNode.methods.add(this.createMain(mainClass)); 56 | classNode.methods.add(this.createFileDecryptor()); 57 | classNode.methods.add(this.createLoadClass()); 58 | classNode.methods.add(this.createStringDecryptor()); 59 | classNode.methods.add(this.createToByteArray()); 60 | this.getClasses().put(classNode.name, classNode); 61 | } 62 | 63 | private MethodNode createInit() { 64 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "", "()V", null, null); 65 | methodNode.visitCode(); 66 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 67 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/ClassLoader", "", "()V", false); 68 | methodNode.visitInsn(Opcodes.RETURN); 69 | methodNode.visitMaxs(1, 1); 70 | methodNode.visitEnd(); 71 | return methodNode; 72 | } 73 | 74 | private MethodNode createMain(final String mainClass) { 75 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", 76 | "([Ljava/lang/String;)V", null, new String[] { "java/lang/Throwable" }); 77 | methodNode.visitCode(); 78 | methodNode.visitTypeInsn(Opcodes.NEW, this.loaderName); 79 | methodNode.visitInsn(Opcodes.DUP); 80 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, this.loaderName, "", "()V", false); 81 | methodNode.visitLdcInsn(mainClass); 82 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, this.loaderName, "loadClass", 83 | "(Ljava/lang/String;)Ljava/lang/Class;", false); 84 | methodNode.visitLdcInsn("main"); 85 | methodNode.visitInsn(Opcodes.ICONST_1); 86 | methodNode.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class"); 87 | methodNode.visitInsn(Opcodes.DUP); 88 | methodNode.visitInsn(Opcodes.ICONST_0); 89 | methodNode.visitLdcInsn(Type.getType("[Ljava/lang/String;")); 90 | methodNode.visitInsn(Opcodes.AASTORE); 91 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod", 92 | "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); 93 | methodNode.visitInsn(Opcodes.ACONST_NULL); 94 | methodNode.visitInsn(Opcodes.ICONST_1); 95 | methodNode.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); 96 | methodNode.visitInsn(Opcodes.DUP); 97 | methodNode.visitInsn(Opcodes.ICONST_0); 98 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 99 | methodNode.visitInsn(Opcodes.AASTORE); 100 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", 101 | "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); 102 | methodNode.visitInsn(Opcodes.POP); 103 | methodNode.visitInsn(Opcodes.RETURN); 104 | methodNode.visitMaxs(6, 1); 105 | methodNode.visitEnd(); 106 | return methodNode; 107 | } 108 | 109 | private MethodNode createLoadClass() { 110 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "loadClass", 111 | "(Ljava/lang/String;)Ljava/lang/Class;", "(Ljava/lang/String;)Ljava/lang/Class<*>;", 112 | new String[] { "java/lang/ClassNotFoundException" }); 113 | methodNode.visitCode(); 114 | final Label firstLabel = new Label(); 115 | final Label secondLabel = new Label(); 116 | final Label thirdLabel = new Label(); 117 | methodNode.visitTryCatchBlock(firstLabel, secondLabel, thirdLabel, "java/lang/Exception"); 118 | final Label forthLabel = new Label(); 119 | final Label fifthLabel = new Label(); 120 | methodNode.visitTryCatchBlock(forthLabel, secondLabel, fifthLabel, "java/lang/Exception"); 121 | final Label sixthLabel = new Label(); 122 | methodNode.visitTryCatchBlock(thirdLabel, sixthLabel, fifthLabel, "java/lang/Exception"); 123 | final Label seventhLabel = new Label(); 124 | methodNode.visitTryCatchBlock(seventhLabel, fifthLabel, fifthLabel, "java/lang/Exception"); 125 | methodNode.visitLabel(forthLabel); 126 | methodNode.visitInsn(Opcodes.ACONST_NULL); 127 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 128 | methodNode.visitLabel(firstLabel); 129 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", 130 | "()Ljava/lang/ClassLoader;", false); 131 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 132 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", 133 | "(Ljava/lang/String;)Ljava/lang/Class;", false); 134 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 135 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 136 | final Label eigthLabel = new Label(); 137 | methodNode.visitJumpInsn(Opcodes.IFNULL, eigthLabel); 138 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 139 | methodNode.visitLabel(secondLabel); 140 | methodNode.visitInsn(Opcodes.ARETURN); 141 | methodNode.visitLabel(thirdLabel); 142 | methodNode.visitFrame(Opcodes.F_FULL, 3, 143 | new Object[] { this.loaderName, "java/lang/String", "java/lang/Class" }, 1, 144 | new Object[] { "java/lang/Exception" }); 145 | methodNode.visitVarInsn(Opcodes.ASTORE, 3); 146 | methodNode.visitLabel(eigthLabel); 147 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 148 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 149 | methodNode.visitJumpInsn(Opcodes.IFNONNULL, seventhLabel); 150 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/String"); 151 | methodNode.visitInsn(Opcodes.DUP); 152 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 153 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 154 | methodNode.visitLdcInsn("."); 155 | methodNode.visitLdcInsn("/"); 156 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "replace", 157 | "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;", false); 158 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, this.loaderName, "stringDecryptor", 159 | "(Ljava/lang/String;)Ljava/lang/String;", false); 160 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/String", "", "(Ljava/lang/String;)V", false); 161 | methodNode.visitVarInsn(Opcodes.ASTORE, 3); 162 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 163 | methodNode.visitVarInsn(Opcodes.ALOAD, 3); 164 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, this.loaderName, "getResource", 165 | "(Ljava/lang/String;)Ljava/net/URL;", false); 166 | methodNode.visitVarInsn(Opcodes.ASTORE, 4); 167 | methodNode.visitVarInsn(Opcodes.ALOAD, 4); 168 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/URL", "openStream", "()Ljava/io/InputStream;", 169 | false); 170 | methodNode.visitVarInsn(Opcodes.ASTORE, 5); 171 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 172 | methodNode.visitVarInsn(Opcodes.ALOAD, 5); 173 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, this.loaderName, "toByteArray", "(Ljava/io/InputStream;)[B", 174 | false); 175 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, this.loaderName, "fileDecryptor", "([B)[B", false); 176 | methodNode.visitVarInsn(Opcodes.ASTORE, 6); 177 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 178 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 179 | methodNode.visitVarInsn(Opcodes.ALOAD, 6); 180 | methodNode.visitInsn(Opcodes.ICONST_0); 181 | methodNode.visitVarInsn(Opcodes.ALOAD, 6); 182 | methodNode.visitInsn(Opcodes.ARRAYLENGTH); 183 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, this.loaderName, "defineClass", 184 | "(Ljava/lang/String;[BII)Ljava/lang/Class;", false); 185 | methodNode.visitLabel(sixthLabel); 186 | methodNode.visitInsn(Opcodes.ARETURN); 187 | methodNode.visitLabel(seventhLabel); 188 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 189 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/ClassNotFoundException"); 190 | methodNode.visitInsn(Opcodes.DUP); 191 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 192 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/ClassNotFoundException", "", 193 | "(Ljava/lang/String;)V", false); 194 | methodNode.visitInsn(Opcodes.ATHROW); 195 | methodNode.visitLabel(fifthLabel); 196 | methodNode.visitFrame(Opcodes.F_FULL, 2, new Object[] { this.loaderName, "java/lang/String" }, 1, 197 | new Object[] { "java/lang/Exception" }); 198 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 199 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/ClassNotFoundException"); 200 | methodNode.visitInsn(Opcodes.DUP); 201 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 202 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/ClassNotFoundException", "", 203 | "(Ljava/lang/String;)V", false); 204 | methodNode.visitInsn(Opcodes.ATHROW); 205 | methodNode.visitMaxs(6, 7); 206 | methodNode.visitEnd(); 207 | return methodNode; 208 | } 209 | 210 | private MethodNode createToByteArray() { 211 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, "toByteArray", 212 | "(Ljava/io/InputStream;)[B", null, new String[] { "java/io/IOException" }); 213 | methodNode.visitCode(); 214 | methodNode.visitTypeInsn(Opcodes.NEW, "java/io/ByteArrayOutputStream"); 215 | methodNode.visitInsn(Opcodes.DUP); 216 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/io/ByteArrayOutputStream", "", "()V", false); 217 | methodNode.visitVarInsn(Opcodes.ASTORE, 1); 218 | methodNode.visitLdcInsn(new Integer(65535)); 219 | methodNode.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE); 220 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 221 | final Label firstLabel = new Label(); 222 | methodNode.visitJumpInsn(Opcodes.GOTO, firstLabel); 223 | final Label secondLabel = new Label(); 224 | methodNode.visitLabel(secondLabel); 225 | methodNode.visitFrame(Opcodes.F_APPEND, 3, 226 | new Object[] { "java/io/ByteArrayOutputStream", "[B", Opcodes.INTEGER }, 0, null); 227 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 228 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 229 | methodNode.visitInsn(Opcodes.ICONST_0); 230 | methodNode.visitVarInsn(Opcodes.ILOAD, 3); 231 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/ByteArrayOutputStream", "write", "([BII)V", false); 232 | methodNode.visitLabel(firstLabel); 233 | methodNode.visitFrame(Opcodes.F_CHOP, 1, null, 0, null); 234 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 235 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 236 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/InputStream", "read", "([B)I", false); 237 | methodNode.visitInsn(Opcodes.DUP); 238 | methodNode.visitVarInsn(Opcodes.ISTORE, 3); 239 | methodNode.visitInsn(Opcodes.ICONST_M1); 240 | methodNode.visitJumpInsn(Opcodes.IF_ICMPNE, secondLabel); 241 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 242 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/ByteArrayOutputStream", "flush", "()V", false); 243 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 244 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/ByteArrayOutputStream", "toByteArray", "()[B", 245 | false); 246 | methodNode.visitInsn(Opcodes.ARETURN); 247 | methodNode.visitMaxs(4, 4); 248 | methodNode.visitEnd(); 249 | return methodNode; 250 | } 251 | 252 | private MethodNode createStringDecryptor() { 253 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_PRIVATE, "stringDecryptor", 254 | "(Ljava/lang/String;)Ljava/lang/String;", null, null); 255 | methodNode.visitCode(); 256 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/String"); 257 | methodNode.visitInsn(Opcodes.DUP); 258 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/String", "", "()V", false); 259 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 260 | methodNode.visitInsn(Opcodes.ICONST_0); 261 | methodNode.visitVarInsn(Opcodes.ISTORE, 3); 262 | final Label firstLabel = new Label(); 263 | methodNode.visitJumpInsn(Opcodes.GOTO, firstLabel); 264 | final Label secondLabel = new Label(); 265 | methodNode.visitLabel(secondLabel); 266 | methodNode.visitFrame(Opcodes.F_APPEND, 2, new Object[] { "java/lang/String", Opcodes.INTEGER }, 0, null); 267 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); 268 | methodNode.visitInsn(Opcodes.DUP); 269 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 270 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "valueOf", 271 | "(Ljava/lang/Object;)Ljava/lang/String;", false); 272 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V", 273 | false); 274 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 275 | methodNode.visitVarInsn(Opcodes.ILOAD, 3); 276 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false); 277 | methodNode.visitIntInsn(ASMUtil.getOpcodeInsn(this.stringDecryptorKey), this.stringDecryptorKey); 278 | methodNode.visitInsn(Opcodes.IXOR); 279 | methodNode.visitInsn(Opcodes.I2C); 280 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", 281 | "(C)Ljava/lang/StringBuilder;", false); 282 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", 283 | false); 284 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 285 | methodNode.visitIincInsn(3, 1); 286 | methodNode.visitLabel(firstLabel); 287 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 288 | methodNode.visitVarInsn(Opcodes.ILOAD, 3); 289 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 290 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 291 | methodNode.visitJumpInsn(Opcodes.IF_ICMPLT, secondLabel); 292 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 293 | methodNode.visitInsn(Opcodes.ARETURN); 294 | methodNode.visitMaxs(3, 4); 295 | methodNode.visitEnd(); 296 | return methodNode; 297 | } 298 | 299 | private MethodNode createFileDecryptor() { 300 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_PRIVATE, "fileDecryptor", "([B)[B", null, null); 301 | methodNode.visitCode(); 302 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 303 | methodNode.visitInsn(Opcodes.ARRAYLENGTH); 304 | methodNode.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE); 305 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 306 | methodNode.visitInsn(Opcodes.ICONST_0); 307 | methodNode.visitVarInsn(Opcodes.ISTORE, 3); 308 | final Label firstLabel = new Label(); 309 | methodNode.visitJumpInsn(Opcodes.GOTO, firstLabel); 310 | final Label secondLabel = new Label(); 311 | methodNode.visitLabel(secondLabel); 312 | methodNode.visitFrame(Opcodes.F_APPEND, 2, new Object[] { "[B", Opcodes.INTEGER }, 0, null); 313 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 314 | methodNode.visitVarInsn(Opcodes.ILOAD, 3); 315 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 316 | methodNode.visitVarInsn(Opcodes.ILOAD, 3); 317 | methodNode.visitInsn(Opcodes.BALOAD); 318 | methodNode.visitIntInsn(ASMUtil.getOpcodeInsn(this.classDecryptorKey), this.classDecryptorKey); 319 | methodNode.visitInsn(Opcodes.IXOR); 320 | methodNode.visitInsn(Opcodes.I2B); 321 | methodNode.visitInsn(Opcodes.BASTORE); 322 | methodNode.visitIincInsn(3, 1); 323 | methodNode.visitLabel(firstLabel); 324 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 325 | methodNode.visitVarInsn(Opcodes.ILOAD, 3); 326 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 327 | methodNode.visitInsn(Opcodes.ARRAYLENGTH); 328 | methodNode.visitJumpInsn(Opcodes.IF_ICMPLT, secondLabel); 329 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 330 | methodNode.visitInsn(Opcodes.ARETURN); 331 | methodNode.visitMaxs(4, 4); 332 | methodNode.visitEnd(); 333 | return methodNode; 334 | } 335 | 336 | } 337 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/ControlFlowTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Arrays; 5 | import java.util.Map; 6 | 7 | import org.objectweb.asm.Label; 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.tree.ClassNode; 10 | import org.objectweb.asm.tree.FieldInsnNode; 11 | import org.objectweb.asm.tree.FieldNode; 12 | import org.objectweb.asm.tree.InsnList; 13 | import org.objectweb.asm.tree.InsnNode; 14 | import org.objectweb.asm.tree.JumpInsnNode; 15 | import org.objectweb.asm.tree.LabelNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | import org.objectweb.asm.tree.TryCatchBlockNode; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import tk.netindev.scuti.core.configuration.Configuration; 22 | import tk.netindev.scuti.core.transform.Transformer; 23 | import tk.netindev.scuti.core.util.ASMUtil; 24 | import tk.netindev.scuti.core.util.RandomUtil; 25 | import tk.netindev.scuti.core.util.StringUtil; 26 | import tk.netindev.scuti.core.util.Util; 27 | 28 | /** 29 | * 30 | * @author netindev 31 | * 32 | */ 33 | public class ControlFlowTransformer extends Transformer implements Opcodes { 34 | 35 | private static final Logger LOGGER = LoggerFactory.getLogger(ControlFlowTransformer.class.getName()); 36 | 37 | private final FieldNode negativeField = new FieldNode(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, 38 | StringUtil.getMassiveString().substring((int) (Short.MAX_VALUE / 2.51)), "I", null, null); 39 | private final FieldNode positiveField = new FieldNode(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, 40 | StringUtil.getMassiveString().substring((int) (Short.MAX_VALUE / 2.49)), "I", null, null); 41 | 42 | int[] conditions = { Opcodes.IF_ICMPLT, Opcodes.IF_ICMPLE, Opcodes.IF_ICMPNE }; 43 | 44 | public ControlFlowTransformer(final Configuration configuration, final Map classes, 45 | final Map dependencies) { 46 | super(configuration, classes, dependencies); 47 | } 48 | 49 | @Override 50 | public void transform() { 51 | LOGGER.info(" Executing control flow (experimental)"); 52 | final ClassNode throwableClass = this.createThrowableClass(StringUtil.getMassiveString()); 53 | this.getClasses().values().stream() 54 | .filter(classNode -> !Modifier.isInterface(classNode.access) 55 | && !this.getConfiguration().getClassEncrypt().getLoaderName().equals(classNode.name)) 56 | .forEach(classNode -> { 57 | classNode.methods.stream().filter(methodNode -> !Modifier.isAbstract(methodNode.access) 58 | && !Modifier.isNative(methodNode.access)).forEach(methodNode -> { 59 | Arrays.stream(methodNode.instructions.toArray()).forEach(insnNode -> { 60 | if (insnNode instanceof LabelNode && (insnNode.getOpcode() != Opcodes.RETURN 61 | || insnNode.getOpcode() != Opcodes.ARETURN)) { 62 | methodNode.instructions.insert(insnNode, 63 | this.getRandomConditionList(classNode)); 64 | final LabelNode labelAfter = new LabelNode(); 65 | final LabelNode labelBefore = new LabelNode(); 66 | final LabelNode labelFinal = new LabelNode(); 67 | methodNode.instructions.insertBefore(insnNode, labelBefore); 68 | methodNode.instructions.insert(insnNode, labelAfter); 69 | methodNode.instructions.insert(labelAfter, labelFinal); 70 | methodNode.instructions.insert(labelBefore, 71 | new JumpInsnNode(Opcodes.GOTO, labelAfter)); 72 | methodNode.instructions.insert(labelAfter, 73 | new JumpInsnNode(Opcodes.GOTO, labelFinal)); 74 | } 75 | }); 76 | this.heavyDoubleAthrow(classNode, methodNode, throwableClass); 77 | }); 78 | final MethodNode staticInitializer = ASMUtil.getOrCreateClinit(classNode); 79 | 80 | final InsnList insnList = new InsnList(); 81 | final int splitable = -RandomUtil.getRandom(Short.MAX_VALUE); 82 | insnList.add(ASMUtil.toInsnNode(-splitable ^ 50 + RandomUtil.getRandom(Short.MAX_VALUE))); 83 | insnList.add(ASMUtil.toInsnNode(splitable)); 84 | insnList.add(new InsnNode(Opcodes.IXOR)); 85 | insnList.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, this.negativeField.name, "I")); 86 | insnList.add(ASMUtil.toInsnNode(splitable ^ 50 + RandomUtil.getRandom(Short.MAX_VALUE))); 87 | insnList.add(ASMUtil.toInsnNode(splitable)); 88 | insnList.add(new InsnNode(Opcodes.IXOR)); 89 | insnList.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, this.positiveField.name, "I")); 90 | staticInitializer.instructions.insert(insnList); 91 | classNode.fields.add(this.negativeField); 92 | classNode.fields.add(this.positiveField); 93 | }); 94 | this.getClasses().put(throwableClass.name, throwableClass); 95 | } 96 | 97 | private ClassNode createThrowableClass(final String className) { 98 | final ClassNode classNode = new ClassNode(); 99 | classNode.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Throwable", null); 100 | 101 | final FieldNode serialVersionUID = new FieldNode(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "serialVersionUID", "J", 102 | null, new Long(1L)); 103 | classNode.fields.add(serialVersionUID); 104 | 105 | final MethodNode methodNode = new MethodNode(ACC_PUBLIC, "", "(Ljava/lang/String;)V", null, null); 106 | final Label firstLabel = new Label(), secondLabel = new Label(), thirdLabel = new Label(); 107 | methodNode.visitCode(); 108 | methodNode.visitLabel(firstLabel); 109 | methodNode.visitLineNumber(10, firstLabel); 110 | methodNode.visitVarInsn(ALOAD, 0); 111 | methodNode.visitVarInsn(ALOAD, 1); 112 | methodNode.visitMethodInsn(INVOKESPECIAL, "java/lang/Throwable", "", "(Ljava/lang/String;)V", false); 113 | methodNode.visitLabel(secondLabel); 114 | methodNode.visitLineNumber(11, secondLabel); 115 | methodNode.visitInsn(RETURN); 116 | methodNode.visitLabel(thirdLabel); 117 | methodNode.visitLocalVariable("this", "LMain;", null, firstLabel, thirdLabel, 0); 118 | methodNode.visitLocalVariable("string", "Ljava/lang/String;", null, firstLabel, thirdLabel, 1); 119 | methodNode.visitMaxs(2, 2); 120 | methodNode.visitEnd(); 121 | 122 | classNode.methods.add(methodNode); 123 | return classNode; 124 | } 125 | 126 | private InsnList getRandomConditionList(final ClassNode classNode) { 127 | final InsnList insnList = new InsnList(); 128 | /* 129 | * switch (RandomUtil.getRandom(6)) { default: final LabelNode startLabel = new 130 | * LabelNode(); insnList.add(new FieldInsnNode(GETSTATIC, classNode.name, 131 | * this.negativeField.name, "I")); insnList.add(new FieldInsnNode(GETSTATIC, 132 | * classNode.name, this.positiveField.name, "I")); insnList.add(new 133 | * JumpInsnNode(Opcodes.IF_ICMPLT, startLabel)); insnList.add(new 134 | * InsnNode(Opcodes.ACONST_NULL)); insnList.add(new InsnNode(Opcodes.ATHROW)); 135 | * insnList.add(startLabel); break; } 136 | */ 137 | final LabelNode startLabel = new LabelNode(); 138 | insnList.add(new FieldInsnNode(GETSTATIC, classNode.name, this.negativeField.name, "I")); 139 | insnList.add(new FieldInsnNode(GETSTATIC, classNode.name, this.positiveField.name, "I")); 140 | insnList.add(new JumpInsnNode(Opcodes.IF_ICMPLT, startLabel)); 141 | insnList.add(new InsnNode(Opcodes.ACONST_NULL)); 142 | insnList.add(new InsnNode(Opcodes.ATHROW)); 143 | insnList.add(startLabel); 144 | return insnList; 145 | } 146 | 147 | private void heavyDoubleAthrow(final ClassNode classNode, final MethodNode methodNode, 148 | final ClassNode throwableClass) { 149 | final InsnList insnlist = new InsnList(); 150 | final LabelNode firstLabel = new LabelNode(); 151 | final LabelNode secondLabel = new LabelNode(); 152 | final TryCatchBlockNode firstTryCatch = new TryCatchBlockNode(firstLabel, secondLabel, secondLabel, 153 | throwableClass.name); 154 | final LabelNode thirdLabel = new LabelNode(); 155 | final TryCatchBlockNode secondTryCatch = new TryCatchBlockNode(secondLabel, thirdLabel, firstLabel, 156 | throwableClass.name); 157 | insnlist.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, this.negativeField.name, "I")); 158 | insnlist.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, this.positiveField.name, "I")); 159 | insnlist.add(new JumpInsnNode(this.conditions[RandomUtil.getRandom(this.conditions.length)], thirdLabel)); 160 | insnlist.add(new InsnNode(Opcodes.ACONST_NULL)); 161 | insnlist.add(firstLabel); 162 | insnlist.add(new InsnNode(Opcodes.ATHROW)); 163 | insnlist.add(secondLabel); 164 | insnlist.add(new InsnNode(Opcodes.ATHROW)); 165 | insnlist.add(thirdLabel); 166 | methodNode.instructions.insert(insnlist); 167 | methodNode.tryCatchBlocks.add(firstTryCatch); 168 | methodNode.tryCatchBlocks.add(secondTryCatch); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/HideCodeTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Map; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import tk.netindev.scuti.core.configuration.Configuration; 13 | import tk.netindev.scuti.core.transform.Transformer; 14 | import tk.netindev.scuti.core.util.ASMUtil; 15 | 16 | /** 17 | * 18 | * @author netindev 19 | * 20 | */ 21 | public class HideCodeTransformer extends Transformer { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(HideCodeTransformer.class.getName()); 24 | private final AtomicInteger atomicInteger = new AtomicInteger(); 25 | 26 | public HideCodeTransformer(final Configuration configuration, final Map classes, 27 | final Map dependencies) { 28 | super(configuration, classes, dependencies); 29 | LOGGER.info(" Hide Code Transformer ->"); 30 | } 31 | 32 | @Override 33 | public void transform() { 34 | this.getClasses().values().forEach(classNode -> { 35 | this.pushSynthetic(classNode); 36 | this.pushBridge(classNode); 37 | }); 38 | LOGGER.info(" - Inserted " + this.atomicInteger.get() + " synthetic/bridge"); 39 | } 40 | 41 | private void pushSynthetic(final ClassNode classNode) { 42 | classNode.access |= Opcodes.ACC_SYNTHETIC; 43 | if (classNode.innerClasses.isEmpty()) { 44 | classNode.innerClasses.forEach(innerClass -> { 45 | innerClass.access |= Opcodes.ACC_SYNTHETIC; 46 | this.atomicInteger.incrementAndGet(); 47 | }); 48 | } 49 | classNode.methods.forEach(methodNode -> { 50 | methodNode.access |= Opcodes.ACC_SYNTHETIC; 51 | this.atomicInteger.incrementAndGet(); 52 | }); 53 | classNode.fields.forEach(fieldNode -> { 54 | fieldNode.access |= Opcodes.ACC_SYNTHETIC; 55 | this.atomicInteger.incrementAndGet(); 56 | }); 57 | } 58 | 59 | private void pushBridge(final ClassNode classNode) { 60 | classNode.methods.stream() 61 | .filter(methodNode -> !ASMUtil.isInitializer(methodNode) && !Modifier.isAbstract(methodNode.access)) 62 | .forEach(methodNode -> { 63 | methodNode.access |= Opcodes.ACC_BRIDGE; 64 | this.atomicInteger.incrementAndGet(); 65 | }); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/InvokeDynamicTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.lang.invoke.CallSite; 4 | import java.lang.invoke.MethodHandles; 5 | import java.lang.invoke.MethodType; 6 | import java.lang.reflect.Modifier; 7 | import java.util.Arrays; 8 | import java.util.Map; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | import org.objectweb.asm.Handle; 13 | import org.objectweb.asm.Label; 14 | import org.objectweb.asm.Opcodes; 15 | import org.objectweb.asm.Type; 16 | import org.objectweb.asm.tree.ClassNode; 17 | import org.objectweb.asm.tree.FieldInsnNode; 18 | import org.objectweb.asm.tree.InvokeDynamicInsnNode; 19 | import org.objectweb.asm.tree.MethodInsnNode; 20 | import org.objectweb.asm.tree.MethodNode; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import tk.netindev.scuti.core.configuration.Configuration; 25 | import tk.netindev.scuti.core.transform.Transformer; 26 | import tk.netindev.scuti.core.util.ASMUtil; 27 | import tk.netindev.scuti.core.util.RandomUtil; 28 | import tk.netindev.scuti.core.util.StringUtil; 29 | import tk.netindev.scuti.core.util.Util; 30 | 31 | /** 32 | * 33 | * @author netindev 34 | * 35 | */ 36 | public class InvokeDynamicTransformer extends Transformer { 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(InvokeDynamicTransformer.class); 39 | private final AtomicInteger atomicInteger = new AtomicInteger(); 40 | 41 | public InvokeDynamicTransformer(final Configuration configuration, final Map classes, 42 | final Map dependencies) { 43 | super(configuration, classes, dependencies); 44 | LOGGER.info(" Invoke Dynamic Transformer ->"); 45 | } 46 | 47 | @Override 48 | public void transform() { 49 | this.getClasses().values().stream() 50 | .filter(classNode -> !Modifier.isInterface(classNode.access) 51 | && (classNode.access & Opcodes.ACC_ENUM) == 0 && classNode.version >= Opcodes.V1_7) 52 | .forEach(classNode -> { 53 | final String bootstrapName = StringUtil.getMassiveString(), 54 | decryptName = StringUtil.getMassiveString(); 55 | final int decryptValue = RandomUtil.getRandom(100, 150); 56 | if (this.insertDynamic(classNode, bootstrapName, decryptName, decryptValue)) { 57 | classNode.methods.add(this.createDecrypt(decryptName, decryptValue)); 58 | classNode.methods.add(this.createBootstrap(classNode.name, bootstrapName, decryptName)); 59 | } 60 | }); 61 | LOGGER.info(" - Replaced " + this.atomicInteger.get() + " calls"); 62 | } 63 | 64 | private boolean insertDynamic(final ClassNode classNode, final String bootstrapName, final String decryptName, 65 | final int decryptValue) { 66 | final AtomicBoolean atomicBoolean = new AtomicBoolean(); 67 | classNode.methods.stream().filter(methodNode -> !Modifier.isAbstract(methodNode.access)).forEach(methodNode -> { 68 | Arrays.asList(methodNode.instructions.toArray()).stream() 69 | .filter(insnNode -> (insnNode instanceof MethodInsnNode || insnNode instanceof FieldInsnNode) 70 | && insnNode.getOpcode() != Opcodes.INVOKESPECIAL) 71 | .forEach(insnNode -> { 72 | if (insnNode instanceof MethodInsnNode) { 73 | final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; 74 | final Handle handle = new Handle(Opcodes.H_INVOKESTATIC, classNode.name, bootstrapName, 75 | MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, 76 | MethodType.class, String.class, String.class, String.class, Integer.class) 77 | .toMethodDescriptorString(), 78 | false); 79 | final InvokeDynamicInsnNode invokeDynamicInsnNode = new InvokeDynamicInsnNode( 80 | StringUtil.getMassiveString(), 81 | methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC ? methodInsnNode.desc 82 | : methodInsnNode.desc.replace("(", "(Ljava/lang/Object;"), 83 | handle, this.encrypt(methodInsnNode.owner.replace("/", "."), decryptValue), 84 | this.encrypt(methodInsnNode.name, decryptValue), 85 | this.encrypt(methodInsnNode.desc, decryptValue), 86 | methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC ? 0 : 1); 87 | methodNode.instructions.insert(insnNode, invokeDynamicInsnNode); 88 | methodNode.instructions.remove(insnNode); 89 | atomicBoolean.set(true); 90 | this.atomicInteger.incrementAndGet(); 91 | } 92 | }); 93 | }); 94 | return atomicBoolean.get(); 95 | } 96 | 97 | private MethodNode createBootstrap(final String className, final String methodName, final String decryptName) { 98 | final MethodNode methodNode = new MethodNode( 99 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_BRIDGE, methodName, 100 | MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, 101 | String.class, String.class, String.class, Integer.class).toMethodDescriptorString(), 102 | null, null); 103 | methodNode.visitCode(); 104 | final Label firstLabel = new Label(); 105 | final Label secondLabel = new Label(); 106 | final Label thirthLabel = new Label(); 107 | methodNode.visitTryCatchBlock(firstLabel, secondLabel, thirthLabel, "java/lang/Exception"); 108 | final Label fourthLabel = new Label(); 109 | final Label fifthLabel = new Label(); 110 | methodNode.visitTryCatchBlock(fourthLabel, fifthLabel, thirthLabel, "java/lang/Exception"); 111 | methodNode.visitVarInsn(Opcodes.ALOAD, 3); 112 | methodNode.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String"); 113 | methodNode.visitVarInsn(Opcodes.ASTORE, 7); 114 | methodNode.visitVarInsn(Opcodes.ALOAD, 4); 115 | methodNode.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String"); 116 | methodNode.visitVarInsn(Opcodes.ASTORE, 8); 117 | methodNode.visitVarInsn(Opcodes.ALOAD, 5); 118 | methodNode.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String"); 119 | methodNode.visitVarInsn(Opcodes.ASTORE, 9); 120 | methodNode.visitVarInsn(Opcodes.ALOAD, 6); 121 | methodNode.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); 122 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); 123 | methodNode.visitVarInsn(Opcodes.ISTORE, 10); 124 | methodNode.visitVarInsn(Opcodes.ALOAD, 9); 125 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, className, decryptName, 126 | "(Ljava/lang/String;)Ljava/lang/String;", false); 127 | methodNode.visitLdcInsn(Type.getType("L" + className + ";")); 128 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", 129 | "()Ljava/lang/ClassLoader;", false); 130 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodType", "fromMethodDescriptorString", 131 | "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", false); 132 | methodNode.visitVarInsn(Opcodes.ASTORE, 11); 133 | methodNode.visitLabel(firstLabel); 134 | methodNode.visitVarInsn(Opcodes.ILOAD, 10); 135 | methodNode.visitInsn(Opcodes.ICONST_1); 136 | methodNode.visitJumpInsn(Opcodes.IF_ICMPNE, fourthLabel); 137 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); 138 | methodNode.visitInsn(Opcodes.DUP); 139 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 140 | methodNode.visitVarInsn(Opcodes.ALOAD, 7); 141 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, className, decryptName, 142 | "(Ljava/lang/String;)Ljava/lang/String;", false); 143 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", 144 | "(Ljava/lang/String;)Ljava/lang/Class;", false); 145 | methodNode.visitVarInsn(Opcodes.ALOAD, 8); 146 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, className, decryptName, 147 | "(Ljava/lang/String;)Ljava/lang/String;", false); 148 | methodNode.visitVarInsn(Opcodes.ALOAD, 11); 149 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findVirtual", 150 | "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", 151 | false); 152 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 153 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "asType", 154 | "(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); 155 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", 156 | "(Ljava/lang/invoke/MethodHandle;)V", false); 157 | methodNode.visitLabel(secondLabel); 158 | methodNode.visitInsn(Opcodes.ARETURN); 159 | methodNode.visitLabel(fourthLabel); 160 | methodNode.visitFrame(Opcodes.F_FULL, 12, 161 | new Object[] { "java/lang/invoke/MethodHandles$Lookup", "java/lang/String", 162 | "java/lang/invoke/MethodType", "java/lang/Object", "java/lang/Object", "java/lang/Object", 163 | "java/lang/Object", "java/lang/String", "java/lang/String", "java/lang/String", Opcodes.INTEGER, 164 | "java/lang/invoke/MethodType" }, 165 | 0, new Object[] {}); 166 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); 167 | methodNode.visitInsn(Opcodes.DUP); 168 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 169 | methodNode.visitVarInsn(Opcodes.ALOAD, 7); 170 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, className, decryptName, 171 | "(Ljava/lang/String;)Ljava/lang/String;", false); 172 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", 173 | "(Ljava/lang/String;)Ljava/lang/Class;", false); 174 | methodNode.visitVarInsn(Opcodes.ALOAD, 8); 175 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, className, decryptName, 176 | "(Ljava/lang/String;)Ljava/lang/String;", false); 177 | methodNode.visitVarInsn(Opcodes.ALOAD, 11); 178 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", 179 | "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", 180 | false); 181 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 182 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "asType", 183 | "(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); 184 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", 185 | "(Ljava/lang/invoke/MethodHandle;)V", false); 186 | methodNode.visitLabel(fifthLabel); 187 | methodNode.visitInsn(Opcodes.ARETURN); 188 | methodNode.visitLabel(thirthLabel); 189 | methodNode.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Exception" }); 190 | methodNode.visitVarInsn(Opcodes.ASTORE, 12); 191 | methodNode.visitInsn(Opcodes.ACONST_NULL); 192 | methodNode.visitInsn(Opcodes.ARETURN); 193 | methodNode.visitMaxs(6, 13); 194 | methodNode.visitEnd(); 195 | return methodNode; 196 | } 197 | 198 | private MethodNode createDecrypt(final String methodName, final int decryptValue) { 199 | final MethodNode methodNode = new MethodNode( 200 | Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_BRIDGE + Opcodes.ACC_SYNTHETIC, methodName, 201 | "(Ljava/lang/String;)Ljava/lang/String;", null, null); 202 | methodNode.visitCode(); 203 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); 204 | methodNode.visitInsn(Opcodes.DUP); 205 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false); 206 | methodNode.visitVarInsn(Opcodes.ASTORE, 1); 207 | methodNode.visitInsn(Opcodes.ICONST_0); 208 | methodNode.visitVarInsn(Opcodes.ISTORE, 2); 209 | final Label firstLabel = new Label(); 210 | methodNode.visitJumpInsn(Opcodes.GOTO, firstLabel); 211 | final Label secondLabel = new Label(); 212 | methodNode.visitLabel(secondLabel); 213 | methodNode.visitFrame(Opcodes.F_APPEND, 2, new Object[] { "java/lang/StringBuilder", Opcodes.INTEGER }, 0, 214 | null); 215 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 216 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 217 | methodNode.visitVarInsn(Opcodes.ILOAD, 2); 218 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false); 219 | methodNode.visitIntInsn(ASMUtil.getOpcodeInsn(decryptValue), decryptValue); 220 | methodNode.visitInsn(Opcodes.IXOR); 221 | methodNode.visitInsn(Opcodes.I2C); 222 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", 223 | "(C)Ljava/lang/StringBuilder;", false); 224 | methodNode.visitInsn(Opcodes.POP); 225 | methodNode.visitIincInsn(2, 1); 226 | methodNode.visitLabel(firstLabel); 227 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 228 | methodNode.visitVarInsn(Opcodes.ILOAD, 2); 229 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 230 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 231 | methodNode.visitJumpInsn(Opcodes.IF_ICMPLT, secondLabel); 232 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 233 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", 234 | false); 235 | methodNode.visitInsn(Opcodes.ARETURN); 236 | methodNode.visitMaxs(3, 3); 237 | methodNode.visitEnd(); 238 | return methodNode; 239 | } 240 | 241 | private String encrypt(final String string, final int decryptValue) { 242 | final StringBuilder stringBuilder = new StringBuilder(); 243 | for (int i = 0; i < string.length(); i++) { 244 | stringBuilder.append((char) (string.charAt(i) ^ decryptValue)); 245 | } 246 | return stringBuilder.toString(); 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/MiscellaneousObfuscationTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | import org.objectweb.asm.Opcodes; 13 | import org.objectweb.asm.tree.AnnotationNode; 14 | import org.objectweb.asm.tree.ClassNode; 15 | import org.objectweb.asm.tree.InsnNode; 16 | import org.objectweb.asm.tree.IntInsnNode; 17 | import org.objectweb.asm.tree.LdcInsnNode; 18 | import org.objectweb.asm.tree.LocalVariableNode; 19 | import org.objectweb.asm.tree.MethodNode; 20 | import org.objectweb.asm.tree.VarInsnNode; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import tk.netindev.scuti.core.configuration.Configuration; 25 | import tk.netindev.scuti.core.transform.Transformer; 26 | import tk.netindev.scuti.core.transform.Transformers.Obfuscation; 27 | import tk.netindev.scuti.core.util.ASMUtil; 28 | import tk.netindev.scuti.core.util.RandomUtil; 29 | import tk.netindev.scuti.core.util.StringUtil; 30 | 31 | /** 32 | * 33 | * @author netindev 34 | * 35 | */ 36 | public class MiscellaneousObfuscationTransformer extends Transformer { 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(MiscellaneousObfuscationTransformer.class.getName());; 39 | private final List randomExceptions = new ArrayList<>(); 40 | 41 | public MiscellaneousObfuscationTransformer(final Configuration configuration, final Map classes, 42 | final Map dependencies) { 43 | super(configuration, classes, dependencies); 44 | LOGGER.info(" Miscellaneous Transformer ->"); 45 | } 46 | 47 | @Override 48 | public void transform() { 49 | if (this.getConfiguration().getMiscellaneousObfuscation().isRandomExceptions()) { 50 | this.getClasses().values().forEach(classNode -> { 51 | if (Modifier.isPublic(classNode.access) && !classNode.name.contains("$")) { 52 | randomExceptions.add(classNode); 53 | } 54 | }); 55 | } 56 | this.getClasses().values().forEach(classNode -> { 57 | if (!Modifier.isInterface(classNode.access)) { 58 | if (this.getConfiguration().getMiscellaneousObfuscation().isPushTransient()) { 59 | this.pushTransient(classNode); 60 | } 61 | if (this.getConfiguration().getMiscellaneousObfuscation().isPushVarargs() && !this.getConfiguration() 62 | .getTransformers().contains(Obfuscation.INVOKE_DYNAMIC_TRANSFORMER)) { 63 | this.pushVarargs(classNode); 64 | } 65 | if (this.getConfiguration().getMiscellaneousObfuscation().isVariableDescritor()) { 66 | this.randomDescriptor(classNode); 67 | } 68 | if (this.getConfiguration().getMiscellaneousObfuscation().isDuplicateVariables()) { 69 | classNode.methods.forEach(methodNode -> { 70 | this.popDupLoads(methodNode); 71 | this.popSwapIntegers(methodNode); 72 | this.popSwapLoads(methodNode); 73 | }); 74 | } 75 | } 76 | if (this.getConfiguration().getMiscellaneousObfuscation().isRandomExceptions()) { 77 | classNode.methods.forEach(methodNode -> { 78 | this.randomExceptions(methodNode); 79 | }); 80 | } 81 | if (this.getConfiguration().getMiscellaneousObfuscation().isMassiveSignature()) { 82 | this.massiveSignature(classNode); 83 | } 84 | if (this.getConfiguration().getMiscellaneousObfuscation().isMassiveSource()) { 85 | this.massiveSource(classNode); 86 | } 87 | if (this.getConfiguration().getMiscellaneousObfuscation().isInvalidAnnotation()) { 88 | this.invalidAnnotations(classNode); 89 | } 90 | }); 91 | } 92 | 93 | private void randomExceptions(final MethodNode methodNode) { 94 | if (RandomUtil.getRandom()) { 95 | for (int i = 0; i < RandomUtil.getRandom(3); i++) { 96 | methodNode.exceptions.add(randomExceptions.get(RandomUtil.getRandom(randomExceptions.size())).name); 97 | } 98 | } 99 | } 100 | 101 | private void popDupLoads(final MethodNode methodNode) { 102 | Arrays.stream(methodNode.instructions.toArray()).filter(insnNode -> insnNode instanceof VarInsnNode) 103 | .forEach(insnNode -> { 104 | if (insnNode.getOpcode() == Opcodes.ALOAD || insnNode.getOpcode() == Opcodes.FLOAD 105 | || insnNode.getOpcode() == Opcodes.ILOAD) { 106 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.POP2)); 107 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.DUP)); 108 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.DUP)); 109 | } 110 | }); 111 | } 112 | 113 | private void popSwapLoads(final MethodNode methodNode) { 114 | Arrays.stream(methodNode.instructions.toArray()).filter(insnNode -> insnNode instanceof VarInsnNode) 115 | .forEach(insnNode -> { 116 | if (insnNode.getOpcode() == Opcodes.ALOAD || insnNode.getOpcode() == Opcodes.FLOAD 117 | || insnNode.getOpcode() == Opcodes.ILOAD) { 118 | methodNode.instructions.insertBefore(insnNode, 119 | new VarInsnNode(insnNode.getOpcode(), ((VarInsnNode) insnNode).var)); 120 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.POP)); 121 | for (int i = 0; i < RandomUtil.getRandom(1, 4); i++) { 122 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.SWAP)); 123 | } 124 | } 125 | }); 126 | } 127 | 128 | private void popSwapIntegers(final MethodNode methodNode) { 129 | Arrays.stream(methodNode.instructions.toArray()).forEach(insnNode -> { 130 | if (insnNode.getOpcode() >= Opcodes.ICONST_M1 && insnNode.getOpcode() <= Opcodes.ICONST_5) { 131 | methodNode.instructions.insertBefore(insnNode, new InsnNode(insnNode.getOpcode())); 132 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.POP)); 133 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.SWAP)); 134 | } else if (insnNode instanceof IntInsnNode && insnNode.getOpcode() != Opcodes.NEWARRAY) { 135 | methodNode.instructions.insertBefore(insnNode, 136 | new IntInsnNode(insnNode.getOpcode(), ((IntInsnNode) insnNode).operand)); 137 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.POP)); 138 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.SWAP)); 139 | } else if (insnNode instanceof LdcInsnNode && ((LdcInsnNode) insnNode).cst instanceof Integer) { 140 | methodNode.instructions.insertBefore(insnNode, new LdcInsnNode(((LdcInsnNode) insnNode).cst)); 141 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.POP)); 142 | methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.SWAP)); 143 | } 144 | }); 145 | } 146 | 147 | private void massiveSignature(final ClassNode classNode) { 148 | final String signature = StringUtil.getMassiveString(); 149 | classNode.fields.forEach(fieldNode -> { 150 | fieldNode.signature = signature; 151 | }); 152 | classNode.methods.forEach(methodNode -> { 153 | methodNode.signature = signature; 154 | if (methodNode.localVariables != null) { 155 | methodNode.localVariables.forEach(variableNode -> { 156 | variableNode.signature = signature; 157 | }); 158 | } 159 | }); 160 | classNode.signature = signature; 161 | } 162 | 163 | private void massiveSource(final ClassNode classNode) { 164 | classNode.sourceFile = StringUtil.getMassiveString(); 165 | classNode.sourceDebug = StringUtil.getMassiveString(); 166 | } 167 | 168 | private void pushVarargs(final ClassNode classNode) { 169 | if (!Modifier.isInterface(classNode.access)) { 170 | classNode.methods.stream().filter(methodNode -> (methodNode.access & Opcodes.ACC_SYNTHETIC) == 0 171 | && (methodNode.access & Opcodes.ACC_BRIDGE) == 0).forEach(methodNode -> { 172 | methodNode.access |= Opcodes.ACC_VARARGS; 173 | }); 174 | } 175 | } 176 | 177 | private void pushTransient(final ClassNode classNode) { 178 | if (!Modifier.isInterface(classNode.access)) { 179 | classNode.fields.forEach(fieldNode -> { 180 | fieldNode.access |= Opcodes.ACC_TRANSIENT; 181 | }); 182 | } 183 | } 184 | 185 | private void invalidAnnotations(final ClassNode classNode) { 186 | if (classNode.visibleAnnotations == null) { 187 | classNode.visibleAnnotations = new ArrayList<>(); 188 | } 189 | if (classNode.invisibleAnnotations == null) { 190 | classNode.invisibleAnnotations = new ArrayList<>(); 191 | } 192 | classNode.visibleAnnotations.add(new AnnotationNode("@")); 193 | classNode.invisibleAnnotations.add(new AnnotationNode("@")); 194 | classNode.methods.forEach(methodNode -> { 195 | if (methodNode.visibleAnnotations == null) { 196 | methodNode.visibleAnnotations = new ArrayList<>(); 197 | } 198 | if (methodNode.invisibleAnnotations == null) { 199 | methodNode.invisibleAnnotations = new ArrayList<>(); 200 | } 201 | methodNode.visibleAnnotations.add(new AnnotationNode("@")); 202 | methodNode.invisibleAnnotations.add(new AnnotationNode("@")); 203 | }); 204 | classNode.fields.forEach(fieldNode -> { 205 | if (fieldNode.visibleAnnotations == null) { 206 | fieldNode.visibleAnnotations = new ArrayList<>(); 207 | } 208 | if (fieldNode.invisibleAnnotations == null) { 209 | fieldNode.invisibleAnnotations = new ArrayList<>(); 210 | } 211 | fieldNode.visibleAnnotations.add(new AnnotationNode("@")); 212 | fieldNode.invisibleAnnotations.add(new AnnotationNode("@")); 213 | }); 214 | } 215 | 216 | private void randomDescriptor(final ClassNode classNode) { 217 | classNode.methods.stream().filter(methodNode -> ASMUtil.hasLabels(methodNode)).forEach(methodNode -> { 218 | final Set set = new HashSet<>(); 219 | methodNode.localVariables = new ArrayList<>(); 220 | Arrays.stream(methodNode.instructions.toArray()).filter(insnNode -> insnNode instanceof VarInsnNode) 221 | .forEach(insnNode -> set.add(((VarInsnNode) insnNode).var)); 222 | set.forEach(value -> { 223 | methodNode.localVariables.add(new LocalVariableNode(new String(), 224 | this.getConfiguration().getMiscellaneousObfuscation().getVariableDescriptorList() 225 | .get(RandomUtil.getRandom(this.getConfiguration().getMiscellaneousObfuscation() 226 | .getVariableDescriptorList().size())), 227 | null, ASMUtil.getFirstLabel(methodNode), ASMUtil.getLastLabel(methodNode), value)); 228 | }); 229 | }); 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/NumberObfuscationTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Arrays; 5 | import java.util.Map; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.tree.ClassNode; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.IntInsnNode; 13 | import org.objectweb.asm.tree.LdcInsnNode; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import tk.netindev.scuti.core.configuration.Configuration; 18 | import tk.netindev.scuti.core.transform.Transformer; 19 | import tk.netindev.scuti.core.util.ASMUtil; 20 | import tk.netindev.scuti.core.util.RandomUtil; 21 | import tk.netindev.scuti.core.util.Util; 22 | 23 | /** 24 | * 25 | * @author netindev 26 | * 27 | */ 28 | public class NumberObfuscationTransformer extends Transformer { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(NumberObfuscationTransformer.class.getName()); 31 | 32 | public NumberObfuscationTransformer(final Configuration configuration, final Map classes, 33 | final Map dependencies) { 34 | super(configuration, classes, dependencies); 35 | LOGGER.info(" Number Obfuscation Transformer ->"); 36 | } 37 | 38 | @Override 39 | public void transform() { 40 | AtomicInteger atomicInteger = new AtomicInteger(); 41 | this.getClasses().values().forEach(classNode -> { 42 | classNode.methods.stream().filter( 43 | methodNode -> !Modifier.isAbstract(methodNode.access) && !Modifier.isNative(methodNode.access)) 44 | .forEach(methodNode -> { 45 | for (int i = 0; i < (this.getConfiguration().getNumberObfuscation().isExecuteTwice() ? 2 46 | : 1); i++) { 47 | Arrays.stream(methodNode.instructions.toArray()) 48 | .filter(insnNode -> insnNode.getOpcode() != Opcodes.NEWARRAY).forEach(insnNode -> { 49 | if (insnNode.getOpcode() >= 0x2 && insnNode.getOpcode() <= 0x8) { 50 | methodNode.instructions.insertBefore(insnNode, 51 | this.getInsnList(insnNode.getOpcode() - 0x3)); 52 | methodNode.instructions.remove(insnNode); 53 | atomicInteger.incrementAndGet(); 54 | } else if (insnNode instanceof IntInsnNode) { 55 | final int operand = ((IntInsnNode) insnNode).operand; 56 | methodNode.instructions.insertBefore(insnNode, this.getInsnList(operand)); 57 | methodNode.instructions.remove(insnNode); 58 | atomicInteger.incrementAndGet(); 59 | } else if (insnNode instanceof LdcInsnNode 60 | && ((LdcInsnNode) insnNode).cst instanceof Integer) { 61 | final int value = (int) ((LdcInsnNode) insnNode).cst; 62 | if (value < -(Short.MAX_VALUE * 8) + Integer.MAX_VALUE) { 63 | methodNode.instructions.insertBefore(insnNode, this.getInsnList(value)); 64 | methodNode.instructions.remove(insnNode); 65 | atomicInteger.incrementAndGet(); 66 | } 67 | } 68 | 69 | }); 70 | } 71 | }); 72 | }); 73 | LOGGER.info(" - Obfuscated " + atomicInteger.get() + " numbers"); 74 | } 75 | 76 | private InsnList getInsnList(final int value) { 77 | return /* Util.getRandom() ? */this.xor(value);// : sub(value); 78 | } 79 | 80 | private InsnList xor(final int value) { 81 | final InsnList insnList = new InsnList(); 82 | final int first = RandomUtil.getRandom(Short.MAX_VALUE) + value, 83 | second = -RandomUtil.getRandom(Short.MAX_VALUE) + value; 84 | final int random = RandomUtil.getRandom(Short.MAX_VALUE); 85 | insnList.add(ASMUtil.toInsnNode(first ^ value)); 86 | insnList.add(ASMUtil.toInsnNode(second ^ value + random)); 87 | insnList.add(new InsnNode(Opcodes.IXOR)); 88 | insnList.add(ASMUtil.toInsnNode(first ^ value + random)); 89 | insnList.add(new InsnNode(Opcodes.IXOR)); 90 | insnList.add(ASMUtil.toInsnNode(second)); 91 | insnList.add(new InsnNode(Opcodes.IXOR)); 92 | return insnList; 93 | } 94 | 95 | /* 96 | * private InsnList sub(int value) { final InsnList insnList = new InsnList(); 97 | * final int first = Util.getRandom(Short.MAX_VALUE) + value, second = 98 | * -Util.getRandom(Short.MAX_VALUE) + value; final int random = 99 | * Util.getRandom(Short.MAX_VALUE); insnList.add(ASM.toInsnNode(first + value)); 100 | * insnList.add(ASM.toInsnNode(second + (value + random))); insnList.add(new 101 | * InsnNode(Opcodes.ISUB)); insnList.add(ASM.toInsnNode(first - (value + 102 | * random))); insnList.add(new InsnNode(Opcodes.ISUB)); 103 | * insnList.add(ASM.toInsnNode(second - value + value)); insnList.add(new 104 | * InsnNode(Opcodes.IADD)); return insnList; } 105 | */ 106 | 107 | } 108 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/RenameMembersTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import org.objectweb.asm.Opcodes; 11 | import org.objectweb.asm.commons.ClassRemapper; 12 | import org.objectweb.asm.commons.Remapper; 13 | import org.objectweb.asm.commons.SimpleRemapper; 14 | import org.objectweb.asm.tree.ClassNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import tk.netindev.scuti.core.configuration.Configuration; 21 | import tk.netindev.scuti.core.dictionary.Dictionary; 22 | import tk.netindev.scuti.core.dictionary.Types; 23 | import tk.netindev.scuti.core.dictionary.Types.CustomDictionary; 24 | import tk.netindev.scuti.core.rewrite.Hierarchy; 25 | import tk.netindev.scuti.core.transform.Transformer; 26 | import tk.netindev.scuti.core.util.ASMUtil; 27 | import tk.netindev.scuti.core.util.Util; 28 | 29 | /** 30 | * 31 | * @author netindev 32 | * 33 | */ 34 | public class RenameMembersTransformer extends Transformer { 35 | 36 | private final Hierarchy remmapingHierarchy = new Hierarchy(this.getClasses(), this.getDependencies()); 37 | private final Map classMapping = new HashMap<>(), fieldMapping = new HashMap<>(), 38 | methodMapping = new HashMap<>(); 39 | 40 | private static final Logger LOGGER = LoggerFactory.getLogger(RenameMembersTransformer.class); 41 | 42 | public RenameMembersTransformer(final Configuration configuration, final Map classes, 43 | final Map dependencies) { 44 | super(configuration, classes, dependencies); 45 | LOGGER.info(" Rename Transfomer ->"); 46 | } 47 | 48 | @Override 49 | public void transform() { 50 | if (this.getConfiguration().getRenameMembers().isRenameFields()) { 51 | this.remapFields(); 52 | this.applyFields(); 53 | } 54 | if (this.getConfiguration().getRenameMembers().isRenameMethods()) { 55 | this.remapMethods(); 56 | this.applyMethods(); 57 | } 58 | if (this.getConfiguration().getRenameMembers().isRenameClasses()) { 59 | this.remapClasses(); 60 | this.applyClasses(); 61 | } 62 | if (this.classMapping.values().size() > 0) { 63 | LOGGER.info(" - Remapped " + this.classMapping.values().size() + " classes"); 64 | } 65 | if (this.methodMapping.values().size() > 0) { 66 | LOGGER.info(" - Remapped " + this.methodMapping.values().size() + " methods"); 67 | } 68 | if (this.fieldMapping.values().size() > 0) { 69 | LOGGER.info(" - Remapped " + this.fieldMapping.values().size() + " fields"); 70 | } 71 | } 72 | 73 | private void remapMethods() { 74 | final Dictionary methodsDictionary = Types.getDictionary(this.getConfiguration(), CustomDictionary.METHODS); 75 | this.getClasses().values().stream().filter(classNode -> ((classNode.access & Opcodes.ACC_ENUM) == 0)) 76 | .forEach(classNode -> { 77 | classNode.methods.stream().filter(methodNode -> !this.isExcludedMethod(classNode, methodNode) 78 | && !ASMUtil.isMainMethod(methodNode) && !ASMUtil.isLambdaMethod(methodNode) 79 | && !ASMUtil.isInitializer(methodNode) 80 | && !this.iterateParents(this.remmapingHierarchy.getTree(classNode.name).getParentClasses(), 81 | new HashSet<>()).contains(methodNode.name) 82 | && !this.iterateInterfaces(classNode.interfaces, new HashSet<>()).contains(methodNode.name)) 83 | .forEach(methodNode -> { 84 | String next = methodsDictionary.next(); 85 | this.methodMapping.put(classNode.name + "." + methodNode.name + "." + methodNode.desc, 86 | next); 87 | methodNode.name = next; 88 | }); 89 | }); 90 | this.getClasses().values().forEach(classNode -> { 91 | this.remapParentMethods(this.remmapingHierarchy.getTree(classNode.name).getParentClasses(), classNode); 92 | }); 93 | } 94 | 95 | private void applyMethods() { 96 | final Map remappedMethods = new HashMap<>(); 97 | this.getClasses().values().forEach(classNode -> { 98 | final ClassNode remappingNode = new ClassNode(); 99 | classNode.accept(new ClassRemapper(remappingNode, new SimpleRemapper(this.methodMapping) { 100 | @Override 101 | public String mapMethodName(final String owner, final String name, final String desc) { 102 | final String remappedName = this.map(owner + "." + name + "." + desc); 103 | return remappedName != null ? remappedName : name; 104 | } 105 | })); 106 | remappedMethods.put(remappingNode.name, remappingNode); 107 | }); 108 | this.getClasses().clear(); 109 | this.getClasses().putAll(remappedMethods); 110 | } 111 | 112 | private void remapFields() { 113 | final Dictionary fieldsDictionary = Types.getDictionary(this.getConfiguration(), CustomDictionary.FIELDS); 114 | this.getClasses().values().forEach(classNode -> { 115 | classNode.fields.stream().filter(fieldNode -> !this.isExcludedField(classNode, fieldNode)) 116 | .forEach(fieldNode -> { 117 | this.fieldMapping.put(classNode.name + "." + fieldNode.name + "." + fieldNode.desc, 118 | fieldsDictionary.next()); 119 | }); 120 | }); 121 | this.getClasses().values().forEach(classNode -> { 122 | this.remapParentFields(this.remmapingHierarchy.getTree(classNode.name).getParentClasses(), classNode); 123 | }); 124 | } 125 | 126 | private void applyFields() { 127 | final Map remappedFields = new HashMap<>(); 128 | this.getClasses().values().forEach(classNode -> { 129 | final ClassNode remappingNode = new ClassNode(); 130 | classNode.accept(new ClassRemapper(remappingNode, new SimpleRemapper(this.fieldMapping) { 131 | @Override 132 | public String mapFieldName(final String owner, final String name, final String desc) { 133 | final String remappedName = this.map(owner + "." + name + "." + desc); 134 | return remappedName != null ? remappedName : name; 135 | } 136 | })); 137 | remappedFields.put(remappingNode.name, remappingNode); 138 | }); 139 | this.getClasses().clear(); 140 | this.getClasses().putAll(remappedFields); 141 | } 142 | 143 | private void remapClasses() { 144 | final Dictionary packagesDictionary = Types.getDictionary(this.getConfiguration(), CustomDictionary.PACKAGES), 145 | classesDictionary = Types.getDictionary(this.getConfiguration(), CustomDictionary.CLASSES); 146 | final Map packages = new HashMap<>(); 147 | final Map classes = new HashMap<>(); 148 | Util.sortByComparator(this.getClasses()).values().forEach(classNode -> { 149 | if (!this.isExcludedClass(classNode)) { 150 | if (this.getConfiguration().getRenameMembers().isRemovePackages() || !classNode.name.contains("/")) { 151 | final String classNext = classesDictionary.next(); 152 | this.classMapping.put(classNode.name, classNext); 153 | classNode.name = classNext; 154 | } else { 155 | final String packageName = classNode.name.substring(0, classNode.name.lastIndexOf('/')); 156 | if (!packages.containsKey(packageName)) { 157 | classesDictionary.reset(); 158 | packages.put(packageName, packagesDictionary.next()); 159 | } 160 | String classNext = classesDictionary.next(); 161 | final String packageNext = packages.get(packageName) + "/"; 162 | while (this.classMapping.containsValue(packageNext + classNext)) { 163 | classNext = classesDictionary.next(); 164 | } 165 | this.classMapping.put(classNode.name, packageNext + classNext); 166 | classNode.name = packageNext + classNext; 167 | } 168 | } 169 | classes.put(classNode.name, classNode); 170 | }); 171 | this.getClasses().clear(); 172 | this.getClasses().putAll(classes); 173 | } 174 | 175 | private void applyClasses() { 176 | final Map remappedClasses = new HashMap<>(); 177 | this.getClasses().values().forEach(classNode -> { 178 | final ClassNode remappingNode = new ClassNode(); 179 | classNode.accept(new ClassRemapper(remappingNode, new Remapper() { 180 | @Override 181 | public String map(final String type) { 182 | if (!RenameMembersTransformer.this.classMapping.containsKey(type)) { 183 | return type; 184 | } 185 | return RenameMembersTransformer.this.classMapping.get(type); 186 | } 187 | })); 188 | remappedClasses.put(remappingNode.name, remappingNode); 189 | }); 190 | this.getClasses().values().clear(); 191 | this.getClasses().keySet().clear(); 192 | this.getClasses().putAll(remappedClasses); 193 | } 194 | 195 | private Set iterateParents(final Set parents, final Set set) { 196 | parents.stream().map(parent -> this.remmapingHierarchy.getClassNode(parent)).forEach(parent -> { 197 | set.addAll(this.iterateInterfaces(parent.interfaces, new HashSet<>())); 198 | parent.methods.forEach(methodNode -> set.add(methodNode.name)); 199 | }); 200 | parents.forEach(parent -> { 201 | this.iterateParents(this.remmapingHierarchy.getTree(parent).getParentClasses(), set); 202 | }); 203 | return set; 204 | } 205 | 206 | private Set iterateInterfaces(final List interfaces, final Set set) { 207 | interfaces.stream().map(interfaceNode -> this.remmapingHierarchy.getClassNode(interfaceNode)) 208 | .forEach(parent -> { 209 | parent.methods.forEach(methodNode -> set.add(methodNode.name)); 210 | }); 211 | interfaces.forEach(parent -> { 212 | this.iterateInterfaces(this.remmapingHierarchy.getTree(parent).getClassNode().interfaces, set); 213 | }); 214 | return set; 215 | } 216 | 217 | private void remapParentFields(final Set tree, final ClassNode classNode) { 218 | tree.stream().map(parent -> this.remmapingHierarchy.getClassNode(parent)) 219 | .filter(parent -> !this.getDependencies().containsKey(parent.name)).forEach(parent -> { 220 | parent.fields.forEach(fieldNode -> { 221 | this.fieldMapping.put(classNode.name + "." + fieldNode.name + "." + fieldNode.desc, 222 | this.fieldMapping.get(parent.name + "." + fieldNode.name + "." + fieldNode.desc)); 223 | }); 224 | }); 225 | tree.forEach(parent -> { 226 | if (!this.getDependencies().containsKey(parent)) { 227 | this.remapParentFields(this.remmapingHierarchy.getTree(parent).getParentClasses(), classNode); 228 | } 229 | }); 230 | } 231 | 232 | private void remapParentMethods(final Set tree, final ClassNode classNode) { 233 | tree.stream().map(parent -> this.remmapingHierarchy.getClassNode(parent)) 234 | .filter(parent -> !this.getDependencies().containsKey(parent.name)).forEach(parent -> { 235 | parent.methods.forEach(methodNode -> { 236 | this.methodMapping.put(classNode.name + "." + methodNode.name + "." + methodNode.desc, 237 | this.methodMapping.get(parent.name + "." + methodNode.name + "." + methodNode.desc)); 238 | }); 239 | }); 240 | tree.forEach(parent -> { 241 | if (!this.getDependencies().containsKey(parent)) { 242 | this.remapParentMethods(this.remmapingHierarchy.getTree(parent).getParentClasses(), classNode); 243 | } 244 | }); 245 | } 246 | 247 | private boolean isExcludedClass(final ClassNode classNode) { 248 | final AtomicBoolean atomicBoolean = new AtomicBoolean(false); 249 | this.getConfiguration().getRenameMembers().getExcludeClasses().forEach(classNode_ -> { 250 | if (classNode.name.startsWith(classNode_) || classNode.name.equals(classNode_)) { 251 | atomicBoolean.set(true); 252 | } 253 | }); 254 | return atomicBoolean.get(); 255 | } 256 | 257 | private boolean isExcludedField(final ClassNode classNode, final FieldNode fieldNode) { 258 | final AtomicBoolean atomicBoolean = new AtomicBoolean(false); 259 | this.getConfiguration().getRenameMembers().getExcludeFields().forEach(classNode_ -> { 260 | if (classNode.name.startsWith(classNode_) || classNode.name.equals(classNode_) 261 | || classNode_.equals(classNode.name + "/" + fieldNode.name)) { 262 | atomicBoolean.set(true); 263 | } 264 | }); 265 | return atomicBoolean.get(); 266 | } 267 | 268 | private boolean isExcludedMethod(final ClassNode classNode, final MethodNode methodNode) { 269 | final AtomicBoolean atomicBoolean = new AtomicBoolean(false); 270 | this.getConfiguration().getRenameMembers().getExcludeMethods().forEach(classNode_ -> { 271 | if (classNode.name.startsWith(classNode_) || classNode.name.equals(classNode_) 272 | || classNode_.equals(classNode.name + "/" + methodNode.name)) { 273 | atomicBoolean.set(true); 274 | } 275 | }); 276 | return atomicBoolean.get(); 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/ResourceEncryptTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | public class ResourceEncryptTransformer { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/ShuffleMembersTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import org.objectweb.asm.tree.ClassNode; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import tk.netindev.scuti.core.configuration.Configuration; 12 | import tk.netindev.scuti.core.transform.Transformer; 13 | 14 | /** 15 | * 16 | * @author netindev 17 | * 18 | */ 19 | public class ShuffleMembersTransformer extends Transformer { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(StringEncryptionTransformer.class.getName()); 22 | 23 | public ShuffleMembersTransformer(final Configuration configuration, final Map classes, 24 | final Map dependencies) { 25 | super(configuration, classes, dependencies); 26 | LOGGER.info(" Shuffle Member Transformer ->"); 27 | } 28 | 29 | @Override 30 | public void transform() { 31 | final AtomicInteger atomicInteger = new AtomicInteger(); 32 | this.getClasses().values().forEach(classNode -> { 33 | Collections.shuffle(classNode.methods); 34 | atomicInteger.addAndGet(classNode.methods.size()); 35 | Collections.shuffle(classNode.fields); 36 | atomicInteger.addAndGet(classNode.fields.size()); 37 | }); 38 | LOGGER.info(" - Shuffled " + atomicInteger.get() + " members"); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/obfuscation/StringEncryptionTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.obfuscation; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Arrays; 5 | import java.util.Map; 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import org.objectweb.asm.Label; 10 | import org.objectweb.asm.Opcodes; 11 | import org.objectweb.asm.tree.ClassNode; 12 | import org.objectweb.asm.tree.InsnList; 13 | import org.objectweb.asm.tree.InsnNode; 14 | import org.objectweb.asm.tree.LdcInsnNode; 15 | import org.objectweb.asm.tree.MethodInsnNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import tk.netindev.scuti.core.configuration.Configuration; 21 | import tk.netindev.scuti.core.configuration.option.obfuscation.StringEncryption.EncryptionType; 22 | import tk.netindev.scuti.core.transform.Transformer; 23 | import tk.netindev.scuti.core.util.RandomUtil; 24 | import tk.netindev.scuti.core.util.StringUtil; 25 | import tk.netindev.scuti.core.util.Util; 26 | 27 | /** 28 | * 29 | * @author netindev 30 | * 31 | */ 32 | public class StringEncryptionTransformer extends Transformer { 33 | 34 | private static final Logger LOGGER = LoggerFactory.getLogger(StringEncryptionTransformer.class.getName()); 35 | private final AtomicInteger atomicInteger = new AtomicInteger(); 36 | 37 | public StringEncryptionTransformer(final Configuration configuration, final Map classes, 38 | final Map dependencies) { 39 | super(configuration, classes, dependencies); 40 | LOGGER.info(" String Encryption Transformer ->"); 41 | } 42 | 43 | @Override 44 | public void transform() { 45 | this.getClasses().values().stream().filter(classNode -> !Modifier.isInterface(classNode.access)) 46 | .forEach(classNode -> { 47 | final AtomicBoolean atomicBoolean = new AtomicBoolean(); 48 | final String randomString = StringUtil.getRandomString(); 49 | if (this.getConfiguration().getStringEncryption().getEncryptionType() == EncryptionType.FAST) { 50 | final int random = RandomUtil.getRandom(0x8, 0x800); 51 | classNode.methods.stream().filter(methodNode -> !Modifier.isAbstract(methodNode.access)) 52 | .forEach(methodNode -> { 53 | Arrays.asList(methodNode.instructions.toArray()).stream() 54 | .filter(insnNode -> insnNode instanceof LdcInsnNode 55 | && ((LdcInsnNode) insnNode).cst instanceof String) 56 | .forEach(insnNode -> { 57 | methodNode.instructions.insert(insnNode, 58 | new MethodInsnNode(Opcodes.INVOKESTATIC, classNode.name, 59 | randomString, "(Ljava/lang/String;)Ljava/lang/String;", 60 | false)); 61 | ((LdcInsnNode) insnNode).cst = this 62 | .xor((String) ((LdcInsnNode) insnNode).cst, random); 63 | this.atomicInteger.incrementAndGet(); 64 | atomicBoolean.set(true); 65 | }); 66 | }); 67 | if (atomicBoolean.get()) { 68 | classNode.methods.add(this.createDecryptFast(randomString, random)); 69 | } 70 | } else if (this.getConfiguration().getStringEncryption() 71 | .getEncryptionType() == EncryptionType.STRONG) { 72 | classNode.methods.stream().filter(methodNode -> !Modifier.isAbstract(methodNode.access)) 73 | .forEach(methodNode -> { 74 | Arrays.stream(methodNode.instructions.toArray()).forEach(insnNode -> { 75 | if (insnNode instanceof LdcInsnNode 76 | && ((LdcInsnNode) insnNode).cst instanceof String) { 77 | final LdcInsnNode ldcInsnNode = (LdcInsnNode) insnNode; 78 | final int random = RandomUtil.getRandom(0x8, 0x800); 79 | methodNode.instructions.insert(insnNode, 80 | this.getInsnListStrong(this.encryptStrong((String) ldcInsnNode.cst, 81 | randomString, random), random, classNode.name, 82 | randomString)); 83 | methodNode.instructions.remove(ldcInsnNode); 84 | this.atomicInteger.incrementAndGet(); 85 | atomicBoolean.set(true); 86 | } 87 | }); 88 | }); 89 | if (atomicBoolean.get()) { 90 | classNode.methods.add(this.createDecryptStrong(randomString)); 91 | } 92 | } else { 93 | throw new RuntimeException("Unknown type value on EncryptionType"); 94 | } 95 | }); 96 | LOGGER.info(" - Encrypted " + this.atomicInteger.get() + " strings"); 97 | } 98 | 99 | /* FAST */ 100 | 101 | private MethodNode createDecryptFast(final String methodName, final int decryptValue) { 102 | final MethodNode methodNode = new MethodNode( 103 | Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_BRIDGE, methodName, 104 | "(Ljava/lang/String;)Ljava/lang/String;", null, null); 105 | methodNode.visitCode(); 106 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); 107 | methodNode.visitInsn(Opcodes.DUP); 108 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false); 109 | methodNode.visitVarInsn(Opcodes.ASTORE, 1); 110 | methodNode.visitInsn(Opcodes.ICONST_0); 111 | methodNode.visitVarInsn(Opcodes.ISTORE, 2); 112 | final Label firstLabel = new Label(); 113 | methodNode.visitJumpInsn(Opcodes.GOTO, firstLabel); 114 | final Label secondLabel = new Label(); 115 | methodNode.visitLabel(secondLabel); 116 | methodNode.visitFrame(Opcodes.F_APPEND, 2, new Object[] { "java/lang/StringBuilder", Opcodes.INTEGER }, 0, 117 | null); 118 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 119 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 120 | methodNode.visitVarInsn(Opcodes.ILOAD, 2); 121 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false); 122 | if (decryptValue > Byte.MAX_VALUE) { 123 | methodNode.visitIntInsn(Opcodes.SIPUSH, decryptValue); 124 | } else { 125 | methodNode.visitIntInsn(Opcodes.BIPUSH, decryptValue); 126 | } 127 | methodNode.visitInsn(Opcodes.IXOR); 128 | methodNode.visitInsn(Opcodes.I2C); 129 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", 130 | "(C)Ljava/lang/StringBuilder;", false); 131 | methodNode.visitInsn(Opcodes.POP); 132 | methodNode.visitIincInsn(2, 1); 133 | methodNode.visitLabel(firstLabel); 134 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 135 | methodNode.visitVarInsn(Opcodes.ILOAD, 2); 136 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 137 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 138 | methodNode.visitJumpInsn(Opcodes.IF_ICMPLT, secondLabel); 139 | methodNode.visitVarInsn(Opcodes.ALOAD, 1); 140 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", 141 | false); 142 | methodNode.visitInsn(Opcodes.ARETURN); 143 | methodNode.visitMaxs(3, 3); 144 | methodNode.visitEnd(); 145 | return methodNode; 146 | } 147 | 148 | private String xor(final String string, final int xor) { 149 | final StringBuilder stringBuilder = new StringBuilder(); 150 | for (int i = 0; i < string.length(); i++) { 151 | stringBuilder.append((char) (string.charAt(i) ^ xor)); 152 | } 153 | return stringBuilder.toString(); 154 | } 155 | 156 | /* STRONG */ 157 | 158 | private InsnList getInsnListStrong(final String encrypt, final int random, final String className, 159 | final String methodName) { 160 | int even = RandomUtil.getRandom(1, 9); 161 | if (even % 2 != 0) { 162 | even = even + 1; 163 | } 164 | final InsnList insnList = new InsnList(); 165 | insnList.add(new LdcInsnNode(encrypt)); 166 | insnList.add(new LdcInsnNode(new Integer(random))); 167 | if (RandomUtil.getRandom()) { 168 | insnList.add(new InsnNode(Opcodes.SWAP)); 169 | insnList.add(new InsnNode(Opcodes.SWAP)); 170 | } 171 | for (int i = 0; i < even; i++) { 172 | insnList.add(new InsnNode(Opcodes.INEG)); 173 | } 174 | insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, className, methodName, 175 | "(Ljava/lang/String;I)Ljava/lang/String;", false)); 176 | return insnList; 177 | } 178 | 179 | private String encryptStrong(final String string, final String methodName, final int random) { 180 | final int xor = random ^ methodName.hashCode(); 181 | final char[] array = new char[string.length()]; 182 | for (int i = 0; i < string.length(); i++) { 183 | array[i] = (char) (string.charAt(i) ^ xor); 184 | } 185 | return new StringBuilder(string.length()).append(array).toString(); 186 | } 187 | 188 | private String decryptStrong(final String string, final int random) { 189 | final StackTraceElement stackTrace = Thread.currentThread().getStackTrace()[2]; 190 | String methodName; 191 | if (stackTrace.getMethodName() == null) { 192 | methodName = ""; 193 | } else { 194 | methodName = stackTrace.getMethodName(); 195 | } 196 | final int xor = random ^ methodName.hashCode(); 197 | final char[] array = new char[string.length()]; 198 | for (int i = 0; i < string.length(); i++) { 199 | array[i] = (char) (string.charAt(i) ^ xor); 200 | } 201 | return new String(array); 202 | } 203 | 204 | private MethodNode createDecryptStrong(final String methodName) { 205 | final MethodNode methodNode = new MethodNode( 206 | Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_BRIDGE, methodName, 207 | "(Ljava/lang/String;I)Ljava/lang/String;", null, null); 208 | final Label firstLabel = new Label(), secondLabel = new Label(), thirdLabel = new Label(), 209 | fourthLabel = new Label(); 210 | methodNode.visitCode(); 211 | methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", 212 | false); 213 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Thread", "getStackTrace", 214 | "()[Ljava/lang/StackTraceElement;", false); 215 | methodNode.visitInsn(Opcodes.ICONST_1); 216 | methodNode.visitInsn(Opcodes.AALOAD); 217 | methodNode.visitVarInsn(Opcodes.ASTORE, 2); 218 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 219 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StackTraceElement", "getMethodName", 220 | "()Ljava/lang/String;", false); 221 | methodNode.visitJumpInsn(Opcodes.IFNONNULL, firstLabel); 222 | methodNode.visitLdcInsn(""); 223 | methodNode.visitVarInsn(Opcodes.ASTORE, 3); 224 | methodNode.visitJumpInsn(Opcodes.GOTO, secondLabel); 225 | methodNode.visitLabel(firstLabel); 226 | methodNode.visitFrame(Opcodes.F_APPEND, 1, new Object[] { "java/lang/StackTraceElement" }, 0, null); 227 | methodNode.visitVarInsn(Opcodes.ALOAD, 2); 228 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StackTraceElement", "getMethodName", 229 | "()Ljava/lang/String;", false); 230 | methodNode.visitVarInsn(Opcodes.ASTORE, 3); 231 | methodNode.visitLabel(secondLabel); 232 | methodNode.visitFrame(Opcodes.F_APPEND, 1, new Object[] { "java/lang/String" }, 0, null); 233 | methodNode.visitVarInsn(Opcodes.ALOAD, 3); 234 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false); 235 | methodNode.visitVarInsn(Opcodes.ISTORE, 4); 236 | methodNode.visitVarInsn(Opcodes.ILOAD, 1); 237 | methodNode.visitVarInsn(Opcodes.ILOAD, 4); 238 | methodNode.visitInsn(Opcodes.IXOR); 239 | methodNode.visitVarInsn(Opcodes.ISTORE, 5); 240 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 241 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 242 | methodNode.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_CHAR); 243 | methodNode.visitVarInsn(Opcodes.ASTORE, 6); 244 | methodNode.visitInsn(Opcodes.ICONST_0); 245 | methodNode.visitVarInsn(Opcodes.ISTORE, 7); 246 | methodNode.visitJumpInsn(Opcodes.GOTO, thirdLabel); 247 | methodNode.visitLabel(fourthLabel); 248 | methodNode.visitFrame( 249 | Opcodes.F_FULL, 8, new Object[] { "java/lang/String", Opcodes.INTEGER, "java/lang/StackTraceElement", 250 | "java/lang/String", Opcodes.INTEGER, Opcodes.INTEGER, "[C", Opcodes.INTEGER }, 251 | 0, new Object[] {}); 252 | methodNode.visitVarInsn(Opcodes.ALOAD, 6); 253 | methodNode.visitVarInsn(Opcodes.ILOAD, 7); 254 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 255 | methodNode.visitVarInsn(Opcodes.ILOAD, 7); 256 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false); 257 | methodNode.visitVarInsn(Opcodes.ILOAD, 5); 258 | methodNode.visitInsn(Opcodes.IXOR); 259 | methodNode.visitInsn(Opcodes.I2C); 260 | methodNode.visitInsn(Opcodes.CASTORE); 261 | methodNode.visitIincInsn(7, 1); 262 | methodNode.visitLabel(thirdLabel); 263 | methodNode.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 264 | methodNode.visitVarInsn(Opcodes.ILOAD, 7); 265 | methodNode.visitVarInsn(Opcodes.ALOAD, 0); 266 | methodNode.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 267 | methodNode.visitJumpInsn(Opcodes.IF_ICMPLT, fourthLabel); 268 | methodNode.visitTypeInsn(Opcodes.NEW, "java/lang/String"); 269 | methodNode.visitInsn(Opcodes.DUP); 270 | methodNode.visitVarInsn(Opcodes.ALOAD, 6); 271 | methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/String", "", "([C)V", false); 272 | methodNode.visitInsn(Opcodes.ARETURN); 273 | methodNode.visitMaxs(4, 8); 274 | methodNode.visitEnd(); 275 | return methodNode; 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/optimization/ConstantTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.optimization; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.tree.ClassNode; 6 | 7 | import tk.netindev.scuti.core.configuration.Configuration; 8 | import tk.netindev.scuti.core.transform.Transformer; 9 | 10 | /** 11 | * 12 | * @author netindev 13 | * 14 | */ 15 | public class ConstantTransformer extends Transformer { 16 | 17 | public ConstantTransformer(final Configuration configuration, final Map classes, 18 | final Map dependencies) { 19 | super(configuration, classes, dependencies); 20 | } 21 | 22 | @Override 23 | public void transform() { 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/optimization/DeadCodeTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.optimization; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Arrays; 5 | import java.util.Map; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.tree.ClassNode; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import tk.netindev.scuti.core.configuration.Configuration; 14 | import tk.netindev.scuti.core.transform.Transformer; 15 | 16 | /** 17 | * 18 | * @author netindev 19 | * 20 | */ 21 | public class DeadCodeTransformer extends Transformer { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(DeadCodeTransformer.class.getName()); 24 | 25 | public DeadCodeTransformer(final Configuration configuration, final Map classes, 26 | final Map dependencies) { 27 | super(configuration, classes, dependencies); 28 | LOGGER.info(" Dead Code Transformer -> "); 29 | } 30 | 31 | @Override 32 | public void transform() { 33 | final AtomicInteger insnCount = new AtomicInteger(); 34 | this.getClasses().values() 35 | .forEach(classNode -> classNode.methods.stream().filter( 36 | methodNode -> (!Modifier.isAbstract(methodNode.access) || !Modifier.isNative(methodNode.access)) 37 | && methodNode.instructions.getFirst() != null) 38 | .forEach(methodNode -> { 39 | Arrays.stream(methodNode.instructions.toArray()).forEach(insnNode -> { 40 | if (insnNode.getOpcode() == Opcodes.POP) { 41 | if (insnNode.getPrevious().getOpcode() == Opcodes.ILOAD 42 | || insnNode.getPrevious().getOpcode() == Opcodes.FLOAD 43 | || insnNode.getPrevious().getOpcode() == Opcodes.ALOAD 44 | || insnNode.getPrevious().getOpcode() == Opcodes.LLOAD) { 45 | methodNode.instructions.remove(insnNode.getPrevious()); 46 | methodNode.instructions.remove(insnNode); 47 | insnCount.addAndGet(2); 48 | } 49 | } else if (insnNode.getOpcode() == Opcodes.POP2) { 50 | if (insnNode.getPrevious().getOpcode() == Opcodes.DLOAD 51 | || insnNode.getPrevious().getOpcode() == Opcodes.LLOAD) { 52 | methodNode.instructions.remove(insnNode.getPrevious()); 53 | methodNode.instructions.remove(insnNode); 54 | insnCount.addAndGet(2); 55 | } else if (insnNode.getPrevious().getOpcode() == Opcodes.ILOAD 56 | || insnNode.getPrevious().getOpcode() == Opcodes.FLOAD 57 | || insnNode.getPrevious().getOpcode() == Opcodes.ALOAD) { 58 | if (insnNode.getPrevious().getPrevious().getOpcode() == Opcodes.ILOAD 59 | || insnNode.getPrevious().getPrevious().getOpcode() == Opcodes.FLOAD 60 | || insnNode.getPrevious().getPrevious().getOpcode() == Opcodes.ALOAD) { 61 | methodNode.instructions.remove(insnNode.getPrevious().getPrevious()); 62 | methodNode.instructions.remove(insnNode.getPrevious()); 63 | methodNode.instructions.remove(insnNode); 64 | insnCount.addAndGet(3); 65 | } 66 | } 67 | } 68 | }); 69 | })); 70 | if (insnCount.get() > 0) { 71 | LOGGER.info(" - Removed " + insnCount.get() + " instructions"); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/optimization/LoopTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.optimization; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.tree.ClassNode; 6 | 7 | import tk.netindev.scuti.core.configuration.Configuration; 8 | import tk.netindev.scuti.core.transform.Transformer; 9 | 10 | public class LoopTransformer extends Transformer { 11 | 12 | public LoopTransformer(final Configuration configuration, final Map classes, 13 | final Map dependencies) { 14 | super(configuration, classes, dependencies); 15 | } 16 | 17 | @Override 18 | public void transform() { 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/optimization/NoOperationTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.optimization; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import tk.netindev.scuti.core.configuration.Configuration; 13 | import tk.netindev.scuti.core.transform.Transformer; 14 | import tk.netindev.scuti.core.transform.obfuscation.ClassEncryptTransformer; 15 | 16 | /** 17 | * 18 | * @author netindev 19 | * 20 | */ 21 | public class NoOperationTransformer extends Transformer { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(ClassEncryptTransformer.class.getName()); 24 | 25 | public NoOperationTransformer(final Configuration configuration, final Map classes, 26 | final Map dependencies) { 27 | super(configuration, classes, dependencies); 28 | LOGGER.info(" No Operation Transformer ->"); 29 | } 30 | 31 | @Override 32 | public void transform() { 33 | final AtomicInteger insnCount = new AtomicInteger(); 34 | this.getClasses().values().forEach( 35 | classNode -> classNode.methods.forEach(methodNode -> Arrays.stream(methodNode.instructions.toArray()) 36 | .filter(insnNode -> insnNode.getOpcode() == Opcodes.NOP).forEach(insnNode -> { 37 | insnCount.incrementAndGet(); 38 | methodNode.instructions.remove(insnNode); 39 | }))); 40 | if (insnCount.get() > 0) { 41 | LOGGER.info(" - Removed " + insnCount.get() + " instructions"); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/optimization/PeepholeTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.optimization; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.tree.ClassNode; 6 | 7 | import tk.netindev.scuti.core.configuration.Configuration; 8 | import tk.netindev.scuti.core.transform.Transformer; 9 | 10 | public class PeepholeTransformer extends Transformer { 11 | 12 | public PeepholeTransformer(final Configuration configuration, final Map classes, 13 | final Map dependencies) { 14 | super(configuration, classes, dependencies); 15 | } 16 | 17 | @Override 18 | public void transform() { 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/optimization/RedundantTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.optimization; 2 | 3 | import java.util.Map; 4 | 5 | import org.objectweb.asm.tree.ClassNode; 6 | 7 | import tk.netindev.scuti.core.configuration.Configuration; 8 | import tk.netindev.scuti.core.transform.Transformer; 9 | 10 | public class RedundantTransformer extends Transformer { 11 | 12 | public RedundantTransformer(final Configuration configuration, final Map classes, 13 | final Map dependencies) { 14 | super(configuration, classes, dependencies); 15 | } 16 | 17 | @Override 18 | public void transform() { 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/shrinking/InnerClassTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.shrinking; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | import org.objectweb.asm.tree.ClassNode; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import tk.netindev.scuti.core.configuration.Configuration; 11 | import tk.netindev.scuti.core.transform.Transformer; 12 | 13 | /** 14 | * 15 | * @author netindev 16 | * 17 | */ 18 | public class InnerClassTransformer extends Transformer { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(InnerClassTransformer.class.getName()); 21 | 22 | public InnerClassTransformer(final Configuration configuration, final Map classes, 23 | final Map dependencies) { 24 | super(configuration, classes, dependencies); 25 | LOGGER.info(" Inner Class Tranformer ->"); 26 | } 27 | 28 | @Override 29 | public void transform() { 30 | final AtomicInteger innerCount = new AtomicInteger(); 31 | this.getClasses().values().forEach(classNode -> { 32 | classNode.outerClass = null; 33 | classNode.outerMethod = null; 34 | classNode.outerMethodDesc = null; 35 | 36 | innerCount.addAndGet(classNode.innerClasses.size()); 37 | classNode.innerClasses.clear(); 38 | }); 39 | if (innerCount.get() > 0) { 40 | LOGGER.info(" - Removed " + innerCount.get() + " inner classes"); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/transform/shrinking/UnusedMemberTransformer.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.transform.shrinking; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import org.objectweb.asm.Handle; 10 | import org.objectweb.asm.Type; 11 | import org.objectweb.asm.tree.AbstractInsnNode; 12 | import org.objectweb.asm.tree.AnnotationNode; 13 | import org.objectweb.asm.tree.ClassNode; 14 | import org.objectweb.asm.tree.FieldInsnNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | import org.objectweb.asm.tree.FrameNode; 17 | import org.objectweb.asm.tree.InvokeDynamicInsnNode; 18 | import org.objectweb.asm.tree.LdcInsnNode; 19 | import org.objectweb.asm.tree.MethodInsnNode; 20 | import org.objectweb.asm.tree.MethodNode; 21 | import org.objectweb.asm.tree.MultiANewArrayInsnNode; 22 | import org.objectweb.asm.tree.TryCatchBlockNode; 23 | import org.objectweb.asm.tree.TypeInsnNode; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import tk.netindev.scuti.core.configuration.Configuration; 28 | import tk.netindev.scuti.core.rewrite.Hierarchy; 29 | import tk.netindev.scuti.core.transform.Transformer; 30 | import tk.netindev.scuti.core.util.ASMUtil; 31 | import tk.netindev.scuti.core.util.Util; 32 | 33 | /** 34 | * 35 | * @author netindev 36 | * 37 | */ 38 | public class UnusedMemberTransformer extends Transformer { 39 | 40 | private static final Logger LOGGER = LoggerFactory.getLogger(UnusedMemberTransformer.class); 41 | 42 | private final Hierarchy unusedHierarchy = new Hierarchy(this.getClasses(), this.getDependencies()); 43 | 44 | public UnusedMemberTransformer(final Configuration configuration, final Map classes, 45 | final Map dependencies) { 46 | super(configuration, classes, dependencies); 47 | LOGGER.info(" Unused Members Transformer ->"); 48 | } 49 | 50 | @Override 51 | public void transform() { 52 | LOGGER.info(" - Analysing..."); 53 | final Set classes = new HashSet<>(); 54 | final AtomicInteger foundField = new AtomicInteger(), foundMethod = new AtomicInteger(); 55 | Util.sortByComparator(this.getClasses()).values().forEach(classNode -> { 56 | if (!this.getConfiguration().getUnusedMembers().getKeepClasses().contains(classNode.name)) { 57 | if (this.getConfiguration().getUnusedMembers().isClasses()) { 58 | if (this.isUnusedClass(classNode)) { 59 | classes.add(classNode); 60 | } 61 | } 62 | if (this.getConfiguration().getUnusedMembers().isMethods()) { 63 | final Set methods = new HashSet<>(); 64 | classNode.methods.forEach(methodNode -> { 65 | if (this.isUnusedMethod(classNode, methodNode)) { 66 | foundMethod.incrementAndGet(); 67 | methods.add(methodNode); 68 | } 69 | }); 70 | classNode.methods.removeAll(methods); 71 | } 72 | if (this.getConfiguration().getUnusedMembers().isFields()) { 73 | final Set fields = new HashSet<>(); 74 | classNode.fields.forEach(fieldNode -> { 75 | if (this.isUnusedField(fieldNode)) { 76 | foundField.incrementAndGet(); 77 | fields.add(fieldNode); 78 | } 79 | }); 80 | classNode.fields.removeAll(fields); 81 | } 82 | } 83 | }); 84 | if (classes.size() > 0) { 85 | LOGGER.info(" - Removed " + classes.size() + " classes"); 86 | } 87 | if (foundMethod.get() > 0) { 88 | LOGGER.info(" - Removed " + foundMethod.get() + " methods"); 89 | } 90 | if (foundField.get() > 0) { 91 | LOGGER.info(" - Removed " + foundField.get() + " fields"); 92 | } 93 | classes.forEach(classNode -> this.getClasses().remove(classNode.name, classNode)); 94 | } 95 | 96 | private boolean isUnusedClass(final ClassNode classNode) { 97 | if (this.hasMain(classNode)) { 98 | return false; 99 | } 100 | for (final ClassNode classNode_ : this.getClasses().values()) { 101 | if (classNode_.superName.equals(classNode.name)) { 102 | return false; 103 | } else if (classNode_.interfaces.contains(classNode.name)) { 104 | return false; 105 | } 106 | for (final MethodNode methodNode : classNode_.methods) { 107 | if (this.hasAnnotation(methodNode.visibleAnnotations, classNode)) { 108 | return false; 109 | } else if (this.contains(methodNode.exceptions, classNode)) { 110 | return false; 111 | } else if (methodNode.desc.contains(classNode.name)) { 112 | return false; 113 | } 114 | for (final TryCatchBlockNode tryCatchBlockNode : methodNode.tryCatchBlocks) { 115 | if (tryCatchBlockNode.type != null) { 116 | if (tryCatchBlockNode.type.contains(classNode.name)) { 117 | return false; 118 | } 119 | } 120 | } 121 | for (final AbstractInsnNode insnNode : methodNode.instructions.toArray()) { 122 | if (insnNode instanceof MethodInsnNode) { 123 | final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; 124 | if (methodInsnNode.owner.equals(classNode.name)) { 125 | return false; 126 | } else if (methodInsnNode.desc.contains(classNode.name)) { 127 | return false; 128 | } 129 | } else if (insnNode instanceof FieldInsnNode) { 130 | final FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode; 131 | if (fieldInsnNode.owner.equals(classNode.name)) { 132 | return false; 133 | } else if (fieldInsnNode.desc.contains(classNode.name)) { 134 | return false; 135 | } 136 | } else if (insnNode instanceof TypeInsnNode) { 137 | final TypeInsnNode typeInsnNode = (TypeInsnNode) insnNode; 138 | if (typeInsnNode.desc.contains(classNode.name)) { 139 | return false; 140 | } 141 | } else if (insnNode instanceof LdcInsnNode) { 142 | final LdcInsnNode ldcInsnNode = (LdcInsnNode) insnNode; 143 | if (ldcInsnNode.cst instanceof Type) { 144 | if (((Type) ldcInsnNode.cst).getDescriptor().contains(classNode.name)) { 145 | return false; 146 | } 147 | } else if (ldcInsnNode.cst instanceof String) { 148 | if (((String) ldcInsnNode.cst).contains(classNode.name)) { 149 | return false; 150 | } 151 | } 152 | } else if (insnNode instanceof InvokeDynamicInsnNode) { 153 | final InvokeDynamicInsnNode invokeDynamicInsnNode = (InvokeDynamicInsnNode) insnNode; 154 | if (invokeDynamicInsnNode.desc.contains(classNode.name)) { 155 | return false; 156 | } 157 | final Object[] bsmArgs = invokeDynamicInsnNode.bsmArgs; 158 | for (final Object bsmArg : bsmArgs) { 159 | if (bsmArg instanceof Type) { 160 | if (((Type) bsmArg).getDescriptor().contains(classNode.name)) { 161 | return false; 162 | } 163 | } 164 | if (bsmArg instanceof Handle) { 165 | final Handle handle = (Handle) bsmArg; 166 | if (handle.getOwner().equals(classNode.name) 167 | || handle.getDesc().endsWith(classNode.name)) { 168 | return false; 169 | } 170 | } 171 | } 172 | } else if (insnNode instanceof FrameNode) { 173 | final FrameNode frameNode = (FrameNode) insnNode; 174 | if (frameNode.local != null) { 175 | for (final Object local : frameNode.local) { 176 | if (local instanceof String) { 177 | if (((String) local).contains(classNode.name)) { 178 | return false; 179 | } 180 | } 181 | } 182 | } 183 | if (frameNode.stack != null) { 184 | for (final Object stack : frameNode.stack) { 185 | if (stack instanceof String) { 186 | if (((String) stack).contains(classNode.name)) { 187 | return false; 188 | } 189 | } 190 | } 191 | } 192 | } else if (insnNode instanceof MultiANewArrayInsnNode) { 193 | final MultiANewArrayInsnNode multiANewArrayInsnNode = (MultiANewArrayInsnNode) insnNode; 194 | if (multiANewArrayInsnNode.desc.contains(classNode.name)) { 195 | return false; 196 | } 197 | } 198 | } 199 | } 200 | for (final FieldNode fieldNode : classNode_.fields) { 201 | if (this.hasAnnotation(fieldNode.visibleAnnotations, classNode)) { 202 | return false; 203 | } else if (fieldNode.desc.contains(classNode.name)) { 204 | return false; 205 | } 206 | } 207 | if (this.hasAnnotation(classNode_.visibleAnnotations, classNode)) { 208 | return false; 209 | } 210 | } 211 | return true; 212 | } 213 | 214 | private boolean isUnusedMethod(final ClassNode classNode, final MethodNode methodNode) { 215 | if (ASMUtil.isInitializer(methodNode)) { 216 | return false; 217 | } else if (methodNode.name.equals("main") && methodNode.desc.equals("([Ljava/lang/String;)V")) { 218 | return false; 219 | } else if (methodNode.name.startsWith("lambda$")) { 220 | return false; 221 | } else if (methodNode.visibleAnnotations != null && !methodNode.visibleAnnotations.isEmpty()) { 222 | return false; 223 | } 224 | for (final ClassNode classNode_ : this.getClasses().values()) { 225 | for (final MethodNode methodNode_ : classNode_.methods) { 226 | for (final AbstractInsnNode insnNode : methodNode_.instructions.toArray()) { 227 | if (insnNode instanceof MethodInsnNode) { 228 | if (((MethodInsnNode) insnNode).name.equals(methodNode.name)) { 229 | return false; 230 | } 231 | } else if (insnNode instanceof InvokeDynamicInsnNode) { 232 | for (final Object args : ((InvokeDynamicInsnNode) insnNode).bsmArgs) { 233 | if (args.toString().contains(methodNode.name)) { 234 | return false; 235 | } 236 | } 237 | } 238 | } 239 | } 240 | } 241 | if (this.iterateParents(this.unusedHierarchy.getTree(classNode.name).getParentClasses(), new HashSet<>()) 242 | .contains(methodNode.name) 243 | || this.iterateInterfaces(classNode.interfaces, new HashSet<>()).contains(methodNode.name)) { 244 | return false; 245 | } 246 | return true; 247 | } 248 | 249 | private Set iterateParents(final Set parents, final Set set) { 250 | parents.stream().map(parent -> this.unusedHierarchy.getClassNode(parent)).forEach(parent -> { 251 | set.addAll(this.iterateInterfaces(parent.interfaces, new HashSet<>())); 252 | parent.methods.forEach(methodNode -> set.add(methodNode.name)); 253 | }); 254 | parents.forEach(parent -> { 255 | this.iterateParents(this.unusedHierarchy.getTree(parent).getParentClasses(), set); 256 | }); 257 | return set; 258 | } 259 | 260 | private Set iterateInterfaces(final List interfaces, final Set set) { 261 | interfaces.stream().map(interfaceNode -> this.unusedHierarchy.getClassNode(interfaceNode)) 262 | .forEach(interfaceNode -> { 263 | interfaceNode.methods.forEach(methodNode -> set.add(methodNode.name)); 264 | }); 265 | interfaces.forEach(parent -> { 266 | this.iterateInterfaces(this.unusedHierarchy.getTree(parent).getClassNode().interfaces, set); 267 | }); 268 | return set; 269 | } 270 | 271 | private boolean isUnusedField(final FieldNode fieldNode) { 272 | if (fieldNode.visibleAnnotations != null && !fieldNode.visibleAnnotations.isEmpty()) { 273 | return false; 274 | } 275 | for (final ClassNode classNode : this.getClasses().values()) { 276 | for (final MethodNode methodNode : classNode.methods) { 277 | for (final AbstractInsnNode insnNode : methodNode.instructions.toArray()) { 278 | if (insnNode instanceof FieldInsnNode) { 279 | if (((FieldInsnNode) insnNode).name.equals(fieldNode.name)) { 280 | return false; 281 | } 282 | } 283 | } 284 | } 285 | } 286 | return true; 287 | } 288 | 289 | private boolean hasMain(final ClassNode classNode) { 290 | for (final MethodNode methodNode : classNode.methods) { 291 | if (methodNode.name.equals("main") && methodNode.desc.equals("([Ljava/lang/String;)V")) { 292 | return true; 293 | } 294 | } 295 | return false; 296 | } 297 | 298 | private boolean hasAnnotation(final List list, final ClassNode classNode) { 299 | if (list != null) { 300 | for (final AnnotationNode annotationNode : list) { 301 | if (this.getName(annotationNode.desc).equals(this.getName(classNode.name))) { 302 | return true; 303 | } 304 | } 305 | } 306 | return false; 307 | } 308 | 309 | private boolean contains(final List list, final ClassNode classNode) { 310 | if (list != null) { 311 | for (final String string : list) { 312 | if (string.equals(classNode.name)) { 313 | return true; 314 | } 315 | } 316 | } 317 | return false; 318 | } 319 | 320 | private String getName(final String string) { 321 | return string.contains("/") ? string.substring(string.lastIndexOf("/") + 1).replace(";", "") 322 | : string.replace(";", ""); 323 | } 324 | 325 | } 326 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/util/ASMUtil.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Optional; 5 | 6 | import org.objectweb.asm.Opcodes; 7 | import org.objectweb.asm.tree.AbstractInsnNode; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.objectweb.asm.tree.InsnNode; 10 | import org.objectweb.asm.tree.IntInsnNode; 11 | import org.objectweb.asm.tree.LabelNode; 12 | import org.objectweb.asm.tree.LdcInsnNode; 13 | import org.objectweb.asm.tree.MethodNode; 14 | 15 | /** 16 | * 17 | * @author netindev 18 | * 19 | */ 20 | public class ASMUtil { 21 | 22 | public static boolean isInitializer(final MethodNode methodNode) { 23 | return methodNode.name.contains("<") || methodNode.name.contains(">"); 24 | } 25 | 26 | public static int getOpcodeInsn(final int value) { 27 | if (value >= -1 && value <= 5) { 28 | return value + 0x3; /* ICONST_M1 0x2 (-0x1 + 0x3 = 0x2)... */ 29 | } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 30 | return Opcodes.BIPUSH; 31 | } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 32 | return Opcodes.SIPUSH; 33 | } 34 | throw new RuntimeException("Expected value over -1 and under Short.MAX_VALUE"); 35 | } 36 | 37 | public static AbstractInsnNode toInsnNode(final int value) { 38 | if (value >= -1 && value <= 5) { 39 | return new InsnNode(value + 0x3); /* ICONST_M1 0x2 (-0x1 + 0x3 = 0x2)... */ 40 | } else if (value > Byte.MIN_VALUE && value < Byte.MAX_VALUE) { 41 | return new IntInsnNode(Opcodes.BIPUSH, value); 42 | } else if (value > Short.MIN_VALUE && value < Short.MAX_VALUE) { 43 | return new IntInsnNode(Opcodes.SIPUSH, value); 44 | } 45 | return new LdcInsnNode(value); 46 | } 47 | 48 | public static boolean isMainMethod(MethodNode methodNode) { 49 | return methodNode.name.equals("main") && methodNode.desc.equals("([Ljava/lang/String;)V"); 50 | } 51 | 52 | public static boolean isLambdaMethod(MethodNode methodNode) { 53 | return methodNode.name.startsWith("lambda$"); 54 | } 55 | 56 | public static MethodNode getOrCreateClinit(final ClassNode classNode) { 57 | final Optional optional = classNode.methods.stream() 58 | .filter(methodNode -> methodNode.name.equals("")).findFirst(); 59 | if (optional.isPresent()) { 60 | return optional.get(); 61 | } 62 | final MethodNode methodNode = new MethodNode(Opcodes.ACC_STATIC, "", "()V", null, null); 63 | methodNode.visitCode(); 64 | methodNode.visitInsn(Opcodes.RETURN); 65 | methodNode.visitEnd(); 66 | classNode.methods.add(methodNode); 67 | return methodNode; 68 | } 69 | 70 | public static LabelNode getFirstLabel(final MethodNode methodNode) { 71 | return (LabelNode) Arrays.stream(methodNode.instructions.toArray()) 72 | .filter(insnNode -> insnNode instanceof LabelNode).findFirst() 73 | .orElseThrow(() -> new RuntimeException("That shouldn't happen")); 74 | } 75 | 76 | public static LabelNode getLastLabel(final MethodNode methodNode) { 77 | return (LabelNode) Arrays.stream(methodNode.instructions.toArray()) 78 | .filter(insnNode -> insnNode instanceof LabelNode).reduce((first, second) -> second) 79 | .orElseThrow(() -> new RuntimeException("That shouldn't happen")); 80 | } 81 | 82 | public static boolean hasLabels(final MethodNode methodNode) { 83 | return Arrays.stream(methodNode.instructions.toArray()).filter(insnNode -> insnNode instanceof LabelNode) 84 | .findFirst().isPresent(); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/util/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.util; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | 6 | /** 7 | * 8 | * @author netindev 9 | * 10 | */ 11 | public class RandomUtil { 12 | 13 | private static final Random RANDOM; 14 | 15 | public static boolean getRandom() { 16 | return RANDOM.nextBoolean(); 17 | } 18 | 19 | public static int getRandom(final int bound) { 20 | return RANDOM.nextInt(bound); 21 | } 22 | 23 | public static int getRandom(final int min, final int max) { 24 | return min + RANDOM.nextInt(max - min); 25 | } 26 | 27 | static { 28 | RANDOM = new Random(ThreadLocalRandom.current().nextInt()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.util; 2 | 3 | import java.text.DecimalFormat; 4 | 5 | /** 6 | * 7 | * @author netindev 8 | * 9 | */ 10 | public class StringUtil { 11 | 12 | private static final StringBuilder MASSIVE_STRING; 13 | 14 | public static String getMassiveString() { 15 | if (MASSIVE_STRING.length() > 0) { 16 | return MASSIVE_STRING.toString(); 17 | } 18 | for (int i = 0; i < Short.MAX_VALUE; i++) { 19 | MASSIVE_STRING.append(" "); 20 | } 21 | return MASSIVE_STRING.toString(); 22 | } 23 | 24 | public static String getRandomString() { 25 | return Long.toHexString(Double.doubleToLongBits(Math.random())); 26 | } 27 | 28 | public static String getFileSize(final long size) { 29 | final DecimalFormat decimalFormat = new DecimalFormat("0.00"); 30 | final float kilobytes = 1024.0F, megabytes = kilobytes * kilobytes; 31 | return size < megabytes ? decimalFormat.format(size / kilobytes) + " KB" 32 | : decimalFormat.format(size / megabytes) + " MB"; 33 | } 34 | 35 | static { 36 | MASSIVE_STRING = new StringBuilder(Short.MAX_VALUE - 1); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /scuti-core/src/main/java/tk/netindev/scuti/core/util/Util.java: -------------------------------------------------------------------------------- 1 | package tk.netindev.scuti.core.util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.lang.reflect.Field; 7 | import java.util.Collections; 8 | import java.util.LinkedHashMap; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Map.Entry; 13 | import java.util.zip.CRC32; 14 | import java.util.zip.ZipOutputStream; 15 | 16 | import org.objectweb.asm.tree.ClassNode; 17 | 18 | /** 19 | * 20 | * @author netindev 21 | * 22 | */ 23 | public class Util { 24 | 25 | public static byte[] toByteArray(final InputStream inputStream) throws IOException { 26 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 27 | final byte[] buffer = new byte[0xFFFF]; 28 | int length; 29 | while ((length = inputStream.read(buffer)) != -1) { 30 | outputStream.write(buffer, 0, length); 31 | } 32 | outputStream.flush(); 33 | return outputStream.toByteArray(); 34 | } 35 | 36 | public static String xor(final String string, final int key) { 37 | final StringBuilder stringBuilder = new StringBuilder(); 38 | for (int i = 0; i < string.length(); ++i) { 39 | stringBuilder.append((char) (string.charAt(i) ^ key)); 40 | } 41 | return stringBuilder.toString(); 42 | } 43 | 44 | public static byte[] xor(final byte[] array, final int key) { 45 | final byte[] bytes = new byte[array.length]; 46 | for (int i = 0; i < array.length; ++i) { 47 | bytes[i] = (byte) (array[i] ^ key); 48 | } 49 | return bytes; 50 | } 51 | 52 | public static Map sortByComparator(final Map unsortMap) { 53 | final List> list = new LinkedList<>(unsortMap.entrySet()); 54 | Collections.sort(list, (first, second) -> first.getValue().name.compareTo(second.getValue().name)); 55 | final Map sortedMap = new LinkedHashMap<>(); 56 | for (final Entry entry : list) { 57 | sortedMap.put(entry.getKey(), entry.getValue()); 58 | } 59 | return sortedMap; 60 | } 61 | 62 | public static void corruptCRC32(final ZipOutputStream outputStream) throws Exception { 63 | final Field field = ZipOutputStream.class.getDeclaredField("crc"); 64 | field.setAccessible(true); 65 | field.set(outputStream, new CRC32() { 66 | 67 | @Override 68 | public void update(final byte[] bytes, final int i, final int length) { 69 | return; 70 | } 71 | 72 | @Override 73 | public long getValue() { 74 | return RandomUtil.getRandom(0, Integer.MAX_VALUE); 75 | } 76 | }); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /scuti-core/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.logFile=System.out 2 | org.slf4j.simpleLogger.showDateTime=true 3 | org.slf4j.simpleLogger.dateTimeFormat=[HH:mm:ss] 4 | org.slf4j.simpleLogger.showThreadName=false 5 | org.slf4j.simpleLogger.showLogName=false 6 | org.slf4j.simpleLogger.levelInBrackets=true --------------------------------------------------------------------------------