Package cuchaz.enigma

Source Code of cuchaz.enigma.Deobfuscator$ProgressListener

/*******************************************************************************
* Copyright (c) 2014 Jeff Martin.\
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
*     Jeff Martin - initial API and implementation
******************************************************************************/
package cuchaz.enigma;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import javassist.CtClass;
import javassist.bytecode.Descriptor;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.strobel.assembler.metadata.MetadataSystem;
import com.strobel.assembler.metadata.TypeDefinition;
import com.strobel.decompiler.DecompilerContext;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.decompiler.PlainTextOutput;
import com.strobel.decompiler.languages.java.JavaOutputVisitor;
import com.strobel.decompiler.languages.java.ast.AstBuilder;
import com.strobel.decompiler.languages.java.ast.CompilationUnit;
import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;

import cuchaz.enigma.analysis.EntryReference;
import cuchaz.enigma.analysis.JarClassIterator;
import cuchaz.enigma.analysis.JarIndex;
import cuchaz.enigma.analysis.SourceIndex;
import cuchaz.enigma.analysis.SourceIndexVisitor;
import cuchaz.enigma.analysis.Token;
import cuchaz.enigma.mapping.ArgumentEntry;
import cuchaz.enigma.mapping.BehaviorEntry;
import cuchaz.enigma.mapping.BehaviorEntryFactory;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.ClassMapping;
import cuchaz.enigma.mapping.ConstructorEntry;
import cuchaz.enigma.mapping.Entry;
import cuchaz.enigma.mapping.FieldEntry;
import cuchaz.enigma.mapping.FieldMapping;
import cuchaz.enigma.mapping.Mappings;
import cuchaz.enigma.mapping.MappingsRenamer;
import cuchaz.enigma.mapping.MethodEntry;
import cuchaz.enigma.mapping.MethodMapping;
import cuchaz.enigma.mapping.TranslationDirection;
import cuchaz.enigma.mapping.Translator;

public class Deobfuscator
{
  public interface ProgressListener
  {
    void init( int totalWork, String title );
    void onProgress( int numDone, String message );
  }
 
  private File m_file;
  private JarFile m_jar;
  private DecompilerSettings m_settings;
  private JarIndex m_jarIndex;
  private Mappings m_mappings;
  private MappingsRenamer m_renamer;
  private Map<TranslationDirection,Translator> m_translatorCache;
 
  public Deobfuscator( File file )
  throws IOException
  {
    m_file = file;
    m_jar = new JarFile( m_file );
   
    // build the jar index
    m_jarIndex = new JarIndex();
    m_jarIndex.indexJar( m_jar, true );
   
    // config the decompiler
    m_settings = DecompilerSettings.javaDefaults();
    m_settings.setMergeVariables( true );
    m_settings.setForceExplicitImports( true );
    m_settings.setForceExplicitTypeArguments( true );
    // DEBUG
    //m_settings.setShowSyntheticMembers( true );
   
    // init defaults
    m_translatorCache = Maps.newTreeMap();
   
    // init mappings
    setMappings( new Mappings() );
  }
 
  public String getJarName( )
  {
    return m_file.getName();
  }
 
  public JarIndex getJarIndex( )
  {
    return m_jarIndex;
  }
 
