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