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