Merge pull request #86 from Szperak/master

Added Allatori string deofuscator
This commit is contained in:
Kalen (Konloch) Kinloch 2015-08-31 13:42:53 -06:00
commit dd7f8a1757

AI 샘플 코드 생성 중입니다

Loading...
9 changed files with 400 additions and 112 deletions

View File

@ -63,26 +63,28 @@ public class JarUtils {
try { try {
final String name = entry.getName(); final String name = entry.getName();
final byte[] bytes = getBytes(jis); final byte[] bytes = getBytes(jis);
if (!name.endsWith(".class")) { if(!files.containsKey(name)){
if(!entry.isDirectory()) if (!name.endsWith(".class")) {
files.put(name, bytes); if(!entry.isDirectory())
} else { files.put(name, bytes);
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 { } 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) { } catch(Exception e) {
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e);
} finally { } finally {

View File

@ -1,6 +1,7 @@
package the.bytecode.club.bytecodeviewer.api; package the.bytecode.club.bytecodeviewer.api;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.ArrayList; import java.util.ArrayList;
@ -62,50 +63,60 @@ public class BytecodeViewer {
return cl; return cl;
} }
/** /**
* Re-instances the URLClassLoader and loads a jar to it. * 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 * @return The loaded classes into the new URLClassLoader instance
* @author Cafebabe * @author Cafebabe
* @throws IOException
* @throws ClassNotFoundException
*/ */
public static List<Class<?>> loadClassesIntoClassLoader() { @SuppressWarnings("deprecation")
try { public static List<Class<?>> loadClassesIntoClassLoader(
File f = new File( ArrayList<ClassNode> nodeList) throws IOException,
the.bytecode.club.bytecodeviewer.BytecodeViewer.tempDirectory + ClassNotFoundException {
the.bytecode.club.bytecodeviewer.BytecodeViewer.fs +
"loaded_temp.jar"); File f = new File(
JarUtils.saveAsJar(BytecodeViewer.getLoadedClasses(), f.getAbsolutePath()); the.bytecode.club.bytecodeviewer.BytecodeViewer.tempDirectory
JarFile jarFile = new JarFile(""+f.getAbsolutePath()); + the.bytecode.club.bytecodeviewer.BytecodeViewer.fs
Enumeration<JarEntry> e = jarFile.entries(); + "loaded_temp.jar");
URL[] urls = { new URL("jar:file:" + ""+f.getAbsolutePath()+"!/") }; JarUtils.saveAsJarClassesOnly(nodeList, f.getAbsolutePath());
cl = URLClassLoader.newInstance(urls);
List<Class<?>> ret = new ArrayList<Class<?>>(); JarFile jarFile = new JarFile("" + f.getAbsolutePath());
Enumeration<JarEntry> e = jarFile.entries();
while (e.hasMoreElements()) cl = URLClassLoader.newInstance(new URL[]{ f.toURL() });
{ List<Class<?>> ret = new ArrayList<Class<?>>();
JarEntry je = (JarEntry) e.nextElement();
if(je.isDirectory() || !je.getName().endsWith(".class")) while (e.hasMoreElements()) {
continue; JarEntry je = (JarEntry) e.nextElement();
String className = je.getName().replace("/", ".").replace(".class", ""); if (je.isDirectory() || !je.getName().endsWith(".class"))
className = className.replace('/', '.'); continue;
try{ String className = je.getName().replace("/", ".").replace(".class", "");
ret.add(cl.loadClass(className)); className = className.replace('/', '.');
} ret.add(cl.loadClass(className));
catch(Exception e2)
{ }
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e2); jarFile.close();
}
} return ret;
jarFile.close();
}
return ret;
}
catch(Exception e) /**
{ * Re-instances the URLClassLoader and loads a jar to it.
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); * @return The loaded classes into the new URLClassLoader instance
} * @author Cafebabe
return null; * @throws IOException
} * @throws ClassNotFoundException
*/
public static List<Class<?>> loadAllClassesIntoClassLoader() throws ClassNotFoundException, IOException {
return loadClassesIntoClassLoader(getLoadedClasses());
}
/** /**
* Creates a new instance of the ClassNode loader. * Creates a new instance of the ClassNode loader.

View File

@ -110,7 +110,7 @@ public final class ClassNodeLoader extends ClassLoader {
if (classes.containsKey(name)) { if (classes.containsKey(name)) {
return nodeToClass(classes.get(name)); return nodeToClass(classes.get(name));
} else { } else {
return super.loadClass(name); return super.findClass(name);
} }
} }

View File

@ -304,7 +304,7 @@ public class InstructionPrinter {
protected String printInvokeDynamicInsNode(InvokeDynamicInsnNode idin) { protected String printInvokeDynamicInsNode(InvokeDynamicInsnNode idin) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(nameOpcode(idin.opcode()) + " " + idin.name + "("); sb.append(nameOpcode(idin.opcode()) + " " + idin.bsm.getName() + "(");
String desc = idin.desc; String desc = idin.desc;
String partedDesc = idin.desc.substring(2); String partedDesc = idin.desc.substring(2);

View File

@ -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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
/**
* 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;
}

View File

@ -311,36 +311,6 @@ public class FileNavigationPane extends VisibleComponent implements
ImageRenderer renderer = new ImageRenderer(); ImageRenderer renderer = new ImageRenderer();
tree.setCellRenderer(renderer); 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()) { if(!container.files.isEmpty()) {
for (final Entry<String, byte[]> entry : container.files.entrySet()) { for (final Entry<String, byte[]> entry : container.files.entrySet()) {

View File

@ -2029,8 +2029,12 @@ public class MainViewerGUI extends JFrame implements FileChangeNotifier {
}); });
mntmNewMenuItem_2.addActionListener(new ActionListener() { mntmNewMenuItem_2.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent arg0) {
PluginManager.runPlugin(new AllatoriStringDecrypter()); 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() { mntmNewMenuItem_1.addActionListener(new ActionListener() {

View File

@ -1,11 +1,27 @@
package the.bytecode.club.bytecodeviewer.plugin.preinstalled; package the.bytecode.club.bytecodeviewer.plugin.preinstalled;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList; 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.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.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.Plugin;
import the.bytecode.club.bytecodeviewer.api.PluginConsole;
/*************************************************************************** /***************************************************************************
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite * * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
@ -29,14 +45,218 @@ import the.bytecode.club.bytecodeviewer.api.Plugin;
* Coming soon. * Coming soon.
* *
* @author Konloch * @author Konloch
* @author Szperak
* *
*/ */
public class AllatoriStringDecrypter extends Plugin { 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 @Override
public void execute(ArrayList<ClassNode> classNodeList) { public void execute(ArrayList<ClassNode> 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<Class<?>> decrypterclasslist = the.bytecode.club.bytecodeviewer.api.BytecodeViewer
.loadClassesIntoClassLoader(new ArrayList<ClassNode>(
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;
}
}
} }

View File

@ -3,6 +3,7 @@ package the.bytecode.club.bytecodeviewer.plugin.preinstalled;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bytecodeviewer.BytecodeViewer; 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.Plugin;
import the.bytecode.club.bytecodeviewer.api.PluginConsole; import the.bytecode.club.bytecodeviewer.api.PluginConsole;
@ -63,28 +64,31 @@ public class ZStringArrayDecrypter extends Plugin {
if (result == 0) { if (result == 0) {
boolean needsWarning = false; boolean needsWarning = false;
for (Class<?> debug : the.bytecode.club.bytecodeviewer.api.BytecodeViewer.loadClassesIntoClassLoader()) { try {
try { for (Class<?> debug : the.bytecode.club.bytecodeviewer.api.BytecodeViewer.loadAllClassesIntoClassLoader()) {
Field[] fields = debug.getDeclaredFields(); try {
for ( Field field : fields ) { Field[] fields = debug.getDeclaredFields();
if ( field.getName().equals("z") ) { for ( Field field : fields ) {
out.append(debug.getName() + ":" + BytecodeViewer.nl); if ( field.getName().equals("z") ) {
field.setAccessible(true); out.append(debug.getName() + ":" + BytecodeViewer.nl);
if(field.get(null) != null && field.get(null) instanceof String[] && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { field.setAccessible(true);
String[] fieldVal = (String[]) field.get(null); if(field.get(null) != null && field.get(null) instanceof String[] && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
for ( int i = 0; i < fieldVal.length; i++ ) { String[] fieldVal = (String[]) field.get(null);
out.append(" z[" + i + "] = " + fieldVal[i] + BytecodeViewer.nl); for ( int i = 0; i < fieldVal.length; i++ ) {
out.append(" z[" + i + "] = " + fieldVal[i] + BytecodeViewer.nl);
}
} }
} }
} }
} } catch(NoClassDefFoundError | Exception e) {
} catch(NoClassDefFoundError | Exception e) { System.err.println("Failed loading class " + debug.getName());
System.err.println("Failed loading class " + debug.getName()); e.printStackTrace();
e.printStackTrace(); needsWarning = true;
needsWarning = true; }
} }
} } catch (Exception e){
new ExceptionUI(e);
}
if(needsWarning) { 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."); 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.");
} }