diff options
| author | 2014-08-15 01:43:48 -0400 | |
|---|---|---|
| committer | 2014-08-15 01:43:48 -0400 | |
| commit | 37467e4a7b5e05e4da413a1e06e597fa806b72e4 (patch) | |
| tree | 4c76a76aa3379fc236977646af48ec63dcf1712e /src | |
| 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...
Diffstat (limited to 'src')
| -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 |
5 files changed, 396 insertions, 108 deletions
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 | } | ||