diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java index 37e8dbcb..6ba67294 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java @@ -24,13 +24,7 @@ import the.bytecode.club.bytecodeviewer.obfuscators.rename.RenameClasses; import the.bytecode.club.bytecodeviewer.obfuscators.rename.RenameFields; import the.bytecode.club.bytecodeviewer.obfuscators.rename.RenameMethods; import the.bytecode.club.bytecodeviewer.plugin.PluginManager; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.AllatoriStringDecrypter; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.CodeSequenceDiagram; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.ShowAllStrings; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.ShowMainMethods; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.StackFramesRemover; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.ZKMStringDecrypter; -import the.bytecode.club.bytecodeviewer.plugin.preinstalled.ZStringArrayDecrypter; +import the.bytecode.club.bytecodeviewer.plugin.preinstalled.*; import the.bytecode.club.bytecodeviewer.resources.exporting.Export; import the.bytecode.club.bytecodeviewer.util.*; import the.bytecode.club.bytecodeviewer.resources.ResourceDecompiling; @@ -577,7 +571,7 @@ public class MainViewerGUI extends JFrame showAllStrings.addActionListener(e -> PluginManager.runPlugin(new ShowAllStrings())); replaceStrings.addActionListener(arg0 -> ReplaceStringsOptions.open()); stackFramesRemover.addActionListener(e -> PluginManager.runPlugin(new StackFramesRemover())); - allatoriStringDecrypter.addActionListener(e -> PluginManager.runPlugin(new AllatoriStringDecrypter())); + allatoriStringDecrypter.addActionListener(e -> PluginManager.runPlugin(new AllatoriStringDecrypterOptions())); ZKMStringDecrypter.addActionListener(e -> PluginManager.runPlugin(new ZKMStringDecrypter())); zStringArrayDecrypter.addActionListener(arg0 -> PluginManager.runPlugin(new ZStringArrayDecrypter())); } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java b/src/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java index fe984741..57d2bb9f 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypter.java @@ -1,9 +1,24 @@ package the.bytecode.club.bytecodeviewer.plugin.preinstalled; +import java.awt.*; +import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; -import org.objectweb.asm.tree.ClassNode; + +import org.objectweb.asm.tree.*; import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.Constants; +import the.bytecode.club.bytecodeviewer.Resources; +import the.bytecode.club.bytecodeviewer.api.ASMUtil; +import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.api.Plugin; +import the.bytecode.club.bytecodeviewer.api.PluginConsole; +import the.bytecode.club.bytecodeviewer.gui.components.MultipleChoiceDialogue; +import the.bytecode.club.bytecodeviewer.plugin.PluginManager; + +import javax.swing.*; + +import static the.bytecode.club.bytecodeviewer.Constants.nl; /*************************************************************************** * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * @@ -24,15 +39,250 @@ import the.bytecode.club.bytecodeviewer.api.Plugin; ***************************************************************************/ /** - * Coming soon. + * An Allatori String Decrypter, targets an unknown (old) version. * * @author Konloch + * @author Szperak + * @since 08/15/2015 */ -public class AllatoriStringDecrypter extends Plugin { - - @Override - public void execute(ArrayList classNodeList) { - BytecodeViewer.showMessage("This is a planned feature."); - } +public class AllatoriStringDecrypter extends Plugin +{ + final PluginConsole frame = new PluginConsole("Allatori String Decrypter"); + final StringBuilder out = new StringBuilder(); + final String className; + + public AllatoriStringDecrypter(String className) {this.className = className;} + + @Override + public void execute(ArrayList classNodeList) + { + MultipleChoiceDialogue dialogue = new MultipleChoiceDialogue("Bytecode Viewer - WARNING", + "WARNING: This will load the classes into the JVM and execute the allatori decrypter function" + + nl + "for each class. IF THE FILE YOU'RE LOADING IS MALICIOUS, DO NOT CONTINUE.", + new String[]{"Continue", "Cancel"}); + + if (dialogue.promptChoice() == 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(Constants.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.getOpcode() == 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 = ASMUtil.getClassNode(decrypterFileContents); + + if (decrypterclassnode != null) + { + MethodNode decryptermethodnode = ASMUtil.getMethodByName(decrypterclassnode, 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); + + Class decrypterClassList = the.bytecode.club.bytecodeviewer.api.BytecodeViewer + .loadClassIntoClassLoader(decrypterclassnode); + + String decrypted = invokeDecrypter(decrypterClassList, 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.getOpcode() == 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/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypterOptions.java b/src/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypterOptions.java new file mode 100644 index 00000000..680cfd2a --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/plugin/preinstalled/AllatoriStringDecrypterOptions.java @@ -0,0 +1,85 @@ +package the.bytecode.club.bytecodeviewer.plugin.preinstalled; + +import org.objectweb.asm.tree.*; +import the.bytecode.club.bytecodeviewer.Resources; +import the.bytecode.club.bytecodeviewer.api.Plugin; +import the.bytecode.club.bytecodeviewer.plugin.PluginManager; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; + +/*************************************************************************** + * 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 . * + ***************************************************************************/ + +/** + * An Allatori String Decrypter, targets an unknown version. + * + * @author Konloch + * @author Szperak + * @since 08/15/2015 + */ + +public class AllatoriStringDecrypterOptions extends Plugin +{ + @Override + public void execute(ArrayList classNodeList) + { + new AllatoriStringDecrypterOptionsFrame().setVisible(true); + } + + public static class AllatoriStringDecrypterOptionsFrame extends JFrame + { + private JTextField textField; + + public AllatoriStringDecrypterOptionsFrame() + { + this.setIconImages(Resources.iconList); + setSize(new Dimension(250, 120)); + setResizable(false); + setTitle("Allatori String 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(arg0 -> + { + PluginManager.runPlugin(new the.bytecode.club.bytecodeviewer.plugin.preinstalled.AllatoriStringDecrypter(textField.getText())); + dispose(); + }); + + this.setLocationRelativeTo(null); + } + + private static final long serialVersionUID = -2662514582647810868L; + } +}