diff options
| author | 2014-08-15 01:43:48 -0400 | |
|---|---|---|
| committer | 2014-08-15 01:43:48 -0400 | |
| commit | 37467e4a7b5e05e4da413a1e06e597fa806b72e4 (patch) | |
| tree | 4c76a76aa3379fc236977646af48ec63dcf1712e | |
| parent | Added tag v0.1 beta for changeset 7beed0616320 (diff) | |
| download | enigma-fork-37467e4a7b5e05e4da413a1e06e597fa806b72e4.tar.gz enigma-fork-37467e4a7b5e05e4da413a1e06e597fa806b72e4.tar.xz enigma-fork-37467e4a7b5e05e4da413a1e06e597fa806b72e4.zip | |
trying to get inner/anonymous classes working... I have a working heuristic in place to detect anonymous classes, but I can't seem to get Procyon to decompile them correctly. I'm writing the InnerClasses attribute and translating all the inner class names, but there must be something else I'm missing...
| -rw-r--r-- | .hgignore | 4 | ||||
| -rw-r--r-- | src/cuchaz/enigma/Deobfuscator.java | 18 | ||||
| -rw-r--r-- | src/cuchaz/enigma/TranslatingTypeLoader.java | 86 | ||||
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarClassIterator.java | 134 | ||||
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarIndex.java | 187 | ||||
| -rw-r--r-- | src/cuchaz/enigma/bytecode/InnerClassWriter.java | 79 |
6 files changed, 399 insertions, 109 deletions
| @@ -10,4 +10,6 @@ syntax: regexp | |||
| 10 | syntax: regexp | 10 | syntax: regexp |
| 11 | ^libs$ | 11 | ^libs$ |
| 12 | syntax: regexp | 12 | syntax: regexp |
| 13 | ^build$ \ No newline at end of file | 13 | ^build$ |
| 14 | syntax: regexp | ||
| 15 | ^data$ \ No newline at end of file | ||
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 770172e..127a0d9 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java | |||
| @@ -11,9 +11,7 @@ | |||
| 11 | package cuchaz.enigma; | 11 | package cuchaz.enigma; |
| 12 | 12 | ||
| 13 | import java.io.File; | 13 | import java.io.File; |
| 14 | import java.io.FileInputStream; | ||
| 15 | import java.io.IOException; | 14 | import java.io.IOException; |
| 16 | import java.io.InputStream; | ||
| 17 | import java.io.StringWriter; | 15 | import java.io.StringWriter; |
| 18 | import java.util.List; | 16 | import java.util.List; |
| 19 | import java.util.jar.JarFile; | 17 | import java.util.jar.JarFile; |
| @@ -58,18 +56,9 @@ public class Deobfuscator | |||
| 58 | m_file = file; | 56 | m_file = file; |
| 59 | m_jar = new JarFile( m_file ); | 57 | m_jar = new JarFile( m_file ); |
| 60 | 58 | ||
| 61 | // build the ancestries | 59 | // build the jar index |
| 62 | InputStream jarIn = null; | 60 | m_jarIndex = new JarIndex(); |
| 63 | try | 61 | m_jarIndex.indexJar( m_jar ); |
| 64 | { | ||
| 65 | m_jarIndex = new JarIndex( m_jar ); | ||
| 66 | jarIn = new FileInputStream( m_file ); | ||
| 67 | m_jarIndex.indexJar( jarIn ); | ||
| 68 | } | ||
| 69 | finally | ||
| 70 | { | ||
| 71 | Util.closeQuietly( jarIn ); | ||
| 72 | } | ||
| 73 | 62 | ||
| 74 | // config the decompiler | 63 | // config the decompiler |
| 75 | m_settings = DecompilerSettings.javaDefaults(); | 64 | m_settings = DecompilerSettings.javaDefaults(); |
| @@ -105,6 +94,7 @@ public class Deobfuscator | |||
| 105 | // update decompiler options | 94 | // update decompiler options |
| 106 | m_settings.setTypeLoader( new TranslatingTypeLoader( | 95 | m_settings.setTypeLoader( new TranslatingTypeLoader( |
| 107 | m_jar, | 96 | m_jar, |
| 97 | m_jarIndex, | ||
| 108 | getTranslator( TranslationDirection.Obfuscating ), | 98 | getTranslator( TranslationDirection.Obfuscating ), |
| 109 | getTranslator( TranslationDirection.Deobfuscating ) | 99 | getTranslator( TranslationDirection.Deobfuscating ) |
| 110 | ) ); | 100 | ) ); |
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index f5112e0..fdfcea0 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java | |||
| @@ -17,42 +17,77 @@ import java.util.jar.JarEntry; | |||
| 17 | import java.util.jar.JarFile; | 17 | import java.util.jar.JarFile; |
| 18 | 18 | ||
| 19 | import javassist.ByteArrayClassPath; | 19 | import javassist.ByteArrayClassPath; |
| 20 | import javassist.CannotCompileException; | ||
| 20 | import javassist.ClassPool; | 21 | import javassist.ClassPool; |
| 21 | import javassist.CtClass; | 22 | import javassist.CtClass; |
| 23 | import javassist.NotFoundException; | ||
| 22 | import javassist.bytecode.Descriptor; | 24 | import javassist.bytecode.Descriptor; |
| 23 | 25 | ||
| 24 | import com.strobel.assembler.metadata.Buffer; | 26 | import com.strobel.assembler.metadata.Buffer; |
| 25 | import com.strobel.assembler.metadata.ITypeLoader; | 27 | import com.strobel.assembler.metadata.ITypeLoader; |
| 26 | 28 | ||
| 29 | import cuchaz.enigma.analysis.JarIndex; | ||
| 27 | import cuchaz.enigma.bytecode.ClassTranslator; | 30 | import cuchaz.enigma.bytecode.ClassTranslator; |
| 31 | import cuchaz.enigma.bytecode.InnerClassWriter; | ||
| 28 | import cuchaz.enigma.bytecode.MethodParameterWriter; | 32 | import cuchaz.enigma.bytecode.MethodParameterWriter; |
| 29 | import cuchaz.enigma.mapping.Translator; | 33 | import cuchaz.enigma.mapping.Translator; |
| 30 | 34 | ||
| 31 | public class TranslatingTypeLoader implements ITypeLoader | 35 | public class TranslatingTypeLoader implements ITypeLoader |
| 32 | { | 36 | { |
| 33 | private JarFile m_jar; | 37 | private JarFile m_jar; |
| 38 | private JarIndex m_jarIndex; | ||
| 34 | private Translator m_obfuscatingTranslator; | 39 | private Translator m_obfuscatingTranslator; |
| 35 | private Translator m_deobfuscatingTranslator; | 40 | private Translator m_deobfuscatingTranslator; |
| 36 | 41 | ||
| 37 | public TranslatingTypeLoader( JarFile jar, Translator obfuscatingTranslator, Translator deobfuscatingTranslator ) | 42 | public TranslatingTypeLoader( JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator ) |
| 38 | { | 43 | { |
| 39 | m_jar = jar; | 44 | m_jar = jar; |
| 45 | m_jarIndex = jarIndex; | ||
| 40 | m_obfuscatingTranslator = obfuscatingTranslator; | 46 | m_obfuscatingTranslator = obfuscatingTranslator; |
| 41 | m_deobfuscatingTranslator = deobfuscatingTranslator; | 47 | m_deobfuscatingTranslator = deobfuscatingTranslator; |
| 42 | } | 48 | } |
| 43 | 49 | ||
| 44 | @Override | 50 | @Override |
| 45 | public boolean tryLoadType( String name, Buffer out ) | 51 | public boolean tryLoadType( String deobfClassName, Buffer out ) |
| 46 | { | 52 | { |
| 47 | // is this a deobufscated class name? | 53 | // TEMP |
| 48 | String obfName = m_obfuscatingTranslator.translateClass( name ); | 54 | if( !deobfClassName.startsWith( "java" ) && !deobfClassName.startsWith( "org" ) ) |
| 49 | if( obfName != null ) | ||
| 50 | { | 55 | { |
| 51 | // point to the obfuscated class | 56 | System.out.println( "Looking for: " + deobfClassName ); |
| 52 | name = obfName; | ||
| 53 | } | 57 | } |
| 54 | 58 | ||
| 55 | JarEntry entry = m_jar.getJarEntry( name + ".class" ); | 59 | // what class file should we actually load? |
| 60 | String obfClassName = m_obfuscatingTranslator.translateClass( deobfClassName ); | ||
| 61 | if( obfClassName == null ) | ||
| 62 | { | ||
| 63 | obfClassName = deobfClassName; | ||
| 64 | } | ||
| 65 | String classFileName = obfClassName; | ||
| 66 | |||
| 67 | // is this a properly-referenced inner class? | ||
| 68 | boolean isInnerClass = deobfClassName.indexOf( '$' ) >= 0; | ||
| 69 | if( isInnerClass ) | ||
| 70 | { | ||
| 71 | // get just the bare inner class name | ||
| 72 | String[] parts = deobfClassName.split( "\\$" ); | ||
| 73 | String deobfClassFileName = parts[parts.length - 1]; | ||
| 74 | |||
| 75 | // make sure the bare inner class name is obfuscated | ||
| 76 | classFileName = m_obfuscatingTranslator.translateClass( deobfClassFileName ); | ||
| 77 | if( classFileName == null ) | ||
| 78 | { | ||
| 79 | classFileName = deobfClassFileName; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | // TEMP | ||
| 84 | if( !deobfClassName.startsWith( "java" ) && !deobfClassName.startsWith( "org" ) ) | ||
| 85 | { | ||
| 86 | System.out.println( "\tLooking at class file: " + classFileName ); | ||
| 87 | } | ||
| 88 | |||
| 89 | // get the jar entry | ||
| 90 | JarEntry entry = m_jar.getJarEntry( classFileName + ".class" ); | ||
| 56 | if( entry == null ) | 91 | if( entry == null ) |
| 57 | { | 92 | { |
| 58 | return false; | 93 | return false; |
| @@ -73,32 +108,43 @@ public class TranslatingTypeLoader implements ITypeLoader | |||
| 73 | } | 108 | } |
| 74 | data.write( buf, 0, bytesRead ); | 109 | data.write( buf, 0, bytesRead ); |
| 75 | } | 110 | } |
| 111 | data.close(); | ||
| 112 | in.close(); | ||
| 76 | buf = data.toByteArray(); | 113 | buf = data.toByteArray(); |
| 77 | 114 | ||
| 78 | // translate the class | 115 | // load the javassist handle to the class |
| 79 | String javaName = Descriptor.toJavaName( name ); | 116 | String javaClassFileName = Descriptor.toJavaName( classFileName ); |
| 80 | ClassPool classPool = new ClassPool(); | 117 | ClassPool classPool = new ClassPool(); |
| 81 | classPool.insertClassPath( new ByteArrayClassPath( javaName, buf ) ); | 118 | classPool.insertClassPath( new ByteArrayClassPath( javaClassFileName, buf ) ); |
| 82 | try | 119 | CtClass c = classPool.get( javaClassFileName ); |
| 120 | |||
| 121 | if( isInnerClass ) | ||
| 83 | { | 122 | { |
| 84 | CtClass c = classPool.get( javaName ); | 123 | // rename the class to what procyon expects |
| 85 | new MethodParameterWriter( m_deobfuscatingTranslator ).writeMethodArguments( c ); | 124 | c.setName( deobfClassName ); |
| 86 | new ClassTranslator( m_deobfuscatingTranslator ).translate( c ); | ||
| 87 | buf = c.toBytecode(); | ||
| 88 | } | 125 | } |
| 89 | catch( Exception ex ) | 126 | else |
| 90 | { | 127 | { |
| 91 | throw new Error( ex ); | 128 | // maybe it's an outer class |
| 129 | new InnerClassWriter( m_deobfuscatingTranslator, m_jarIndex ).writeInnerClasses( c ); | ||
| 92 | } | 130 | } |
| 93 | 131 | ||
| 94 | // pass it along to the decompiler | 132 | new MethodParameterWriter( m_deobfuscatingTranslator ).writeMethodArguments( c ); |
| 133 | new ClassTranslator( m_deobfuscatingTranslator ).translate( c ); | ||
| 134 | |||
| 135 | assert( Descriptor.toJvmName( c.getName() ).equals( deobfClassName ) ); | ||
| 136 | assert( c.getClassFile().getName().equals( deobfClassName ) ); | ||
| 137 | |||
| 138 | buf = c.toBytecode(); | ||
| 139 | |||
| 140 | // pass the transformed class along to the decompiler | ||
| 95 | out.reset( buf.length ); | 141 | out.reset( buf.length ); |
| 96 | System.arraycopy( buf, 0, out.array(), out.position(), buf.length ); | 142 | System.arraycopy( buf, 0, out.array(), out.position(), buf.length ); |
| 97 | out.position( 0 ); | 143 | out.position( 0 ); |
| 98 | 144 | ||
| 99 | return true; | 145 | return true; |
| 100 | } | 146 | } |
| 101 | catch( IOException ex ) | 147 | catch( IOException | NotFoundException | CannotCompileException ex ) |
| 102 | { | 148 | { |
| 103 | throw new Error( ex ); | 149 | throw new Error( ex ); |
| 104 | } | 150 | } |
diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java new file mode 100644 index 0000000..cf6df80 --- /dev/null +++ b/src/cuchaz/enigma/analysis/JarClassIterator.java | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.Enumeration; | ||
| 17 | import java.util.Iterator; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.jar.JarEntry; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import javassist.ByteArrayClassPath; | ||
| 23 | import javassist.ClassPool; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.NotFoundException; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.beust.jcommander.internal.Lists; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Constants; | ||
| 31 | |||
| 32 | public class JarClassIterator implements Iterator<CtClass> | ||
| 33 | { | ||
| 34 | private JarFile m_jar; | ||
| 35 | private Iterator<JarEntry> m_iter; | ||
| 36 | |||
| 37 | public JarClassIterator( JarFile jar ) | ||
| 38 | { | ||
| 39 | this( jar, getClassEntries( jar ) ); | ||
| 40 | } | ||
| 41 | |||
| 42 | public JarClassIterator( JarFile jar, List<JarEntry> entries ) | ||
| 43 | { | ||
| 44 | m_jar = jar; | ||
| 45 | m_iter = entries.iterator(); | ||
| 46 | } | ||
| 47 | |||
| 48 | @Override | ||
| 49 | public boolean hasNext( ) | ||
| 50 | { | ||
| 51 | return m_iter.hasNext(); | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public CtClass next( ) | ||
| 56 | { | ||
| 57 | JarEntry entry = m_iter.next(); | ||
| 58 | try | ||
| 59 | { | ||
| 60 | // read the class into a buffer | ||
| 61 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| 62 | byte[] buf = new byte[Constants.KiB]; | ||
| 63 | int totalNumBytesRead = 0; | ||
| 64 | InputStream in = m_jar.getInputStream( entry ); | ||
| 65 | while( in.available() > 0 ) | ||
| 66 | { | ||
| 67 | int numBytesRead = in.read( buf ); | ||
| 68 | if( numBytesRead < 0 ) | ||
| 69 | { | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | bos.write( buf, 0, numBytesRead ); | ||
| 73 | |||
| 74 | // sanity checking | ||
| 75 | totalNumBytesRead += numBytesRead; | ||
| 76 | if( totalNumBytesRead > Constants.MiB ) | ||
| 77 | { | ||
| 78 | throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" ); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | // determine the class name (ie chop off the ".class") | ||
| 83 | String className = Descriptor.toJavaName( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) ); | ||
| 84 | |||
| 85 | // get a javassist handle for the class | ||
| 86 | ClassPool classPool = new ClassPool(); | ||
| 87 | classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) ); | ||
| 88 | return classPool.get( className ); | ||
| 89 | } | ||
| 90 | catch( IOException ex ) | ||
| 91 | { | ||
| 92 | throw new Error( "Unable to read class: " + entry.getName() ); | ||
| 93 | } | ||
| 94 | catch( NotFoundException ex ) | ||
| 95 | { | ||
| 96 | throw new Error( "Unable to load class: " + entry.getName() ); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | @Override | ||
| 101 | public void remove( ) | ||
| 102 | { | ||
| 103 | throw new UnsupportedOperationException(); | ||
| 104 | } | ||
| 105 | |||
| 106 | public static List<JarEntry> getClassEntries( JarFile jar ) | ||
| 107 | { | ||
| 108 | List<JarEntry> classes = Lists.newArrayList(); | ||
| 109 | Enumeration<JarEntry> entries = jar.entries(); | ||
| 110 | while( entries.hasMoreElements() ) | ||
| 111 | { | ||
| 112 | JarEntry entry = entries.nextElement(); | ||
| 113 | |||
| 114 | // is this a class file? | ||
| 115 | if( !entry.isDirectory() && entry.getName().endsWith( ".class" ) ) | ||
| 116 | { | ||
| 117 | classes.add( entry ); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | return classes; | ||
| 121 | } | ||
| 122 | |||
| 123 | public static Iterable<CtClass> classes( final JarFile jar ) | ||
| 124 | { | ||
| 125 | return new Iterable<CtClass>( ) | ||
| 126 | { | ||
| 127 | @Override | ||
| 128 | public Iterator<CtClass> iterator( ) | ||
| 129 | { | ||
| 130 | return new JarClassIterator( jar ); | ||
| 131 | } | ||
| 132 | }; | ||
| 133 | } | ||
| 134 | } | ||
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 845be60..9962bfa 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java | |||
| @@ -10,27 +10,21 @@ | |||
| 10 | ******************************************************************************/ | 10 | ******************************************************************************/ |
| 11 | package cuchaz.enigma.analysis; | 11 | package cuchaz.enigma.analysis; |
| 12 | 12 | ||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.Collection; | 13 | import java.util.Collection; |
| 17 | import java.util.Enumeration; | ||
| 18 | import java.util.List; | 14 | import java.util.List; |
| 15 | import java.util.Map; | ||
| 19 | import java.util.Set; | 16 | import java.util.Set; |
| 20 | import java.util.jar.JarEntry; | 17 | import java.util.jar.JarEntry; |
| 21 | import java.util.jar.JarFile; | 18 | import java.util.jar.JarFile; |
| 22 | import java.util.zip.ZipEntry; | ||
| 23 | import java.util.zip.ZipInputStream; | ||
| 24 | 19 | ||
| 25 | import javassist.ByteArrayClassPath; | ||
| 26 | import javassist.CannotCompileException; | 20 | import javassist.CannotCompileException; |
| 27 | import javassist.ClassPool; | ||
| 28 | import javassist.CtBehavior; | 21 | import javassist.CtBehavior; |
| 29 | import javassist.CtClass; | 22 | import javassist.CtClass; |
| 30 | import javassist.CtConstructor; | 23 | import javassist.CtConstructor; |
| 31 | import javassist.CtMethod; | 24 | import javassist.CtMethod; |
| 32 | import javassist.NotFoundException; | 25 | import javassist.bytecode.AccessFlag; |
| 33 | import javassist.bytecode.Descriptor; | 26 | import javassist.bytecode.Descriptor; |
| 27 | import javassist.bytecode.FieldInfo; | ||
| 34 | import javassist.expr.ConstructorCall; | 28 | import javassist.expr.ConstructorCall; |
| 35 | import javassist.expr.ExprEditor; | 29 | import javassist.expr.ExprEditor; |
| 36 | import javassist.expr.FieldAccess; | 30 | import javassist.expr.FieldAccess; |
| @@ -39,10 +33,10 @@ import javassist.expr.NewExpr; | |||
| 39 | 33 | ||
| 40 | import com.google.common.collect.HashMultimap; | 34 | import com.google.common.collect.HashMultimap; |
| 41 | import com.google.common.collect.Lists; | 35 | import com.google.common.collect.Lists; |
| 36 | import com.google.common.collect.Maps; | ||
| 42 | import com.google.common.collect.Multimap; | 37 | import com.google.common.collect.Multimap; |
| 43 | import com.google.common.collect.Sets; | 38 | import com.google.common.collect.Sets; |
| 44 | 39 | ||
| 45 | import cuchaz.enigma.Constants; | ||
| 46 | import cuchaz.enigma.mapping.ClassEntry; | 40 | import cuchaz.enigma.mapping.ClassEntry; |
| 47 | import cuchaz.enigma.mapping.ConstructorEntry; | 41 | import cuchaz.enigma.mapping.ConstructorEntry; |
| 48 | import cuchaz.enigma.mapping.Entry; | 42 | import cuchaz.enigma.mapping.Entry; |
| @@ -57,89 +51,52 @@ public class JarIndex | |||
| 57 | private Multimap<String,MethodEntry> m_methodImplementations; | 51 | private Multimap<String,MethodEntry> m_methodImplementations; |
| 58 | private Multimap<Entry,Entry> m_methodCalls; | 52 | private Multimap<Entry,Entry> m_methodCalls; |
| 59 | private Multimap<FieldEntry,Entry> m_fieldCalls; | 53 | private Multimap<FieldEntry,Entry> m_fieldCalls; |
| 54 | private Multimap<String,String> m_innerClasses; | ||
| 55 | private Map<String,String> m_outerClasses; | ||
| 60 | 56 | ||
| 61 | public JarIndex( JarFile jar ) | 57 | public JarIndex( ) |
| 62 | { | 58 | { |
| 63 | m_obfClassNames = Sets.newHashSet(); | 59 | m_obfClassNames = Sets.newHashSet(); |
| 64 | m_ancestries = new Ancestries(); | 60 | m_ancestries = new Ancestries(); |
| 65 | m_methodImplementations = HashMultimap.create(); | 61 | m_methodImplementations = HashMultimap.create(); |
| 66 | m_methodCalls = HashMultimap.create(); | 62 | m_methodCalls = HashMultimap.create(); |
| 67 | m_fieldCalls = HashMultimap.create(); | 63 | m_fieldCalls = HashMultimap.create(); |
| 68 | 64 | m_innerClasses = HashMultimap.create(); | |
| 69 | // read the class names | 65 | m_outerClasses = Maps.newHashMap(); |
| 70 | Enumeration<JarEntry> enumeration = jar.entries(); | 66 | } |
| 71 | while( enumeration.hasMoreElements() ) | 67 | |
| 68 | public void indexJar( JarFile jar ) | ||
| 69 | { | ||
| 70 | // pass 1: read the class names | ||
| 71 | for( JarEntry entry : JarClassIterator.getClassEntries( jar ) ) | ||
| 72 | { | 72 | { |
| 73 | JarEntry entry = enumeration.nextElement(); | ||
| 74 | |||
| 75 | // filter out non-classes | ||
| 76 | if( entry.isDirectory() || !entry.getName().endsWith( ".class" ) ) | ||
| 77 | { | ||
| 78 | continue; | ||
| 79 | } | ||
| 80 | |||
| 81 | String className = entry.getName().substring( 0, entry.getName().length() - 6 ); | 73 | String className = entry.getName().substring( 0, entry.getName().length() - 6 ); |
| 82 | m_obfClassNames.add( Descriptor.toJvmName( className ) ); | 74 | m_obfClassNames.add( Descriptor.toJvmName( className ) ); |
| 83 | } | 75 | } |
| 84 | } | ||
| 85 | |||
| 86 | public void indexJar( InputStream in ) | ||
| 87 | throws IOException | ||
| 88 | { | ||
| 89 | ClassPool classPool = new ClassPool(); | ||
| 90 | 76 | ||
| 91 | ZipInputStream zin = new ZipInputStream( in ); | 77 | // pass 2: index the types, methods |
| 92 | ZipEntry entry; | 78 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 93 | while( ( entry = zin.getNextEntry() ) != null ) | ||
| 94 | { | 79 | { |
| 95 | // filter out non-classes | 80 | m_ancestries.addSuperclass( c.getName(), c.getClassFile().getSuperclass() ); |
| 96 | if( entry.isDirectory() || !entry.getName().endsWith( ".class" ) ) | 81 | for( CtBehavior behavior : c.getDeclaredBehaviors() ) |
| 97 | { | 82 | { |
| 98 | continue; | 83 | indexBehavior( behavior ); |
| 99 | } | 84 | } |
| 100 | 85 | } | |
| 101 | // read the class into a buffer | 86 | |
| 102 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); | 87 | // pass 2: index inner classes |
| 103 | byte[] buf = new byte[Constants.KiB]; | 88 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 104 | int totalNumBytesRead = 0; | 89 | { |
| 105 | while( zin.available() > 0 ) | 90 | String outerClassName = isInnerClass( c ); |
| 106 | { | 91 | if( outerClassName != null ) |
| 107 | int numBytesRead = zin.read( buf ); | ||
| 108 | if( numBytesRead < 0 ) | ||
| 109 | { | ||
| 110 | break; | ||
| 111 | } | ||
| 112 | bos.write( buf, 0, numBytesRead ); | ||
| 113 | |||
| 114 | // sanity checking | ||
| 115 | totalNumBytesRead += numBytesRead; | ||
| 116 | if( totalNumBytesRead > Constants.MiB ) | ||
| 117 | { | ||
| 118 | throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" ); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | // determine the class name (ie chop off the ".class") | ||
| 123 | String className = Descriptor.toJavaName( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) ); | ||
| 124 | |||
| 125 | // get a javassist handle for the class | ||
| 126 | classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) ); | ||
| 127 | try | ||
| 128 | { | ||
| 129 | CtClass c = classPool.get( className ); | ||
| 130 | m_ancestries.addSuperclass( c.getName(), c.getClassFile().getSuperclass() ); | ||
| 131 | for( CtBehavior behavior : c.getDeclaredBehaviors() ) | ||
| 132 | { | ||
| 133 | indexBehavior( behavior ); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | catch( NotFoundException ex ) | ||
| 137 | { | 92 | { |
| 138 | throw new Error( "Unable to load class: " + className ); | 93 | String innerClassName = Descriptor.toJvmName( c.getName() ); |
| 94 | m_innerClasses.put( outerClassName, innerClassName ); | ||
| 95 | m_outerClasses.put( innerClassName, outerClassName ); | ||
| 139 | } | 96 | } |
| 140 | } | 97 | } |
| 141 | } | 98 | } |
| 142 | 99 | ||
| 143 | private void indexBehavior( CtBehavior behavior ) | 100 | private void indexBehavior( CtBehavior behavior ) |
| 144 | { | 101 | { |
| 145 | // get the method entry | 102 | // get the method entry |
| @@ -226,6 +183,78 @@ public class JarIndex | |||
| 226 | } | 183 | } |
| 227 | } | 184 | } |
| 228 | 185 | ||
| 186 | @SuppressWarnings( "unchecked" ) | ||
| 187 | private String isInnerClass( CtClass c ) | ||
| 188 | { | ||
| 189 | String innerClassName = Descriptor.toJvmName( c.getName() ); | ||
| 190 | |||
| 191 | // first, is this an anonymous class? | ||
| 192 | // for anonymous classes: | ||
| 193 | // the outer class is always a synthetic field | ||
| 194 | // there's at least one constructor with the type of the synthetic field as an argument | ||
| 195 | // this constructor is called exactly once by the class of the synthetic field | ||
| 196 | |||
| 197 | for( FieldInfo field : (List<FieldInfo>)c.getClassFile().getFields() ) | ||
| 198 | { | ||
| 199 | boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; | ||
| 200 | if( !isSynthetic ) | ||
| 201 | { | ||
| 202 | continue; | ||
| 203 | } | ||
| 204 | |||
| 205 | // skip non-class types | ||
| 206 | if( !field.getDescriptor().startsWith( "L" ) ) | ||
| 207 | { | ||
| 208 | continue; | ||
| 209 | } | ||
| 210 | |||
| 211 | // get the outer class from the field type | ||
| 212 | String outerClassName = Descriptor.toJvmName( Descriptor.toClassName( field.getDescriptor() ) ); | ||
| 213 | |||
| 214 | // look for a constructor where this type is the first parameter | ||
| 215 | CtConstructor targetConstructor = null; | ||
| 216 | for( CtConstructor constructor : c.getDeclaredConstructors() ) | ||
| 217 | { | ||
| 218 | String signature = Descriptor.getParamDescriptor( constructor.getMethodInfo().getDescriptor() ); | ||
| 219 | if( Descriptor.numOfParameters( signature ) < 1 ) | ||
| 220 | { | ||
| 221 | continue; | ||
| 222 | } | ||
| 223 | |||
| 224 | // match the first parameter to the outer class | ||
| 225 | Descriptor.Iterator iter = new Descriptor.Iterator( signature ); | ||
| 226 | int pos = iter.next(); | ||
| 227 | if( iter.isParameter() && signature.charAt( pos ) == 'L' ) | ||
| 228 | { | ||
| 229 | String argumentDesc = signature.substring( pos, signature.indexOf(';', pos) + 1 ); | ||
| 230 | String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) ); | ||
| 231 | if( argumentClassName.equals( outerClassName ) ) | ||
| 232 | { | ||
| 233 | // is this constructor called exactly once? | ||
| 234 | ConstructorEntry constructorEntry = new ConstructorEntry( | ||
| 235 | new ClassEntry( innerClassName ), | ||
| 236 | constructor.getMethodInfo().getDescriptor() | ||
| 237 | ); | ||
| 238 | if( this.getMethodCallers( constructorEntry ).size() == 1 ) | ||
| 239 | { | ||
| 240 | targetConstructor = constructor; | ||
| 241 | break; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | } | ||
| 245 | } | ||
| 246 | if( targetConstructor == null ) | ||
| 247 | { | ||
| 248 | continue; | ||
| 249 | } | ||
| 250 | |||
| 251 | // yeah, this is an inner class | ||
| 252 | return outerClassName; | ||
| 253 | } | ||
| 254 | |||
| 255 | return null; | ||
| 256 | } | ||
| 257 | |||
| 229 | public Set<String> getObfClassNames( ) | 258 | public Set<String> getObfClassNames( ) |
| 230 | { | 259 | { |
| 231 | return m_obfClassNames; | 260 | return m_obfClassNames; |
| @@ -304,4 +333,14 @@ public class JarIndex | |||
| 304 | { | 333 | { |
| 305 | return m_methodCalls.get( entry ); | 334 | return m_methodCalls.get( entry ); |
| 306 | } | 335 | } |
| 336 | |||
| 337 | public Collection<String> getInnerClasses( String obfOuterClassName ) | ||
| 338 | { | ||
| 339 | return m_innerClasses.get( obfOuterClassName ); | ||
| 340 | } | ||
| 341 | |||
| 342 | public String getOuterClass( String obfInnerClassName ) | ||
| 343 | { | ||
| 344 | return m_outerClasses.get( obfInnerClassName ); | ||
| 345 | } | ||
| 307 | } | 346 | } |
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java new file mode 100644 index 0000000..d4abe4e --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | |||
| 15 | import javassist.CtClass; | ||
| 16 | import javassist.bytecode.AccessFlag; | ||
| 17 | import javassist.bytecode.Descriptor; | ||
| 18 | import javassist.bytecode.InnerClassesAttribute; | ||
| 19 | import cuchaz.enigma.analysis.JarIndex; | ||
| 20 | import cuchaz.enigma.mapping.Translator; | ||
| 21 | |||
| 22 | public class InnerClassWriter | ||
| 23 | { | ||
| 24 | private Translator m_deobfuscatingTranslator; | ||
| 25 | private JarIndex m_jarIndex; | ||
| 26 | |||
| 27 | public InnerClassWriter( Translator deobfuscatingTranslator, JarIndex jarIndex ) | ||
| 28 | { | ||
| 29 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 30 | m_jarIndex = jarIndex; | ||
| 31 | } | ||
| 32 | |||
| 33 | public void writeInnerClasses( CtClass c ) | ||
| 34 | { | ||
| 35 | // is this an outer class with inner classes? | ||
| 36 | String obfOuterClassName = Descriptor.toJvmName( c.getName() ); | ||
| 37 | Collection<String> obfInnerClassNames = m_jarIndex.getInnerClasses( obfOuterClassName ); | ||
| 38 | if( obfInnerClassNames != null && !obfInnerClassNames.isEmpty() ) | ||
| 39 | { | ||
| 40 | writeInnerClasses( c, obfInnerClassNames ); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | private void writeInnerClasses( CtClass c, Collection<String> obfInnerClassNames ) | ||
| 45 | { | ||
| 46 | String obfOuterClassName = Descriptor.toJvmName( c.getName() ); | ||
| 47 | InnerClassesAttribute attr = new InnerClassesAttribute( c.getClassFile().getConstPool() ); | ||
| 48 | c.getClassFile().addAttribute( attr ); | ||
| 49 | for( String obfInnerClassName : obfInnerClassNames ) | ||
| 50 | { | ||
| 51 | // deobfuscate the class names | ||
| 52 | String deobfOuterClassName = m_deobfuscatingTranslator.translateClass( obfOuterClassName ); | ||
| 53 | if( deobfOuterClassName == null ) | ||
| 54 | { | ||
| 55 | deobfOuterClassName = obfOuterClassName; | ||
| 56 | } | ||
| 57 | String deobfInnerClassName = m_deobfuscatingTranslator.translateClass( obfInnerClassName ); | ||
| 58 | if( deobfInnerClassName == null ) | ||
| 59 | { | ||
| 60 | deobfInnerClassName = obfInnerClassName; | ||
| 61 | } | ||
| 62 | |||
| 63 | // update the attribute | ||
| 64 | String deobfOuterInnerClassName = deobfOuterClassName + "$" + deobfInnerClassName; | ||
| 65 | attr.append( | ||
| 66 | deobfOuterInnerClassName, | ||
| 67 | deobfOuterClassName, | ||
| 68 | deobfInnerClassName, | ||
| 69 | c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER | ||
| 70 | ); | ||
| 71 | |||
| 72 | // make sure the outer class references only the new inner class names | ||
| 73 | c.replaceClassName( obfInnerClassName, deobfOuterInnerClassName ); | ||
| 74 | |||
| 75 | // TEMP | ||
| 76 | System.out.println( "\tInner " + obfInnerClassName + " -> " + deobfOuterInnerClassName ); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||