/******************************************************************************* * Copyright (c) 2015 Jeff Martin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ package cuchaz.enigma; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javassist.ByteArrayClassPath; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.NotFoundException; import javassist.bytecode.Descriptor; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ClasspathTypeLoader; import com.strobel.assembler.metadata.ITypeLoader; import cuchaz.enigma.analysis.BridgeMarker; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.bytecode.ClassRenamer; import cuchaz.enigma.bytecode.ClassTranslator; import cuchaz.enigma.bytecode.InnerClassWriter; import cuchaz.enigma.bytecode.LocalVariableRenamer; import cuchaz.enigma.bytecode.MethodParameterWriter; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Translator; public class TranslatingTypeLoader implements ITypeLoader { private JarFile m_jar; private JarIndex m_jarIndex; private Translator m_obfuscatingTranslator; private Translator m_deobfuscatingTranslator; private Map<String, byte[]> m_cache; private ClasspathTypeLoader m_defaultTypeLoader; public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) { this(jar, jarIndex, new Translator(), new Translator()); } public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { m_jar = jar; m_jarIndex = jarIndex; m_obfuscatingTranslator = obfuscatingTranslator; m_deobfuscatingTranslator = deobfuscatingTranslator; m_cache = Maps.newHashMap(); m_defaultTypeLoader = new ClasspathTypeLoader(); } public void clearCache() { m_cache.clear(); } @Override public boolean tryLoadType(String className, Buffer out) { // check the cache byte[] data; if(m_cache.containsKey(className)) data = m_cache.get(className); else { data = loadType(className); m_cache.put(className, data); } if(data == null) // chain to default type loader return m_defaultTypeLoader.tryLoadType(className, out); // send the class to the decompiler out.reset(data.length); System.arraycopy(data, 0, out.array(), out.position(), data.length); out.position(0); return true; } public CtClass loadClass(String deobfClassName) { byte[] data = loadType(deobfClassName); if(data == null) return null; // return a javassist handle for the class String javaClassFileName = Descriptor.toJavaName(deobfClassName); ClassPool classPool = new ClassPool(); classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); try { return classPool.get(javaClassFileName); }catch(NotFoundException ex) { throw new Error(ex); } } private byte[] loadType(String className) { // NOTE: don't know if class name is obf or deobf ClassEntry classEntry = new ClassEntry(className); ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry); // is this an inner class referenced directly? (ie trying to load b // instead of a$b) if(!obfClassEntry.isInnerClass()) { List<ClassEntry> classChain = m_jarIndex.getObfClassChain(obfClassEntry); if(classChain.size() > 1) { System.err .println(String .format( "WARNING: no class %s after inner class reconstruction. Try %s", className, obfClassEntry.buildClassEntry(classChain))); return null; } } // is this a class we should even know about? if(!m_jarIndex.containsObfClass(obfClassEntry)) return null; // DEBUG // System.out.println(String.format("Looking for %s (obf: %s)", // classEntry.getName(), obfClassEntry.getName())); // find the class in the jar String classInJarName = findClassInJar(obfClassEntry); if(classInJarName == null) // couldn't find it return null; try { // read the class file into a buffer ByteArrayOutputStream data = new ByteArrayOutputStream(); byte[] buf = new byte[1024 * 1024]; // 1 KiB InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class")); while(true) { int bytesRead = in.read(buf); if(bytesRead <= 0) break; data.write(buf, 0, bytesRead); } data.close(); in.close(); buf = data.toByteArray(); // load the javassist handle to the raw class ClassPool classPool = new ClassPool(); String classInJarJavaName = Descriptor.toJavaName(classInJarName); classPool.insertClassPath(new ByteArrayClassPath( classInJarJavaName, buf)); CtClass c = classPool.get(classInJarJavaName); c = transformClass(c); // sanity checking assertClassName(c, classEntry); // DEBUG // Util.writeClass( c ); // we have a transformed class! return c.toBytecode(); }catch(IOException | NotFoundException | CannotCompileException ex) { throw new Error(ex); } } private String findClassInJar(ClassEntry obfClassEntry) { // try to find the class in the jar for(String className : getClassNamesToTry(obfClassEntry)) { JarEntry jarEntry = m_jar.getJarEntry(className + ".class"); if(jarEntry != null) return className; } // didn't find it ;_; return null; } public List<String> getClassNamesToTry(String className) { return getClassNamesToTry(m_obfuscatingTranslator .translateEntry(new ClassEntry(className))); } public List<String> getClassNamesToTry(ClassEntry obfClassEntry) { List<String> classNamesToTry = Lists.newArrayList(); classNamesToTry.add(obfClassEntry.getName()); if(obfClassEntry.getPackageName().equals(Constants.NonePackage)) // taking off the none package, if any classNamesToTry.add(obfClassEntry.getSimpleName()); if(obfClassEntry.isInnerClass()) // try just the inner class name classNamesToTry.add(obfClassEntry.getInnermostClassName()); return classNamesToTry; } public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException { // we moved a lot of classes out of the default package into the none // package // make sure all the class references are consistent ClassRenamer .moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); // reconstruct inner classes new InnerClassWriter(m_jarIndex).write(c); // re-get the javassist handle since we changed class names ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); ClassPool classPool = new ClassPool(); classPool.insertClassPath(new ByteArrayClassPath( javaClassReconstructedName, c.toBytecode())); c = classPool.get(javaClassReconstructedName); // check that the file is correct after inner class reconstruction (ie // cause Javassist to fail fast if something is wrong) assertClassName(c, obfClassEntry); // do all kinds of deobfuscating transformations on the class new BridgeMarker(m_jarIndex).markBridges(c); new MethodParameterWriter(m_deobfuscatingTranslator) .writeMethodArguments(c); new LocalVariableRenamer(m_deobfuscatingTranslator).rename(c); new ClassTranslator(m_deobfuscatingTranslator).translate(c); return c; } private void assertClassName(CtClass c, ClassEntry obfClassEntry) { String name1 = Descriptor.toJvmName(c.getName()); assert name1.equals(obfClassEntry.getName()) : String.format( "Looking for %s, instead found %s", obfClassEntry.getName(), name1); String name2 = Descriptor.toJvmName(c.getClassFile().getName()); assert name2.equals(obfClassEntry.getName()) : String.format( "Looking for %s, instead found %s", obfClassEntry.getName(), name2); } }