summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/Deobfuscator.java
diff options
context:
space:
mode:
authorGravatar Michael Smith2015-05-21 23:30:00 +0100
committerGravatar Michael Smith2015-05-21 23:30:00 +0100
commite3f452250e51b7271f3989c7dfd12e4422934942 (patch)
tree5aa482f9a6e21eb318a3e23e7d8274d77c73faf6 /src/cuchaz/enigma/Deobfuscator.java
downloadenigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.tar.gz
enigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.tar.xz
enigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.zip
Support Gradle alongside SSJB
This makes builds faster, simpler and better automated but still keeps Cuchaz happy. :)
Diffstat (limited to 'src/cuchaz/enigma/Deobfuscator.java')
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java548
1 files changed, 548 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
new file mode 100644
index 0000000..08a974a
--- /dev/null
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -0,0 +1,548 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileOutputStream;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.io.StringWriter;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarEntry;
22import java.util.jar.JarFile;
23import java.util.jar.JarOutputStream;
24
25import javassist.CtClass;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Maps;
29import com.google.common.collect.Sets;
30import com.strobel.assembler.metadata.MetadataSystem;
31import com.strobel.assembler.metadata.TypeDefinition;
32import com.strobel.assembler.metadata.TypeReference;
33import com.strobel.decompiler.DecompilerContext;
34import com.strobel.decompiler.DecompilerSettings;
35import com.strobel.decompiler.PlainTextOutput;
36import com.strobel.decompiler.languages.java.JavaOutputVisitor;
37import com.strobel.decompiler.languages.java.ast.AstBuilder;
38import com.strobel.decompiler.languages.java.ast.CompilationUnit;
39import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
40
41import cuchaz.enigma.analysis.EntryReference;
42import cuchaz.enigma.analysis.JarClassIterator;
43import cuchaz.enigma.analysis.JarIndex;
44import cuchaz.enigma.analysis.SourceIndex;
45import cuchaz.enigma.analysis.SourceIndexVisitor;
46import cuchaz.enigma.analysis.Token;
47import cuchaz.enigma.bytecode.ClassProtectifier;
48import cuchaz.enigma.bytecode.ClassPublifier;
49import cuchaz.enigma.mapping.ArgumentEntry;
50import cuchaz.enigma.mapping.BehaviorEntry;
51import cuchaz.enigma.mapping.ClassEntry;
52import cuchaz.enigma.mapping.ClassMapping;
53import cuchaz.enigma.mapping.ConstructorEntry;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.FieldMapping;
57import cuchaz.enigma.mapping.Mappings;
58import cuchaz.enigma.mapping.MappingsChecker;
59import cuchaz.enigma.mapping.MappingsRenamer;
60import cuchaz.enigma.mapping.MethodEntry;
61import cuchaz.enigma.mapping.MethodMapping;
62import cuchaz.enigma.mapping.TranslationDirection;
63import cuchaz.enigma.mapping.Translator;
64
65public class Deobfuscator {
66
67 public interface ProgressListener {
68 void init(int totalWork, String title);
69 void onProgress(int numDone, String message);
70 }
71
72 private JarFile m_jar;
73 private DecompilerSettings m_settings;
74 private JarIndex m_jarIndex;
75 private Mappings m_mappings;
76 private MappingsRenamer m_renamer;
77 private Map<TranslationDirection,Translator> m_translatorCache;
78
79 public Deobfuscator(JarFile jar) throws IOException {
80 m_jar = jar;
81
82 // build the jar index
83 m_jarIndex = new JarIndex();
84 m_jarIndex.indexJar(m_jar, true);
85
86 // config the decompiler
87 m_settings = DecompilerSettings.javaDefaults();
88 m_settings.setMergeVariables(true);
89 m_settings.setForceExplicitImports(true);
90 m_settings.setForceExplicitTypeArguments(true);
91 m_settings.setShowDebugLineNumbers(true);
92 // DEBUG
93 //m_settings.setShowSyntheticMembers(true);
94
95 // init defaults
96 m_translatorCache = Maps.newTreeMap();
97
98 // init mappings
99 setMappings(new Mappings());
100 }
101
102 public JarFile getJar() {
103 return m_jar;
104 }
105
106 public String getJarName() {
107 return m_jar.getName();
108 }
109
110 public JarIndex getJarIndex() {
111 return m_jarIndex;
112 }
113
114 public Mappings getMappings() {
115 return m_mappings;
116 }
117
118 public void setMappings(Mappings val) {
119 setMappings(val, true);
120 }
121
122 public void setMappings(Mappings val, boolean warnAboutDrops) {
123 if (val == null) {
124 val = new Mappings();
125 }
126
127 // drop mappings that don't match the jar
128 MappingsChecker checker = new MappingsChecker(m_jarIndex);
129 checker.dropBrokenMappings(val);
130 if (warnAboutDrops) {
131 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
132 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
133 }
134 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
135 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
136 }
137 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
138 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
139 }
140 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
141 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
142 }
143 }
144
145 // check for related method inconsistencies
146 if (checker.getRelatedMethodChecker().hasProblems()) {
147 throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + checker.getRelatedMethodChecker().getReport());
148 }
149
150 m_mappings = val;
151 m_renamer = new MappingsRenamer(m_jarIndex, val);
152 m_translatorCache.clear();
153 }
154
155 public Translator getTranslator(TranslationDirection direction) {
156 Translator translator = m_translatorCache.get(direction);
157 if (translator == null) {
158 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
159 m_translatorCache.put(direction, translator);
160 }
161 return translator;
162 }
163
164 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
165 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
166 // skip inner classes
167 if (obfClassEntry.isInnerClass()) {
168 continue;
169 }
170
171 // separate the classes
172 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
173 if (!deobfClassEntry.equals(obfClassEntry)) {
174 // if the class has a mapping, clearly it's deobfuscated
175 deobfClasses.add(deobfClassEntry);
176 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
177 // also call it deobufscated if it's not in the none package
178 deobfClasses.add(obfClassEntry);
179 } else {
180 // otherwise, assume it's still obfuscated
181 obfClasses.add(obfClassEntry);
182 }
183 }
184 }
185
186 public CompilationUnit getSourceTree(String className) {
187
188 // we don't know if this class name is obfuscated or deobfuscated
189 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
190 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
191
192 // first, assume class name is deobf
193 String deobfClassName = className;
194
195 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
196 ClassMapping classMapping = m_mappings.getClassByObf(className);
197 if (classMapping != null && classMapping.getDeobfName() != null) {
198 deobfClassName = classMapping.getDeobfName();
199 }
200
201 // set the type loader
202 TranslatingTypeLoader loader = new TranslatingTypeLoader(
203 m_jar,
204 m_jarIndex,
205 getTranslator(TranslationDirection.Obfuscating),
206 getTranslator(TranslationDirection.Deobfuscating)
207 );
208 m_settings.setTypeLoader(loader);
209
210 // see if procyon can find the type
211 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
212 if (type == null) {
213 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s",
214 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
215 ));
216 }
217 TypeDefinition resolvedType = type.resolve();
218
219 // decompile it!
220 DecompilerContext context = new DecompilerContext();
221 context.setCurrentType(resolvedType);
222 context.setSettings(m_settings);
223 AstBuilder builder = new AstBuilder(context);
224 builder.addType(resolvedType);
225 builder.runTransformations(null);
226 return builder.getCompilationUnit();
227 }
228
229 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
230 return getSourceIndex(sourceTree, source, null);
231 }
232
233 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) {
234
235 // build the source index
236 SourceIndex index;
237 if (ignoreBadTokens != null) {
238 index = new SourceIndex(source, ignoreBadTokens);
239 } else {
240 index = new SourceIndex(source);
241 }
242 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
243
244 // DEBUG
245 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
246
247 // resolve all the classes in the source references
248 for (Token token : index.referenceTokens()) {
249 EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token);
250
251 // get the obfuscated entry
252 Entry obfEntry = obfuscateEntry(deobfReference.entry);
253
254 // try to resolve the class
255 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
256 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
257 // change the class of the entry
258 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
259
260 // save the new deobfuscated reference
261 deobfReference.entry = deobfuscateEntry(obfEntry);
262 index.replaceDeobfReference(token, deobfReference);
263 }
264
265 // DEBUG
266 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
267 }
268
269 return index;
270 }
271
272 public String getSource(CompilationUnit sourceTree) {
273 // render the AST into source
274 StringWriter buf = new StringWriter();
275 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
276 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
277 return buf.toString();
278 }
279
280 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
281 // get the classes to decompile
282 Set<ClassEntry> classEntries = Sets.newHashSet();
283 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
284 // skip inner classes
285 if (obfClassEntry.isInnerClass()) {
286 continue;
287 }
288
289 classEntries.add(obfClassEntry);
290 }
291
292 if (progress != null) {
293 progress.init(classEntries.size(), "Decompiling classes...");
294 }
295
296 // DEOBFUSCATE ALL THE THINGS!! @_@
297 int i = 0;
298 for (ClassEntry obfClassEntry : classEntries) {
299 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
300 if (progress != null) {
301 progress.onProgress(i++, deobfClassEntry.toString());
302 }
303
304 try {
305 // get the source
306 String source = getSource(getSourceTree(obfClassEntry.getName()));
307
308 // write the file
309 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
310 file.getParentFile().mkdirs();
311 try (FileWriter out = new FileWriter(file)) {
312 out.write(source);
313 }
314 } catch (Throwable t) {
315 throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t);
316 }
317 }
318 if (progress != null) {
319 progress.onProgress(i, "Done!");
320 }
321 }
322
323 public void writeJar(File out, ProgressListener progress) {
324 final TranslatingTypeLoader loader = new TranslatingTypeLoader(
325 m_jar,
326 m_jarIndex,
327 getTranslator(TranslationDirection.Obfuscating),
328 getTranslator(TranslationDirection.Deobfuscating)
329 );
330 transformJar(out, progress, new ClassTransformer() {
331
332 @Override
333 public CtClass transform(CtClass c) throws Exception {
334 return loader.transformClass(c);
335 }
336 });
337 }
338
339 public void protectifyJar(File out, ProgressListener progress) {
340 transformJar(out, progress, new ClassTransformer() {
341
342 @Override
343 public CtClass transform(CtClass c) throws Exception {
344 return ClassProtectifier.protectify(c);
345 }
346 });
347 }
348
349 public void publifyJar(File out, ProgressListener progress) {
350 transformJar(out, progress, new ClassTransformer() {
351
352 @Override
353 public CtClass transform(CtClass c) throws Exception {
354 return ClassPublifier.publify(c);
355 }
356 });
357 }
358
359 private interface ClassTransformer {
360 public CtClass transform(CtClass c) throws Exception;
361 }
362 private void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
363 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
364 if (progress != null) {
365 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Transforming classes...");
366 }
367
368 int i = 0;
369 for (CtClass c : JarClassIterator.classes(m_jar)) {
370 if (progress != null) {
371 progress.onProgress(i++, c.getName());
372 }
373
374 try {
375 c = transformer.transform(c);
376 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
377 outJar.write(c.toBytecode());
378 outJar.closeEntry();
379 } catch (Throwable t) {
380 throw new Error("Unable to transform class " + c.getName(), t);
381 }
382 }
383 if (progress != null) {
384 progress.onProgress(i, "Done!");
385 }
386
387 outJar.close();
388 } catch (IOException ex) {
389 throw new Error("Unable to write to Jar file!");
390 }
391 }
392
393 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
394 if (deobfEntry == null) {
395 return null;
396 }
397 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
398 }
399
400 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
401 if (obfEntry == null) {
402 return null;
403 }
404 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
405 }
406
407 public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) {
408 if (deobfReference == null) {
409 return null;
410 }
411 return new EntryReference<E,C>(
412 obfuscateEntry(deobfReference.entry),
413 obfuscateEntry(deobfReference.context),
414 deobfReference
415 );
416 }
417
418 public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) {
419 if (obfReference == null) {
420 return null;
421 }
422 return new EntryReference<E,C>(
423 deobfuscateEntry(obfReference.entry),
424 deobfuscateEntry(obfReference.context),
425 obfReference
426 );
427 }
428
429 public boolean isObfuscatedIdentifier(Entry obfEntry) {
430
431 if (obfEntry instanceof MethodEntry) {
432
433 // HACKHACK: Object methods are not obfuscated identifiers
434 MethodEntry obfMethodEntry = (MethodEntry)obfEntry;
435 String name = obfMethodEntry.getName();
436 String sig = obfMethodEntry.getSignature().toString();
437 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
438 return false;
439 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
440 return false;
441 } else if (name.equals("finalize") && sig.equals("()V")) {
442 return false;
443 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
444 return false;
445 } else if (name.equals("hashCode") && sig.equals("()I")) {
446 return false;
447 } else if (name.equals("notify") && sig.equals("()V")) {
448 return false;
449 } else if (name.equals("notifyAll") && sig.equals("()V")) {
450 return false;
451 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
452 return false;
453 } else if (name.equals("wait") && sig.equals("()V")) {
454 return false;
455 } else if (name.equals("wait") && sig.equals("(J)V")) {
456 return false;
457 } else if (name.equals("wait") && sig.equals("(JI)V")) {
458 return false;
459 }
460 }
461
462 return m_jarIndex.containsObfEntry(obfEntry);
463 }
464
465 public boolean isRenameable(EntryReference<Entry,Entry> obfReference) {
466 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
467 }
468
469 // NOTE: these methods are a bit messy... oh well
470
471 public boolean hasDeobfuscatedName(Entry obfEntry) {
472 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
473 if (obfEntry instanceof ClassEntry) {
474 ClassEntry obfClass = (ClassEntry)obfEntry;
475 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClass);
476 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
477 return classMapping != null && classMapping.getDeobfName() != null;
478 } else if (obfEntry instanceof FieldEntry) {
479 return translator.translate((FieldEntry)obfEntry) != null;
480 } else if (obfEntry instanceof MethodEntry) {
481 return translator.translate((MethodEntry)obfEntry) != null;
482 } else if (obfEntry instanceof ConstructorEntry) {
483 // constructors have no names
484 return false;
485 } else if (obfEntry instanceof ArgumentEntry) {
486 return translator.translate((ArgumentEntry)obfEntry) != null;
487 } else {
488 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
489 }
490 }
491
492 public void rename(Entry obfEntry, String newName) {
493 if (obfEntry instanceof ClassEntry) {
494 m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName));
495 } else if (obfEntry instanceof FieldEntry) {
496 m_renamer.setFieldName((FieldEntry)obfEntry, newName);
497 } else if (obfEntry instanceof MethodEntry) {
498 m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName);
499 } else if (obfEntry instanceof ConstructorEntry) {
500 throw new IllegalArgumentException("Cannot rename constructors");
501 } else if (obfEntry instanceof ArgumentEntry) {
502 m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName);
503 } else {
504 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
505 }
506
507 // clear caches
508 m_translatorCache.clear();
509 }
510
511 public void removeMapping(Entry obfEntry) {
512 if (obfEntry instanceof ClassEntry) {
513 m_renamer.removeClassMapping((ClassEntry)obfEntry);
514 } else if (obfEntry instanceof FieldEntry) {
515 m_renamer.removeFieldMapping((FieldEntry)obfEntry);
516 } else if (obfEntry instanceof MethodEntry) {
517 m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry);
518 } else if (obfEntry instanceof ConstructorEntry) {
519 throw new IllegalArgumentException("Cannot rename constructors");
520 } else if (obfEntry instanceof ArgumentEntry) {
521 m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry);
522 } else {
523 throw new Error("Unknown entry type: " + obfEntry);
524 }
525
526 // clear caches
527 m_translatorCache.clear();
528 }
529
530 public void markAsDeobfuscated(Entry obfEntry) {
531 if (obfEntry instanceof ClassEntry) {
532 m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry);
533 } else if (obfEntry instanceof FieldEntry) {
534 m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry);
535 } else if (obfEntry instanceof MethodEntry) {
536 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry);
537 } else if (obfEntry instanceof ConstructorEntry) {
538 throw new IllegalArgumentException("Cannot rename constructors");
539 } else if (obfEntry instanceof ArgumentEntry) {
540 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry);
541 } else {
542 throw new Error("Unknown entry type: " + obfEntry);
543 }
544
545 // clear caches
546 m_translatorCache.clear();
547 }
548}