diff --git a/src/the/bytecode/club/bytecodeviewer/JarUtils.java b/src/the/bytecode/club/bytecodeviewer/JarUtils.java index 8e148d00..98240b66 100644 --- a/src/the/bytecode/club/bytecodeviewer/JarUtils.java +++ b/src/the/bytecode/club/bytecodeviewer/JarUtils.java @@ -63,26 +63,28 @@ public class JarUtils { try { final String name = entry.getName(); final byte[] bytes = getBytes(jis); - if (!name.endsWith(".class")) { - if(!entry.isDirectory()) - files.put(name, bytes); - } else { - String cafebabe = String.format("%02X", bytes[0]) - + String.format("%02X", bytes[1]) - + String.format("%02X", bytes[2]) - + String.format("%02X", bytes[3]); - if(cafebabe.toLowerCase().equals("cafebabe")) { - try { - final ClassNode cn = getNode(bytes); - container.classes.add(cn); - } catch(Exception e) { - e.printStackTrace(); - } + if(!files.containsKey(name)){ + if (!name.endsWith(".class")) { + if(!entry.isDirectory()) + files.put(name, bytes); } else { - System.out.println(jarFile+">"+name+": Header does not start with CAFEBABE, ignoring."); + String cafebabe = String.format("%02X", bytes[0]) + + String.format("%02X", bytes[1]) + + String.format("%02X", bytes[2]) + + String.format("%02X", bytes[3]); + if(cafebabe.toLowerCase().equals("cafebabe")) { + try { + final ClassNode cn = getNode(bytes); + container.classes.add(cn); + } catch(Exception e) { + e.printStackTrace(); + } + } else { + System.out.println(jarFile+">"+name+": Header does not start with CAFEBABE, ignoring."); + } + files.put(name, bytes); } } - } catch(Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } finally { diff --git a/src/the/bytecode/club/bytecodeviewer/api/BytecodeViewer.java b/src/the/bytecode/club/bytecodeviewer/api/BytecodeViewer.java index 2cbc345c..dc5d8296 100644 --- a/src/the/bytecode/club/bytecodeviewer/api/BytecodeViewer.java +++ b/src/the/bytecode/club/bytecodeviewer/api/BytecodeViewer.java @@ -1,6 +1,7 @@ package the.bytecode.club.bytecodeviewer.api; import java.io.File; +import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; @@ -62,50 +63,60 @@ public class BytecodeViewer { return cl; } + + /** * Re-instances the URLClassLoader and loads a jar to it. - * @param path + * + * @param nodeList + * The list of ClassNodes to be loaded * @return The loaded classes into the new URLClassLoader instance * @author Cafebabe + * @throws IOException + * @throws ClassNotFoundException */ - public static List> loadClassesIntoClassLoader() { - try { - File f = new File( - the.bytecode.club.bytecodeviewer.BytecodeViewer.tempDirectory + - the.bytecode.club.bytecodeviewer.BytecodeViewer.fs + - "loaded_temp.jar"); - JarUtils.saveAsJar(BytecodeViewer.getLoadedClasses(), f.getAbsolutePath()); - JarFile jarFile = new JarFile(""+f.getAbsolutePath()); - Enumeration e = jarFile.entries(); - URL[] urls = { new URL("jar:file:" + ""+f.getAbsolutePath()+"!/") }; - cl = URLClassLoader.newInstance(urls); - List> ret = new ArrayList>(); - - while (e.hasMoreElements()) - { - JarEntry je = (JarEntry) e.nextElement(); - if(je.isDirectory() || !je.getName().endsWith(".class")) - continue; - String className = je.getName().replace("/", ".").replace(".class", ""); - className = className.replace('/', '.'); - try{ - ret.add(cl.loadClass(className)); - } - catch(Exception e2) - { - new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e2); - } - } - jarFile.close(); - - return ret; - } - catch(Exception e) - { - new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); - } - return null; - } + @SuppressWarnings("deprecation") + public static List> loadClassesIntoClassLoader( + ArrayList nodeList) throws IOException, + ClassNotFoundException { + + File f = new File( + the.bytecode.club.bytecodeviewer.BytecodeViewer.tempDirectory + + the.bytecode.club.bytecodeviewer.BytecodeViewer.fs + + "loaded_temp.jar"); + JarUtils.saveAsJarClassesOnly(nodeList, f.getAbsolutePath()); + + JarFile jarFile = new JarFile("" + f.getAbsolutePath()); + Enumeration e = jarFile.entries(); + cl = URLClassLoader.newInstance(new URL[]{ f.toURL() }); + List> ret = new ArrayList>(); + + while (e.hasMoreElements()) { + JarEntry je = (JarEntry) e.nextElement(); + if (je.isDirectory() || !je.getName().endsWith(".class")) + continue; + String className = je.getName().replace("/", ".").replace(".class", ""); + className = className.replace('/', '.'); + ret.add(cl.loadClass(className)); + + } + jarFile.close(); + + return ret; + + } + + + /** + * Re-instances the URLClassLoader and loads a jar to it. + * @return The loaded classes into the new URLClassLoader instance + * @author Cafebabe + * @throws IOException + * @throws ClassNotFoundException + */ + public static List> loadAllClassesIntoClassLoader() throws ClassNotFoundException, IOException { + return loadClassesIntoClassLoader(getLoadedClasses()); + } /** * Creates a new instance of the ClassNode loader. diff --git a/src/the/bytecode/club/bytecodeviewer/api/ClassNodeLoader.java b/src/the/bytecode/club/bytecodeviewer/api/ClassNodeLoader.java index 1a06011f..9c05d2d6 100644 --- a/src/the/bytecode/club/bytecodeviewer/api/ClassNodeLoader.java +++ b/src/the/bytecode/club/bytecodeviewer/api/ClassNodeLoader.java @@ -110,7 +110,7 @@ public final class ClassNodeLoader extends ClassLoader { if (classes.containsKey(name)) { return nodeToClass(classes.get(name)); } else { - return super.loadClass(name); + return super.findClass(name); } } diff --git a/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java index b607152e..baa5d2a3 100644 --- a/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java +++ b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/InstructionPrinter.java @@ -304,7 +304,7 @@ public class InstructionPrinter { protected String printInvokeDynamicInsNode(InvokeDynamicInsnNode idin) { StringBuilder sb = new StringBuilder(); - sb.append(nameOpcode(idin.opcode()) + " " + idin.name + "("); + sb.append(nameOpcode(idin.opcode()) + " " + idin.bsm.getName() + "("); String desc = idin.desc; String partedDesc = idin.desc.substring(2); diff --git a/src/the/bytecode/club/bytecodeviewer/gui/AllatoriStringDecrypterOptions.java b/src/the/bytecode/club/bytecodeviewer/gui/AllatoriStringDecrypterOptions.java new file mode 100644 index 00000000..60842357 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/AllatoriStringDecrypterOptions.java @@ -0,0 +1,77 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JTextField; + +import the.bytecode.club.bytecodeviewer.Resources; +import the.bytecode.club.bytecodeviewer.plugin.PluginManager; +import the.bytecode.club.bytecodeviewer.plugin.preinstalled.AllatoriStringDecrypter; + +/*************************************************************************** + * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * + * Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + ***************************************************************************/ + +/** + * The UI for replace strings plugin. + * + * @author Konloch + * + */ + +public class AllatoriStringDecrypterOptions extends JFrame { + public AllatoriStringDecrypterOptions() { + this.setIconImages(Resources.iconList); + setSize(new Dimension(250, 120)); + setResizable(false); + setTitle("Allatori decrypter"); + getContentPane().setLayout(null); + + JButton btnNewButton = new JButton("Decrypt"); + btnNewButton.setBounds(6, 56, 232, 23); + getContentPane().add(btnNewButton); + + + JLabel lblNewLabel = new JLabel("Class:"); + lblNewLabel.setBounds(6, 20, 67, 14); + getContentPane().add(lblNewLabel); + + textField = new JTextField(); + textField.setToolTipText("* will search all classes"); + textField.setText("*"); + textField.setBounds(80, 17, 158, 20); + getContentPane().add(textField); + textField.setColumns(10); + + + btnNewButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + PluginManager.runPlugin(new AllatoriStringDecrypter(textField.getText())); + dispose(); + } + }); + this.setLocationRelativeTo(null); + } + + private static final long serialVersionUID = -2662514582647810868L; + private JTextField textField; +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java b/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java index 70a37c1c..149a121b 100644 --- a/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java +++ b/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java @@ -311,36 +311,6 @@ public class FileNavigationPane extends VisibleComponent implements ImageRenderer renderer = new ImageRenderer(); tree.setCellRenderer(renderer); - if(!container.classes.isEmpty()) { - for(ClassNode c : container.classes) { - String name = c.name; - final String[] spl = name.split("/"); - if (spl.length < 2) { - root.add(new MyTreeNode(name+".class")); - } else { - MyTreeNode parent = root; - for (int i1 = 0; i1 < spl.length; i1++) { - String s = spl[i1]; - MyTreeNode child = null; - for (int i = 0; i < parent.getChildCount(); i++) { - if (((MyTreeNode) parent.getChildAt(i)).getUserObject() - .equals(s)) { - child = (MyTreeNode) parent.getChildAt(i); - break; - } - } - if (child == null) { - if(i1 == spl.length-1) - child = new MyTreeNode(s+".class"); - else - child = new MyTreeNode(s); - parent.add(child); - } - parent = child; - } - } - } - } if(!container.files.isEmpty()) { for (final Entry entry : container.files.entrySet()) { diff --git a/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java b/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java index 22164216..b72872d1 100644 --- a/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java +++ b/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java @@ -2029,8 +2029,12 @@ public class MainViewerGUI extends JFrame implements FileChangeNotifier { }); mntmNewMenuItem_2.addActionListener(new ActionListener() { @Override - public void actionPerformed(ActionEvent e) { - PluginManager.runPlugin(new AllatoriStringDecrypter()); + public void actionPerformed(ActionEvent arg0) { + if(BytecodeViewer.getLoadedClasses().isEmpty()) { + BytecodeViewer.showMessage("First open a class, jar, zip, apk or dex file."); + return; + } + new AllatoriStringDecrypterOptions().setVisible(true); } }); mntmNewMenuItem_1.addActionListener(new ActionListener() { diff --git a/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java b/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java index a2dbed53..f7f67d6c 100644 --- a/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java +++ b/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java @@ -1,11 +1,27 @@ package the.bytecode.club.bytecodeviewer.plugin.preinstalled; +import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.swing.JDialog; +import javax.swing.JOptionPane; + +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.JarUtils; +import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.api.Plugin; +import the.bytecode.club.bytecodeviewer.api.PluginConsole; /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * @@ -29,14 +45,218 @@ import the.bytecode.club.bytecodeviewer.api.Plugin; * Coming soon. * * @author Konloch + * @author Szperak * */ public class AllatoriStringDecrypter extends Plugin { + + PluginConsole frame = new PluginConsole("Allatori decrypter"); + StringBuilder out = new StringBuilder(); + + private String className; + + public AllatoriStringDecrypter(String className) { + this.className = className; + } + @Override public void execute(ArrayList classNodeList) { - BytecodeViewer.showMessage("This is a planned feature."); + JOptionPane pane = new JOptionPane( + "WARNING: This will load the classes into the JVM and execute allatori decrypter function" + + BytecodeViewer.nl + + "for each class. IF THE FILE YOU'RE LOADING IS MALICIOUS, DO NOT CONTINUE."); + Object[] options = new String[] { "Continue", "Cancel" }; + pane.setOptions(options); + JDialog dialog = pane.createDialog(BytecodeViewer.viewer, + "Bytecode Viewer - WARNING"); + dialog.setVisible(true); + Object obj = pane.getValue(); + int result = -1; + for (int k = 0; k < options.length; k++) + if (options[k].equals(obj)) + result = k; + + if (result == 0) { + try { + + if (!className.equals("*")) { + for (ClassNode classNode : classNodeList) { + if (classNode.name.equals(className)) + scanClassNode(classNode); + } + } else { + for (ClassNode classNode : classNodeList) { + scanClassNode(classNode); + } + } + }catch(Exception e){ + new ExceptionUI(e, "github.com/Szperak"); + } finally { + frame.appendText(out.toString()); + frame.setVisible(true); + } + } } + private void log(String msg){ + out.append(msg); + out.append(BytecodeViewer.nl); + } + + public void scanClassNode(ClassNode classNode) throws Exception { + for(MethodNode method : classNode.methods){ + scanMethodNode(classNode, method); + } + + } + + + public int readUnsignedShort(byte[] b, final int index) { + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + private int getConstantPoolSize(String className){ + byte[] fileContents = BytecodeViewer.getFileContents(className+".class"); + return readUnsignedShort(fileContents, 8); + } + + + public void scanMethodNode(ClassNode classNode, MethodNode methodNode) throws Exception { + InsnList iList = methodNode.instructions; + + log("Scanning method " + methodNode.name+" of " + classNode.name); + + LdcInsnNode laststringldconstack = null; + for (AbstractInsnNode i : iList.toArray()) { + if(i instanceof LdcInsnNode) { + LdcInsnNode ldci = (LdcInsnNode) i; + if(ldci.cst instanceof String){ + laststringldconstack = ldci; + } + continue; + } else if(i instanceof MethodInsnNode) { + MethodInsnNode methodi = (MethodInsnNode) i; + + + + if(laststringldconstack != null && methodi.opcode() == 0xb8) { // Decryption is always a static call - 0xb8 - invokestatic + String decrypterclassname = methodi.owner; + String decryptermethodname = methodi.name; + + if(decrypterclassname.contains("$")) { // Decrypter is always a static method of other class's inner class + byte[] decrypterFileContents = BytecodeViewer.getFileContents(decrypterclassname+".class"); + + // We have to create new node for editing + // Also, one decrypter method could be used for multiple methods in code, what gives us only part of string decrypted + ClassNode decrypterclassnode = JarUtils.getNode(decrypterFileContents); + + if(decrypterclassnode != null) { + MethodNode decryptermethodnode = decrypterclassnode.getMethodByName(decryptermethodname); + if(decryptermethodnode != null) { + + String keyString = (getConstantPoolSize(classNode.name)+ + classNode.name+ + methodNode.name+ + getConstantPoolSize(classNode.name) + ); + + int newHashCode = keyString.hashCode(); + + scanDecrypter(decryptermethodnode, newHashCode); + + try { + System.out.println("loading " + decrypterclassname); + + List> decrypterclasslist = the.bytecode.club.bytecodeviewer.api.BytecodeViewer + .loadClassesIntoClassLoader(new ArrayList( + Arrays.asList(new ClassNode[] { decrypterclassnode }))); + + String decrypted = invokeDecrypter(decrypterclasslist.get(0), decryptermethodname, (String) laststringldconstack.cst); + + if (decrypted != null) { + log("Succesfully invoked decrypter method: "+decrypted); + laststringldconstack.cst = decrypted; + iList.remove(methodi); + } + } catch (IndexOutOfBoundsException | ClassNotFoundException | IOException e) { + e.printStackTrace(); + log("Could not load decrypter class: " + decrypterclassname); + } + + }else{ + log("Could not find decrypter method ("+decryptermethodname+") of class "+decrypterclassname); + } + }else{ + log("Could not find decrypter ClassNode of class "+decrypterclassname); + } + } + } + + }else if(i instanceof InvokeDynamicInsnNode){ + InvokeDynamicInsnNode methodi = (InvokeDynamicInsnNode) i; + if(methodi.opcode() == 0xba){ + // TODO: Safe-reflection deobfuscator here + // Allatori replaces invokeinterface and invokestatic with invokedynamic + + //log(methodi.bsm.getOwner()+" dot "+methodi.bsm.getName()); + //iList.set(methodi, new MethodInsnNode(0xb8, methodi.bsm.getOwner(), methodi.bsm.getName(), methodi.bsm.getDesc(), false)); + + } + + + } + laststringldconstack = null; + } + } + + + private boolean scanDecrypter(MethodNode decryptermethodnode, int newHashCode){ + InsnList iList = decryptermethodnode.instructions; + + AbstractInsnNode insn = null, removeInsn = null; + for (AbstractInsnNode i : iList.toArray()) { + if(i instanceof MethodInsnNode){ + MethodInsnNode methodi = ((MethodInsnNode) i); + if("currentThread".equals(methodi.name)){ // find code form this instruction + insn = i; + break; + } + + } + + } + if(insn == null){ + return false; + } + + while(insn != null){ + if(insn instanceof MethodInsnNode){ + MethodInsnNode methodi = ((MethodInsnNode) insn); + if("hashCode".equals(methodi.name)){ // to this instruction + break; + } + } + removeInsn = insn; + insn = insn.getNext(); + iList.remove(removeInsn); // and remove it + } + if(insn == null) return false; + iList.set(insn, new LdcInsnNode(newHashCode)); // then replace it with pre-computed key LDC + return true; + } + + private String invokeDecrypter(Class decrypterclass, String name, String arg) throws Exception{ + try { + Method decryptermethod = decrypterclass.getDeclaredMethod(name, String.class); + + decryptermethod.setAccessible(true); + return (String) decryptermethod.invoke(null, arg); + + } catch (Exception e) { + log("Could not invoke decrypter method: "+name+" of class "+decrypterclass.getName()); + throw e; + } + } + } diff --git a/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/ZStringArrayDecrypter.java b/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/ZStringArrayDecrypter.java index e30cd6ed..a986f4ff 100644 --- a/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/ZStringArrayDecrypter.java +++ b/src/the/bytecode/club/bytecodeviewer/plugin/preinstalled/ZStringArrayDecrypter.java @@ -3,6 +3,7 @@ package the.bytecode.club.bytecodeviewer.plugin.preinstalled; import org.objectweb.asm.tree.ClassNode; import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.api.Plugin; import the.bytecode.club.bytecodeviewer.api.PluginConsole; @@ -63,28 +64,31 @@ public class ZStringArrayDecrypter extends Plugin { if (result == 0) { boolean needsWarning = false; - for (Class debug : the.bytecode.club.bytecodeviewer.api.BytecodeViewer.loadClassesIntoClassLoader()) { - try { - Field[] fields = debug.getDeclaredFields(); - for ( Field field : fields ) { - if ( field.getName().equals("z") ) { - out.append(debug.getName() + ":" + BytecodeViewer.nl); - field.setAccessible(true); - if(field.get(null) != null && field.get(null) instanceof String[] && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { - String[] fieldVal = (String[]) field.get(null); - for ( int i = 0; i < fieldVal.length; i++ ) { - out.append(" z[" + i + "] = " + fieldVal[i] + BytecodeViewer.nl); + try { + for (Class debug : the.bytecode.club.bytecodeviewer.api.BytecodeViewer.loadAllClassesIntoClassLoader()) { + try { + Field[] fields = debug.getDeclaredFields(); + for ( Field field : fields ) { + if ( field.getName().equals("z") ) { + out.append(debug.getName() + ":" + BytecodeViewer.nl); + field.setAccessible(true); + if(field.get(null) != null && field.get(null) instanceof String[] && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { + String[] fieldVal = (String[]) field.get(null); + for ( int i = 0; i < fieldVal.length; i++ ) { + out.append(" z[" + i + "] = " + fieldVal[i] + BytecodeViewer.nl); + } } - } - } - } - } catch(NoClassDefFoundError | Exception e) { - System.err.println("Failed loading class " + debug.getName()); - e.printStackTrace(); - needsWarning = true; - } - } - + } + } + } catch(NoClassDefFoundError | Exception e) { + System.err.println("Failed loading class " + debug.getName()); + e.printStackTrace(); + needsWarning = true; + } + } + } catch (Exception e){ + new ExceptionUI(e); + } if(needsWarning) { BytecodeViewer.showMessage("Some classes failed to decrypt, if you'd like to decrypt all of them"+BytecodeViewer.nl+"makes sure you include ALL the libraries it requires."); }