├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── wtf │ └── pants │ └── stamp │ ├── Stamp.java │ ├── annotations │ ├── StampPreserve.java │ └── StampStringRename.java │ ├── asm │ ├── ASM.java │ └── AccessUtil.java │ ├── mapping │ ├── ClassCollector.java │ ├── MappingManager.java │ ├── ObfuscationAssigner.java │ ├── exceptions │ │ ├── ClassMapNotFoundException.java │ │ └── MethodNotFoundException.java │ └── obj │ │ ├── ClassMap.java │ │ ├── FieldObj.java │ │ └── MethodObj.java │ ├── obfuscator │ ├── Obfuscator.java │ ├── ObfuscatorManager.java │ └── obfuscators │ │ ├── ObfuscatorFields.java │ │ ├── ObfuscatorLocalVars.java │ │ ├── ObfuscatorMethods.java │ │ ├── ObfuscatorStrings.java │ │ └── classes │ │ ├── ClassInsnModifier.java │ │ └── ObfuscatorClasses.java │ └── util │ ├── Log.java │ ├── ObfUtil.java │ └── ZipUtils.java └── test └── java └── wtf └── pants └── stamp ├── asm ├── ASMTest.java └── AccessUtilTest.java └── mapping └── obj └── MethodObjTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | *.jar 4 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stamp Java Obfuscator 2 | Java bytecode obfuscator 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | wtf.pants.stamp 8 | stamp-obf 9 | 0.1 10 | 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-compiler-plugin 16 | 3.6.0 17 | 18 | 1.8 19 | 1.8 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.ow2.asm 28 | asm 29 | 5.0.4 30 | 31 | 32 | org.ow2.asm 33 | asm-tree 34 | 5.0.4 35 | 36 | 37 | org.ow2.asm 38 | asm-util 39 | 5.0.4 40 | 41 | 42 | com.google.guava 43 | guava 44 | 20.0 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | 1.16.12 50 | provided 51 | 52 | 53 | junit 54 | junit 55 | 4.12 56 | test 57 | 58 | 59 | commons-cli 60 | commons-cli 61 | 1.3.1 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/Stamp.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp; 2 | 3 | 4 | import com.google.common.io.ByteStreams; 5 | import lombok.Getter; 6 | import org.apache.commons.cli.*; 7 | import org.objectweb.asm.ClassReader; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import wtf.pants.stamp.mapping.ClassCollector; 10 | import wtf.pants.stamp.mapping.MappingManager; 11 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 12 | import wtf.pants.stamp.mapping.obj.ClassMap; 13 | import wtf.pants.stamp.obfuscator.ObfuscatorManager; 14 | import wtf.pants.stamp.util.Log; 15 | import wtf.pants.stamp.util.ZipUtils; 16 | 17 | import java.io.*; 18 | import java.util.zip.ZipEntry; 19 | import java.util.zip.ZipFile; 20 | import java.util.zip.ZipOutputStream; 21 | 22 | /** 23 | * @author Pants 24 | */ 25 | public class Stamp { 26 | 27 | private final File inputFile, outputFile; 28 | private final String[] libs; 29 | private final String[] exclusions; 30 | 31 | private ObfuscatorManager obfuscatorManager; 32 | 33 | @Getter 34 | private ClassCollector collector; 35 | 36 | public Stamp(File inputFile, File outputFile, String[] libs, String[] exclusions) { 37 | this.collector = new ClassCollector(); 38 | this.obfuscatorManager = new ObfuscatorManager(this); 39 | 40 | this.inputFile = inputFile; 41 | this.outputFile = outputFile; 42 | 43 | this.libs = libs; 44 | this.exclusions = exclusions; 45 | } 46 | 47 | private byte[] modifyManifestFile(byte[] bytes) throws IOException { 48 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes))); 49 | final ByteArrayOutputStream b = new ByteArrayOutputStream(bytes.length); 50 | 51 | bufferedReader.lines().forEach(line -> { 52 | try { 53 | if (line.contains("Class: ")) { 54 | final String mainClass = line.split(": ")[1]; 55 | Log.log("Found main class: %s", mainClass); 56 | final ClassMap mainClassMap = collector.getClassMap(mainClass.replace(".", "/")); 57 | 58 | if(mainClassMap.isObfuscated()) { 59 | final String newLine = line.replace(mainClass, mainClassMap.getObfClassName()) + "\n"; 60 | b.write(newLine.getBytes()); 61 | } 62 | else { 63 | b.write((line + "\n").getBytes()); 64 | } 65 | } else { 66 | b.write((line + "\n").getBytes()); 67 | } 68 | } catch (ClassMapNotFoundException e) { 69 | Log.error("Main class has not been mapped."); 70 | System.exit(0); 71 | } catch (IOException e) { 72 | Log.error("Error writing to manifest file"); 73 | System.exit(0); 74 | } 75 | }); 76 | 77 | return b.toByteArray(); 78 | } 79 | 80 | private void handleZipEntry(ZipOutputStream zipOutputStream, ZipFile zipFile, ZipEntry c) throws IOException { 81 | if (c.getName().endsWith("/")) { 82 | return; 83 | } 84 | 85 | final InputStream is = zipFile.getInputStream(c); 86 | final byte[] bytes = ByteStreams.toByteArray(is); 87 | 88 | Log.log("\t> %s", c.getName()); 89 | if (c.getName().endsWith(".class")) { 90 | final ClassReader cr = new ClassReader(bytes); 91 | final ClassNode cn = new ClassNode(); 92 | 93 | final byte[] obfuscatedBytes = obfuscatorManager.obfuscate(cr, cn); 94 | 95 | Log.info("Saving: %s (Old Name: %s)", cn.name, c.getName()); 96 | 97 | byte[] classBytes = c.getName().endsWith(".class") ? obfuscatedBytes : bytes; 98 | ZipUtils.addFileToZip(zipOutputStream, cn.name + ".class", classBytes); 99 | } else if (c.getName().endsWith("MANIFEST.MF")) { 100 | ZipUtils.addFileToZip(zipOutputStream, c.getName(), modifyManifestFile(bytes)); 101 | } else { 102 | ZipUtils.addFileToZip(zipOutputStream, c.getName(), bytes); 103 | } 104 | 105 | is.close(); 106 | } 107 | 108 | private void obfuscateJar(File inputFile, File outputFile) throws IOException { 109 | final FileOutputStream fos = new FileOutputStream(outputFile, false); 110 | final ZipOutputStream zipOutputStream = new ZipOutputStream(fos); 111 | final ZipFile zipFile = new ZipFile(inputFile); 112 | 113 | zipFile.stream().forEach(c -> { 114 | try { 115 | handleZipEntry(zipOutputStream, zipFile, c); 116 | } catch (IOException e) { 117 | e.printStackTrace(); 118 | } 119 | }); 120 | 121 | zipOutputStream.close(); 122 | zipFile.close(); 123 | } 124 | 125 | private void start() { 126 | Log.DEBUG = true; 127 | try { 128 | Log.info("Mapping classes..."); 129 | MappingManager mappingHandler = new MappingManager(collector); 130 | mappingHandler.mapClasses(inputFile, exclusions); 131 | 132 | System.out.println("\n\n----------------------------------"); 133 | Log.info("Obfuscating bytecode..."); 134 | obfuscateJar(inputFile, outputFile); 135 | } catch (IOException e) { 136 | e.printStackTrace(); 137 | } 138 | } 139 | 140 | private static void addCliOptions(Options options){ 141 | options.addOption( 142 | new Option("i", "input", true, "Input file to obfuscate")); 143 | 144 | options.addOption( 145 | new Option("o", "output", true, "File to output to after obfuscation")); 146 | 147 | options.addOption( 148 | new Option("lib", "libraries", true, "Libraries that the input file uses (separator: ';')")); 149 | 150 | options.addOption( 151 | new Option("x", "exclude", true, "Packages to exclude (separator: ';')")); 152 | 153 | final Option helpOpt = new Option("help", false, "Displays possible arguments"); 154 | helpOpt.setOptionalArg(true); 155 | options.addOption(helpOpt); 156 | } 157 | 158 | public static void main(String[] args) { 159 | final String usageMsg = "java -jar stamp.jar -i INPUT.jar"; 160 | 161 | final Options options = new Options(); 162 | addCliOptions(options); 163 | 164 | final CommandLineParser parser = new DefaultParser(); 165 | final HelpFormatter helpFormatter = new HelpFormatter(); 166 | 167 | final CommandLine cli; 168 | 169 | try { 170 | cli = parser.parse(options, args); 171 | } catch (ParseException e) { 172 | Log.error(e.getMessage()); 173 | helpFormatter.printHelp(usageMsg, options); 174 | return; 175 | } 176 | 177 | if(cli.hasOption("-help")){ 178 | helpFormatter.printHelp("java -jar stamp.jar", options, true); 179 | return; 180 | } 181 | 182 | String inputFilename = cli.getOptionValue("input"); 183 | String outputFilename = cli.getOptionValue("output"); 184 | 185 | if(inputFilename == null){ 186 | helpFormatter.printHelp(usageMsg, options); 187 | return; 188 | } 189 | 190 | if (!inputFilename.endsWith(".jar")) { 191 | inputFilename += ".jar"; 192 | } 193 | 194 | if (outputFilename == null) { 195 | outputFilename = "Obfuscated_" + inputFilename; 196 | } 197 | 198 | final File file = new File(inputFilename); 199 | 200 | if(!file.exists()){ 201 | Log.error("Input file doesn't exist: %s", inputFilename); 202 | return; 203 | } 204 | 205 | final String libVal = cli.getOptionValue("libraries"); 206 | final String exVal = cli.getOptionValue("exclude"); 207 | 208 | //Correctly splits the excluded files and libs into an array 209 | final String[] libs = libVal != null ? libVal.split(";") : null; 210 | final String[] exclude = exVal != null ? exVal.split(";") : null; 211 | 212 | Log.info("Obfuscating '%s' and outputting to '%s'", inputFilename, outputFilename); 213 | Stamp instance = new Stamp(file, new File(outputFilename), libs, exclude); 214 | instance.start(); 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/annotations/StampPreserve.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author Pants 10 | * 11 | * Currently only works for methods 12 | */ 13 | @Retention(RetentionPolicy.CLASS) 14 | @Target({ElementType.METHOD}) 15 | public @interface StampPreserve { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/annotations/StampStringRename.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author Pants 10 | * 11 | * To use this, reference stamp as a dependency and place the annotation at the top of a method. 12 | * @StampStringRename("wtf/pants/something/Something.printStuff()V") 13 | * Above would replace strings containing "printStuff" with its obfuscated name 14 | * TODO: Make this more friendly to use 15 | */ 16 | @Retention(RetentionPolicy.CLASS) 17 | @Target({ElementType.FIELD, ElementType.METHOD}) 18 | public @interface StampStringRename { 19 | 20 | String[] value(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/asm/ASM.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.asm; 2 | 3 | import lombok.Setter; 4 | import org.objectweb.asm.Type; 5 | import org.objectweb.asm.tree.*; 6 | 7 | import static org.objectweb.asm.Opcodes.*; 8 | 9 | /** 10 | * @author Pants 11 | */ 12 | public class ASM { 13 | 14 | @Setter 15 | private InsnList insn; 16 | 17 | public ASM() {} 18 | 19 | public ASM(InsnList insn) { 20 | this.insn = insn; 21 | } 22 | 23 | private int getConst(int i) { 24 | switch (i) { 25 | case 0: 26 | return ICONST_0; 27 | case 1: 28 | return ICONST_1; 29 | case 2: 30 | return ICONST_2; 31 | case 3: 32 | return ICONST_3; 33 | case 4: 34 | return ICONST_4; 35 | case 5: 36 | return ICONST_5; 37 | default: 38 | return -1; 39 | } 40 | } 41 | 42 | /** 43 | * Pushes the correct int to the stack. 44 | * 'ICONST_#' if it's less than 6 45 | * 'BIPUSH #' if it's -127 to -1, and 6 to 127 46 | * 'SIPUSH #' if it's anything else 47 | * 48 | * @param i The int to push to the stack 49 | */ 50 | public void pushInt(int i) { 51 | final int ICONST = getConst(i); 52 | 53 | if (ICONST != -1) { 54 | insn.add(new InsnNode(ICONST)); 55 | } else if (i > -128 && i < 128) { 56 | insn.add(new IntInsnNode(BIPUSH, i)); 57 | } else { 58 | insn.add(new IntInsnNode(SIPUSH, i)); 59 | } 60 | } 61 | 62 | public void ldc(String type){ 63 | insn.add(new LdcInsnNode(type)); 64 | } 65 | 66 | public void ldcType(String type){ 67 | insn.add(new LdcInsnNode(Type.getType(type))); 68 | } 69 | 70 | public void dup() { 71 | insn.add(new InsnNode(DUP)); 72 | } 73 | 74 | public void isub(){ 75 | insn.add(new InsnNode(ISUB)); 76 | } 77 | 78 | public void aastore() { 79 | insn.add(new InsnNode(AASTORE)); 80 | } 81 | 82 | public void aaload() { 83 | insn.add(new InsnNode(AALOAD)); 84 | } 85 | 86 | public void field(int opcode, String owner, String name, String desc) { 87 | insn.add(new FieldInsnNode(opcode, owner, name, desc)); 88 | } 89 | 90 | public void aload(int i) { 91 | insn.add(new VarInsnNode(ALOAD, i)); 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/asm/AccessUtil.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.asm; 2 | 3 | import static org.objectweb.asm.Opcodes.ACC_FINAL; 4 | import static org.objectweb.asm.Opcodes.ACC_STATIC; 5 | 6 | /** 7 | * @author Pants 8 | */ 9 | public class AccessUtil { 10 | 11 | public static boolean isFinal(int access) { 12 | return (access & ACC_FINAL) != 0; 13 | } 14 | 15 | public static boolean isStatic(int access) { 16 | return (access & ACC_STATIC) != 0; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/ClassCollector.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping; 2 | 3 | import lombok.Data; 4 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 5 | import wtf.pants.stamp.mapping.obj.ClassMap; 6 | import wtf.pants.stamp.mapping.obj.MethodObj; 7 | 8 | import java.util.*; 9 | 10 | /** 11 | * @author Pants 12 | */ 13 | @Data 14 | public class ClassCollector { 15 | 16 | private final List classes; 17 | 18 | private String mainClass; 19 | 20 | public ClassCollector() { 21 | this.classes = new ArrayList<>(); 22 | } 23 | 24 | /** 25 | * Adds a class to the collector 26 | * 27 | * @param classMap ClassMap instance 28 | */ 29 | public void addClass(ClassMap classMap) { 30 | this.classes.add(classMap); 31 | } 32 | 33 | /** 34 | * Looks for a ClassMap from all the mapped classes 35 | * 36 | * @param className Class name you're looking for 37 | * @return Returns ClassMap 38 | * @throws ClassMapNotFoundException Exception thrown if className was not found 39 | */ 40 | public ClassMap getClassMap(String className) throws ClassMapNotFoundException { 41 | final Optional classMap = 42 | classes.stream().filter(c -> c.getClassName().equals(className)).findFirst(); 43 | 44 | if (classMap.isPresent()) { 45 | return classMap.get(); 46 | } else { 47 | throw new ClassMapNotFoundException(); 48 | } 49 | } 50 | 51 | /** 52 | * If it has one, this will get the class' parent class 53 | * 54 | * @param classMap Mapped class 55 | * @return Returns parent ClassMap 56 | * @throws ClassMapNotFoundException Throws ClassMapNotFoundException if the parent is not mapped 57 | */ 58 | public ClassMap getParent(ClassMap classMap) throws ClassMapNotFoundException { 59 | final String parentClassName = classMap.getParent(); 60 | 61 | Optional optional = classes.stream() 62 | .filter(clazz -> parentClassName.equals(clazz.getClassName())) 63 | .findAny(); 64 | 65 | if (optional.isPresent()) 66 | return optional.get(); 67 | else 68 | throw new ClassMapNotFoundException(classMap.getParent()); 69 | } 70 | 71 | /** 72 | * Tries to get the class' overridden methods by comparing the child's methods to the parent's 73 | * 74 | * @param parentClass Parent class to compare to 75 | * @param childClass Child class to compare to 76 | * @return Returns a list of the overridden methods 77 | */ 78 | public Map getOverriddenMethods(ClassMap parentClass, ClassMap childClass) { 79 | final Map methods = new HashMap<>(); 80 | 81 | parentClass.getMethods().stream() 82 | .filter(MethodObj::isSafeMethod) 83 | .forEach(parentMethod -> { 84 | for (MethodObj childMethod : childClass.getMethods()) { 85 | if (childMethod.isSafeMethod() && childMethod.getMethod().equals(parentMethod.getMethod())) { 86 | methods.put(parentMethod, childMethod); 87 | break; 88 | } 89 | } 90 | }); 91 | 92 | return methods; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/MappingManager.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping; 2 | 3 | import com.google.common.io.ByteStreams; 4 | import org.objectweb.asm.ClassReader; 5 | import org.objectweb.asm.tree.ClassNode; 6 | import org.objectweb.asm.tree.FieldNode; 7 | import org.objectweb.asm.tree.MethodNode; 8 | import wtf.pants.stamp.annotations.StampPreserve; 9 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 10 | import wtf.pants.stamp.mapping.exceptions.MethodNotFoundException; 11 | import wtf.pants.stamp.mapping.obj.ClassMap; 12 | import wtf.pants.stamp.mapping.obj.FieldObj; 13 | import wtf.pants.stamp.mapping.obj.MethodObj; 14 | import wtf.pants.stamp.asm.AccessUtil; 15 | import wtf.pants.stamp.util.Log; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.util.List; 21 | import java.util.zip.ZipFile; 22 | 23 | /** 24 | * @author Pants 25 | */ 26 | @SuppressWarnings("unchecked call") 27 | public class MappingManager { 28 | 29 | private final ClassCollector collector; 30 | private final ObfuscationAssigner obfuscationHandler; 31 | 32 | public MappingManager(ClassCollector collector) { 33 | this.collector = collector; 34 | this.obfuscationHandler = new ObfuscationAssigner(collector); 35 | } 36 | 37 | /** 38 | * Adds methods from a class to another class 39 | * 40 | * @param child Child class 41 | * @param parent Parent Class 42 | */ 43 | private void addMethod(ClassMap child, ClassMap parent) { 44 | parent.getMethods().stream() 45 | .filter(pm -> !AccessUtil.isFinal(pm.getAccess())) 46 | .filter(MethodObj::isSafeMethod) 47 | .forEach(p -> { 48 | try { 49 | child.getMethodFromShort(p.getMethod()); 50 | } catch (MethodNotFoundException e) { 51 | child.addMethod(new MethodObj(child.getClassName(), p.getMethodName(), p.getMethodDesc(), p.getAccess())); 52 | } 53 | }); 54 | } 55 | 56 | /** 57 | * Adds all of the non-final methods from the parent class to the child class' mappings. 58 | * This is so when inside one of the child class' methods later on and it calls a parent method 59 | * it will be able to obfuscate it with the correct name 60 | */ 61 | private void addParentMethods() { 62 | collector.getClasses().stream() 63 | .filter(c -> (c.hasParent() || c.hasImplementedClasses())) 64 | .forEach(child -> { 65 | if (child.hasParent()) { 66 | try { 67 | ClassMap parent = collector.getParent(child); 68 | addMethod(child, parent); 69 | } catch (ClassMapNotFoundException e) { 70 | Log.warning("Parent class not found: %s", child.getParent()); 71 | } 72 | } 73 | 74 | if (child.hasImplementedClasses()) { 75 | child.getInterfaces().forEach(inter -> { 76 | try { 77 | ClassMap parentClass = collector.getClassMap(inter); 78 | addMethod(child, parentClass); 79 | } catch (ClassMapNotFoundException e) { 80 | Log.warning("Interface Class not found: %s", inter); 81 | } 82 | }); 83 | } 84 | }); 85 | } 86 | 87 | private void mapFields(ClassNode cn, ClassMap classMap) { 88 | if (cn.fields == null) 89 | return; 90 | 91 | final List fields = cn.fields; 92 | 93 | fields.forEach(field -> 94 | classMap.addField(new FieldObj(field.desc, field.name))); 95 | } 96 | 97 | private void mapMethods(ClassNode cn, ClassMap classMap) { 98 | if (cn.methods == null) 99 | return; 100 | 101 | final List methods = cn.methods; 102 | 103 | methods.forEach(method -> { 104 | MethodObj methodObj = new MethodObj(cn.name, method.name, method.desc, method.access); 105 | classMap.addMethod(methodObj); 106 | methodObj.setObfuscationDisable(classMap.hasAnnotation(StampPreserve.class, method.invisibleAnnotations)); 107 | }); 108 | } 109 | 110 | /** 111 | * Maps the class file. Maps the methods, fields, super class, and interfaces 112 | * 113 | * @param bytecode The class file's bytes 114 | */ 115 | private void mapClassFile(byte[] bytecode) { 116 | final ClassReader cr = new ClassReader(bytecode); 117 | final ClassNode cn = new ClassNode(); 118 | cr.accept(cn, 0); 119 | 120 | final ClassMap classMap = new ClassMap(cn.name); 121 | Log.log("Reading class file: %s.class", cn.name); 122 | 123 | if (cn.superName != null && !cn.superName.equals("java/lang/Object")) { 124 | Log.debug("Class has a parent: %s", cn.superName); 125 | classMap.setParent(cn.superName); 126 | } 127 | 128 | if (cn.interfaces != null) { 129 | classMap.setInterfaces(cn.interfaces); 130 | } 131 | 132 | mapMethods(cn, classMap); 133 | mapFields(cn, classMap); 134 | 135 | collector.addClass(classMap); 136 | } 137 | 138 | /** 139 | * This will iterate through the files within the target jar and map them for obfuscation. After mapping, it will 140 | * assign each mapped class and obfuscated name for later 141 | * 142 | * @param inputFile Target file 143 | * @param exclusions An array of the excluded classes 144 | */ 145 | public void mapClasses(File inputFile, String[] exclusions) throws IOException { 146 | final ZipFile zipFile = new ZipFile(inputFile); 147 | 148 | Log.info("Mapping classes..."); 149 | 150 | zipFile.stream() 151 | .filter(file -> file.getName().endsWith(".class")) 152 | .forEach(c -> { 153 | try { 154 | final InputStream is = zipFile.getInputStream(c); 155 | byte[] classBytecode = ByteStreams.toByteArray(is); 156 | mapClassFile(classBytecode); 157 | is.close(); 158 | } catch (IOException e) { 159 | e.printStackTrace(); 160 | } 161 | }); 162 | 163 | Log.info("Classes mapped: %s", collector.getClasses().size()); 164 | Log.info("Assigning obfuscated names..."); 165 | addParentMethods(); 166 | obfuscationHandler.assignObfuscatedNames(exclusions); 167 | 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/ObfuscationAssigner.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping; 2 | 3 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 4 | import wtf.pants.stamp.mapping.obj.ClassMap; 5 | import wtf.pants.stamp.mapping.obj.MethodObj; 6 | import wtf.pants.stamp.util.Log; 7 | import wtf.pants.stamp.util.ObfUtil; 8 | 9 | /** 10 | * @author Pants 11 | */ 12 | class ObfuscationAssigner { 13 | 14 | private final ClassCollector collector; 15 | 16 | ObfuscationAssigner(ClassCollector cc) { 17 | this.collector = cc; 18 | } 19 | 20 | /** 21 | * Assigns obfuscated names to the overridden methods 22 | * 23 | * @param parentClass Parent Class 24 | * @param classObj Child Class 25 | * @param exclusions Array of excluded classes 26 | */ 27 | private void obfuscateParentChild(ClassMap parentClass, ClassMap classObj, String[] exclusions) { 28 | collector.getOverriddenMethods(parentClass, classObj).forEach((parentMethod, childMethod) -> { 29 | if (!isExcluded(parentClass.getClassName(), exclusions)) { 30 | if (!parentMethod.isObfuscated()) { 31 | parentMethod.setObfMethodName(ObfUtil.getRandomObfString()); 32 | } 33 | 34 | childMethod.setObfMethodName(parentMethod.getObfMethodName()); 35 | } else { 36 | childMethod.setObfuscationDisable(true); 37 | } 38 | 39 | Log.log("Obfuscated Overridden Method: %s. Renamed to: %s", childMethod.getMethodName(), childMethod.getObfMethodName()); 40 | }); 41 | } 42 | 43 | /** 44 | * Obfuscates the implemented class' overridden methods. If the implemented class is not found it will disable the 45 | * target class from being obfuscated. TODO: Remove the second sentence when libraries are done 46 | * 47 | * @param classMap Instance of ClassMap 48 | * @param exclusions Array of excluded classes 49 | */ 50 | private void obfuscateInterfaceMethods(ClassMap classMap, String[] exclusions) { 51 | classMap.getInterfaces().forEach(inter -> { 52 | try { 53 | ClassMap interfaceClass = collector.getClassMap(inter); 54 | obfuscateParentChild(interfaceClass, classMap, exclusions); 55 | } catch (ClassMapNotFoundException e) { 56 | classMap.methods.stream() 57 | .filter(methodObj -> !methodObj.isObfuscated()) 58 | .forEach(m -> m.setObfuscationDisable(true)); 59 | Log.log("Interface class not found. Parent: %s", classMap.getClassName(), classMap.getParent()); 60 | e.printStackTrace(); 61 | } 62 | }); 63 | } 64 | 65 | /** 66 | * Obfuscates the paren'ts methods (recursively, if the parent has a parent) 67 | * 68 | * @param classObj Instance of ClassMap 69 | * @param exclusions Array of excluded classes 70 | */ 71 | private void obfuscateParentMethods(ClassMap classObj, String[] exclusions) { 72 | try { 73 | ClassMap parentClass = collector.getParent(classObj); 74 | 75 | //Recursively add parent methods 76 | if (parentClass.hasParent()) { 77 | obfuscateParentMethods(parentClass, exclusions); 78 | 79 | if (parentClass.hasImplementedClasses()) { 80 | obfuscateInterfaceMethods(parentClass, exclusions); 81 | } 82 | } 83 | 84 | obfuscateParentChild(parentClass, classObj, exclusions); 85 | } catch (ClassMapNotFoundException e) { 86 | classObj.methods.stream() 87 | .filter(methodObj -> !methodObj.isObfuscated()) 88 | .forEach(m -> m.setObfuscationDisable(true)); 89 | 90 | Log.log("%s's parent class not found. Parent: %s", classObj.getClassName(), classObj.getParent()); 91 | } 92 | } 93 | 94 | private boolean isExcluded(String clazz, String[] exclusions) { 95 | if (exclusions == null) 96 | return false; 97 | 98 | for (String exclusion : exclusions) { 99 | if (clazz.toLowerCase().startsWith(exclusion.toLowerCase())) { 100 | Log.info("Excluding %s", clazz); 101 | return true; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Goes through the mapped classes and assigns an obfuscated name to each class for actually obfuscating 110 | * 111 | * @param exclusions String array of paths/classes to exclude from assigning obfuscated names 112 | */ 113 | void assignObfuscatedNames(String[] exclusions) { 114 | collector.getClasses().stream() 115 | .filter(cm -> !isExcluded(cm.getClassName(), exclusions)) 116 | .forEach(classObj -> { 117 | if (classObj.hasParent()) { 118 | obfuscateParentMethods(classObj, exclusions); 119 | } 120 | 121 | if (classObj.hasImplementedClasses()) { 122 | obfuscateInterfaceMethods(classObj, exclusions); 123 | } 124 | 125 | classObj.getMethods().stream() 126 | .filter(m -> !m.isObfuscationDisable()) 127 | .filter(m -> !m.isObfuscated()) 128 | .filter(MethodObj::isSafeMethod) 129 | .forEach(m -> { 130 | m.setObfMethodName(ObfUtil.getRandomObfString()); 131 | Log.log("Method: %s -> %s", m.getMethodName(), m.getObfMethodName()); 132 | }); 133 | 134 | classObj.getFields().stream() 135 | .filter(f -> !f.isObfuscated()) 136 | .forEach(f -> { 137 | f.setObfFieldName(ObfUtil.getRandomObfString()); 138 | Log.log("Field: %s -> %s", f.getFieldName(), f.getObfFieldName()); 139 | }); 140 | 141 | classObj.setObfClassName(ObfUtil.getRandomObfString()); 142 | }); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/exceptions/ClassMapNotFoundException.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping.exceptions; 2 | 3 | /** 4 | * @author Pants 5 | */ 6 | public class ClassMapNotFoundException extends Exception { 7 | 8 | public ClassMapNotFoundException() { 9 | } 10 | 11 | public ClassMapNotFoundException(String message){ 12 | super("Class Map not found: " + message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/exceptions/MethodNotFoundException.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping.exceptions; 2 | 3 | /** 4 | * @author Pants 5 | */ 6 | public class MethodNotFoundException extends Exception { 7 | public MethodNotFoundException(String message) { 8 | super("Method not found: " + message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/obj/ClassMap.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping.obj; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.objectweb.asm.tree.AnnotationNode; 6 | import org.objectweb.asm.tree.MethodNode; 7 | import wtf.pants.stamp.annotations.StampPreserve; 8 | import wtf.pants.stamp.mapping.exceptions.MethodNotFoundException; 9 | import wtf.pants.stamp.util.Log; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Iterator; 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | /** 17 | * @author Pants 18 | */ 19 | @Getter 20 | @Setter 21 | public class ClassMap { 22 | 23 | public final List fields; 24 | public final List methods; 25 | 26 | private final String className; 27 | 28 | private String obfClassName; 29 | private String parent; 30 | private List interfaces; 31 | 32 | private boolean library = false; 33 | 34 | public ClassMap(String className) { 35 | this.methods = new ArrayList<>(); 36 | this.fields = new ArrayList<>(); 37 | 38 | this.interfaces = new ArrayList<>(); 39 | this.className = className; 40 | } 41 | 42 | /** 43 | * Gets the method from it's full path. eg:'this/is/a/pkg/Class.method()V' 44 | * getMethodFromShort(String) for just the method name 45 | * 46 | * @param methodId Method path 47 | * @return Returns the found MethodObj 48 | * @throws MethodNotFoundException Throws if the method has not been mapped in this class 49 | */ 50 | public MethodObj getMethod(String methodId) throws MethodNotFoundException { 51 | Optional methodObj = methods.stream().filter(m -> m.getFullMethod().equals(methodId)).findFirst(); 52 | 53 | if (methodObj.isPresent()) 54 | return methodObj.get(); 55 | else 56 | throw new MethodNotFoundException(methodId); 57 | } 58 | 59 | /** 60 | * Gets a method from just the method name and desc 61 | * getMethod(String) for the full name, package and class 62 | * 63 | * @param methodId Method name 64 | * @return Returns the found MethodObj 65 | * @throws MethodNotFoundException Throws if the method has not been mapped in this class 66 | */ 67 | public MethodObj getMethodFromShort(String methodId) throws MethodNotFoundException { 68 | Optional methodObj = methods.stream().filter(m -> m.getMethod().equals(methodId)).findFirst(); 69 | 70 | if (methodObj.isPresent()) 71 | return methodObj.get(); 72 | else 73 | throw new MethodNotFoundException(methodId); 74 | } 75 | 76 | public FieldObj getField(String fieldName) { 77 | Optional fieldObj = fields.stream().filter(f -> f.getFieldName().equals(fieldName)).findFirst(); 78 | 79 | return fieldObj.orElse(null); 80 | } 81 | 82 | /** 83 | * Adds a mapped field to the class 84 | * 85 | * @param fieldObj FieldObj instance 86 | */ 87 | public void addField(FieldObj fieldObj) { 88 | this.fields.add(fieldObj); 89 | Log.log("+ Added Field: %s", fieldObj.getFieldName()); 90 | } 91 | 92 | /** 93 | * Adds a mapped method to the class 94 | * 95 | * @param methodObj MethodObj instance 96 | */ 97 | public void addMethod(MethodObj methodObj) { 98 | this.methods.add(methodObj); 99 | Log.log("+ Added Method: %s", methodObj.getFullMethod()); 100 | } 101 | 102 | public boolean isObfuscated() { 103 | return obfClassName != null; 104 | } 105 | 106 | public boolean hasParent() { 107 | return parent != null; 108 | } 109 | 110 | public boolean hasImplementedClasses() { 111 | return !interfaces.isEmpty(); 112 | } 113 | 114 | /** 115 | * Checks to see if a list of available annotations provided contains an annotation class 116 | * @param c Annotation class to look for 117 | * @param availableAnnotations List of annotations 118 | * @return Returns true if it does contain the annotation 119 | */ 120 | public boolean hasAnnotation(Class c, List availableAnnotations) { 121 | if (availableAnnotations != null) { 122 | Iterator annotations = availableAnnotations.iterator(); 123 | 124 | while (annotations.hasNext()) { 125 | AnnotationNode annotation = annotations.next(); 126 | 127 | if (annotation.desc.equals("L" + c.getName().replace(".", "/") + ";")) { 128 | return true; 129 | } 130 | } 131 | } 132 | return false; 133 | } 134 | 135 | /** 136 | * Checks to see if a list of available annotations provided contains an annotation class and then removes it 137 | * @param c Annotation class to look for 138 | * @param availableAnnotations List of annotations 139 | */ 140 | public void removeAnnotation(Class c, List availableAnnotations) { 141 | if (availableAnnotations != null) { 142 | Iterator annotations = availableAnnotations.iterator(); 143 | 144 | while (annotations.hasNext()) { 145 | AnnotationNode annotation = annotations.next(); 146 | 147 | if (annotation.desc.equals("L" + c.getName().replace(".", "/") + ";")) { 148 | annotations.remove(); 149 | return; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/obj/FieldObj.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping.obj; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author Pants 7 | */ 8 | @Data 9 | public class FieldObj { 10 | 11 | private final String pkg, fieldName; 12 | private String obfPkg, obfFieldName; 13 | 14 | public FieldObj(String pkg, String fieldName) { 15 | this.pkg = pkg; 16 | this.fieldName = fieldName; 17 | } 18 | 19 | public String getField() { 20 | return pkg + "." + fieldName; 21 | } 22 | 23 | /** 24 | * Check to see if the method has had an obfuscated name assigned to it 25 | * 26 | * @return Returns true if the method has an obfuscated name set 27 | */ 28 | public boolean isObfuscated() { 29 | return obfFieldName != null; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/mapping/obj/MethodObj.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping.obj; 2 | 3 | import lombok.Data; 4 | 5 | import static wtf.pants.stamp.asm.AccessUtil.isStatic; 6 | 7 | /** 8 | * @author Pants 9 | */ 10 | @Data 11 | public class MethodObj { 12 | 13 | private String obfPkg, obfMethodName, obfMethodDesc; 14 | 15 | private final String pkg, methodName, methodDesc; 16 | private final int access; 17 | 18 | private boolean obfuscationDisable = false; 19 | 20 | public MethodObj(String pkg, String methodName, String methodDesc, int access) { 21 | this.pkg = pkg; 22 | this.methodName = methodName; 23 | this.methodDesc = methodDesc; 24 | this.access = access; 25 | } 26 | 27 | /** 28 | * Gets the full method name. The package, the method name and desc 29 | * 30 | * @return Returns the full method path and desc "wtf/pants/stamp/mapping/obj/MethodObj.getFullMethod()V" 31 | */ 32 | public String getFullMethod() { 33 | return pkg + "." + methodName + methodDesc; 34 | } 35 | 36 | /** 37 | * Gets the method name and desc 38 | * 39 | * @return Returns the method name and desc eg "getMethod()V" 40 | */ 41 | public String getMethod() { 42 | return methodName + methodDesc; 43 | } 44 | 45 | /** 46 | * Check to see if the method has had an obfuscated name assigned to it 47 | * 48 | * @return Returns true if the method has an obfuscated name set 49 | */ 50 | public boolean isObfuscated() { 51 | return obfMethodName != null; 52 | } 53 | 54 | /** 55 | * Checks if the method is 'safe'. If the method is a constructor or main method it should not be obfuscated 56 | * and deemed unsafe for obfuscation. 57 | * 58 | * @return Returns true if the method is not a constructor or main method 59 | */ 60 | public boolean isSafeMethod() { 61 | return !(methodName.startsWith("<") //, 62 | || (methodName.equals("main") && methodDesc.equals("([Ljava/lang/String;)V") && isStatic(access))); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/Obfuscator.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.objectweb.asm.ClassReader; 6 | import org.objectweb.asm.tree.ClassNode; 7 | import wtf.pants.stamp.asm.ASM; 8 | import wtf.pants.stamp.mapping.ClassCollector; 9 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 10 | import wtf.pants.stamp.mapping.obj.ClassMap; 11 | 12 | /** 13 | * @author Pants 14 | */ 15 | @Getter 16 | public abstract class Obfuscator extends ASM { 17 | 18 | private final String name; 19 | //Higher the priority, the sooner it will executed 20 | private final int priority; 21 | 22 | @Setter 23 | @Getter 24 | private boolean enabled = true; 25 | 26 | public Obfuscator(String name) { 27 | this(name, 0); 28 | } 29 | 30 | public Obfuscator(String name, int priority) { 31 | this.name = name; 32 | this.priority = priority; 33 | } 34 | 35 | protected boolean isClassObfuscated(ClassCollector collector, ClassNode cn){ 36 | try { 37 | ClassMap classMap = collector.getClassMap(cn.name); 38 | 39 | return classMap.isObfuscated(); 40 | } catch (ClassMapNotFoundException ex) { 41 | //This will be thrown when the class has already been obfuscated 42 | return true; 43 | } 44 | } 45 | 46 | public abstract void obfuscate(ClassReader classReader, ClassNode cn, int pass); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/ObfuscatorManager.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.ClassWriter; 5 | import org.objectweb.asm.tree.ClassNode; 6 | import wtf.pants.stamp.Stamp; 7 | import wtf.pants.stamp.obfuscator.obfuscators.*; 8 | import wtf.pants.stamp.obfuscator.obfuscators.classes.ObfuscatorClasses; 9 | import wtf.pants.stamp.util.Log; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * @author Pants 16 | */ 17 | public class ObfuscatorManager { 18 | 19 | private List obfuscatorList; 20 | 21 | public ObfuscatorManager(Stamp stamp) { 22 | this.obfuscatorList = new ArrayList<>(); 23 | this.obfuscatorList.add(new ObfuscatorMethods(stamp.getCollector())); 24 | this.obfuscatorList.add(new ObfuscatorFields(stamp.getCollector())); 25 | this.obfuscatorList.add(new ObfuscatorClasses(stamp.getCollector())); 26 | this.obfuscatorList.add(new ObfuscatorStrings(stamp.getCollector())); 27 | this.obfuscatorList.add(new ObfuscatorLocalVars(stamp.getCollector())); 28 | } 29 | 30 | public byte[] obfuscate(ClassReader cr, ClassNode cn) { 31 | final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 32 | 33 | cr.accept(cn, 0); 34 | obfuscatorList.stream() 35 | .sorted((o1, o2) -> Integer.compare(o2.getPriority(), o1.getPriority())) //o1.get, o2.get 36 | .filter(Obfuscator::isEnabled) 37 | .forEach(o -> o.obfuscate(cr, cn, 0)); 38 | 39 | Log.debug("Accepting class: %s", cn.name); 40 | cn.accept(cw); 41 | Log.debug("Accepted class"); 42 | 43 | return cw.toByteArray(); 44 | } 45 | 46 | /** 47 | * Disables an obfuscation type. 48 | * 49 | * @param name The name of the obfuscation type to disable 50 | */ 51 | public void disableObfuscation(String name) { 52 | obfuscatorList.stream().filter(o -> o.getName().equalsIgnoreCase(name)).forEach(o -> o.setEnabled(false)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/obfuscators/ObfuscatorFields.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator.obfuscators; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.tree.*; 5 | import wtf.pants.stamp.mapping.ClassCollector; 6 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 7 | import wtf.pants.stamp.mapping.obj.ClassMap; 8 | import wtf.pants.stamp.mapping.obj.FieldObj; 9 | import wtf.pants.stamp.obfuscator.Obfuscator; 10 | import wtf.pants.stamp.util.Log; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * @author Pants 16 | */ 17 | @SuppressWarnings("unchecked") 18 | public class ObfuscatorFields extends Obfuscator { 19 | 20 | private final ClassCollector cc; 21 | 22 | public ObfuscatorFields(ClassCollector cc) { 23 | super("Fields", 1); 24 | this.cc = cc; 25 | } 26 | 27 | /** 28 | * Searches a method's instructions for field nodes and renames them to the assigned obfuscated names 29 | * @param cn ClassNode 30 | * @param methodNode MethodNode to search 31 | */ 32 | private void searchMethodInsn(ClassNode cn, MethodNode methodNode) { 33 | if (methodNode.instructions == null) { 34 | return; 35 | } 36 | 37 | final InsnList insnList = methodNode.instructions; 38 | 39 | for (int i = 0; i < insnList.size(); i++) { 40 | AbstractInsnNode node = insnList.get(i); 41 | 42 | if (node instanceof FieldInsnNode) { 43 | FieldInsnNode fieldInsnNode = (FieldInsnNode) node; 44 | try { 45 | final FieldObj fieldObj = cc.getClassMap(fieldInsnNode.owner).getField(fieldInsnNode.name); 46 | 47 | if (fieldObj != null && fieldObj.isObfuscated()) { 48 | fieldInsnNode.name = fieldObj.getObfFieldName(); 49 | } 50 | } catch (ClassMapNotFoundException e) { 51 | Log.error("Class not found...? %s", fieldInsnNode.name); 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public void obfuscate(ClassReader classReader, ClassNode cn, int pass) { 59 | if (cn.fields != null) { 60 | final List list = cn.fields; 61 | 62 | list.forEach(fieldNode -> { 63 | try { 64 | FieldObj fieldObj = cc.getClassMap(cn.name).getField(fieldNode.name); 65 | if (fieldObj != null && fieldObj.isObfuscated()) { 66 | Log.log("[field] Obfuscated %s -> %s", fieldObj.getObfFieldName(), fieldNode.name); 67 | fieldNode.name = fieldObj.getObfFieldName(); 68 | } 69 | } catch (ClassMapNotFoundException e) { 70 | Log.error("Class not found...? %s", cn.name); 71 | } 72 | }); 73 | } 74 | 75 | if (cn.methods != null) { 76 | final List methodNodes = cn.methods; 77 | methodNodes.forEach(methodNode -> searchMethodInsn(cn, methodNode)); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/obfuscators/ObfuscatorLocalVars.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator.obfuscators; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.tree.ClassNode; 5 | import org.objectweb.asm.tree.LocalVariableNode; 6 | import org.objectweb.asm.tree.MethodNode; 7 | import wtf.pants.stamp.mapping.ClassCollector; 8 | import wtf.pants.stamp.obfuscator.Obfuscator; 9 | import wtf.pants.stamp.util.ObfUtil; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author Pants 15 | */ 16 | @SuppressWarnings("unchecked") 17 | public class ObfuscatorLocalVars extends Obfuscator { 18 | 19 | private final ClassCollector collector; 20 | 21 | public ObfuscatorLocalVars(ClassCollector collector) { 22 | super("Local Vars", 0); 23 | this.collector = collector; 24 | } 25 | 26 | @Override 27 | public void obfuscate(ClassReader classReader, ClassNode cn, int pass) { 28 | if(!isClassObfuscated(collector, cn)) 29 | return; 30 | 31 | final List list = cn.methods; 32 | list.forEach(m -> { 33 | if (m.localVariables != null) { 34 | List local = m.localVariables; 35 | local.forEach(l -> l.name = ObfUtil.getRandomObfString()); 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/obfuscators/ObfuscatorMethods.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator.obfuscators; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.Handle; 5 | import org.objectweb.asm.tree.*; 6 | import wtf.pants.stamp.annotations.StampPack; 7 | import wtf.pants.stamp.annotations.StampPreserve; 8 | import wtf.pants.stamp.mapping.ClassCollector; 9 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 10 | import wtf.pants.stamp.mapping.exceptions.MethodNotFoundException; 11 | import wtf.pants.stamp.mapping.obj.ClassMap; 12 | import wtf.pants.stamp.mapping.obj.MethodObj; 13 | import wtf.pants.stamp.obfuscator.Obfuscator; 14 | import wtf.pants.stamp.util.Log; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.Iterator; 19 | import java.util.List; 20 | 21 | /** 22 | * @author Pants 23 | */ 24 | @SuppressWarnings("unchecked") 25 | public class ObfuscatorMethods extends Obfuscator { 26 | 27 | private final ClassCollector cc; 28 | 29 | public ObfuscatorMethods(ClassCollector cc) { 30 | super("Methods", 1); 31 | this.cc = cc; 32 | } 33 | 34 | private String getMethodId(ClassNode cn, MethodNode m) { 35 | return String.format("%s.%s%s", cn.name, m.name, m.desc); 36 | } 37 | 38 | @Override 39 | public void obfuscate(ClassReader classReader, ClassNode cn, int pass) { 40 | try { 41 | final List methodNodes = cn.methods; 42 | final ClassMap classMap = cc.getClassMap(cn.name); 43 | 44 | methodNodes.forEach(method -> obfuscateMethod(classMap, cn, method)); 45 | } catch (ClassMapNotFoundException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | private void obfuscateMethod(ClassMap classMap, ClassNode cn, MethodNode method) { 51 | try { 52 | final String methodId = getMethodId(cn, method); 53 | final MethodObj methodObj = classMap.getMethod(methodId); 54 | 55 | Log.log("Obfuscating Method: %s", method.name); 56 | 57 | //Removes @StampPreserve annotation 58 | classMap.removeAnnotation(StampPreserve.class, method.invisibleAnnotations); 59 | 60 | obfuscateInstructions(method, classMap); 61 | 62 | //Method name gets obfuscated after the instructions for logging purposes 63 | if (methodObj.isObfuscated()) { 64 | method.name = methodObj.getObfMethodName(); 65 | } 66 | } catch (MethodNotFoundException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | 71 | /** 72 | * Goes through the method's instructions for method calls and lambdas 73 | * 74 | * @param method instance of the method we're searching 75 | * @param map ClassMap instance 76 | */ 77 | private void obfuscateInstructions(MethodNode method, ClassMap map) { 78 | final InsnList array = method.instructions; 79 | final List stringsToReplace = new ArrayList<>(); 80 | 81 | if (method.invisibleAnnotations != null) { 82 | Iterator annotations = method.invisibleAnnotations.iterator(); 83 | 84 | while (annotations.hasNext()) { 85 | AnnotationNode annotation = annotations.next(); 86 | 87 | if (annotation.desc.equals("Lwtf/pants/stamp/annotations/StampStringRename;")) { 88 | stringsToReplace.addAll((List) annotation.values.get(1)); 89 | annotations.remove(); 90 | } 91 | } 92 | } 93 | 94 | for (int i = 0; i < array.size(); i++) { 95 | final AbstractInsnNode node = array.get(i); 96 | 97 | try { 98 | if (node instanceof MethodInsnNode) { 99 | modifyMethodInstruction(map, method, (MethodInsnNode) node); 100 | } else if (node instanceof InvokeDynamicInsnNode) { 101 | modifyLambdaInstruction(map, method, (InvokeDynamicInsnNode) node); 102 | } else if (node instanceof LdcInsnNode) { 103 | LdcInsnNode ldc = (LdcInsnNode) node; 104 | if (ldc.cst instanceof String) { 105 | modifyLdcInstruction(map, method, ldc, stringsToReplace); 106 | } 107 | } 108 | } catch (MethodNotFoundException | ClassMapNotFoundException e) { 109 | //TODO Debate on printing if the method is not found. 110 | } 111 | } 112 | } 113 | 114 | private void modifyMethodInstruction(ClassMap map, MethodNode method, MethodInsnNode methodNode) throws MethodNotFoundException, ClassMapNotFoundException { 115 | final String methodId = methodNode.owner + "." + methodNode.name + "" + methodNode.desc; 116 | 117 | final boolean selfMethod = methodNode.owner.equals(map.getClassName()); 118 | final MethodObj methodMap = 119 | selfMethod ? map.getMethod(methodId) : cc.getClassMap(methodNode.owner).getMethod(methodId); 120 | 121 | if (methodMap.isObfuscated()) { 122 | Log.log("[%s] %s to %s", method.name, methodNode.name, methodMap.getObfMethodName()); 123 | methodNode.name = methodMap.getObfMethodName(); 124 | } 125 | } 126 | 127 | private void modifyLambdaInstruction(ClassMap map, MethodNode method, InvokeDynamicInsnNode in) throws MethodNotFoundException { 128 | if (in.bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")) { 129 | final Handle h = ((Handle) in.bsmArgs[1]); 130 | final String methodId = h.getOwner() + "." + h.getName() + h.getDesc(); 131 | 132 | final MethodObj methodObj = map.getMethod(methodId); 133 | 134 | if (methodObj.isObfuscated()) { 135 | Log.log("[%s] %s to %s", method.name, h.getName(), methodObj.getObfMethodName()); 136 | 137 | final Handle newHandle = new Handle(h.getTag(), h.getOwner(), methodObj.getObfMethodName(), h.getDesc()); 138 | in.bsmArgs[1] = newHandle; 139 | } 140 | } 141 | } 142 | 143 | private void modifyLdcInstruction(ClassMap map, MethodNode method, LdcInsnNode in, List strings) throws MethodNotFoundException, ClassMapNotFoundException { 144 | for (String s : strings) { 145 | final String owner = s.split("\\.")[0]; 146 | final String methodName = s.split("\\.")[1].split("\\(")[0]; 147 | 148 | if (in.cst.toString().equals(methodName)) { 149 | final boolean selfMethod = owner.equals(map.getClassName()); 150 | 151 | final MethodObj methodMap = 152 | selfMethod ? 153 | map.getMethod(s) : 154 | cc.getClassMap(owner).getMethod(s); 155 | 156 | if (methodMap.isObfuscated()) { 157 | in.cst = methodMap.getObfMethodName(); 158 | return; 159 | } 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/obfuscators/ObfuscatorStrings.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator.obfuscators; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.Opcodes; 5 | import org.objectweb.asm.tree.*; 6 | import wtf.pants.stamp.mapping.ClassCollector; 7 | import wtf.pants.stamp.obfuscator.Obfuscator; 8 | import wtf.pants.stamp.util.Log; 9 | import wtf.pants.stamp.util.ObfUtil; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.objectweb.asm.Opcodes.*; 15 | 16 | /** 17 | * @author Pants 18 | */ 19 | @SuppressWarnings("unchecked") 20 | public class ObfuscatorStrings extends Obfuscator { 21 | 22 | private final ClassCollector collector; 23 | 24 | public ObfuscatorStrings(ClassCollector collector) { 25 | super("Strings", -1); 26 | this.collector = collector; 27 | } 28 | 29 | /** 30 | * Searches a MethodNode for strings (LdcInsnNode). It will add the found strings to the List parameter also 31 | * replacing the Ldc node to get from a string array 32 | * 33 | * @param strings A List that will have strings found added to it 34 | * @param fieldName The name of the string array that the Ldc node will be replaced with 35 | * @param classNode The node of the class the method is in 36 | * @param methodNode The method to be searched 37 | */ 38 | private void searchAndReplaceStrings(List strings, String fieldName, ClassNode classNode, MethodNode methodNode) { 39 | final InsnList methodInsn = methodNode.instructions; 40 | final InsnList insnList = new InsnList(); 41 | 42 | setInsn(insnList); 43 | 44 | AbstractInsnNode[] insnNodes = methodInsn.toArray(); 45 | 46 | for (AbstractInsnNode insn : insnNodes) { 47 | if (insn instanceof LdcInsnNode) { 48 | final LdcInsnNode min = (LdcInsnNode) insn; 49 | 50 | //Makes sure the LdcInsnNode's value is a string and not something else 51 | if (min.cst instanceof String && !min.cst.toString().equals("")) { 52 | final String ldcString = min.cst.toString(); 53 | 54 | strings.add(ldcString); 55 | final int id = strings.size(); 56 | 57 | //[] 58 | { 59 | field(GETSTATIC, classNode.name, fieldName, "[Ljava/lang/String;"); 60 | pushInt(id - 1); 61 | aaload(); 62 | } 63 | 64 | continue; 65 | } 66 | } 67 | insnList.add(insn); 68 | } 69 | 70 | methodNode.instructions = insnList; 71 | } 72 | 73 | @Override 74 | public void obfuscate(ClassReader classReader, ClassNode classNode, int pass) { 75 | if(!isClassObfuscated(collector, classNode)) 76 | return; 77 | 78 | Log.log("Obfuscating strings in %s", classNode.name); 79 | 80 | final String stringsFieldName = ObfUtil.getRandomObfString(); 81 | final List strings = new ArrayList<>(); 82 | 83 | List methodNodes = classNode.methods; 84 | 85 | 86 | 87 | if ((classNode.access & ACC_INTERFACE) != 0) { 88 | Log.log("%s is an interface, skipping", classNode.name); 89 | return; 90 | } 91 | 92 | classNode.fields.add(new FieldNode(ACC_PRIVATE + ACC_STATIC, stringsFieldName, "[Ljava/lang/String;", null, null)); 93 | 94 | methodNodes.forEach(m -> searchAndReplaceStrings(strings, stringsFieldName, classNode, m)); 95 | 96 | if (strings.size() == 0) { 97 | Log.error("Class had no strings!"); 98 | return; 99 | } 100 | 101 | MethodNode mn = createStringMethod(strings, stringsFieldName, classNode); 102 | 103 | MethodNode clinit = null; 104 | 105 | for (Object method : classNode.methods) { 106 | if (method instanceof MethodNode) { 107 | if (((MethodNode) method).name.equals("")) { 108 | clinit = (MethodNode) method; 109 | break; 110 | } 111 | } 112 | } 113 | 114 | if (clinit == null) { 115 | Log.info("Adding method"); 116 | classNode.methods.add(mn); 117 | } else { 118 | Log.error("Existing method found! Inserting after"); 119 | //TODO: No idea if this works need to test it 120 | if (mn != null) { 121 | clinit.instructions.add(mn.instructions); 122 | } 123 | return; 124 | } 125 | 126 | Log.log("Finished obfuscate strings"); 127 | } 128 | 129 | /** 130 | * Creates the method that holds and loads the obfuscated strings 131 | * 132 | * @param strings A list containing the strings found within the class 133 | * @param fieldName The string array field name 134 | * @param cn The class the method is being added to 135 | * @return Returns a newly created method 136 | */ 137 | private MethodNode createStringMethod(List strings, String fieldName, ClassNode cn) { 138 | if (strings.size() == 0) { 139 | return null; 140 | } 141 | 142 | final MethodNode methodNode = new MethodNode(ACC_STATIC, "", "()V", null, null); 143 | final InsnList array = methodNode.instructions; 144 | 145 | setInsn(array); 146 | 147 | pushInt(strings.size()); 148 | array.add(new TypeInsnNode(ANEWARRAY, "[I")); 149 | array.add(new VarInsnNode(ASTORE, 0)); 150 | 151 | int placement = 0; 152 | 153 | //adds the encoded strings to the 2d int array 154 | for (final String string : strings) { 155 | final char[] charArray = string.toCharArray(); 156 | 157 | array.add(new VarInsnNode(ALOAD, 0)); 158 | //Place in array 159 | pushInt(placement); 160 | pushInt(charArray.length); 161 | array.add(new IntInsnNode(NEWARRAY, T_INT)); 162 | dup(); 163 | 164 | for (int id = 0; id < charArray.length; id++) { 165 | final int charId = ((int) charArray[id]) + (placement + 1); 166 | 167 | pushInt(id); 168 | pushInt(charId); 169 | 170 | array.add(new InsnNode(IASTORE)); 171 | 172 | if (id != charArray.length - 1) { 173 | dup(); 174 | } else { 175 | aastore(); 176 | } 177 | } 178 | 179 | //Log.log("Added: '" + string.replace("%", "%%") + "'"); 180 | 181 | placement++; 182 | } 183 | 184 | addDecoder(fieldName, array, cn); 185 | 186 | return methodNode; 187 | } 188 | 189 | /** 190 | * Generates the method that loads the strings into a string array 191 | * TODO: This needs to be redone more eye friendly 192 | * 193 | * @param fieldName String array field name 194 | * @param array The instructions of the method we created 195 | * @param cn The Class we're adding this to 196 | */ 197 | private void addDecoder(String fieldName, InsnList array, ClassNode cn) { 198 | //Initializes 'static String[] strings' 199 | array.add(new VarInsnNode(ALOAD, 0)); 200 | array.add(new InsnNode(ARRAYLENGTH)); 201 | array.add(new TypeInsnNode(ANEWARRAY, "java/lang/String")); 202 | array.add(new FieldInsnNode(PUTSTATIC, cn.name, fieldName, "[Ljava/lang/String;")); 203 | 204 | 205 | //Int offset 206 | array.add(new InsnNode(ICONST_1)); 207 | array.add(new VarInsnNode(ISTORE, 1)); 208 | 209 | //for (int[] obfArray : obfStringArray) 210 | array.add(new VarInsnNode(ALOAD, 0)); 211 | array.add(new VarInsnNode(ASTORE, 2)); 212 | array.add(new VarInsnNode(ALOAD, 2)); 213 | array.add(new InsnNode(ARRAYLENGTH)); 214 | array.add(new VarInsnNode(ISTORE, 3)); 215 | array.add(new InsnNode(ICONST_0)); 216 | array.add(new VarInsnNode(ISTORE, 4)); 217 | 218 | // 219 | LabelNode l4 = new LabelNode(); 220 | array.add(new LabelNode(l4.getLabel())); 221 | array.add(new FrameNode(Opcodes.F_FULL, 5, new Object[]{"[[I", Opcodes.INTEGER, "[[I", Opcodes.INTEGER, Opcodes.INTEGER}, 0, new Object[]{})); 222 | array.add(new VarInsnNode(ILOAD, 4)); 223 | array.add(new VarInsnNode(ILOAD, 3)); 224 | // 225 | LabelNode l5 = new LabelNode(); 226 | array.add(new JumpInsnNode(IF_ICMPGE, l5)); 227 | // //IF_ICMPGE L5 228 | // 229 | array.add(new VarInsnNode(ALOAD, 2)); 230 | array.add(new VarInsnNode(ILOAD, 4)); 231 | array.add(new InsnNode(AALOAD)); 232 | array.add(new VarInsnNode(ASTORE, 5)); 233 | // 234 | // 235 | //String s = "" 236 | array.add(new LdcInsnNode("")); 237 | array.add(new VarInsnNode(ASTORE, 6)); 238 | 239 | //for (int i = 0; i < obfArray.length; i++) 240 | array.add(new InsnNode(ICONST_0)); 241 | array.add(new VarInsnNode(ISTORE, 7)); 242 | // 243 | LabelNode l8 = new LabelNode(); 244 | array.add(new LabelNode(l8.getLabel())); 245 | array.add(new FrameNode(Opcodes.F_APPEND, 3, new Object[]{"[I", "java/lang/String", Opcodes.INTEGER}, 0, null)); 246 | array.add(new VarInsnNode(ILOAD, 7)); 247 | array.add(new VarInsnNode(ALOAD, 5)); 248 | array.add(new InsnNode(ARRAYLENGTH)); 249 | 250 | LabelNode l9 = new LabelNode(); 251 | array.add(new JumpInsnNode(IF_ICMPGE, l9)); 252 | // //IF_ICMPGE L9 253 | // 254 | array.add(new TypeInsnNode(NEW, "java/lang/StringBuilder")); 255 | array.add(new InsnNode(DUP)); 256 | array.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false)); 257 | array.add(new VarInsnNode(ALOAD, 6)); 258 | array.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)); 259 | array.add(new VarInsnNode(ALOAD, 5)); 260 | array.add(new VarInsnNode(ILOAD, 7)); 261 | array.add(new InsnNode(IALOAD)); 262 | array.add(new VarInsnNode(ILOAD, 1)); 263 | array.add(new InsnNode(ISUB)); 264 | array.add(new InsnNode(I2C)); 265 | array.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(C)Ljava/lang/StringBuilder;", false)); 266 | array.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)); 267 | array.add(new VarInsnNode(ASTORE, 6)); 268 | // 269 | array.add(new IincInsnNode(7, 1)); 270 | array.add(new JumpInsnNode(GOTO, l8)); 271 | 272 | array.add(new LabelNode(l9.getLabel())); 273 | array.add(new FrameNode(F_CHOP, 1, null, 0, null)); 274 | field(GETSTATIC, cn.name, fieldName, "[Ljava/lang/String;"); 275 | array.add(new IincInsnNode(1, 1)); 276 | array.add(new VarInsnNode(ILOAD, 1)); 277 | pushInt(2); 278 | isub(); 279 | aload(6); 280 | aastore(); 281 | 282 | 283 | array.add(new IincInsnNode(4, 1)); 284 | array.add(new JumpInsnNode(GOTO, l4)); 285 | 286 | array.add(new LabelNode(l5.getLabel())); 287 | array.add(new FrameNode(F_FULL, 0, new Object[]{}, 0, new Object[]{})); 288 | 289 | array.add(new InsnNode(RETURN)); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/obfuscators/classes/ClassInsnModifier.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator.obfuscators.classes; 2 | 3 | import org.objectweb.asm.Handle; 4 | import org.objectweb.asm.Type; 5 | import org.objectweb.asm.tree.*; 6 | import wtf.pants.stamp.mapping.obj.ClassMap; 7 | 8 | /** 9 | * @author Pants 10 | */ 11 | @SuppressWarnings("unchecked") 12 | class ClassInsnModifier { 13 | 14 | private String obfuscateString(ClassMap classMap, String s) { 15 | final String cn = classMap.getClassName(); 16 | final String ob = classMap.getObfClassName(); 17 | 18 | if(cn.equalsIgnoreCase(s)) 19 | return ob; 20 | 21 | return s.replace(cn + ";", ob + ";") 22 | .replace(cn + ".", ob + "."); 23 | } 24 | 25 | private Handle obfuscateHandle(ClassMap c, Handle h) { 26 | int tag = h.getTag(); 27 | String owner = h.getOwner(); 28 | String name = h.getName(); 29 | String desc = h.getDesc(); 30 | 31 | owner = obfuscateString(c, owner); 32 | desc = obfuscateString(c, desc); 33 | 34 | return new Handle(tag, owner, name, desc); 35 | } 36 | 37 | void obfuscateMethodInsn(ClassMap c, MethodInsnNode method) { 38 | if (method.owner.contains(c.getClassName())) { 39 | method.owner = obfuscateString(c, method.owner); 40 | } 41 | 42 | if (method.desc.contains(c.getClassName())) { 43 | method.desc = obfuscateString(c, method.desc); 44 | } 45 | } 46 | 47 | void obfuscateFieldInsn(ClassMap c, FieldInsnNode field) { 48 | if (field.owner.contains(c.getClassName())) { 49 | field.owner = obfuscateString(c, field.owner); 50 | } 51 | 52 | if (field.desc.contains(c.getClassName())) { 53 | field.desc = obfuscateString(c, field.desc); 54 | } 55 | } 56 | 57 | void obfuscateTypeInsn(ClassMap c, TypeInsnNode typeInsn) { 58 | if (typeInsn.desc.contains(c.getClassName())) { 59 | typeInsn.desc = obfuscateString(c, typeInsn.desc); 60 | } 61 | } 62 | 63 | void obfuscateFrameInsn(ClassMap c, FrameNode frameNode) { 64 | if (frameNode.local != null) { 65 | for (int i1 = 0; i1 < frameNode.local.size(); i1++) { 66 | Object o = frameNode.local.get(i1); 67 | if (o instanceof String) { 68 | if (o.toString().contains(c.getClassName())) 69 | frameNode.local.set(i1, obfuscateString(c, o.toString())); 70 | } 71 | } 72 | } 73 | 74 | if (frameNode.stack != null) { 75 | for (int j = 0; j < frameNode.stack.size(); j++) { 76 | Object o = frameNode.stack.get(j); 77 | if (o instanceof String) { 78 | if (o.toString().contains(c.getClassName())) 79 | frameNode.stack.set(j, obfuscateString(c, o.toString())); 80 | } 81 | } 82 | } 83 | } 84 | 85 | void obfuscateLdc(ClassMap c, LdcInsnNode ldc) { 86 | if (ldc.cst instanceof Type) { 87 | Type type = (Type) ldc.cst; 88 | ldc.cst = Type.getType(obfuscateString(c, type.getDescriptor())); 89 | } 90 | } 91 | 92 | void obfuscateLambda(ClassMap c, InvokeDynamicInsnNode lambda) { 93 | lambda.bsm = obfuscateHandle(c, lambda.bsm); 94 | 95 | if (lambda.desc != null) { 96 | lambda.desc = obfuscateString(c, lambda.desc); 97 | } 98 | 99 | //TODO: Make this recursively check the args for Object[] 100 | for (int i1 = 0; i1 < lambda.bsmArgs.length; i1++) { 101 | if (lambda.bsmArgs[i1] instanceof Handle) { 102 | Handle handle = (Handle) lambda.bsmArgs[i1]; 103 | lambda.bsmArgs[i1] = obfuscateHandle(c, handle); 104 | } 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/obfuscator/obfuscators/classes/ObfuscatorClasses.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.obfuscator.obfuscators.classes; 2 | 3 | import org.objectweb.asm.*; 4 | import org.objectweb.asm.tree.*; 5 | import wtf.pants.stamp.mapping.ClassCollector; 6 | import wtf.pants.stamp.mapping.exceptions.ClassMapNotFoundException; 7 | import wtf.pants.stamp.mapping.obj.ClassMap; 8 | import wtf.pants.stamp.obfuscator.Obfuscator; 9 | import wtf.pants.stamp.util.Log; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author Pants 15 | */ 16 | @SuppressWarnings("unchecked") 17 | public class ObfuscatorClasses extends Obfuscator { 18 | 19 | private final ClassCollector cc; 20 | private ClassInsnModifier insnHandler; 21 | 22 | public ObfuscatorClasses(ClassCollector cc) { 23 | super("Classes", 0); 24 | this.cc = cc; 25 | this.insnHandler = new ClassInsnModifier(); 26 | } 27 | 28 | private String obfuscateString(ClassMap classMap, String s) { 29 | final String cn = classMap.getClassName(); 30 | final String ob = classMap.getObfClassName(); 31 | 32 | if (cn.equalsIgnoreCase(s)) 33 | return ob; 34 | 35 | return s.replace(cn + ";", ob + ";") 36 | .replace(cn + ".", ob + "."); 37 | } 38 | 39 | private void obfuscateExceptions(ClassMap c, MethodNode methodNode) { 40 | for (int i = 0; i < methodNode.exceptions.size(); i++) { 41 | if (methodNode.exceptions.get(i) instanceof String) { 42 | final String exception = methodNode.exceptions.get(i).toString(); 43 | 44 | if (exception.contains(c.getClassName())) { 45 | methodNode.exceptions.set(i, c.getObfClassName()); 46 | Log.info("Changed exception: %s -> %s", exception, c.getObfClassName()); 47 | } 48 | } 49 | } 50 | 51 | if (methodNode.tryCatchBlocks != null) { 52 | List tryCatchBlocks = methodNode.tryCatchBlocks; 53 | 54 | tryCatchBlocks.forEach(t -> { 55 | if (t.type != null) 56 | t.type = obfuscateString(c, t.type); 57 | }); 58 | } 59 | } 60 | 61 | private void obfuscateMethod(MethodNode methodNode) { 62 | cc.getClasses().stream() 63 | .filter(ClassMap::isObfuscated) 64 | .forEach(c -> { 65 | if (methodNode.exceptions != null) { 66 | obfuscateExceptions(c, methodNode); 67 | } 68 | 69 | if (methodNode.desc.contains(c.getClassName())) { 70 | final String oldDesc = methodNode.desc; 71 | 72 | methodNode.desc = obfuscateString(c, methodNode.desc); 73 | Log.info("Renamed method desc: %s -> %s", oldDesc, methodNode.desc); 74 | } 75 | 76 | if (methodNode.localVariables != null) { 77 | final List localVars = methodNode.localVariables; 78 | 79 | localVars.stream() 80 | .filter(l -> l.desc.contains(c.getClassName())) 81 | .forEach(l -> l.desc = obfuscateString(c, l.desc)); 82 | } 83 | 84 | searchMethodInsn(c, methodNode); 85 | }); 86 | } 87 | 88 | /** 89 | * Search the method's instructions for Methods, Fields, Types, Frames, and Lambdas 90 | * 91 | * @param c ClassMap instance 92 | * @param methodNode Target method to search 93 | */ 94 | private void searchMethodInsn(ClassMap c, MethodNode methodNode) { 95 | final InsnList insnList = methodNode.instructions; 96 | 97 | for (int i = 0; i < insnList.size(); i++) { 98 | final AbstractInsnNode node = insnList.get(i); 99 | 100 | if (node instanceof MethodInsnNode) 101 | insnHandler.obfuscateMethodInsn(c, (MethodInsnNode) node); 102 | else if (node instanceof FieldInsnNode) 103 | insnHandler.obfuscateFieldInsn(c, (FieldInsnNode) node); 104 | else if (node instanceof TypeInsnNode) 105 | insnHandler.obfuscateTypeInsn(c, (TypeInsnNode) node); 106 | else if (node instanceof FrameNode) 107 | insnHandler.obfuscateFrameInsn(c, (FrameNode) node); 108 | else if (node instanceof InvokeDynamicInsnNode) 109 | insnHandler.obfuscateLambda(c, (InvokeDynamicInsnNode) node); 110 | else if (node instanceof LdcInsnNode) 111 | insnHandler.obfuscateLdc(c, (LdcInsnNode) node); 112 | } 113 | } 114 | 115 | private void modifyInterfaceNames(ClassNode cn) { 116 | for (int i = 0; i < cn.interfaces.size(); i++) { 117 | if (cn.interfaces.get(i) instanceof String) { 118 | try { 119 | String in = (String) cn.interfaces.get(i); 120 | ClassMap superClass = cc.getClassMap(in); 121 | if (superClass.isObfuscated()) { 122 | cn.interfaces.set(i, obfuscateString(superClass, in)); 123 | } 124 | } catch (ClassMapNotFoundException ignored) { 125 | } 126 | } 127 | } 128 | } 129 | 130 | private void obfuscateFields(ClassNode cn) { 131 | final List fieldNodes = cn.fields; 132 | 133 | cc.getClasses().stream() 134 | .filter(ClassMap::isObfuscated) 135 | .forEach(c -> fieldNodes.forEach(field -> { 136 | if (field.signature != null) { 137 | field.signature = obfuscateString(c, field.signature); 138 | } 139 | 140 | if (field.desc.contains(c.getClassName())) { 141 | field.desc = obfuscateString(c, field.desc); 142 | } 143 | })); 144 | } 145 | 146 | @Override 147 | public void obfuscate(ClassReader classReader, ClassNode cn, int pass) { 148 | try { 149 | final ClassMap classMap = cc.getClassMap(cn.name); 150 | 151 | if (classMap.isObfuscated()) { 152 | //TODO: Make this a toggle between null 153 | cn.sourceFile = classMap.getObfClassName() + ".java"; 154 | cn.sourceDebug = null; 155 | cn.name = classMap.getObfClassName(); 156 | } 157 | 158 | if (cn.superName != null) { 159 | try { 160 | ClassMap superClass = cc.getClassMap(cn.superName); 161 | if (superClass.isObfuscated()) { 162 | cn.superName = superClass.getObfClassName(); 163 | Log.log("Modified %s's extended class' name", classMap.getClassName()); 164 | } 165 | } catch (ClassMapNotFoundException ignored) { 166 | } 167 | } 168 | 169 | if (cn.interfaces != null) { 170 | modifyInterfaceNames(cn); 171 | } 172 | 173 | if (cn.fields != null) { 174 | obfuscateFields(cn); 175 | } 176 | 177 | final List methodNodes = cn.methods; 178 | 179 | if (cn.methods != null) { 180 | methodNodes.forEach(this::obfuscateMethod); 181 | } 182 | } catch (ClassMapNotFoundException e) { 183 | e.printStackTrace(); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/util/Log.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.util; 2 | 3 | /** 4 | * @author Pants 5 | */ 6 | public class Log { 7 | 8 | public static boolean DEBUG = false; 9 | 10 | public static void info(String msg, Object... o) { 11 | print("INFO", msg, o); 12 | } 13 | 14 | public static void log(String msg, Object... o) { 15 | print("LOG", msg, o); 16 | } 17 | 18 | public static void error(String msg, Object... o) { 19 | print("ERROR", msg, o); 20 | } 21 | 22 | public static void warning(String msg, Object... o) { 23 | print("WARN", msg, o); 24 | } 25 | 26 | public static void debug(String msg, Object... o) { 27 | if (DEBUG) { 28 | print("DEBUG", msg, o); 29 | } 30 | } 31 | 32 | public static void success(String msg, Object... o) { 33 | print("SUCCESS", msg, o); 34 | } 35 | 36 | public static void print(String flag, String msg, Object... o) { 37 | print(flag, false, msg, o); 38 | } 39 | 40 | public static void print(String flag, boolean err, String msg, Object... o) { 41 | if (!err) 42 | System.out.println("[" + flag.toUpperCase() + "] " + String.format(msg, o)); 43 | else 44 | System.err.println("[" + flag.toUpperCase() + "] " + String.format(msg, o)); 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/util/ObfUtil.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.util; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * @author Pants 7 | */ 8 | public class ObfUtil { 9 | 10 | private static final String[] obfuscationChars = {"I", "l", "i", "!", "1", "|"}; 11 | private static Random random = new Random(); 12 | 13 | /** 14 | * Generates a random string to use for obfuscated names 15 | * @return Returns 12 character string looking similar to: 'I|1I|Li!il||' 16 | */ 17 | public static String getRandomObfString() { 18 | int length = 12; 19 | int charSize = obfuscationChars.length; 20 | 21 | StringBuilder randString = new StringBuilder(); 22 | for (int i = 0; i < length; i++) { 23 | randString.append(obfuscationChars[random.nextInt(charSize)]); 24 | } 25 | 26 | return randString.toString(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/wtf/pants/stamp/util/ZipUtils.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.util; 2 | 3 | import java.io.IOException; 4 | import java.util.zip.ZipEntry; 5 | import java.util.zip.ZipOutputStream; 6 | 7 | /** 8 | * @author Pants 9 | */ 10 | public class ZipUtils { 11 | 12 | /** 13 | * Adds a directory to a .zip file 14 | * 15 | * @param zipOutputStream instance of ZipOutputStream 16 | * @param dirName Directory name you want to add to the .zip file 17 | * @throws IOException 18 | */ 19 | public static void addDirectoryToZip(ZipOutputStream zipOutputStream, String dirName) throws IOException { 20 | zipOutputStream.putNextEntry(new ZipEntry(dirName)); 21 | zipOutputStream.closeEntry(); 22 | } 23 | 24 | /** 25 | * Adds a file to a .zip file 26 | * 27 | * @param zipOutputStream instance of ZipOutputStream 28 | * @param path Path to where you want to place the file inside the .zip 29 | * @param bytes The bytes of the file you want to add to the .zip 30 | * @throws IOException 31 | */ 32 | public static void addFileToZip(ZipOutputStream zipOutputStream, String path, byte[] bytes) throws IOException { 33 | zipOutputStream.putNextEntry(new ZipEntry(path)); 34 | zipOutputStream.write(bytes, 0, bytes.length); 35 | zipOutputStream.closeEntry(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/wtf/pants/stamp/asm/ASMTest.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.asm; 2 | 3 | import org.junit.Test; 4 | import org.objectweb.asm.Opcodes; 5 | import org.objectweb.asm.tree.InsnList; 6 | import wtf.pants.stamp.asm.ASM; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | 11 | /** 12 | * @author Pants 13 | */ 14 | public class ASMTest { 15 | 16 | @Test 17 | public void testPushInt() throws Exception { 18 | 19 | final InsnList insnList = new InsnList(); 20 | final ASM asm = new ASM(insnList); 21 | 22 | //0 - 5 should all be ICONST_# 23 | { 24 | asm.pushInt(0); 25 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.ICONST_0); 26 | 27 | asm.pushInt(3); 28 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.ICONST_3); 29 | } 30 | 31 | //-127 - -1 and 6 - 127, should always be BIPUSH 32 | { 33 | asm.pushInt(-3); 34 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.BIPUSH); 35 | 36 | asm.pushInt(10); 37 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.BIPUSH); 38 | 39 | asm.pushInt(127); 40 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.BIPUSH); 41 | 42 | asm.pushInt(-127); 43 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.BIPUSH); 44 | } 45 | 46 | //Everything <= -128 and everything >= 128 should be SIPUSH 47 | { 48 | asm.pushInt(128); 49 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.SIPUSH); 50 | 51 | asm.pushInt(-128); 52 | assertEquals(insnList.get(insnList.size() - 1).getOpcode(), Opcodes.SIPUSH); 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/wtf/pants/stamp/asm/AccessUtilTest.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.asm; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | 6 | import static org.objectweb.asm.Opcodes.*; 7 | 8 | 9 | /** 10 | * @author Pants 11 | */ 12 | public class AccessUtilTest { 13 | 14 | private int public_static_final = ACC_PUBLIC + ACC_STATIC + ACC_FINAL; 15 | private int public_access = ACC_PUBLIC; 16 | 17 | @Test 18 | public void testIsFinal() throws Exception { 19 | //Access is final 20 | assertTrue(AccessUtil.isFinal(public_static_final)); 21 | 22 | //Access is not final 23 | assertFalse(AccessUtil.isFinal(public_access)); 24 | } 25 | 26 | @Test 27 | public void testIsStatic() throws Exception { 28 | //Access is static 29 | assertTrue(AccessUtil.isStatic(public_static_final)); 30 | 31 | //Access is not static 32 | assertFalse(AccessUtil.isStatic(public_access)); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/wtf/pants/stamp/mapping/obj/MethodObjTest.java: -------------------------------------------------------------------------------- 1 | package wtf.pants.stamp.mapping.obj; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | import org.objectweb.asm.Opcodes; 7 | 8 | 9 | /** 10 | * @author Pants 11 | */ 12 | public class MethodObjTest { 13 | 14 | @Test 15 | public void testIsSafeMethod() throws Exception { 16 | //Safe method: Is not a constructor or main method 17 | final MethodObj safeMethod = new MethodObj("a/pkg/AClass", "testMethod", "(Ljava/lang/String;)V", Opcodes.ACC_PUBLIC); 18 | assertTrue(safeMethod.isSafeMethod()); 19 | 20 | //Not safe: Method is a constructor () 21 | final MethodObj initMethod = new MethodObj("a/pkg/AClass", "", "()V", Opcodes.ACC_PUBLIC); 22 | assertFalse(initMethod.isSafeMethod()); 23 | 24 | //Not safe: Method is a main method 25 | final MethodObj mainMethod = new MethodObj("a/pkg/AClass", "main", "([Ljava/lang/String;)V", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC); 26 | assertFalse(mainMethod.isSafeMethod()); 27 | } 28 | } --------------------------------------------------------------------------------