From b75cb67df33821722ad47724d6afa5c2cc342f0b Mon Sep 17 00:00:00 2001 From: Szperak Date: Mon, 31 Aug 2015 16:42:54 +0200 Subject: [PATCH] Added Allatori string decrypter --- .../gui/AllatoriStringDecrypterOptions.java | 77 ++++++ .../bytecodeviewer/gui/MainViewerGUI.java | 8 +- .../preinstalled/AllatoriStringDecrypter.java | 222 +++++++++++++++++- 3 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 src/the/bytecode/club/bytecodeviewer/gui/AllatoriStringDecrypterOptions.java 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/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; + } + } + }