├── .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 | }
--------------------------------------------------------------------------------