  public Mappings getMappings( )
  {
    return m_mappings;
  }
  public void setMappings( Mappings val )
  {
    if( val == null )
    {
      val = new Mappings();
    }
   
    // pass 1: look for any classes that got moved to inner classes
    Map<String,String> renames = Maps.newHashMap();
    for( ClassMapping classMapping : val.classes() )
    {
      // make sure we strip the packages off of obfuscated inner classes
      String innerClassName = new ClassEntry( classMapping.getObfName() ).getSimpleName();
      String outerClassName = m_jarIndex.getOuterClass( innerClassName );
      if( outerClassName != null )
      {
        // build the composite class name
        String newName = outerClassName + "$" + innerClassName;
       
        // add a rename
        renames.put( classMapping.getObfName(), newName );
       
        System.out.println( String.format( "Converted class mapping %s to %s", classMapping.getObfName(), newName ) );
      }
    }
    for( Map.Entry<String,String> entry : renames.entrySet() )
    {
      val.renameObfClass( entry.getKey(), entry.getValue() );
    }
   
    // pass 2: look for fields/methods that are actually declared in superclasses
    MappingsRenamer renamer = new MappingsRenamer( m_jarIndex, val );
    for( ClassMapping classMapping : val.classes() )
    {
      ClassEntry obfClassEntry = new ClassEntry( classMapping.getObfName() );
     
      // fields
      for( FieldMapping fieldMapping : Lists.newArrayList( classMapping.fields() ) )
      {
        FieldEntry fieldEntry = new FieldEntry( obfClassEntry, fieldMapping.getObfName() );
        ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass( fieldEntry );
        if( resolvedObfClassEntry != null && !resolvedObfClassEntry.equals( fieldEntry.getClassEntry() ) )
        {
          boolean wasMoved = renamer.moveFieldToObfClass( classMapping, fieldMapping, resolvedObfClassEntry );
          if( wasMoved )
          {
            System.out.println( String.format( "Moved field %s to class %s", fieldEntry, resolvedObfClassEntry ) );
          }
          else
          {
            System.err.println( String.format( "WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry ) );
          }
        }
      }
     
      // methods
      for( MethodMapping methodMapping : Lists.newArrayList( classMapping.methods() ) )
      {
        // skip constructors
        if( methodMapping.isConstructor() )
        {
          continue;
        }
       
        MethodEntry methodEntry = new MethodEntry( obfClassEntry, methodMapping.getObfName(), methodMapping.getObfSignature() );
        ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass( methodEntry );
        if( resolvedObfClassEntry != null && !resolvedObfClassEntry.equals( methodEntry.getClassEntry() ) )
        {
          boolean wasMoved = renamer.moveMethodToObfClass( classMapping, methodMapping, resolvedObfClassEntry );
          if( wasMoved )
          {
            System.out.println( String.format( "Moved method %s to class %s", methodEntry, resolvedObfClassEntry ) );
          }
          else
          {
            System.err.println( String.format( "WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry ) );
          }
        }
      }
     
      // TODO: recurse to inner classes?
    }
   
    // drop mappings that don't match the jar
    List<ClassEntry> unknownClasses = Lists.newArrayList();
    for( ClassMapping classMapping : val.classes() )
    {
      checkClassMapping( unknownClasses, classMapping );
    }
    if( !unknownClasses.isEmpty() )
    {
      throw new Error( "Unable to find classes in jar: " + unknownClasses );
    }
   
    m_mappings = val;
    m_renamer = renamer;
    m_translatorCache.clear();
  }
 
  private void checkClassMapping( List<ClassEntry> unknownClasses, ClassMapping classMapping )
  {
    // check the class
    ClassEntry classEntry = new ClassEntry( classMapping.getObfName() );
    String outerClassName = m_jarIndex.getOuterClass( classEntry.getSimpleName() );
    if( outerClassName != null )
    {
      classEntry = new ClassEntry( outerClassName + "$" + classMapping.getObfName() );
    }
    if( !m_jarIndex.getObfClassEntries().contains( classEntry ) )
    {
      unknownClasses.add( classEntry );
    }
   
    // check the fields
    for( FieldMapping fieldMapping : Lists.newArrayList( classMapping.fields() ) )
    {
      FieldEntry fieldEntry = new FieldEntry( classEntry, fieldMapping.getObfName() );
      if( !m_jarIndex.containsObfField( fieldEntry ) )
      {
        System.err.println( "WARNING: unable to find field " + fieldEntry + ". dropping mapping." );
        classMapping.removeFieldMapping( fieldMapping );
      }
    }
   
    // check methods
    for( MethodMapping methodMapping : Lists.newArrayList( classMapping.methods() ) )
    {
      BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf( classEntry, methodMapping );
      if( !m_jarIndex.containsObfBehavior( obfBehaviorEntry ) )
      {
        System.err.println( "WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping." );
        classMapping.removeMethodMapping( methodMapping );
     
    }
   
    // check inner classes
    for( ClassMapping innerClassMapping : classMapping.innerClasses() )
    {
      checkClassMapping( unknownClasses, innerClassMapping );
    }
  }

  public Translator getTranslator( TranslationDirection direction )
  {
    Translator translator = m_translatorCache.get( direction );
    if( translator == null )
    {
      translator = m_mappings.getTranslator( direction );
      m_translatorCache.put( direction, translator );
    }
    return translator;
  }
 
  public void getSeparatedClasses( List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses )
  {
    for( ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries() )
    {
      // skip inner classes
      if( obfClassEntry.isInnerClass() )
      {
        continue;
      }
     
      // separate the classes
      ClassEntry deobfClassEntry = deobfuscateEntry( obfClassEntry );
      if( !deobfClassEntry.equals( obfClassEntry ) )
      {
        // if the class has a mapping, clearly it's deobfuscated
        deobfClasses.add( deobfClassEntry );
      }
      else if( !obfClassEntry.getPackageName().equals( Constants.NonePackage ) )
      {
        // also call it deobufscated if it's not in the none package
        deobfClasses.add( obfClassEntry );
      }
      else
      {
        // otherwise, assume it's still obfuscated
        obfClasses.add( obfClassEntry );
      }
    }
  }
 
  public CompilationUnit getSourceTree( String obfClassName )
  {
    // is this class deobfuscated?
    // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
    // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name
    String lookupClassName = obfClassName;
    ClassMapping classMapping = m_mappings.getClassByObf( obfClassName );
    if( classMapping != null && classMapping.getDeobfName() != null )
    {
      lookupClassName = classMapping.getDeobfName();
    }
   
    // is this class even in the jar?
    if( !m_jarIndex.containsObfClass( new ClassEntry( obfClassName ) ) )
    {
      return null;
    }
   
    // set the type loader
    m_settings.setTypeLoader( new TranslatingTypeLoader(
      m_jar,
      m_jarIndex,
      getTranslator( TranslationDirection.Obfuscating ),
      getTranslator( TranslationDirection.Deobfuscating )
    ) );
   
    // decompile it!
    TypeDefinition resolvedType = new MetadataSystem( m_settings.getTypeLoader() ).lookupType( lookupClassName ).resolve();
    DecompilerContext context = new DecompilerContext();
    context.setCurrentType( resolvedType );
    context.setSettings( m_settings );
    AstBuilder builder = new AstBuilder( context );
    builder.addType( resolvedType );
    builder.runTransformations( null );
    return builder.getCompilationUnit();
  }
 
  public SourceIndex getSourceIndex( CompilationUnit sourceTree, String source )
  {
    // build the source index
    SourceIndex index = new SourceIndex( source );
    sourceTree.acceptVisitor( new SourceIndexVisitor(), index );
   
    // DEBUG
    //sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
   
    // resolve all the classes in the source references
    for( Token token : index.referenceTokens() )
    {
      EntryReference<Entry,Entry> deobfReference = index.getDeobfReference( token );
     
      // get the obfuscated entry
      Entry obfEntry = obfuscateEntry( deobfReference.entry );
     
      // try to resolve the class
      ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass( obfEntry );
      if( resolvedObfClassEntry != null && !resolvedObfClassEntry.equals( obfEntry.getClassEntry() ) )
      {
        // change the class of the entry
        obfEntry = obfEntry.cloneToNewClass( resolvedObfClassEntry );
       
        // save the new deobfuscated reference
        deobfReference.entry = deobfuscateEntry( obfEntry );
        index.replaceDeobfReference( token, deobfReference );
      }
     
      // DEBUG
      //System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
    }
   
    return index;
  }
 
  public String getSource( CompilationUnit sourceTree )
  {
    // render the AST into source
    StringWriter buf = new StringWriter();
    sourceTree.acceptVisitor( new InsertParenthesesVisitor(), null );
    sourceTree.acceptVisitor( new JavaOutputVisitor( new PlainTextOutput( buf ), m_settings ), null );
    return buf.toString();
  }
 
  public void writeSources( File dirOut, ProgressListener progress )
  throws IOException
  {
    // get the classes to decompile
    Set<ClassEntry> classEntries = Sets.newHashSet();
    for( ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries() )
    {
      // skip inner classes
      if( obfClassEntry.isInnerClass() )
      {
        continue;
      }
     
      classEntries.add( obfClassEntry );
    }
   
    if( progress != null )
    {
      progress.init( classEntries.size(), "Decompiling classes..." );
    }
   
    // DEOBFUSCATE ALL THE THINGS!! @_@
    int i = 0;
    for( ClassEntry obfClassEntry : classEntries )
    {
      ClassEntry deobfClassEntry = deobfuscateEntry( new ClassEntry( obfClassEntry ) );
      if( progress != null )
      {
        progress.onProgress( i++, deobfClassEntry.toString() );
      }
     
      try
      {
        // get the source
        String source = getSource( getSourceTree( obfClassEntry.getName() ) );
       
        // write the file
        File file = new File( dirOut, deobfClassEntry.getName().replace( '.', '/' ) + ".java" );
        file.getParentFile().mkdirs();
        try( FileWriter out = new FileWriter( file ) )
        {
          out.write( source );
        }
      }
      catch( Throwable t )
      {
        throw new Error( "Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t );
      }
    }
    if( progress != null )
    {
      progress.onProgress( i, "Done!" );
    }
  }
 
  public void writeJar( File out, ProgressListener progress )
  {
    try( JarOutputStream outJar = new JarOutputStream( new FileOutputStream( out ) ) )
    {
      if( progress != null )
      {
        progress.init( JarClassIterator.getClassEntries( m_jar ).size(), "Translating classes..." );
      }
     
      // prep the loader
      TranslatingTypeLoader loader = new TranslatingTypeLoader(
        m_jar,
        m_jarIndex,
        getTranslator( TranslationDirection.Obfuscating ),
        getTranslator( TranslationDirection.Deobfuscating )
      );
     
      int i = 0;
      for( CtClass c : JarClassIterator.classes( m_jar ) )
      {
        if( progress != null )
        {
          progress.onProgress( i++, c.getName() );
        }
       
        try
        {
          c = loader.transformClass( c );
          outJar.putNextEntry( new JarEntry( c.getName().replace( '.', '/' ) + ".class" ) );
          outJar.write( c.toBytecode() );
          outJar.closeEntry();
        }
        catch( Throwable t )
        {
          throw new Error( "Unable to deobfuscate class " + c.getName(), t );
        }
      }
      if( progress != null )
      {
        progress.onProgress( i, "Done!" );
      }
     
      outJar.close();
    }
    catch( IOException ex )
    {
      throw new Error( "Unable to write to Jar file!" );
    }
  }
 
  public <T extends Entry> T obfuscateEntry( T deobfEntry )
  {
    if( deobfEntry == null )
    {
      return null;
    }
    return getTranslator( TranslationDirection.Obfuscating ).translateEntry( deobfEntry );
  }
 
  public <T extends Entry> T deobfuscateEntry( T obfEntry )
  {
    if( obfEntry == null )
    {
      return null;
    }
    return getTranslator( TranslationDirection.Deobfuscating ).translateEntry( obfEntry );
  }
 
  public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference( EntryReference<E,C> deobfReference )
  {
    if( deobfReference == null )
    {
      return null;
    }
    return new EntryReference<E,C>(
      obfuscateEntry( deobfReference.entry ),
      obfuscateEntry( deobfReference.context ),
      deobfReference
    );
  }
 
  public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference( EntryReference<E,C> obfReference )
  {
    if( obfReference == null )
    {
      return null;
    }
    return new EntryReference<E,C>(
      deobfuscateEntry( obfReference.entry ),
      deobfuscateEntry( obfReference.context ),
      obfReference
    );
  }
 
  public boolean isObfuscatedIdentifier( Entry obfEntry )
  {
    return m_jarIndex.containsObfEntry( obfEntry );
  }
 
  public boolean isRenameable( EntryReference<Entry,Entry> obfReference )
  {
    return obfReference.isNamed() && isObfuscatedIdentifier( obfReference.getNameableEntry() );
  }
 
 
  // NOTE: these methods are a bit messy... oh well

  public boolean hasDeobfuscatedName( Entry obfEntry )
  {
    Translator translator = getTranslator( TranslationDirection.Deobfuscating );
    if( obfEntry instanceof ClassEntry )
    {
      return translator.translate( (ClassEntry)obfEntry ) != null;
    }
    else if( obfEntry instanceof FieldEntry )
    {
      return translator.translate( (FieldEntry)obfEntry ) != null;
    }
    else if( obfEntry instanceof MethodEntry )
    {
      return translator.translate( (MethodEntry)obfEntry ) != null;
    }
    else if( obfEntry instanceof ConstructorEntry )
    {
      // constructors have no names
      return false;
    }
    else if( obfEntry instanceof ArgumentEntry )
    {
      return translator.translate( (ArgumentEntry)obfEntry ) != null;
    }
    else
    {
      throw new Error( "Unknown entry type: " + obfEntry.getClass().getName() );
    }
  }

  public void rename( Entry obfEntry, String newName )
  {
    if( obfEntry instanceof ClassEntry )
    {
      m_renamer.setClassName( (ClassEntry)obfEntry, Descriptor.toJvmName( newName ) );
    }
    else if( obfEntry instanceof FieldEntry )
    {
      m_renamer.setFieldName( (FieldEntry)obfEntry, newName );
    }
    else if( obfEntry instanceof MethodEntry )
    {
      m_renamer.setMethodTreeName( (MethodEntry)obfEntry, newName );
    }
    else if( obfEntry instanceof ConstructorEntry )
    {
      throw new IllegalArgumentException( "Cannot rename constructors" );
    }
    else if( obfEntry instanceof ArgumentEntry )
    {
      m_renamer.setArgumentName( (ArgumentEntry)obfEntry, newName );
    }
    else
    {
      throw new Error( "Unknown entry type: " + obfEntry.getClass().getName() );
    }
   
    // clear caches
    m_translatorCache.clear();
  }
 
  public void removeMapping( Entry obfEntry )
  {
    if( obfEntry instanceof ClassEntry )
    {
      m_renamer.removeClassMapping( (ClassEntry)obfEntry );
    }
    else if( obfEntry instanceof FieldEntry )
    {
      m_renamer.removeFieldMapping( (FieldEntry)obfEntry );
    }
    else if( obfEntry instanceof MethodEntry )
    {
      m_renamer.removeMethodTreeMapping( (MethodEntry)obfEntry );
    }
    else if( obfEntry instanceof ConstructorEntry )
    {
      throw new IllegalArgumentException( "Cannot rename constructors" );
    }
    else if( obfEntry instanceof ArgumentEntry )
    {
      m_renamer.removeArgumentMapping( (ArgumentEntry)obfEntry );
    }
    else
    {
      throw new Error( "Unknown entry type: " + obfEntry );
    }
   
    // clear caches
    m_translatorCache.clear();
  }
 
  public void markAsDeobfuscated( Entry obfEntry )
  {
    if( obfEntry instanceof ClassEntry )
    {
      m_renamer.markClassAsDeobfuscated( (ClassEntry)obfEntry );
    }
    else if( obfEntry instanceof FieldEntry )
    {
      m_renamer.markFieldAsDeobfuscated( (FieldEntry)obfEntry );
    }
    else if( obfEntry instanceof MethodEntry )
    {
      m_renamer.markMethodTreeAsDeobfuscated( (MethodEntry)obfEntry );
    }
    else if( obfEntry instanceof ConstructorEntry )
    {
      throw new IllegalArgumentException( "Cannot rename constructors" );
    }
    else if( obfEntry instanceof ArgumentEntry )
    {
      m_renamer.markArgumentAsDeobfuscated( (ArgumentEntry)obfEntry );
    }
    else
    {
      throw new Error( "Unknown entry type: " + obfEntry );
    }

    // clear caches
    m_translatorCache.clear();
  }
}
TOP

Related Classes of cuchaz.enigma.Deobfuscator$ProgressListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.