├── .gitattributes ├── .gitignore ├── COPYING ├── README.MD ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── github │ │ └── jasmo │ │ ├── Bootstrap.java │ │ ├── obfuscate │ │ ├── FullAccessFlags.java │ │ ├── InlineAccessors.java │ │ ├── Obfuscator.java │ │ ├── RemoveDebugInfo.java │ │ ├── ScrambleClasses.java │ │ ├── ScrambleFields.java │ │ ├── ScrambleMethods.java │ │ ├── ScrambleStrings.java │ │ ├── ShuffleMembers.java │ │ └── Transformer.java │ │ ├── query │ │ ├── AnyOf.java │ │ ├── Query.java │ │ └── QueryUtil.java │ │ └── util │ │ ├── BytecodeHelper.java │ │ ├── ClassPath.java │ │ ├── QueryGenerator.java │ │ └── UniqueStringGenerator.java └── resources │ └── log4j2.xml └── test ├── all-tests.bash ├── loop-tests.bash ├── obfuscate-self-with-obfuscated.bash └── obfuscate-self.bash /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################ 2 | ### Defaults ### 3 | ################ 4 | Thumbs.db 5 | ehthumbs.db 6 | Desktop.ini 7 | $RECYCLE.BIN/ 8 | *.cab 9 | *.msi 10 | *.msm 11 | *.msp 12 | *.lnk 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | ._* 17 | .Spotlight-V100 18 | .Trashes 19 | .AppleDB 20 | .AppleDesktop 21 | Network Trash Folder 22 | Temporary Items 23 | .apdisk 24 | ################# 25 | ### Generated ### 26 | ################# 27 | .idea 28 | target 29 | ################# -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 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 | # java-asm-obfuscator (jasmo) 2 | Obfuscates compiled java code to make it harder to reverse engineer. 3 | 4 | ```text 5 | usage: java -jar jasmo.jar [-c ] [-h] [-k ] [-p ] [-v] 6 | -c,--cfn Enable 'crazy fucking names' and set name length (large names == large output size) 7 | -h,--help Print help message 8 | -k,--keep Don't rename this class 9 | -p,--package Move obfuscated classes to this package 10 | -v,--verbose Increase verbosity 11 | ``` 12 | 13 | Current Obfuscators: 14 | - 15 | 16 |
17 |
Shuffle Members
18 |
Shuffles the order of classes, methods, fields, annotations, interfaces, exceptions etc
19 |
Inline Accessors
20 |
Removes get/set methods, replacing them with direct field access
21 |
Remove Debug Information
22 |
Removes debugging information left by the compiler such as variable names and line numbers
23 |
Scramble Classes
24 |
Renames classes, optionally skipping classes provided by the --keep option, all classes are moved to the package provided by the --package option (otherwise to the base package)
25 |
Scramble Fields
26 |
Renames fields
27 |
Scramble Methods
28 |
Renames methods, skipping those that are required not to be (such as main), native or are overrides of external libraries (Libraries should be included in java class path).
29 |
Scramble Strings
30 |
Replaces string constants with an "unscramble" method
31 |
32 | 33 | *** 34 | 35 | The project includes two test scripts in the 'test/' directory 36 | * The first test obfuscates the compiled output of it's self. (Make sure that target/java-asm-obfuscator-*.jar has execution permission) 37 | * The second test obfuscates the compiled output of it's self with the obfuscated output generated by the first test; to make sure that the obfuscated version is functional. (Make sure that target/result.jar has execution permission) 38 | 39 | *** 40 | 41 | For analyzing obfuscated code I suggest using [bytecode-viewer](https://github.com/Konloch/bytecode-viewer), it merges many java reverse engineering tools into one and includes many useful features. 42 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 4.0.0 13 | com.github.jasmo 14 | java-asm-obfuscator 15 | jar 16 | 1.0-SNAPSHOT 17 | 18 | UTF-8 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-dependency-plugin 25 | 26 | 27 | copy-dependencies 28 | prepare-package 29 | 30 | copy-dependencies 31 | 32 | 33 | ${project.build.directory} 34 | false 35 | false 36 | true 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.2 45 | 46 | 1.8 47 | 1.8 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-jar-plugin 53 | 2.4 54 | 55 | 56 | 57 | true 58 | com.github.jasmo.Bootstrap 59 | 60 | 61 | 62 | 63 | 64 | 65 | java-asm-obfuscator 66 | https://github.com/CalebWhiting/java-asm-obfuscator 67 | 68 | 69 | org.ow2.asm 70 | asm-debug-all 71 | LATEST 72 | 73 | 74 | org.apache.logging.log4j 75 | log4j-api 76 | 2.8.2 77 | 78 | 79 | org.apache.logging.log4j 80 | log4j-core 81 | 2.17.1 82 | 83 | 84 | commons-cli 85 | commons-cli 86 | LATEST 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/Bootstrap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo; 9 | 10 | import com.github.jasmo.obfuscate.*; 11 | import com.github.jasmo.util.UniqueStringGenerator; 12 | import org.apache.commons.cli.*; 13 | import org.apache.logging.log4j.Level; 14 | import org.apache.logging.log4j.LogManager; 15 | import org.apache.logging.log4j.Logger; 16 | import org.apache.logging.log4j.core.LoggerContext; 17 | import org.apache.logging.log4j.core.config.Configuration; 18 | import org.apache.logging.log4j.core.config.LoggerConfig; 19 | 20 | import java.nio.file.Paths; 21 | 22 | public class Bootstrap { 23 | 24 | private static final Logger log = LogManager.getLogger("Bootstrap"); 25 | 26 | public static void main(String[] args) { 27 | Options options = new Options() 28 | .addOption("h", "help", false, "Print help message") 29 | .addOption("v", "verbose", false, "Increase verbosity") 30 | .addOption("c", "cfn", true, "Enable 'crazy fucking names and set name length (large names == large output size)'") 31 | .addOption("p", "package", true, "Move obfuscated classes to this package") 32 | .addOption("k", "keep", true, "Don't rename this class"); 33 | try { 34 | CommandLineParser clp = new DefaultParser(); 35 | CommandLine cl = clp.parse(options, args); 36 | if (cl.hasOption("help")) { 37 | help(options); 38 | return; 39 | } 40 | if (cl.hasOption("verbose")) { 41 | LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 42 | Configuration config = ctx.getConfiguration(); 43 | LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); 44 | loggerConfig.setLevel(Level.DEBUG); 45 | ctx.updateLoggers(); 46 | } 47 | String[] keep = cl.getOptionValues("keep"); 48 | if (cl.getArgList().size() < 2) { 49 | throw new ParseException("Expected at-least two arguments"); 50 | } 51 | log.debug("Input: {}, Output: {}", cl.getArgList().get(0), cl.getArgList().get(1)); 52 | Obfuscator o = new Obfuscator(); 53 | try { 54 | o.supply(Paths.get(cl.getArgList().get(0))); 55 | } catch (Exception e) { 56 | log.error("An error occurred while reading the source target", e); 57 | return; 58 | } 59 | try { 60 | UniqueStringGenerator usg; 61 | if (cl.hasOption("cfn")) { 62 | int size = Integer.parseInt(cl.getOptionValue("cfn")); 63 | usg = new UniqueStringGenerator.Crazy(size); 64 | } else { 65 | usg = new UniqueStringGenerator.Default(); 66 | } 67 | o.apply(new FullAccessFlags()); 68 | o.apply(new ScrambleStrings()); 69 | o.apply(new ScrambleClasses(usg, cl.getOptionValue("package", ""), keep == null ? new String[0] : keep)); 70 | o.apply(new ScrambleFields(usg)); 71 | o.apply(new ScrambleMethods(usg)); 72 | o.apply(new InlineAccessors()); 73 | o.apply(new RemoveDebugInfo()); 74 | o.apply(new ShuffleMembers()); 75 | } catch (Exception e) { 76 | log.error("An error occurred while applying transform", e); 77 | return; 78 | } 79 | try { 80 | o.write(Paths.get(cl.getArgList().get(1))); 81 | } catch (Exception e) { 82 | log.error("An error occurred while writing to the destination target", e); 83 | return; 84 | } 85 | } catch (ParseException e) { 86 | log.error("Failed to parse command line arguments", e); 87 | help(options); 88 | } 89 | } 90 | 91 | private static void help(Options options) { 92 | HelpFormatter formatter = new HelpFormatter(); 93 | formatter.printHelp("java -jar jasmo.jar ", options, true); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/FullAccessFlags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import org.apache.logging.log4j.Logger; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.objectweb.asm.Opcodes; 13 | import org.objectweb.asm.tree.ClassNode; 14 | 15 | import java.util.Map; 16 | 17 | /** 18 | * Some transforms require all fields to be public, in order to remove accessors or move package etc 19 | */ 20 | public class FullAccessFlags implements Transformer { 21 | 22 | private static final Logger log = LogManager.getLogger("FullAccessFlags"); 23 | 24 | @Override 25 | public void transform(Map classMap) { 26 | classMap.values().forEach(c -> { 27 | c.access = access(c.access); 28 | c.methods.forEach(m -> m.access = access(m.access)); 29 | c.fields.forEach(m -> m.access = access(m.access)); 30 | }); 31 | } 32 | 33 | private int access(int access) { 34 | int a = Opcodes.ACC_PUBLIC; 35 | if ((access & Opcodes.ACC_NATIVE) != 0) a |= Opcodes.ACC_NATIVE; 36 | if ((access & Opcodes.ACC_ABSTRACT) != 0) a |= Opcodes.ACC_ABSTRACT; 37 | if ((access & Opcodes.ACC_ANNOTATION) != 0) a |= Opcodes.ACC_ANNOTATION; 38 | if ((access & Opcodes.ACC_BRIDGE) != 0) a |= Opcodes.ACC_BRIDGE; 39 | //if ((access & Opcodes.ACC_DEPRECATED) != 0) a |= Opcodes.ACC_DEPRECATED; 40 | if ((access & Opcodes.ACC_ENUM) != 0) a |= Opcodes.ACC_ENUM; 41 | if ((access & Opcodes.ACC_FINAL) != 0) a |= Opcodes.ACC_FINAL; 42 | if ((access & Opcodes.ACC_INTERFACE) != 0) a |= Opcodes.ACC_INTERFACE; 43 | if ((access & Opcodes.ACC_MANDATED) != 0) a |= Opcodes.ACC_MANDATED; 44 | if ((access & Opcodes.ACC_MODULE) != 0) a |= Opcodes.ACC_MODULE; 45 | if ((access & Opcodes.ACC_OPEN) != 0) a |= Opcodes.ACC_OPEN; 46 | if ((access & Opcodes.ACC_STATIC) != 0) a |= Opcodes.ACC_STATIC; 47 | if ((access & Opcodes.ACC_STATIC_PHASE) != 0) a |= Opcodes.ACC_STATIC_PHASE; 48 | if ((access & Opcodes.ACC_STRICT) != 0) a |= Opcodes.ACC_STRICT; 49 | if ((access & Opcodes.ACC_SUPER) != 0) a |= Opcodes.ACC_SUPER; 50 | if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) a |= Opcodes.ACC_SYNCHRONIZED; 51 | if ((access & Opcodes.ACC_SYNTHETIC) != 0) a |= Opcodes.ACC_SYNTHETIC; 52 | if ((access & Opcodes.ACC_TRANSIENT) != 0) a |= Opcodes.ACC_TRANSIENT; 53 | if ((access & Opcodes.ACC_TRANSITIVE) != 0) a |= Opcodes.ACC_TRANSITIVE; 54 | if ((access & Opcodes.ACC_VARARGS) != 0) a |= Opcodes.ACC_VARARGS; 55 | if ((access & Opcodes.ACC_VOLATILE) != 0) a |= Opcodes.ACC_VOLATILE; 56 | return a; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/InlineAccessors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import com.github.jasmo.query.Query; 11 | import com.github.jasmo.query.QueryUtil; 12 | import com.github.jasmo.util.BytecodeHelper; 13 | import com.github.jasmo.util.ClassPath; 14 | import org.apache.logging.log4j.LogManager; 15 | import org.apache.logging.log4j.Logger; 16 | import org.objectweb.asm.*; 17 | import org.objectweb.asm.Type; 18 | import org.objectweb.asm.tree.*; 19 | 20 | import java.lang.reflect.*; 21 | import java.util.*; 22 | 23 | /** 24 | * @author Caleb Whiting 25 | */ 26 | public class InlineAccessors implements Transformer { 27 | 28 | private static final Logger log = LogManager.getLogger("InlineAccessors"); 29 | 30 | private Map classMap; 31 | 32 | @Override 33 | public void transform(Map classMap) { 34 | this.classMap = classMap; 35 | for (ClassNode node : new ArrayList<>(classMap.values())) { 36 | for (FieldNode field : node.fields) { 37 | for (MethodNode method : new ArrayList<>(node.methods)) { 38 | if (isGetterFor(node, field, method)) { 39 | node.methods.remove(method); 40 | log.debug("Inlining getter {}.{}{}", node.name, method.name, method.desc); 41 | replace(Opcodes.GETFIELD, node, field, method); 42 | } 43 | if (isSetterFor(node, field, method)) { 44 | node.methods.remove(method); 45 | log.debug("Inlining setter {}.{}{}", node.name, method.name, method.desc); 46 | replace(Opcodes.PUTFIELD, node, field, method); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | private void replace(int opcode, ClassNode owner, FieldNode field, MethodNode m) { 54 | if (Modifier.isStatic(field.access)) 55 | opcode -= 2; 56 | for (ClassNode cn : classMap.values()) { 57 | for (MethodNode mn : cn.methods) { 58 | AbstractInsnNode[] instructions = mn.instructions.toArray(); 59 | for (AbstractInsnNode node : instructions) { 60 | if (node.getType() != AbstractInsnNode.METHOD_INSN) { 61 | continue; 62 | } 63 | MethodInsnNode min = (MethodInsnNode) node; 64 | List owners = getChildNames(owner); 65 | if (owners.contains(min.owner) && min.name.equals(m.name) && min.desc.equals(m.desc)) { 66 | FieldInsnNode fin = new FieldInsnNode(opcode, min.owner, field.name, field.desc); 67 | log.debug(" replace {}.{}.{}, insn: {}", 68 | cn.name, mn.name, mn.desc, QueryUtil.query(fin, "index")); 69 | mn.instructions.set(min, fin); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | private List getChildNames(ClassNode owner) { 77 | List owners = new ArrayList<>(); 78 | Stack stack = new Stack<>(); 79 | stack.add(owner); 80 | while (stack.size() > 0) { 81 | ClassNode c = stack.pop(); 82 | owners.add(c.name); 83 | classMap.values().stream().filter(check -> 84 | check.superName.equals(c.name)).forEach(stack:: push); 85 | } 86 | return owners; 87 | } 88 | 89 | private boolean isGetterFor(ClassNode owner, FieldNode field, MethodNode method) { 90 | if (local(method.access) == local(field.access) && isTopLevel(owner, method)) { 91 | Type type = Type.getType(field.desc); 92 | Type getType = Type.getMethodType(type); 93 | Type methodType = Type.getMethodType(method.desc); 94 | if (methodType.equals(getType)) { 95 | List instructions = getRealInstructions(method); 96 | Query[] queries = getPattern(true, owner, field); 97 | return matches(instructions, queries); 98 | } 99 | } 100 | return false; 101 | } 102 | 103 | private boolean isSetterFor(ClassNode owner, FieldNode field, MethodNode method) { 104 | if (local(method.access) == local(field.access) && isTopLevel(owner, method)) { 105 | Type type = Type.getType(field.desc); 106 | Type setType = Type.getMethodType(Type.VOID_TYPE, type); 107 | Type methodType = Type.getMethodType(method.desc); 108 | if (methodType.equals(setType)) { 109 | List instructions = getRealInstructions(method); 110 | Query[] queries = getPattern(false, owner, field); 111 | return matches(instructions, queries); 112 | } 113 | } 114 | return false; 115 | } 116 | 117 | private Query[] getPattern(boolean get, ClassNode owner, FieldNode field) { 118 | Type type = Type.getType(field.desc); 119 | List queries = new LinkedList<>(); 120 | boolean local = local(field.access); 121 | if (local) 122 | queries.add(new Query("opcode", Opcodes.ALOAD, "var", 0)); 123 | if (get) { 124 | int opcode = local ? Opcodes.GETFIELD : Opcodes.GETSTATIC; 125 | queries.add(new Query("opcode", opcode, "owner", owner.name, "name", field.name, "desc", field.desc)); 126 | queries.add(new Query("opcode", type.getOpcode(Opcodes.IRETURN))); 127 | } else { 128 | int opcode = local ? Opcodes.PUTFIELD : Opcodes.PUTSTATIC; 129 | queries.add(new Query("opcode", type.getOpcode(Opcodes.ILOAD), "var", 0)); 130 | queries.add(new Query("opcode", opcode, "owner", owner.name, "name", field.name, "desc", field.desc)); 131 | queries.add(new Query("opcode", Opcodes.RETURN)); 132 | } 133 | return queries.toArray(new Query[queries.size()]); 134 | } 135 | 136 | private boolean matches(List list, Query... queries) { 137 | if (list.size() != queries.length) return false; 138 | for (int i = 0; i < list.size(); i++) { 139 | AbstractInsnNode node = list.get(i); 140 | if (!QueryUtil.check(node, queries[i].values())) { 141 | return false; 142 | } 143 | } 144 | return true; 145 | } 146 | 147 | private boolean isTopLevel(ClassNode owner, MethodNode method) { 148 | Stack stack = new Stack<>(); 149 | stack.add(owner); 150 | while (stack.size() > 0) { 151 | ClassNode node = stack.pop(); 152 | if (node != owner && BytecodeHelper.getMethod(node, method.name, method.desc) != null) 153 | return false; 154 | ClassNode superClass = getClass(node.superName); 155 | if (superClass != null) 156 | stack.push(superClass); 157 | if (node.interfaces != null) { 158 | node.interfaces.forEach(iface -> { 159 | ClassNode interfaceClass = getClass(iface); 160 | if (interfaceClass != null) 161 | stack.push(interfaceClass); 162 | }); 163 | } 164 | } 165 | return true; 166 | } 167 | 168 | private ClassNode getClass(String name) { 169 | if (name == null) 170 | return null; 171 | ClassNode c = classMap.get(name); 172 | if (c != null) 173 | return c; 174 | return ClassPath.getInstance().get(name); 175 | } 176 | 177 | private List getRealInstructions(MethodNode method) { 178 | List instructions = new LinkedList<>(); 179 | for (AbstractInsnNode node : method.instructions.toArray()) { 180 | if (node.getOpcode() != -1) 181 | instructions.add(node); 182 | } 183 | return instructions; 184 | } 185 | 186 | private boolean local(int access) { 187 | return (access & Opcodes.ACC_STATIC) == 0; 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/Obfuscator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | import org.objectweb.asm.ClassReader; 13 | import org.objectweb.asm.ClassWriter; 14 | import org.objectweb.asm.tree.ClassNode; 15 | 16 | import java.io.ByteArrayOutputStream; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.nio.file.*; 20 | import java.nio.file.attribute.BasicFileAttributes; 21 | import java.util.*; 22 | import java.util.jar.JarEntry; 23 | import java.util.jar.JarFile; 24 | import java.util.jar.JarOutputStream; 25 | 26 | /** 27 | * @author Caleb Whiting 28 | */ 29 | public class Obfuscator { 30 | 31 | private static final Logger log = LogManager.getLogger("Obfuscator"); 32 | 33 | private final Map classMap = new HashMap<>(); 34 | private final Map files = new HashMap<>(); 35 | 36 | private int readFlags = ClassReader.EXPAND_FRAMES; 37 | private int writeFlags = ClassWriter.COMPUTE_MAXS; 38 | 39 | private void supplyJar(JarFile jar) { 40 | Enumeration entries = jar.entries(); 41 | while (entries.hasMoreElements()) { 42 | JarEntry entry = entries.nextElement(); 43 | try (InputStream in = jar.getInputStream(entry)) { 44 | byte[] bytes; 45 | try (ByteArrayOutputStream tmp = new ByteArrayOutputStream()) { 46 | byte[] buf = new byte[256]; 47 | for (int n; (n = in.read(buf)) != -1; ) { 48 | tmp.write(buf, 0, n); 49 | } 50 | bytes = tmp.toByteArray(); 51 | } 52 | if (!entry.getName().endsWith(".class")) { 53 | getFiles().put(entry.getName(), bytes); 54 | continue; 55 | } 56 | ClassNode c = new ClassNode(); 57 | new ClassReader(bytes).accept(c, getReadFlags()); 58 | getClassMap().put(c.name, c); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } 64 | 65 | public void supply(Path root) throws IOException { 66 | if (root.toString().endsWith(".jar")) { 67 | log.debug("Supplying jar file: {}", root); 68 | supplyJar(new JarFile(root.toAbsolutePath().toString())); 69 | return; 70 | } 71 | if (!Files.isDirectory(root)) 72 | throw new IOException("Cannot specify files, only classpath root directory."); 73 | log.debug("Walking file tree: {}", root); 74 | Files.walkFileTree(root, new HashSet<>(), Integer.MAX_VALUE, new SimpleFileVisitor() { 75 | @Override 76 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 77 | if (file.toString().endsWith(".jar")) { 78 | log.debug(" Supplying jar file: {}", file); 79 | supplyJar(new JarFile(file.toAbsolutePath().toString())); 80 | return FileVisitResult.CONTINUE; 81 | } 82 | String relative = root.relativize(file).toString(); 83 | byte[] bytes = Files.readAllBytes(file); 84 | if (relative.endsWith(".class")) { 85 | log.debug(" Class found: {}", relative); 86 | ClassNode node = new ClassNode(); 87 | ClassReader reader = new ClassReader(bytes); 88 | reader.accept(node, getReadFlags()); 89 | getClassMap().put(node.name, node); 90 | } else { 91 | log.debug(" File found: {}", relative); 92 | getFiles().put(relative, bytes); 93 | } 94 | return FileVisitResult.CONTINUE; 95 | } 96 | 97 | @Override 98 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 99 | return FileVisitResult.CONTINUE; 100 | } 101 | }); 102 | } 103 | 104 | public void apply(Transformer transformer) { 105 | transformer.transform(getClassMap()); 106 | // re-populate class map, so that any changed names are registered 107 | List values = new ArrayList<>(getClassMap().values()); 108 | getClassMap().clear(); 109 | for (ClassNode cn : values) 110 | getClassMap().put(cn.name, cn); 111 | } 112 | 113 | public void write(Path dest) throws IOException { 114 | Files.deleteIfExists(dest); 115 | StandardOpenOption[] override = {StandardOpenOption.CREATE, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING}; 116 | if (dest.toString().endsWith(".jar")) { 117 | try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(dest, override))) { 118 | writeJar(out); 119 | } 120 | } else { 121 | for (ClassNode node : getClassMap().values()) { 122 | ClassWriter writer = new ClassWriter(getWriteFlags()); 123 | node.accept(writer); 124 | /**/ 125 | Files.write(Paths.get(dest.toString(), node.name + ".class"), writer.toByteArray(), override); 126 | } 127 | for (Map.Entry entry : getFiles().entrySet()) { 128 | Files.write(Paths.get(dest.toString(), entry.getKey()), entry.getValue(), override); 129 | } 130 | } 131 | } 132 | 133 | private void writeJar(JarOutputStream out) throws IOException { 134 | for (ClassNode node : getClassMap().values()) { 135 | JarEntry entry = new JarEntry(node.name + ".class"); 136 | out.putNextEntry(entry); 137 | ClassWriter writer = new ClassWriter(getWriteFlags()); 138 | node.accept(writer); 139 | out.write(writer.toByteArray()); 140 | out.closeEntry(); 141 | } 142 | for (Map.Entry entry : getFiles().entrySet()) { 143 | out.putNextEntry(new JarEntry(entry.getKey())); 144 | out.write(entry.getValue()); 145 | out.closeEntry(); 146 | } 147 | } 148 | 149 | public int getReadFlags() { 150 | return readFlags; 151 | } 152 | 153 | public void setReadFlags(int flags) { 154 | this.readFlags = flags; 155 | } 156 | 157 | public int getWriteFlags() { 158 | return writeFlags; 159 | } 160 | 161 | public void setWriteFlags(int writeFlags) { 162 | this.writeFlags = writeFlags; 163 | } 164 | 165 | public Map getFiles() { 166 | return files; 167 | } 168 | 169 | public Map getClassMap() { 170 | return classMap; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/RemoveDebugInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | import org.objectweb.asm.*; 13 | import org.objectweb.asm.tree.*; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * @author Caleb Whiting 19 | */ 20 | public class RemoveDebugInfo implements Transformer { 21 | 22 | private static final Logger log = LogManager.getLogger("RemoveDebugInfo"); 23 | 24 | @Override 25 | public void transform(Map classMap) { 26 | Map map = new HashMap<>(); 27 | for (ClassNode cn : classMap.values()) { 28 | log.debug("Removing debug info from class: {}", cn.name); 29 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 30 | cn.accept(writer); 31 | ClassNode clone = new ClassNode(); 32 | new ClassReader(writer.toByteArray()).accept(clone, ClassReader.SKIP_DEBUG); 33 | map.put(clone.name, clone); 34 | } 35 | classMap.clear(); 36 | classMap.putAll(map); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/ScrambleClasses.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import com.github.jasmo.util.BytecodeHelper; 11 | import com.github.jasmo.util.UniqueStringGenerator; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.objectweb.asm.tree.*; 15 | 16 | import java.util.*; 17 | 18 | /** 19 | * @author Caleb Whiting 20 | */ 21 | public class ScrambleClasses implements Transformer { 22 | 23 | private static final Logger log = LogManager.getLogger("ScrambleClasses"); 24 | 25 | private UniqueStringGenerator generator; 26 | private final String basePackage; 27 | private final List skip; 28 | 29 | public ScrambleClasses(UniqueStringGenerator generator, String basePackage, String... skip) { 30 | this.generator = generator; 31 | this.basePackage = basePackage.replace('.', '/'); 32 | for (int i = 0; i < skip.length; i++) { 33 | skip[i] = skip[i].replace('.', '/'); 34 | } 35 | this.skip = Arrays.asList(skip); 36 | } 37 | 38 | @Override 39 | public void transform(Map classMap) { 40 | generator.reset(); 41 | Map remap = new HashMap<>(); 42 | List keys = new ArrayList<>(classMap.keySet()); 43 | // shuffle order in which names are assigned 44 | // so that they're not always assigned the same name 45 | Collections.shuffle(keys); 46 | for (String key : keys) { 47 | ClassNode cn = classMap.get(key); 48 | String name = cn.name; 49 | if (!skip.contains(name)) { 50 | name = generator.next(); 51 | name = basePackage + "/" + name; 52 | } 53 | remap.put(cn.name, name); 54 | log.debug("Mapping class {} to {}", cn.name, name); 55 | } 56 | BytecodeHelper.applyMappings(classMap, remap); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/ScrambleFields.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import com.github.jasmo.util.BytecodeHelper; 11 | import com.github.jasmo.util.UniqueStringGenerator; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.objectweb.asm.tree.*; 15 | 16 | import java.util.*; 17 | import java.util.stream.*; 18 | 19 | /** 20 | * @author Caleb Whiting 21 | */ 22 | public class ScrambleFields implements Transformer { 23 | private static final Logger log = LogManager.getLogger("ScrambleFields"); 24 | private final UniqueStringGenerator generator; 25 | 26 | public ScrambleFields(UniqueStringGenerator generator) { 27 | this.generator = generator; 28 | } 29 | 30 | @Override 31 | public void transform(Map classMap) { 32 | Map remap = new HashMap<>(); 33 | generator.reset(); 34 | List fields = new ArrayList<>(); 35 | for (ClassNode c : classMap.values()) fields.addAll(c.fields); 36 | Collections.shuffle(fields); 37 | for (FieldNode f : fields) { 38 | ClassNode c = getOwner(f, classMap); 39 | String name = generator.next(); 40 | Stack stack = new Stack<>(); 41 | stack.add(c); 42 | while (stack.size() > 0) { 43 | ClassNode node = stack.pop(); 44 | String key = node.name + "." + f.name; 45 | remap.put(key, name); 46 | stack.addAll(classMap.values().stream(). 47 | filter(cn -> cn.superName.equals(node.name)). 48 | collect(Collectors.toList())); 49 | } 50 | } 51 | BytecodeHelper.applyMappings(classMap, remap); 52 | } 53 | 54 | private ClassNode getOwner(FieldNode f, Map classMap) { 55 | for (ClassNode c : classMap.values()) 56 | if (c.fields.contains(f)) 57 | return c; 58 | return null; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/ScrambleMethods.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import com.github.jasmo.util.BytecodeHelper; 11 | import com.github.jasmo.util.ClassPath; 12 | import com.github.jasmo.util.UniqueStringGenerator; 13 | import org.apache.logging.log4j.LogManager; 14 | import org.apache.logging.log4j.Logger; 15 | import org.objectweb.asm.Opcodes; 16 | import org.objectweb.asm.tree.*; 17 | 18 | import java.util.*; 19 | import java.util.function.*; 20 | 21 | /** 22 | * @author Caleb Whiting 23 | *

24 | * Obfuscates the names of methods in the given library 25 | */ 26 | public class ScrambleMethods implements Transformer { 27 | 28 | private static final Logger log = LogManager.getLogger("ScrambleMethods"); 29 | 30 | private ClassPath env; 31 | private Map classMap; 32 | private final UniqueStringGenerator generator; 33 | 34 | public ScrambleMethods(UniqueStringGenerator generator) { 35 | this.generator = generator; 36 | } 37 | 38 | @Override 39 | public void transform(Map classMap) { 40 | this.classMap = classMap; 41 | // loads all jre libraries from 'java.class.path' system property 42 | this.env = ClassPath.getInstance(); 43 | // todo: add more in-depth verification 44 | List pass = Arrays.asList("main", "createUI"); 45 | // reset the unique string generator, so that is starts at 'a' 46 | generator.reset(); 47 | Map mappings = new HashMap<>(); 48 | List methods = new LinkedList<>(); 49 | for (ClassNode c : classMap.values()) 50 | methods.addAll(c.methods); 51 | // shuffle the methods so that there isn't a naming pattern 52 | Collections.shuffle(methods); 53 | // create obfuscated name mappings 54 | methods: 55 | for (MethodNode m : methods) { 56 | ClassNode owner = getOwner(m); 57 | // skip entry points, constructors etc 58 | if (m.name.indexOf('<') != -1 || pass.contains(m.name) || (m.access & Opcodes.ACC_NATIVE) != 0) { 59 | log.debug("Skipping method: {}.{}{}", owner.name, m.name, m.desc); 60 | continue; 61 | } 62 | Stack stack = new Stack<>(); 63 | stack.add(owner); 64 | // check this is the top-level method 65 | while (stack.size() > 0) { 66 | ClassNode node = stack.pop(); 67 | if (node != owner && getMethod(node, m.name, m.desc) != null) 68 | // not top-level member 69 | continue methods; 70 | // push superclass 71 | ClassNode parent = getClassNode(node.superName); 72 | if (parent != null) 73 | stack.push(parent); 74 | // push interfaces 75 | Set interfaces = new HashSet<>(); 76 | String[] interfacesNames = node.interfaces.toArray(new String[node.interfaces.size()]); 77 | for (String iname : interfacesNames) { 78 | ClassNode iface = getClassNode(iname); 79 | if (iface != null) { 80 | interfaces.add(iface); 81 | } 82 | } 83 | stack.addAll(interfaces); 84 | } 85 | // generate obfuscated name 86 | String name = generator.next(); 87 | stack.add(owner); 88 | // go through all sub-classes, and define the new name 89 | // regardless of if the method exists in the given class or not 90 | while (stack.size() > 0) { 91 | ClassNode node = stack.pop(); 92 | String key = node.name + '.' + m.name + m.desc; 93 | mappings.put(key, name); 94 | // push subclasses 95 | classMap.values().forEach(c -> { 96 | if (c.superName.equals(node.name) || c.interfaces.contains(node.name)) 97 | stack.push(c); 98 | }); 99 | } 100 | } 101 | BytecodeHelper.applyMappings(classMap, mappings); 102 | } 103 | 104 | private MethodNode getMethod(ClassNode node, String name, String desc) { 105 | return findFirst(node.methods, m -> m.name.equals(name) && m.desc.equals(desc)); 106 | } 107 | 108 | private ClassNode getClassNode(String name) { 109 | if (name == null) return null; 110 | ClassNode n = classMap.get(name); 111 | return n == null ? env.get(name) : n; 112 | } 113 | 114 | private ClassNode getOwner(MethodNode m) { 115 | return findFirst(classMap.values(), c -> c.methods.contains(m)); 116 | } 117 | 118 | private T findFirst(Collection collection, Predicate predicate) { 119 | for (T t : collection) 120 | if (predicate.test(t)) 121 | return t; 122 | return null; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/ScrambleStrings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import com.github.jasmo.util.BytecodeHelper; 11 | import org.objectweb.asm.MethodVisitor; 12 | import org.objectweb.asm.Opcodes; 13 | import org.objectweb.asm.Type; 14 | import org.objectweb.asm.commons.InstructionAdapter; 15 | import org.objectweb.asm.tree.*; 16 | 17 | import java.io.UnsupportedEncodingException; 18 | import java.util.*; 19 | 20 | import org.apache.logging.log4j.LogManager; 21 | import org.apache.logging.log4j.Logger; 22 | 23 | import static org.objectweb.asm.Opcodes.*; 24 | 25 | /** 26 | * @author Caleb Whiting 27 | */ 28 | public class ScrambleStrings implements Transformer { 29 | 30 | private static final Logger log = LogManager.getLogger("ScrambleStrings"); 31 | 32 | private static final String FIELD_NAME = "string_store"; 33 | private static final String CALL_NAME = "unscramble"; 34 | private static final String CALL_DESC = "(I)Ljava/lang/String;"; 35 | 36 | private ClassNode unscrambleClass; 37 | private List stringList; 38 | 39 | @Override 40 | public void transform(Map classMap) { 41 | stringList = new ArrayList<>(); 42 | do { 43 | unscrambleClass = (ClassNode) classMap.values().toArray()[new Random().nextInt(classMap.size())]; 44 | } while ((unscrambleClass.access & Opcodes.ACC_INTERFACE) != 0); 45 | // Build string list 46 | log.debug("Building string list"); 47 | classMap.values().stream().flatMap(cn -> cn.methods.stream()).forEach(this::buildStringList); 48 | Collections.shuffle(stringList); 49 | // Replace LDC constants with calls to unscramble 50 | log.debug("Scrambling LDC constants"); 51 | classMap.values().forEach(cn -> cn.methods.forEach(mn -> scramble(cn, mn))); 52 | // Add unscrambling handler 53 | log.debug("Creating {} field containing {} strings", FIELD_NAME, stringList.size()); 54 | unscrambleClass.visitField(ACC_PUBLIC | ACC_STATIC, FIELD_NAME, "[Ljava/lang/String;", null, null); 55 | log.debug("Adding unscramble method to {}.{}{}", unscrambleClass.name, CALL_NAME, CALL_DESC); 56 | createUnscramble(); 57 | try { 58 | createStaticConstructor(unscrambleClass); 59 | } catch (Exception ex) { 60 | log.warn("Failed to transform strings", ex); 61 | } 62 | } 63 | 64 | private void buildStringList(MethodNode mn) { 65 | BytecodeHelper.forEach(mn.instructions, LdcInsnNode.class, ldc -> { 66 | if (ldc.cst instanceof String && !stringList.contains(ldc.cst)) { 67 | stringList.add((String) ldc.cst); 68 | } 69 | }); 70 | } 71 | 72 | private void scramble(ClassNode cn, MethodNode mn) { 73 | List ldcNodes = new LinkedList<>(); 74 | BytecodeHelper.forEach(mn.instructions, LdcInsnNode.class, ldcNodes::add); 75 | for (LdcInsnNode node : ldcNodes) { 76 | if (node.cst instanceof String) { 77 | int index = stringList.indexOf(node.cst); 78 | if (index == -1) 79 | continue; 80 | log.debug("Replacing string constant \"{}\" at {}.{}{}", node.cst, cn.name, mn.name, mn.desc); 81 | MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, unscrambleClass.name, CALL_NAME, CALL_DESC, false); 82 | mn.instructions.set(node, call); 83 | mn.instructions.insertBefore(call, BytecodeHelper.newIntegerNode(index)); 84 | } 85 | } 86 | } 87 | 88 | private void createUnscramble() { 89 | MethodVisitor mv = unscrambleClass.visitMethod(ACC_PUBLIC | ACC_STATIC, CALL_NAME, CALL_DESC, null, null); 90 | mv.visitCode(); 91 | mv.visitTypeInsn(NEW, "java/lang/String"); 92 | mv.visitInsn(DUP); 93 | mv.visitMethodInsn(INVOKESTATIC, "java/util/Base64", "getDecoder", "()Ljava/util/Base64$Decoder;", false); 94 | mv.visitFieldInsn(GETSTATIC, unscrambleClass.name, FIELD_NAME, "[Ljava/lang/String;"); 95 | mv.visitVarInsn(ILOAD, 0); 96 | mv.visitInsn(AALOAD); 97 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/Base64$Decoder", "decode", "(Ljava/lang/String;)[B", false); 98 | mv.visitLdcInsn("UTF-8"); 99 | mv.visitMethodInsn(INVOKESPECIAL, "java/lang/String", "", "([BLjava/lang/String;)V", false); 100 | mv.visitInsn(ARETURN); 101 | mv.visitMaxs(0, 0); 102 | mv.visitEnd(); 103 | } 104 | 105 | private void createStaticConstructor(ClassNode owner) throws UnsupportedEncodingException { 106 | MethodNode original = BytecodeHelper.getMethod(owner, "", "()V"); 107 | MethodVisitor mv = owner.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); 108 | // generate instructions 109 | InstructionAdapter builder = new InstructionAdapter(mv); 110 | builder.iconst(stringList.size()); 111 | builder.newarray(Type.getType(String.class)); 112 | for (int i = 0; i < stringList.size(); i++) { 113 | builder.dup(); 114 | builder.iconst(i); 115 | builder.aconst(Base64.getEncoder().encodeToString(stringList.get(i).getBytes("UTF-8"))); 116 | builder.astore(InstructionAdapter.OBJECT_TYPE); 117 | } 118 | builder.putstatic(unscrambleClass.name, FIELD_NAME, "[Ljava/lang/String;"); 119 | // merge with original if it exists 120 | if (original != null) { 121 | // original should already end with RETURN 122 | owner.methods.remove(original); 123 | original.instructions.accept(builder); 124 | } else { 125 | builder.areturn(Type.VOID_TYPE); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/ShuffleMembers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import org.objectweb.asm.tree.ClassNode; 11 | 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public class ShuffleMembers implements Transformer { 17 | 18 | @Override 19 | public void transform(Map classMap) { 20 | classMap.values().forEach(c -> { 21 | shuffle(c.fields); 22 | shuffle(c.methods); 23 | shuffle(c.innerClasses); 24 | shuffle(c.interfaces); 25 | shuffle(c.attrs); 26 | shuffle(c.invisibleAnnotations); 27 | shuffle(c.visibleAnnotations); 28 | shuffle(c.invisibleTypeAnnotations); 29 | shuffle(c.visibleTypeAnnotations); 30 | c.fields.forEach(f -> { 31 | shuffle(f.attrs); 32 | shuffle(f.invisibleAnnotations); 33 | shuffle(f.visibleAnnotations); 34 | shuffle(f.invisibleTypeAnnotations); 35 | shuffle(f.visibleTypeAnnotations); 36 | }); 37 | c.methods.forEach(m -> { 38 | shuffle(m.attrs); 39 | shuffle(m.invisibleAnnotations); 40 | shuffle(m.visibleAnnotations); 41 | shuffle(m.invisibleTypeAnnotations); 42 | shuffle(m.visibleTypeAnnotations); 43 | shuffle(m.exceptions); 44 | shuffle(m.invisibleLocalVariableAnnotations); 45 | shuffle(m.visibleLocalVariableAnnotations); 46 | shuffle(m.localVariables); 47 | shuffle(m.parameters); 48 | }); 49 | c.innerClasses.clear(); 50 | }); 51 | } 52 | 53 | private void shuffle(List list) { 54 | if (list!=null)Collections.shuffle(list); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/obfuscate/Transformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.obfuscate; 9 | 10 | import org.objectweb.asm.tree.*; 11 | 12 | import java.util.*; 13 | 14 | /** 15 | * @author Caleb Whiting 16 | */ 17 | public interface Transformer { 18 | 19 | void transform(Map classMap); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/query/AnyOf.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.query; 9 | 10 | /** 11 | * @author Caleb Whiting 12 | */ 13 | public class AnyOf { 14 | 15 | private final Object[] values; 16 | 17 | public AnyOf(Object... values) { 18 | this.values = values; 19 | } 20 | 21 | public Object[] values() { 22 | return values; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/query/Query.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.query; 9 | 10 | /** 11 | * @author Caleb Whiting 12 | */ 13 | public class Query { 14 | 15 | private final Object[] values; 16 | 17 | public Query(Object... values) { 18 | this.values = values; 19 | } 20 | 21 | public Object[] values() { 22 | return this.values; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/query/QueryUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.query; 9 | 10 | import org.objectweb.asm.tree.AbstractInsnNode; 11 | 12 | import java.lang.reflect.Array; 13 | import java.lang.reflect.Field; 14 | import java.util.Arrays; 15 | 16 | /** 17 | * @author Caleb Whiting 18 | */ 19 | public class QueryUtil { 20 | 21 | private static boolean isEqual(Object o, Object value) { 22 | if (value != null && value instanceof AnyOf) { 23 | for (Object ob : ((AnyOf) value).values()) { 24 | if (isEqual(o, ob)) 25 | return true; 26 | } 27 | return false; 28 | } 29 | if ((o == null && value != null) || (value == null && o != null)) { 30 | return false; 31 | } 32 | if (o == value) { 33 | return true; 34 | } 35 | if (value.getClass().isArray() && o.getClass().isArray()) { 36 | int n = Array.getLength(value); 37 | if (n != Array.getLength(o)) 38 | return false; 39 | Object[] array1 = new Object[n]; 40 | Object[] array2 = new Object[n]; 41 | for (int j = 0; j < n; j++) { 42 | array1[j] = Array.get(value, j); 43 | array2[j] = Array.get(o, j); 44 | } 45 | return Arrays.equals(array1, array2); 46 | } 47 | return value.equals(o); 48 | } 49 | 50 | public static boolean check(AbstractInsnNode node, Object[] values) { 51 | for (int i = 0; i < values.length; i += 2) { 52 | String key = (String) values[i]; 53 | Object value = values[i + 1]; 54 | Object o = query(node, key); 55 | if (!QueryUtil.isEqual(o, value)) 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | public static boolean check(AbstractInsnNode node, Query query) { 62 | return check(node, query.values()); 63 | } 64 | 65 | private static Object reflectField(Object o, String name) { 66 | Class c = o.getClass(); 67 | while (c != null) { 68 | try { 69 | Field field = c.getDeclaredField(name); 70 | field.setAccessible(true); 71 | return field.get(o); 72 | } catch (ReflectiveOperationException ignore) { 73 | } 74 | c = c.getSuperclass(); 75 | } 76 | return null; 77 | } 78 | 79 | @SuppressWarnings("SpellCheckingInspection") 80 | public static Object query(Object o, String key) { 81 | if (o instanceof org.objectweb.asm.tree.ClassNode) { 82 | org.objectweb.asm.tree.ClassNode b = (org.objectweb.asm.tree.ClassNode) o; 83 | if (key.equals("version")) return b.version; 84 | if (key.equals("access")) return b.access; 85 | if (key.equals("name")) return b.name; 86 | if (key.equals("signature")) return b.signature; 87 | if (key.equals("superName")) return b.superName; 88 | if (key.equals("interfaces")) return b.interfaces.toArray(); 89 | if (key.equals("sourceFile")) return b.sourceFile; 90 | if (key.equals("sourceDebug")) return b.sourceDebug; 91 | if (key.equals("module")) return b.module; 92 | if (key.equals("outerClass")) return b.outerClass; 93 | if (key.equals("outerMethod")) return b.outerMethod; 94 | if (key.equals("outerMethodDesc")) return b.outerMethodDesc; 95 | if (key.equals("visibleAnnotations")) return b.visibleAnnotations.toArray(); 96 | if (key.equals("invisibleAnnotations")) return b.invisibleAnnotations.toArray(); 97 | if (key.equals("visibleTypeAnnotations")) return b.visibleTypeAnnotations.toArray(); 98 | if (key.equals("invisibleTypeAnnotations")) return b.invisibleTypeAnnotations.toArray(); 99 | if (key.equals("attrs")) return b.attrs.toArray(); 100 | if (key.equals("innerClasses")) return b.innerClasses.toArray(); 101 | if (key.equals("fields")) return b.fields.toArray(); 102 | if (key.equals("methods")) return b.methods.toArray(); 103 | } 104 | if (o instanceof org.objectweb.asm.tree.FieldNode) { 105 | org.objectweb.asm.tree.FieldNode b = (org.objectweb.asm.tree.FieldNode) o; 106 | if (key.equals("access")) return b.access; 107 | if (key.equals("name")) return b.name; 108 | if (key.equals("desc")) return b.desc; 109 | if (key.equals("signature")) return b.signature; 110 | if (key.equals("value")) return b.value; 111 | if (key.equals("visibleAnnotations")) return b.visibleAnnotations.toArray(); 112 | if (key.equals("invisibleAnnotations")) return b.invisibleAnnotations.toArray(); 113 | if (key.equals("visibleTypeAnnotations")) return b.visibleTypeAnnotations.toArray(); 114 | if (key.equals("invisibleTypeAnnotations")) return b.invisibleTypeAnnotations.toArray(); 115 | if (key.equals("attrs")) return b.attrs.toArray(); 116 | } 117 | if (o instanceof org.objectweb.asm.tree.MethodNode) { 118 | org.objectweb.asm.tree.MethodNode b = (org.objectweb.asm.tree.MethodNode) o; 119 | if (key.equals("access")) return b.access; 120 | if (key.equals("name")) return b.name; 121 | if (key.equals("desc")) return b.desc; 122 | if (key.equals("signature")) return b.signature; 123 | if (key.equals("exceptions")) return b.exceptions.toArray(); 124 | if (key.equals("parameters")) return b.parameters.toArray(); 125 | if (key.equals("visibleAnnotations")) return b.visibleAnnotations.toArray(); 126 | if (key.equals("invisibleAnnotations")) return b.invisibleAnnotations.toArray(); 127 | if (key.equals("visibleTypeAnnotations")) return b.visibleTypeAnnotations.toArray(); 128 | if (key.equals("invisibleTypeAnnotations")) return b.invisibleTypeAnnotations.toArray(); 129 | if (key.equals("attrs")) return b.attrs.toArray(); 130 | if (key.equals("annotationDefault")) return b.annotationDefault; 131 | if (key.equals("visibleParameterAnnotations")) return b.visibleParameterAnnotations; 132 | if (key.equals("invisibleParameterAnnotations")) return b.invisibleParameterAnnotations; 133 | if (key.equals("instructions")) return b.instructions; 134 | if (key.equals("tryCatchBlocks")) return b.tryCatchBlocks.toArray(); 135 | if (key.equals("maxStack")) return b.maxStack; 136 | if (key.equals("maxLocals")) return b.maxLocals; 137 | if (key.equals("localVariables")) return b.localVariables.toArray(); 138 | if (key.equals("visibleLocalVariableAnnotations")) return b.visibleLocalVariableAnnotations.toArray(); 139 | if (key.equals("invisibleLocalVariableAnnotations")) return b.invisibleLocalVariableAnnotations.toArray(); 140 | if (key.equals("visited")) return reflectField(o, "visited"); 141 | } 142 | if (o instanceof org.objectweb.asm.tree.InnerClassNode) { 143 | org.objectweb.asm.tree.InnerClassNode b = (org.objectweb.asm.tree.InnerClassNode) o; 144 | if (key.equals("name")) return b.name; 145 | if (key.equals("outerName")) return b.outerName; 146 | if (key.equals("innerName")) return b.innerName; 147 | if (key.equals("access")) return b.access; 148 | } 149 | if (o instanceof org.objectweb.asm.tree.AnnotationNode) { 150 | org.objectweb.asm.tree.AnnotationNode b = (org.objectweb.asm.tree.AnnotationNode) o; 151 | if (key.equals("desc")) return b.desc; 152 | if (key.equals("values")) return b.values.toArray(); 153 | } 154 | if (o instanceof org.objectweb.asm.tree.ParameterNode) { 155 | org.objectweb.asm.tree.ParameterNode b = (org.objectweb.asm.tree.ParameterNode) o; 156 | if (key.equals("name")) return b.name; 157 | if (key.equals("access")) return b.access; 158 | } 159 | if (o instanceof org.objectweb.asm.tree.TryCatchBlockNode) { 160 | org.objectweb.asm.tree.TryCatchBlockNode b = (org.objectweb.asm.tree.TryCatchBlockNode) o; 161 | if (key.equals("start")) return b.start; 162 | if (key.equals("end")) return b.end; 163 | if (key.equals("handler")) return b.handler; 164 | if (key.equals("type")) return b.type; 165 | if (key.equals("visibleTypeAnnotations")) return b.visibleTypeAnnotations.toArray(); 166 | if (key.equals("invisibleTypeAnnotations")) return b.invisibleTypeAnnotations.toArray(); 167 | } 168 | if (o instanceof org.objectweb.asm.tree.TypeAnnotationNode) { 169 | org.objectweb.asm.tree.TypeAnnotationNode b = (org.objectweb.asm.tree.TypeAnnotationNode) o; 170 | if (key.equals("typeRef")) return b.typeRef; 171 | if (key.equals("typePath")) return b.typePath; 172 | } 173 | if (o instanceof org.objectweb.asm.Label) { 174 | org.objectweb.asm.Label b = (org.objectweb.asm.Label) o; 175 | if (key.equals("info")) return b.info; 176 | if (key.equals("status")) return reflectField(o, "status"); 177 | if (key.equals("line")) return reflectField(o, "line"); 178 | if (key.equals("position")) return reflectField(o, "position"); 179 | if (key.equals("referenceCount")) return reflectField(o, "referenceCount"); 180 | if (key.equals("srcAndRefPositions")) return reflectField(o, "srcAndRefPositions"); 181 | if (key.equals("inputStackTop")) return reflectField(o, "inputStackTop"); 182 | if (key.equals("outputStackMax")) return reflectField(o, "outputStackMax"); 183 | if (key.equals("frame")) return reflectField(o, "frame"); 184 | if (key.equals("successor")) return reflectField(o, "successor"); 185 | if (key.equals("successors")) return reflectField(o, "successors"); 186 | if (key.equals("next")) return reflectField(o, "next"); 187 | } 188 | if (o instanceof org.objectweb.asm.tree.AbstractInsnNode) { 189 | org.objectweb.asm.tree.AbstractInsnNode b = (org.objectweb.asm.tree.AbstractInsnNode) o; 190 | if (key.equals("opcode")) return b.getOpcode(); 191 | if (key.equals("visibleTypeAnnotations")) return b.visibleTypeAnnotations.toArray(); 192 | if (key.equals("invisibleTypeAnnotations")) return b.invisibleTypeAnnotations.toArray(); 193 | if (key.equals("prev")) return b.getPrevious(); 194 | if (key.equals("next")) return b.getNext(); 195 | if (key.equals("index")) return reflectField(o, "index"); 196 | } 197 | if (o instanceof org.objectweb.asm.tree.FieldInsnNode) { 198 | org.objectweb.asm.tree.FieldInsnNode b = (org.objectweb.asm.tree.FieldInsnNode) o; 199 | if (key.equals("owner")) return b.owner; 200 | if (key.equals("name")) return b.name; 201 | if (key.equals("desc")) return b.desc; 202 | } 203 | if (o instanceof org.objectweb.asm.tree.FrameNode) { 204 | org.objectweb.asm.tree.FrameNode b = (org.objectweb.asm.tree.FrameNode) o; 205 | if (key.equals("type")) return b.type; 206 | if (key.equals("local")) return b.local.toArray(); 207 | if (key.equals("stack")) return b.stack.toArray(); 208 | } 209 | if (o instanceof org.objectweb.asm.tree.IincInsnNode) { 210 | org.objectweb.asm.tree.IincInsnNode b = (org.objectweb.asm.tree.IincInsnNode) o; 211 | if (key.equals("var")) return b.var; 212 | if (key.equals("incr")) return b.incr; 213 | } 214 | if (o instanceof org.objectweb.asm.tree.InsnNode) { 215 | org.objectweb.asm.tree.InsnNode b = (org.objectweb.asm.tree.InsnNode) o; 216 | } 217 | if (o instanceof org.objectweb.asm.tree.IntInsnNode) { 218 | org.objectweb.asm.tree.IntInsnNode b = (org.objectweb.asm.tree.IntInsnNode) o; 219 | if (key.equals("operand")) return b.operand; 220 | } 221 | if (o instanceof org.objectweb.asm.tree.InvokeDynamicInsnNode) { 222 | org.objectweb.asm.tree.InvokeDynamicInsnNode b = (org.objectweb.asm.tree.InvokeDynamicInsnNode) o; 223 | if (key.equals("name")) return b.name; 224 | if (key.equals("desc")) return b.desc; 225 | if (key.equals("bsm")) return b.bsm; 226 | if (key.equals("bsmArgs")) return b.bsmArgs; 227 | } 228 | if (o instanceof org.objectweb.asm.tree.JumpInsnNode) { 229 | org.objectweb.asm.tree.JumpInsnNode b = (org.objectweb.asm.tree.JumpInsnNode) o; 230 | if (key.equals("label")) return b.label; 231 | } 232 | if (o instanceof org.objectweb.asm.tree.LabelNode) { 233 | org.objectweb.asm.tree.LabelNode b = (org.objectweb.asm.tree.LabelNode) o; 234 | if (key.equals("label")) return b.getLabel(); 235 | } 236 | if (o instanceof org.objectweb.asm.tree.LdcInsnNode) { 237 | org.objectweb.asm.tree.LdcInsnNode b = (org.objectweb.asm.tree.LdcInsnNode) o; 238 | if (key.equals("cst")) return b.cst; 239 | } 240 | if (o instanceof org.objectweb.asm.tree.LineNumberNode) { 241 | org.objectweb.asm.tree.LineNumberNode b = (org.objectweb.asm.tree.LineNumberNode) o; 242 | if (key.equals("line")) return b.line; 243 | if (key.equals("start")) return b.start; 244 | } 245 | if (o instanceof org.objectweb.asm.tree.LookupSwitchInsnNode) { 246 | org.objectweb.asm.tree.LookupSwitchInsnNode b = (org.objectweb.asm.tree.LookupSwitchInsnNode) o; 247 | if (key.equals("dflt")) return b.dflt; 248 | if (key.equals("keys")) return b.keys.toArray(); 249 | if (key.equals("labels")) return b.labels.toArray(); 250 | } 251 | if (o instanceof org.objectweb.asm.tree.MethodInsnNode) { 252 | org.objectweb.asm.tree.MethodInsnNode b = (org.objectweb.asm.tree.MethodInsnNode) o; 253 | if (key.equals("owner")) return b.owner; 254 | if (key.equals("name")) return b.name; 255 | if (key.equals("desc")) return b.desc; 256 | if (key.equals("itf")) return b.itf; 257 | } 258 | if (o instanceof org.objectweb.asm.tree.MultiANewArrayInsnNode) { 259 | org.objectweb.asm.tree.MultiANewArrayInsnNode b = (org.objectweb.asm.tree.MultiANewArrayInsnNode) o; 260 | if (key.equals("desc")) return b.desc; 261 | if (key.equals("dims")) return b.dims; 262 | } 263 | if (o instanceof org.objectweb.asm.tree.TableSwitchInsnNode) { 264 | org.objectweb.asm.tree.TableSwitchInsnNode b = (org.objectweb.asm.tree.TableSwitchInsnNode) o; 265 | if (key.equals("min")) return b.min; 266 | if (key.equals("max")) return b.max; 267 | if (key.equals("dflt")) return b.dflt; 268 | if (key.equals("labels")) return b.labels.toArray(); 269 | } 270 | if (o instanceof org.objectweb.asm.tree.TypeInsnNode) { 271 | org.objectweb.asm.tree.TypeInsnNode b = (org.objectweb.asm.tree.TypeInsnNode) o; 272 | if (key.equals("desc")) return b.desc; 273 | } 274 | if (o instanceof org.objectweb.asm.tree.VarInsnNode) { 275 | org.objectweb.asm.tree.VarInsnNode b = (org.objectweb.asm.tree.VarInsnNode) o; 276 | if (key.equals("var")) return b.var; 277 | } 278 | return null; 279 | } 280 | 281 | } -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/util/BytecodeHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.util; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | 13 | import org.objectweb.asm.Opcodes; 14 | import org.objectweb.asm.commons.ClassRemapper; 15 | import org.objectweb.asm.commons.SimpleRemapper; 16 | import org.objectweb.asm.tree.*; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Map; 20 | import java.util.function.Consumer; 21 | 22 | public class BytecodeHelper { 23 | 24 | private static final Logger log = LogManager.getLogger("BytecodeHelper"); 25 | 26 | public static MethodNode getMethod(ClassNode node, String name, String desc) { 27 | return node.methods.stream() 28 | .filter(m -> m.name.equals(name)) 29 | .filter(m -> m.desc.equals(desc)) 30 | .findAny() 31 | .orElse(null); 32 | } 33 | 34 | public static FieldNode getField(ClassNode node, String name, String desc) { 35 | return node.fields.stream() 36 | .filter(m -> m.name.equals(name)) 37 | .filter(m -> m.desc.equals(desc)) 38 | .findAny() 39 | .orElse(null); 40 | } 41 | 42 | public static void forEach(InsnList instructions, 43 | Class type, 44 | Consumer consumer) { 45 | AbstractInsnNode[] array = instructions.toArray(); 46 | for (AbstractInsnNode node : array) { 47 | if (node.getClass() == type) { 48 | //noinspection unchecked 49 | consumer.accept((T) node); 50 | } 51 | } 52 | } 53 | 54 | public static void forEach(InsnList instructions, Consumer consumer) { 55 | forEach(instructions, AbstractInsnNode.class, consumer); 56 | } 57 | 58 | public static void applyMappings(Map classMap, Map remap) { 59 | log.debug("Applying mappings ["); 60 | for (Map.Entry entry : remap.entrySet()) { 61 | String k = entry.getKey(); 62 | String v = entry.getValue(); 63 | if (k.equals(v)) 64 | continue; 65 | // skip members with same name 66 | // field format = [ "." : "" ] 67 | // method format = [ ". " : "" ] 68 | int n = k.indexOf('.'); 69 | if (n != -1 && v.length() >= n && v.substring(n).equals(k)) { 70 | continue; 71 | } 72 | log.debug(" Map {} to {}", entry.getKey(), entry.getValue()); 73 | } 74 | log.debug("]"); 75 | SimpleRemapper remapper = new SimpleRemapper(remap); 76 | for (ClassNode node : new ArrayList<>(classMap.values())) { 77 | ClassNode copy = new ClassNode(); 78 | ClassRemapper adapter = new ClassRemapper(copy, remapper); 79 | node.accept(adapter); 80 | classMap.put(node.name, copy); 81 | } 82 | } 83 | 84 | public static AbstractInsnNode newIntegerNode(int i) { 85 | if (i >= -1 && i <= 5) { 86 | return new InsnNode(Opcodes.ICONST_0 + i); 87 | } else if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) { 88 | return new IntInsnNode(Opcodes.BIPUSH, i); 89 | } else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) { 90 | return new IntInsnNode(Opcodes.SIPUSH, i); 91 | } else { 92 | return new LdcInsnNode(i); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/util/ClassPath.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.util; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | import org.objectweb.asm.*; 13 | import org.objectweb.asm.tree.*; 14 | 15 | import java.io.*; 16 | import java.util.*; 17 | import java.util.jar.*; 18 | import java.util.stream.Stream; 19 | 20 | /** 21 | * @author Caleb Whiting 22 | * 23 | * Loads/caches jar files found in java.class.path as {@link ClassNode} objects and stores them in a map for conveneience. 24 | */ 25 | public class ClassPath extends HashMap { 26 | 27 | private static final Logger log = LogManager.getLogger("ClassPath"); 28 | 29 | private static ClassPath instance; 30 | 31 | private ClassPath() { 32 | String[] classpath = System.getProperty("java.class.path").split(System.getProperty("path.separator")); 33 | Stream.of(classpath).filter(path -> path.endsWith(".jar")).forEach(this::append); 34 | } 35 | 36 | private void append(String jarPath) { 37 | log.debug("Loading library from classpath: {}", jarPath); 38 | try(JarFile jar = new JarFile(jarPath)) { 39 | Enumeration entries = jar.entries(); 40 | while (entries.hasMoreElements()) { 41 | JarEntry entry = entries.nextElement(); 42 | try (InputStream in = jar.getInputStream(entry)) { 43 | if (entry.getName().endsWith(".class")) { 44 | byte[] bytes; 45 | try (ByteArrayOutputStream tmp = new ByteArrayOutputStream()) { 46 | byte[] buf = new byte[256]; 47 | for (int n; (n = in.read(buf)) != -1; ) { 48 | tmp.write(buf, 0, n); 49 | } 50 | bytes = tmp.toByteArray(); 51 | } 52 | ClassNode c = new ClassNode(); 53 | ClassReader r = new ClassReader(bytes); 54 | r.accept(c, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); 55 | put(c.name, c); 56 | } 57 | } 58 | } 59 | } catch (IOException e) { 60 | log.error("An error occurred while reading jar file: {}", jarPath, e); 61 | } 62 | } 63 | 64 | public static ClassPath getInstance() { 65 | if (instance == null) 66 | instance = new ClassPath(); 67 | return instance; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/util/QueryGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.util; 9 | 10 | import org.objectweb.asm.Label; 11 | import org.objectweb.asm.tree.*; 12 | 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Modifier; 16 | import java.util.Collection; 17 | import java.util.Collections; 18 | import java.util.LinkedHashSet; 19 | import java.util.Set; 20 | 21 | /** 22 | * Utility to generate {@link com.github.jasmo.query.QueryUtil#query(Object, String)} 23 | */ 24 | public class QueryGenerator { 25 | 26 | public static void main(String[] args) { 27 | Set types = new LinkedHashSet<>(); 28 | Collections.addAll(types, ClassNode.class, FieldNode.class, MethodNode.class, InnerClassNode.class); 29 | Collections.addAll(types, AnnotationNode.class, ParameterNode.class, TryCatchBlockNode.class, TypeAnnotationNode.class, Label.class); 30 | Collections.addAll(types, 31 | AbstractInsnNode.class, FieldInsnNode.class, FrameNode.class, IincInsnNode.class, 32 | InsnNode.class, IntInsnNode.class, InvokeDynamicInsnNode.class, JumpInsnNode.class, 33 | LabelNode.class, LdcInsnNode.class, LineNumberNode.class, LookupSwitchInsnNode.class, 34 | MethodInsnNode.class, MultiANewArrayInsnNode.class, TableSwitchInsnNode.class, 35 | TypeInsnNode.class, VarInsnNode.class); 36 | System.out.println("\tpublic static Object query(Object o, String key) {"); 37 | for (Class type : types) { 38 | lookup(type); 39 | } 40 | System.out.println("\t\treturn null;"); 41 | System.out.println("\t}"); 42 | } 43 | 44 | private static void lookup(Class type) { 45 | System.out.println("\t\tif (o instanceof " + type.getName() + ") {"); 46 | System.out.println("\t\t\t" + type.getName() + " b = (" + type.getName() + ") o;"); 47 | Field[] fields = type.getDeclaredFields(); 48 | for (Field field : fields) { 49 | String accessor = "b." + field.getName(); 50 | if ((field.getModifiers() & Modifier.PUBLIC) == 0) { 51 | try { 52 | String name = field.getName(); 53 | if (name.equals("prev")) name = "previous"; 54 | Method get; 55 | try { 56 | String getName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); 57 | get = type.getDeclaredMethod(getName); 58 | } catch (NoSuchMethodException e) { 59 | String getName = "is" + Character.toUpperCase(name.charAt(0)) + name.substring(1); 60 | get = type.getDeclaredMethod(getName); 61 | } 62 | if ((get.getModifiers() & Modifier.PUBLIC) == 0) { 63 | throw new ReflectiveOperationException(); 64 | } 65 | if (get.getReturnType() != field.getType()) { 66 | throw new ReflectiveOperationException(); 67 | } 68 | if (get.getParameterTypes().length != 0) { 69 | throw new ReflectiveOperationException(); 70 | } 71 | accessor = "b." + get.getName() + "()"; 72 | } catch (ReflectiveOperationException o) { 73 | accessor = "reflectField(o, \"" + field.getName() + "\")"; 74 | } 75 | } 76 | if (Collection.class.isAssignableFrom(field.getType())) { 77 | accessor += ".toArray()"; 78 | } 79 | field.setAccessible(true); 80 | if ((field.getModifiers() & Modifier.STATIC) == 0) 81 | System.out.println("\t\t\tif (key.equals(\"" + field.getName() + "\")) return " + accessor + ";"); 82 | } 83 | System.out.println("\t\t}"); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/jasmo/util/UniqueStringGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2017 Caleb Whiting 3 | * This work is free. You can redistribute it and/or modify it under the 4 | * terms of the Do What The Fuck You Want To Public License, Version 2, 5 | * as published by Sam Hocevar. See the COPYING file for more details. 6 | */ 7 | 8 | package com.github.jasmo.util; 9 | 10 | import java.util.Arrays; 11 | import java.util.HashSet; 12 | import java.util.Random; 13 | import java.util.Set; 14 | 15 | /** 16 | * @author Caleb Whiting 17 | */ 18 | public interface UniqueStringGenerator { 19 | 20 | void reset(); 21 | 22 | String next(); 23 | 24 | /** 25 | * Unpredictable and made up of characters outside of the 'normal' range 26 | */ 27 | class Crazy implements UniqueStringGenerator { 28 | 29 | private static final Random rng = new Random(); 30 | private final byte[] buffer; 31 | private final Set used = new HashSet<>(); 32 | 33 | public Crazy(int size) { 34 | buffer = new byte[size]; 35 | } 36 | 37 | @Override 38 | public void reset() { 39 | used.clear(); 40 | } 41 | 42 | @Override 43 | public String next() { 44 | for (int i = 0; i < buffer.length; i++) { 45 | int low = 127; /* Skip alphabetical/numeric/descriptor special chars etc */ 46 | buffer[i] = (byte) (low + ((byte) rng.nextInt(255-low))); 47 | } 48 | String s = new String(buffer, 0, buffer.length); 49 | return used.add(s) ? s : next(); 50 | } 51 | } 52 | 53 | /** 54 | * Increments similar to a number 55 | * Last number that is < Z is incremented, all values after the incremented value are reset to 'A'. 56 | * If all values are Z then increase length 57 | */ 58 | class Default implements UniqueStringGenerator { 59 | 60 | char[] chars = new char[0]; 61 | 62 | @Override 63 | public void reset() { 64 | chars = new char[0]; 65 | } 66 | 67 | @Override 68 | public String next() { 69 | for (int n = chars.length - 1; n >= 0; n--) { 70 | if (chars[n] < 'Z') { 71 | chars[n]++; 72 | return new String(chars); 73 | } 74 | chars[n] = 'A'; 75 | } 76 | chars = new char[chars.length + 1]; 77 | Arrays.fill(chars, 'A'); 78 | return new String(chars); 79 | } 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/all-tests.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo 'Running Test [1]' && src/test/obfuscate-self.bash && \ 3 | echo 'Running Test [2]' && src/test/obfuscate-self-with-obfuscated.bash 4 | -------------------------------------------------------------------------------- /src/test/loop-tests.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This test is to bulk test the obfuscator 3 | # Because the output is randomly generated it may take multiple tests to find errors 4 | # 5 | # Run this once to make 'target/result.jar' 6 | ./src/test/obfuscate-self.bash 7 | # 8 | # Swaps two files 9 | function swap { # (FileA, FileB) 10 | a=${1} 11 | b=${2} 12 | mv "${a}" "${b}.temp"; mv "${b}" "${a}"; mv "${b}.temp" "${b}" 13 | } 14 | # 15 | while true; do 16 | ./src/test/obfuscate-self-with-obfuscated.bash 17 | if [ ! "$?" = "0" ]; then exit 1; fi 18 | # Faster than running obfuscate-self.bash before each attempt 19 | if [ -f 'target/result.jar' ] && [ -f 'target/result2.jar' ]; then 20 | swap 'target/result.jar' 'target/result2.jar' 21 | fi 22 | done -------------------------------------------------------------------------------- /src/test/obfuscate-self-with-obfuscated.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! -d 'src' ] || [ ! -d 'target' ] || [ ! -f 'pom.xml' ] 3 | then 4 | echo 'We appear to be in the wrong directory, this must be ran from the project root' 5 | echo "This may also be because the file structure hasn't been setup (mvn compile)" 6 | echo $(dir) 7 | exit -1 8 | fi 9 | jar=target/result.jar 10 | java -jar ${jar} \ 11 | --verbose \ 12 | --keep com/github/jasmo/Bootstrap --keep com/github/jasmo/util/QueryGenerator \ 13 | --package com/github/jasmo \ 14 | target/java-asm-obfuscator-*.jar \ 15 | target/result2.jar -------------------------------------------------------------------------------- /src/test/obfuscate-self.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! -d 'src' ] || [ ! -d 'target' ] || [ ! -f 'pom.xml' ] 3 | then 4 | echo 'We appear to be in the wrong directory, this must be ran from the project root' 5 | echo "This may also be because the file structure hasn't been setup (mvn compile)" 6 | echo $(dir) 7 | exit -1 8 | fi 9 | mvn package 10 | jar=target/java-asm-obfuscator-*.jar 11 | java -jar ${jar} \ 12 | --verbose \ 13 | --keep com/github/jasmo/Bootstrap --keep com/github/jasmo/util/QueryGenerator \ 14 | --package com/github/jasmo \ 15 | ${jar} \ 16 | target/result.jar --------------------------------------------------------------------------------