diff --git a/BytecodeViewer.jar b/BytecodeViewer.jar new file mode 100644 index 00000000..3e135fe0 Binary files /dev/null and b/BytecodeViewer.jar differ diff --git a/README.md b/README.md index 026aa98e..f3c82ccb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,19 @@ -bytecode-viewer -=============== +Bytecode Viewer is a Java Bytecode Viewer, GUI FernFlower Java Decompiler, GUI Jar-Jar, Hex Viewer, Code Searcher, Debugger and more. +It's written completely in Java, and it's open sourced. It's currently being maintained and developed by Konloch. -Bytecode Viewer +Code from various projects has been used, including but not limited to: +J-RET by WaterWolf +JHexPane by Sam Koivu +JSynaxPane by Ayman Al +Commons IO by Apache +ASM by OW2 + +Features: +Java Decompiler - It uses a modified version of FernFlower. +Bytecode Decompiler - A modified version of J-RET's. +Hex Viewer - Powered by JHexPane. +Each Decompiler/Viewer is toggleable. +Fully Featured Search System. +A Plugin System With Built In Plugins. (Show All Strings, Malicious Code Scanner, String Decrypters, etc) +Recent Files. +And more! Give it a try for yourself! \ No newline at end of file diff --git a/libs/asm-all-5.0.3.jar b/libs/asm-all-5.0.3.jar new file mode 100644 index 00000000..da5b23e1 Binary files /dev/null and b/libs/asm-all-5.0.3.jar differ diff --git a/libs/commons-codec-1.9.jar b/libs/commons-codec-1.9.jar new file mode 100644 index 00000000..ef35f1c5 Binary files /dev/null and b/libs/commons-codec-1.9.jar differ diff --git a/libs/commons-io-2.4.jar b/libs/commons-io-2.4.jar new file mode 100644 index 00000000..90035a4f Binary files /dev/null and b/libs/commons-io-2.4.jar differ diff --git a/libs/commons-lang3-3.3.2.jar b/libs/commons-lang3-3.3.2.jar new file mode 100644 index 00000000..bb069797 Binary files /dev/null and b/libs/commons-lang3-3.3.2.jar differ diff --git a/libs/fernflower_no_output.jar b/libs/fernflower_no_output.jar new file mode 100644 index 00000000..23d9f664 Binary files /dev/null and b/libs/fernflower_no_output.jar differ diff --git a/libs/groovy-all-2.1.7.jar b/libs/groovy-all-2.1.7.jar new file mode 100644 index 00000000..e24f3005 Binary files /dev/null and b/libs/groovy-all-2.1.7.jar differ diff --git a/libs/jruby.jar b/libs/jruby.jar new file mode 100644 index 00000000..0c0ed733 Binary files /dev/null and b/libs/jruby.jar differ diff --git a/libs/jsyntaxpane-0.9.5-b29 (1).jar b/libs/jsyntaxpane-0.9.5-b29 (1).jar new file mode 100644 index 00000000..8bca1f6e Binary files /dev/null and b/libs/jsyntaxpane-0.9.5-b29 (1).jar differ diff --git a/libs/jython-standalone-2.5.3.jar b/libs/jython-standalone-2.5.3.jar new file mode 100644 index 00000000..9e01e5ca Binary files /dev/null and b/libs/jython-standalone-2.5.3.jar differ diff --git a/plugins/Skeleton.gy b/plugins/Skeleton.gy new file mode 100644 index 00000000..270aade2 --- /dev/null +++ b/plugins/Skeleton.gy @@ -0,0 +1,14 @@ +import the.bytecode.club.bytecodeviewer.plugins.Plugin; +import the.bytecode.club.bytecodeviewer.plugins.PluginConsole; +import java.util.ArrayList; +import org.objectweb.asm.tree.ClassNode; + +public class Skeleton extends Plugin { + + @Override + public void execute(ArrayList classNodesList) { + PluginConsole gui = new PluginConsole("Skeleton"); + gui.setVisible(true); + gui.appendText("exceuted skeleton"); + } +} \ No newline at end of file diff --git a/plugins/Skeleton.rb b/plugins/Skeleton.rb new file mode 100644 index 00000000..5dbf9e1a --- /dev/null +++ b/plugins/Skeleton.rb @@ -0,0 +1,15 @@ +require 'java' + +java_import 'the.bytecode.club.bytecodeviewer.plugins.Plugin' +java_import 'the.bytecode.club.bytecodeviewer.plugins.PluginConsole' +java_import 'java.lang.System' +java_import 'java.util.ArrayList' +java_import 'org.objectweb.asm.tree.ClassNode' + +class Skeleton < Plugin + def execute(classNodeList) + gui = PluginConsole.new "Skeleton" + gui.setVisible(true) + gui.appendText("exceuted skeleton") + end +end \ No newline at end of file diff --git a/plugins/skeleton.py b/plugins/skeleton.py new file mode 100644 index 00000000..d562ef7b --- /dev/null +++ b/plugins/skeleton.py @@ -0,0 +1,13 @@ +from the.bytecode.club.bytecodeviewer.plugins import Plugin +from the.bytecode.club.bytecodeviewer.plugins import PluginConsole +from java.lang import System +from java.lang import Boolean +from java.util import ArrayList +from org.objectweb.asm.tree import ClassNode + +class skeleton(Plugin): + + def execute(classNodeList, poop): #for some reason it requires a second arg + gui = PluginConsole("Skeleton") + gui.setVisible(Boolean.TRUE) + gui.appendText("exceuted skeleton") diff --git a/src/com/jhe/hexed/JHexEditor.java b/src/com/jhe/hexed/JHexEditor.java new file mode 100644 index 00000000..053fef50 --- /dev/null +++ b/src/com/jhe/hexed/JHexEditor.java @@ -0,0 +1,289 @@ +package com.jhe.hexed; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +/** + * Created by IntelliJ IDEA. + * User: laullon + * Date: 08-abr-2003 + * Time: 13:21:09 + */ +public class JHexEditor extends JPanel implements FocusListener,AdjustmentListener,MouseWheelListener +{ + private static final long serialVersionUID = 2289328616534802372L; + byte[] buff; + public int cursor; + protected static Font font=new Font("Monospaced",0,12); + protected int border=2; + public boolean DEBUG=false; + private JPanel panel; + private JScrollBar sb; + private int inicio=0; + private int lineas=10; + + public JHexEditor(byte[] buff) + { + super(); + this.buff=buff; + + this.addMouseWheelListener(this); + + sb=new JScrollBar(JScrollBar.VERTICAL); + sb.addAdjustmentListener(this); + sb.setMinimum(0); + sb.setMaximum(buff.length/getLineas()); + + JPanel p1,p2,p3; + //centro + p1=new JPanel(new BorderLayout(1,1)); + p1.add(new JHexEditorHEX(this),BorderLayout.CENTER); + p1.add(new Columnas(),BorderLayout.NORTH); + + // izq. + p2=new JPanel(new BorderLayout(1,1)); + p2.add(new Filas(),BorderLayout.CENTER); + p2.add(new Caja(),BorderLayout.NORTH); + + // der + p3=new JPanel(new BorderLayout(1,1)); + p3.add(sb,BorderLayout.EAST); + p3.add(new JHexEditorASCII(this),BorderLayout.CENTER); + p3.add(new Caja(),BorderLayout.NORTH); + + panel=new JPanel(); + panel.setLayout(new BorderLayout(1,1)); + panel.add(p1,BorderLayout.CENTER); + panel.add(p2,BorderLayout.WEST); + panel.add(p3,BorderLayout.EAST); + + this.setLayout(new BorderLayout(1,1)); + this.add(panel,BorderLayout.CENTER); + } + + public void paint(Graphics g) + { + FontMetrics fn=getFontMetrics(font); + Rectangle rec=this.getBounds(); + lineas=(rec.height/fn.getHeight())-1; + int n=(buff.length/16)-1; + if(lineas>n) { lineas=n; inicio=0; } + + sb.setValues(getInicio(),+getLineas(),0,buff.length/16); + sb.setValueIsAdjusting(true); + super.paint(g); + } + + protected void actualizaCursor() + { + int n=(cursor/16); + + System.out.print("- "+inicio+"<"+n+"<"+(lineas+inicio)+"("+lineas+")"); + + if(n=inicio+lineas) inicio=n-(lineas-1); + + System.out.println(" - "+inicio+"<"+n+"<"+(lineas+inicio)+"("+lineas+")"); + + repaint(); + } + + protected int getInicio() + { + return inicio; + } + + protected int getLineas() + { + return lineas; + } + + protected void fondo(Graphics g,int x,int y,int s) + { + FontMetrics fn=getFontMetrics(font); + g.fillRect(((fn.stringWidth(" ")+1)*x)+border,(fn.getHeight()*y)+border,((fn.stringWidth(" ")+1)*s),fn.getHeight()+1); + } + + protected void cuadro(Graphics g,int x,int y,int s) + { + FontMetrics fn=getFontMetrics(font); + g.drawRect(((fn.stringWidth(" ")+1)*x)+border,(fn.getHeight()*y)+border,((fn.stringWidth(" ")+1)*s),fn.getHeight()+1); + } + + protected void printString(Graphics g,String s,int x,int y) + { + FontMetrics fn=getFontMetrics(font); + g.drawString(s,((fn.stringWidth(" ")+1)*x)+border,((fn.getHeight()*(y+1))-fn.getMaxDescent())+border); + } + + public void focusGained(FocusEvent e) + { + this.repaint(); + } + + public void focusLost(FocusEvent e) + { + this.repaint(); + } + + public void adjustmentValueChanged(AdjustmentEvent e) + { + inicio=e.getValue(); + if(inicio<0) inicio=0; + repaint(); + } + + public void mouseWheelMoved(MouseWheelEvent e) + { + inicio+=(e.getUnitsToScroll()); + if((inicio+lineas)>=buff.length/16) inicio=(buff.length/16)-lineas; + if(inicio<0) inicio=0; + repaint(); + } + + public void keyPressed(KeyEvent e) + { + /*switch(e.getKeyCode()) + { + case 33: // rep + if(cursor>=(16*lineas)) cursor-=(16*lineas); + actualizaCursor(); + break; + case 34: // fin + if(cursor<(buff.length-(16*lineas))) cursor+=(16*lineas); + actualizaCursor(); + break; + case 35: // fin + cursor=buff.length-1; + actualizaCursor(); + break; + case 36: // ini + cursor=0; + actualizaCursor(); + break; + case 37: // <-- + if(cursor!=0) cursor--; + actualizaCursor(); + break; + case 38: // <-- + if(cursor>15) cursor-=16; + actualizaCursor(); + break; + case 39: // --> + if(cursor!=(buff.length-1)) cursor++; + actualizaCursor(); + break; + case 40: // --> + if(cursor<(buff.length-16)) cursor+=16; + actualizaCursor(); + break; + }*/ + } + + private class Columnas extends JPanel + { + private static final long serialVersionUID = -1734199617526339842L; + + public Columnas() + { + this.setLayout(new BorderLayout(1,1)); + } + public Dimension getPreferredSize() + { + return getMinimumSize(); + } + + public Dimension getMinimumSize() + { + Dimension d=new Dimension(); + FontMetrics fn=getFontMetrics(font); + int h=fn.getHeight(); + int nl=1; + d.setSize(((fn.stringWidth(" ")+1)*+((16*3)-1))+(border*2)+1,h*nl+(border*2)+1); + return d; + } + + public void paint(Graphics g) + { + Dimension d=getMinimumSize(); + g.setColor(Color.white); + g.fillRect(0,0,d.width,d.height); + g.setColor(Color.black); + g.setFont(font); + + for(int n=0;n<16;n++) + { + if(n==(cursor%16)) cuadro(g,n*3,0,2); + String s="00"+Integer.toHexString(n); + s=s.substring(s.length()-2); + printString(g,s,n*3,0); + } + } + } + + private class Caja extends JPanel + { + private static final long serialVersionUID = -6124062720565016834L; + + public Dimension getPreferredSize() + { + return getMinimumSize(); + } + + public Dimension getMinimumSize() + { + Dimension d=new Dimension(); + FontMetrics fn=getFontMetrics(font); + int h=fn.getHeight(); + d.setSize((fn.stringWidth(" ")+1)+(border*2)+1,h+(border*2)+1); + return d; + } + + } + + private class Filas extends JPanel + { + private static final long serialVersionUID = 8797347523486018051L; + + public Filas() + { + this.setLayout(new BorderLayout(1,1)); + } + public Dimension getPreferredSize() + { + return getMinimumSize(); + } + + public Dimension getMinimumSize() + { + Dimension d=new Dimension(); + FontMetrics fn=getFontMetrics(font); + int h=fn.getHeight(); + int nl=getLineas(); + d.setSize((fn.stringWidth(" ")+1)*(8)+(border*2)+1,h*nl+(border*2)+1); + return d; + } + + public void paint(Graphics g) + { + Dimension d=getMinimumSize(); + g.setColor(Color.white); + g.fillRect(0,0,d.width,d.height); + g.setColor(Color.black); + g.setFont(font); + + int ini=getInicio(); + int fin=ini+getLineas(); + int y=0; + for(int n=ini;n126)) s=""+(char)16; + he.printString(g,s,(x++),y); + if(x==16) + { + x=0; + y++; + } + } + + } + + private void debug(String s) + { + if(he.DEBUG) System.out.println("JHexEditorASCII ==> "+s); + } + + // calcular la posicion del raton + public int calcularPosicionRaton(int x,int y) + { + FontMetrics fn=getFontMetrics(JHexEditor.font); + x=x/(fn.stringWidth(" ")+1); + y=y/fn.getHeight(); + debug("x="+x+" ,y="+y); + return x+((y+he.getInicio())*16); + } + + // mouselistener + public void mouseClicked(MouseEvent e) + { + debug("mouseClicked("+e+")"); + he.cursor=calcularPosicionRaton(e.getX(),e.getY()); + this.requestFocus(); + he.repaint(); + } + + public void mousePressed(MouseEvent e) + { + } + + public void mouseReleased(MouseEvent e) + { + } + + public void mouseEntered(MouseEvent e) + { + } + + public void mouseExited(MouseEvent e) + { + } + + //KeyListener + public void keyTyped(KeyEvent e) + { + /*debug("keyTyped("+e+")"); + + he.buff[he.cursor]=(byte)e.getKeyChar(); + + if(he.cursor!=(he.buff.length-1)) he.cursor++; + he.repaint();*/ + } + + public void keyPressed(KeyEvent e) + { + debug("keyPressed("+e+")"); + he.keyPressed(e); + } + + public void keyReleased(KeyEvent e) + { + debug("keyReleased("+e+")"); + } + + public boolean isFocusTraversable() + { + return true; + } +} diff --git a/src/com/jhe/hexed/JHexEditorHEX.java b/src/com/jhe/hexed/JHexEditorHEX.java new file mode 100644 index 00000000..dab30536 --- /dev/null +++ b/src/com/jhe/hexed/JHexEditorHEX.java @@ -0,0 +1,178 @@ +package com.jhe.hexed; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +/** + * Created by IntelliJ IDEA. + * User: laullon + * Date: 09-abr-2003 + * Time: 12:47:32 + */ +public class JHexEditorHEX extends JComponent implements MouseListener,KeyListener +{ + private static final long serialVersionUID = 1481995655372014571L; + private JHexEditor he; + private int cursor=0; + + public JHexEditorHEX(JHexEditor he) + { + this.he=he; + addMouseListener(this); + addKeyListener(this); + addFocusListener(he); + } + + /*public Dimension getPreferredSize() + { + debug("getPreferredSize()"); + return getMinimumSize(); + }*/ + + public Dimension getMaximumSize() + { + debug("getMaximumSize()"); + return getMinimumSize(); + } + + /*public Dimension getMinimumSize() + { + debug("getMinimumSize()"); + + Dimension d=new Dimension(); + FontMetrics fn=getFontMetrics(he.font); + int h=fn.getHeight(); + int nl=he.getLineas(); + d.setSize(((fn.stringWidth(" ")+1)*+((16*3)-1))+(he.border*2)+1,h*nl+(he.border*2)+1); + return d; + }*/ + + public void paint(Graphics g) + { + debug("paint("+g+")"); + debug("cursor="+he.cursor+" buff.length="+he.buff.length); + Dimension d=getMinimumSize(); + g.setColor(Color.white); + g.fillRect(0,0,d.width,d.height); + g.setColor(Color.black); + + g.setFont(JHexEditor.font); + + int ini=he.getInicio()*16; + int fin=ini+(he.getLineas()*16); + if(fin>he.buff.length) fin=he.buff.length; + + //datos hex + int x=0; + int y=0; + for(int n=ini;n "+s); + } + + // calcular la posicion del raton + public int calcularPosicionRaton(int x,int y) + { + FontMetrics fn=getFontMetrics(JHexEditor.font); + x=x/((fn.stringWidth(" ")+1)*3); + y=y/fn.getHeight(); + debug("x="+x+" ,y="+y); + return x+((y+he.getInicio())*16); + } + + // mouselistener + public void mouseClicked(MouseEvent e) + { + debug("mouseClicked("+e+")"); + he.cursor=calcularPosicionRaton(e.getX(),e.getY()); + this.requestFocus(); + he.repaint(); + } + + public void mousePressed(MouseEvent e) + { + } + + public void mouseReleased(MouseEvent e) + { + } + + public void mouseEntered(MouseEvent e) + { + } + + public void mouseExited(MouseEvent e) + { + } + + //KeyListener + public void keyTyped(KeyEvent e) + { + debug("keyTyped("+e+")"); + + /*char c=e.getKeyChar(); + if(((c>='0')&&(c<='9'))||((c>='A')&&(c<='F'))||((c>='a')&&(c<='f'))) + { + char[] str=new char[2]; + String n="00"+Integer.toHexString((int)he.buff[he.cursor]); + if(n.length()>2) n=n.substring(n.length()-2); + str[1-cursor]=n.charAt(1-cursor); + str[cursor]=e.getKeyChar(); + he.buff[he.cursor]=(byte)Integer.parseInt(new String(str),16); + + if(cursor!=1) cursor=1; + else if(he.cursor!=(he.buff.length-1)){ he.cursor++; cursor=0;} + he.actualizaCursor(); + }*/ + } + + public void keyPressed(KeyEvent e) + { + debug("keyPressed("+e+")"); + he.keyPressed(e); + } + + public void keyReleased(KeyEvent e) + { + debug("keyReleased("+e+")"); + } + + public boolean isFocusTraversable() + { + return true; + } +} diff --git a/src/me/konloch/kontainer/io/DiskReader.java b/src/me/konloch/kontainer/io/DiskReader.java new file mode 100644 index 00000000..d330f863 --- /dev/null +++ b/src/me/konloch/kontainer/io/DiskReader.java @@ -0,0 +1,84 @@ +package me.konloch.kontainer.io; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Random; + +/** + * Used to load from the disk, optional caching + * + * @author Konloch + * + */ + +public class DiskReader { + + public static Random random = new Random(); + public static HashMap> map = new HashMap>(); + + /** + * Used to load from file, allows caching + */ + public synchronized static ArrayList loadArrayList(String fileName, boolean cache) { + ArrayList array = new ArrayList(); + if(!map.containsKey(fileName)) { + try { + File file = new File(fileName); + + BufferedReader reader = new BufferedReader(new FileReader(file)); + String add; + + while((add = reader.readLine()) != null) + array.add(add); + + reader.close(); + + if(cache) + map.put(fileName, array); + } catch(Exception e) { + e.printStackTrace(); + } + } else { + array = map.get(fileName); + } + + return array; + + } + + /** + * Used to load a string via line number + * lineNumber = -1 means random. + */ + public static String loadString(String fileName, int lineNumber, boolean cache) throws Exception { + + ArrayList array; + if(!map.containsKey(fileName)) { + array = new ArrayList(); + File file = new File(fileName); + + BufferedReader reader = new BufferedReader(new FileReader(file)); + String add; + + while((add = reader.readLine()) != null) + array.add(add); + + reader.close(); + + if(cache) + map.put(fileName, array); + } else { + array = map.get(fileName); + } + + if(lineNumber == -1) { + int size = array.size(); + return array.get(random.nextInt(size)); + } else + return array.get(lineNumber); + } + +} \ No newline at end of file diff --git a/src/me/konloch/kontainer/io/DiskWriter.java b/src/me/konloch/kontainer/io/DiskWriter.java new file mode 100644 index 00000000..cb0fb204 --- /dev/null +++ b/src/me/konloch/kontainer/io/DiskWriter.java @@ -0,0 +1,176 @@ +package me.konloch.kontainer.io; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; + +/** + * This method will save to disk, if it failed it will keep trying until + * it's saved to disk + * + * @author Konloch + * + */ + +public class DiskWriter { + + /** + * Used to insert a difference string with preserving the file extension + * @param fileName The file name + * @param difference Normally an integer + * @return The filename with the difference inserted and the file extension preserved + */ + public static String insertFileName(String fileName, String difference) { + String[] babe = fileName.split("\\."); + int count = 0; + int math = babe.length; + String m = ""; + + for(String s2 : babe) { + m += s2; + if(math-2 == count) + m += difference+"."; + else if(math-1 != count) + m += "."; + count++; + } + + return m; + } + + /** + * Writes a new line to the file, if it doesn't exist it will automatically create it. + * @param filename + * @param fileContents + * @param debug + */ + public static synchronized void writeNewLine(String filename, byte[] fileContents, boolean debug) { + PrintWriter writer = null; + String original = filename; + int counter = 0; + + boolean saved = false; + while(!saved) { + try { + writer = new PrintWriter(new BufferedWriter(new FileWriter(filename, true))); + writer.println(fileContents); + if(debug) + System.out.println("Saved " + filename + " to disk"); + saved = true; + } catch(Exception e) { + if(debug) + System.out.println("Failed saving, trying to save as " + filename); + if(original.contains(".")) { + filename = insertFileName(original, ""+counter); + } else + filename = original + counter; + counter++; + } + } + writer.close(); + } + + /** + * Writes a string to the file + * @param filename + * @param lineToWrite + * @param debug + */ + public static synchronized void writeNewLine(String filename, String lineToWrite, boolean debug) { + PrintWriter writer = null; + String original = filename; + int counter = 0; + + boolean saved = false; + while(!saved) { + try { + writer = new PrintWriter(new BufferedWriter(new FileWriter(filename, true))); + writer.println(lineToWrite); + if(debug) + System.out.println("Saved " + filename+">"+lineToWrite + " to disk"); + saved = true; + } catch(Exception e) { + if(debug) + System.out.println("Failed saving, trying to save as " + filename); + if(original.contains(".")) { + filename = insertFileName(original, ""+counter); + } else + filename = original + counter; + counter++; + } + } + writer.close(); + } + + /** + * Deletes the original file if it exists, then writes the fileContents[] to the file. + * @param filename + * @param fileContents + * @param debug + */ + public static synchronized void replaceFile(String filename, byte[] fileContents, boolean debug) { + File f = new File(filename); + if(f.exists()) + f.delete(); + PrintWriter writer = null; + String original = filename; + int counter = 0; + + boolean saved = false; + while(!saved) { + try { + writer = new PrintWriter(new BufferedWriter(new FileWriter(filename, true))); + writer.println(fileContents); + if(debug) + System.out.println("Saved " + filename + " to disk"); + saved = true; + } catch(Exception e) { + if(debug) + System.out.println("Failed saving, trying to save as " + filename); + if(original.contains(".")) { + filename = insertFileName(original, ""+counter); + } else + filename = original + counter; + counter++; + } + } + writer.close(); + } + + /** + * Deletes the original file if it exists, then writes the lineToWrite to the file. + * @param filename + * @param lineToWrite + * @param debug + */ + public static synchronized void replaceFile(String filename, String lineToWrite, boolean debug) { + File f = new File(filename); + if(f.exists()) + f.delete(); + PrintWriter writer = null; + String original = filename; + int counter = 0; + + boolean saved = false; + while(!saved) { + try { + writer = new PrintWriter(new BufferedWriter(new FileWriter(filename, true))); + writer.println(lineToWrite); + if(debug) + System.out.println("Saved " + filename+">"+lineToWrite + " to disk"); + saved = true; + } catch(Exception e) { + if(debug) + System.out.println("Failed saving, trying to save as " + filename + "_"); + if(original.contains(".")) { + filename = insertFileName(original, ""+counter); + } else + filename = original + counter; + counter++; + } + } + writer.close(); + } + +} \ No newline at end of file diff --git a/src/resources/1.gif b/src/resources/1.gif new file mode 100644 index 00000000..1441be36 Binary files /dev/null and b/src/resources/1.gif differ diff --git a/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java b/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java new file mode 100644 index 00000000..9d65ba41 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/BytecodeViewer.java @@ -0,0 +1,331 @@ +package the.bytecode.club.bytecodeviewer; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.swing.JDialog; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.UIManager; + +import me.konloch.kontainer.io.DiskReader; +import me.konloch.kontainer.io.DiskWriter; + +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.gui.FileNavigationPane; +import the.bytecode.club.bytecodeviewer.gui.MainViewerGUI; +import the.bytecode.club.bytecodeviewer.gui.SearchingPane; +import the.bytecode.club.bytecodeviewer.gui.WorkPane; +import the.bytecode.club.bytecodeviewer.plugins.PluginManager; + +/** + * A lightweight Java Bytecode Viewer/GUI Decompiler, developed by Konloch - http://konloch.me + * + * Are you a Java Reverse Engineer? Or maybe you want to learn Java Reverse Engineering? + * Join The Bytecode Club - http://the.bytecode.club + * We're noob friendly, and censorship free. + * + * All you have to do is add a jar or class file into the workspace, select the file you want + * then it will start decompiling the class in the background, when it's done it will show + * the Source code, Bytecode and Hexcode of the class file you chose. + * + * There is also a plugin system that will allow you to interact with the loaded classfiles, for example + * you can write a String deobfuscator, a malicious code searcher, or something else you can think of. + * You can either use one of the pre-written plugins, or write your own. It supports groovy, python and + * ruby scripting. Once a plugin is activated, it will send a ClassNode ArrayList of every single + * class loaded in the file system to the execute function, this allows the user to handle it + * completely using ASM. + * + * Bytecode Decompiler, File Navigation Pane, Search Pane and Work Pane based off of J-RET by WaterWolf - https://github.com/Waterwolf/Java-ReverseEngineeringTool + * HexViewer pane based off of Re-Java's by Sami Koivu - http://rejava.sourceforge.net/ + * Java Decompiler is a modified version of FernFlower + * + * TODO: + * Fix the fucking import jar method cause it's a bitch on memory (at the.bytecode.club.bytecodeviewer.JarUtils.getNode(JarUtils.java:83)) + * JSyntaxPane can be horribly slow for really big classfiles, might need to find a work around to this (create the syntaxpane object in the thread, then pass it to the GUI)s + * Make the search results clickable + * Add a tool to build a flowchart of all the classes, and what methods execute what classes, and those method, read chatlog + * Middle mouse click should close tabs + * http://i.imgur.com/yHaai9D.png + * + * 10/4/2014 - Designed a POC GUI, still needs a lot of work. + * 10/4/2014 - Started importing J-RET's backend. + * 10/5/2014 - Finished importing J-RET's backend. + * 10/6/2014 - Started modifying J-RET's UI. + * 10/6/2014 - Added several FernFlower options. + * 10/6/2014 - Fixed the class search function so it doesn't require exact class names. + * 10/6/2014 - Added save as, it'll save all of the loaded classes into one jar file (GUI Jar-Jar now). + * 10/6/2014 - Centered the select jar text inside of the file navigator. + * 10/6/2014 - Properly threaded the open jar function, now fernflower/bytecode decompiler runs in the background. + * 10/6/2014 - Added a hex viewer (Instead of using Re-Java's, I've decided to use a modified version of JHexEditor). + * 10/6/2014 - Made all of the viewer (Sourcecode, Bytecode & Hexcode toggleable). + * 10/7/2014 - Fixed the search function. + * 10/7/2014 - You can now add new files without it creating a new workspace. + * 10/7/2014 - Added new workspace button underneath File, this will reset the workspace. + * 10/7/2014 - Renamed File>Open.. to File>Add.. + * 10/7/2014 - Added recent files. + * 10/7/2014 - Did some bitch work, the project has no warnings now. + * 10/7/2014 - Added waiting cursors to anything that will require waiting or loading. + * 10/8/2014 - Searching now runs in a background thread. + * 10/8/2014 - Added File>About. + * 10/8/2014 - The main GUI now starts in the middle of your screen, same with the about window. + * 10/8/2014 - Made the File Navigator Pane, Workspace Pane & Search Pane a little sexier. + * 10/9/2014 - Started on a Plugin system + * 10/9/2014 - Added a malicious code scanner plugin, based off of the one from J-RET, this searches for a multitude of classes/packages that can be used for malicious purposes. + * 10/9/2014 - Added a show all strings plugin, this grabs all the declared strings and displays them in a nice little window. + * 10/9/2014 - Fixed a bug with Bytecode Decompiler, where it would it display \r and \n as return carriages. + * 10/9/2014 - Fixed the Bytecode Decompiler>Debug Instructions option. + * 10/9/2014 - Save Class Files As is now renamed to Save Files As. + * 10/9/2014 - Save Files As now saves jar resources, not just classfiles. + * 10/9/2014 - Added an 'Are you sure' pane when you click on File>New Workspace. + * 10/9/2014 - Save Files As is no longer dependent on the File System, now if you're on windows and you have a file called AA, and one called Aa, you're fine. + * 10/11/2014 - Modified the FernFlower library, it no longer spits out System.out.println's while processing a method, this has sped it up quite a lot. + * 10/12/2014 - Fix an issue when resizing. + * 10/12/2014 - Modified the core slighty to no longer have a modularized decompiling system (since there are only 2 decompilers anyways). + * 10/12/2014 - Fixed an issue with decompiling multiple files at once. + * 10/12/2014 - The Plugin Console now shows the plugin's name on the title. + * 10/12/2014 - Debug Helpers will now debug all jump instructions by showing what instruction is on the line it's suppose to goto, example: 90. goto 120 // line 120 is PUTFIELD Animable_Sub4.anInt1593 : I + * 10/12/2014 - Now when you select an already opened file, it will automatically go to that opened pane. + * 10/14/2014 - Added the option 'exact' to the class finder. + * 10/14/2014 - Added the option 'exact' to the searcher, now it'll search for .contains when unselected. + * 10/14/2014 - Stopped the use of StringBuffer, replaced all instances with StringBuilder. + * 10/14/2014 - Added Labels and Try-Catch blocks to the Bytecode Decompiler. + * 10/14/2014 - For panes that are not selected, the corresponding decompiler will not execute. + * 10/14/2014 - Added plugin Show Main Methods, this will show every single public static void main(String[]). + * 10/14/2014 - Plugins can no longer be ran when there is no loaded classes. + * 10/14/2014 - The Malicious Code Scanner now has gui option pane before you run it. + * 10/14/2014 - Added a java/io option to the Malicious Code Scanner. + * 10/14/2014 - Added save Java files as. + * 10/15/2014 - Added save as Jar file. (Export as Jar) + * 10/15/2014 - Added the option to ASCII only strings in the Bytecode Decompiler. + * 10/15/2014 - External plugins are now fully functional, same with recent plugins. + * 10/16/2014 - Removed all refences of 'ClassContainer'. + * 10/16/2014 - Rewrote the tempfile system. + * 10/16/2014 - Moved the file import to BytecodeViewer.class. + * 10/16/2014 - Fixed a jTree updating issue. + * 10/16/2014 - Now if you try search with an empty string, it won't search. + * 10/16/2014 - Added Replace Strings plugin. + * 10/16/2014 - Added a loading icon that displays whenever a background task is being executed. + * + * @author Konloch + * + */ + +public class BytecodeViewer { + + public static MainViewerGUI viewer = null; + public static HashMap loadedClasses = new HashMap(); + public static HashMap loadedResources = new HashMap(); + private static String filesName = "recentfiles.bcv"; + private static String pluginsName = "recentplugins.bcv"; + private static ArrayList recentFiles = DiskReader.loadArrayList(filesName, false); + private static ArrayList recentPlugins = DiskReader.loadArrayList(pluginsName, false); + private static int maxRecentFiles = 25; + public static String tempDirectory = "bcv_temp\\"; + + public static void main(String[] args) { + cleanup(); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + cleanup(); + } + }); + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + viewer = new MainViewerGUI(); + viewer.setVisible(true); + resetRecentFilesMenu(); + } + + public static ClassNode getClassNode(String name) { + if(loadedClasses.containsKey(name)) + return loadedClasses.get(name); + return null; + } + + public static ArrayList getLoadedClasses() { + ArrayList a = new ArrayList(); + if(loadedClasses != null) + for (Entry entry : loadedClasses.entrySet()) { + Object value = entry.getValue(); + ClassNode cln = (ClassNode)value; + a.add(cln); + } + return a; + } + + public static void openFiles(File[] files) { + BytecodeViewer.viewer.setC(true); + BytecodeViewer.viewer.setIcon(true); + + for (final File f : files) { + final String fn = f.getName(); + if (fn.endsWith(".jar")) { + try { + JarUtils.put(f, BytecodeViewer.loadedClasses); + } catch (final IOException e) { + e.printStackTrace(); + } + + } + else if (fn.endsWith(".class")) { + + try { + final ClassNode cn = JarUtils.getNode(JarUtils.getBytes(new FileInputStream(f))); + BytecodeViewer.loadedClasses.put(cn.name, cn); + } catch (final Exception e) { + e.printStackTrace(); + } + } + } + + for(File f : files) + BytecodeViewer.addRecentFile(f); + + BytecodeViewer.viewer.setC(false); + BytecodeViewer.viewer.setIcon(false); + + MainViewerGUI.getComponent(FileNavigationPane.class).updateTree(); + } + + public static void startPlugin(File plugin) { + if(!plugin.exists()) + return; + + try { + PluginManager.runPlugin(plugin); + } catch (Exception e) { + e.printStackTrace(); + } + addRecentPlugin(plugin); + } + + public static void showMessage(String message) { + JOptionPane.showMessageDialog(viewer, message); + } + + @SuppressWarnings("deprecation") + public static void resetWorkSpace() { + JOptionPane pane = new JOptionPane("Are you sure you want to reset the workspace?\n\rIt will also reset your file navigator and search."); + Object[] options = new String[] { "Yes", "No" }; + pane.setOptions(options); + JDialog dialog = pane.createDialog(viewer, "Bytecode Viewer - Reset Workspace"); + dialog.show(); + 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) { + loadedResources.clear(); + loadedClasses.clear(); + MainViewerGUI.getComponent(FileNavigationPane.class).resetWorkspace(); + MainViewerGUI.getComponent(WorkPane.class).resetWorkspace(); + MainViewerGUI.getComponent(SearchingPane.class).resetWorkspace(); + } + } + + private static ArrayList killList = new ArrayList(); + public static void addRecentFile(File f) { + for(int i = 0; i < recentFiles.size(); i++) { //remove dead strings + String s = recentFiles.get(i); + if(s.isEmpty() || i > maxRecentFiles) + killList.add(s); + } + if(!killList.isEmpty()) { + for(String s : killList) + recentFiles.remove(s); + killList.clear(); + } + + if(recentFiles.contains(f.getAbsolutePath())) //already added on the list + recentFiles.remove(f.getAbsolutePath()); + if(recentFiles.size() >= maxRecentFiles) + recentFiles.remove(maxRecentFiles-1); //zero indexing + + recentFiles.add(0, f.getAbsolutePath()); + DiskWriter.replaceFile(filesName, quickConvert(recentFiles), false); + resetRecentFilesMenu(); + } + + private static ArrayList killList2 = new ArrayList(); + public static void addRecentPlugin(File f) { + for(int i = 0; i < recentPlugins.size(); i++) { //remove dead strings + String s = recentPlugins.get(i); + if(s.isEmpty() || i > maxRecentFiles) + killList2.add(s); + } + if(!killList2.isEmpty()) { + for(String s : killList2) + recentPlugins.remove(s); + killList2.clear(); + } + + if(recentPlugins.contains(f.getAbsolutePath())) //already added on the list + recentPlugins.remove(f.getAbsolutePath()); + if(recentPlugins.size() >= maxRecentFiles) + recentPlugins.remove(maxRecentFiles-1); //zero indexing + + recentPlugins.add(0, f.getAbsolutePath()); + DiskWriter.replaceFile(pluginsName, quickConvert(recentPlugins), false); + resetRecentFilesMenu(); + } + + public static void resetRecentFilesMenu() { + viewer.mnRecentFiles.removeAll(); + for(String s : recentFiles) + if(!s.isEmpty()) { + JMenuItem m = new JMenuItem(s); + m.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JMenuItem m = (JMenuItem)e.getSource(); + openFiles(new File[]{new File(m.getText())}); + } + }); + viewer.mnRecentFiles.add(m); + } + viewer.mnRecentPlugins.removeAll(); + for(String s : recentPlugins) + if(!s.isEmpty()) { + JMenuItem m = new JMenuItem(s); + m.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JMenuItem m = (JMenuItem)e.getSource(); + startPlugin(new File(m.getText())); + } + }); + viewer.mnRecentPlugins.add(m); + } + } + + private static File tempF = null; + public static void cleanup() { + tempF = new File(tempDirectory); + try { + FileUtils.deleteDirectory(tempF); + } catch (IOException e) { + e.printStackTrace(); + } + tempF.mkdir(); + } + + private static String quickConvert(ArrayList a) { + String s = ""; + for(String r : a) + s += r+"\r"; + return s; + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/FileChangeNotifier.java b/src/the/bytecode/club/bytecodeviewer/FileChangeNotifier.java new file mode 100644 index 00000000..08ab3c3e --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/FileChangeNotifier.java @@ -0,0 +1,14 @@ +package the.bytecode.club.bytecodeviewer; + +import org.objectweb.asm.tree.ClassNode; + +/** + * Used to represent whenever a file has been opened + * + * @author Konloch + * + */ + +public interface FileChangeNotifier { + public void openClassFile(String name, ClassNode cn); +} diff --git a/src/the/bytecode/club/bytecodeviewer/FileDrop.java b/src/the/bytecode/club/bytecodeviewer/FileDrop.java new file mode 100644 index 00000000..15725c08 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/FileDrop.java @@ -0,0 +1,925 @@ +package the.bytecode.club.bytecodeviewer; + +import java.awt.datatransfer.DataFlavor; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.Reader; + +/** + * This class makes it easy to drag and drop files from the operating system to + * a Java program. Any java.awt.Component can be dropped onto, but only + * javax.swing.JComponents will indicate the drop event with a changed + * border. + *

+ * To use this class, construct a new FileDrop by passing it the target + * component and a Listener to receive notification when file(s) have + * been dropped. Here is an example: + *

+ *

+ *      JPanel myPanel = new JPanel();
+ *      new FileDrop( myPanel, new FileDrop.Listener()
+ *      {   public void filesDropped( java.io.File[] files )
+ *          {
+ *              // handle file drop
+ *              ...
+ *          }   // end filesDropped
+ *      }); // end FileDrop.Listener
+ * 
+ *

+ * You can specify the border that will appear when files are being dragged by + * calling the constructor with a javax.swing.border.Border. Only + * JComponents will show any indication with a border. + *

+ * You can turn on some debugging features by passing a PrintStream + * object (such as System.out) into the full constructor. A + * null value will result in no extra debugging information being + * output. + *

+ * + *

+ * I'm releasing this code into the Public Domain. Enjoy. + *

+ *

+ * Original author: Robert Harder, rharder@usa.net + *

+ *

+ * 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added. + *

+ * + * @author Robert Harder + * @author rharder@users.sf.net + * @version 1.0.1 + */ +@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) +public class FileDrop { + private transient javax.swing.border.Border normalBorder; + private transient java.awt.dnd.DropTargetListener dropListener; + + /** Discover if the running JVM is modern enough to have drag and drop. */ + private static Boolean supportsDnD; + + // Default border color + private static java.awt.Color defaultBorderColor = new java.awt.Color(0f, + 0f, 1f, 0.25f); + + /** + * Constructs a {@link FileDrop} with a default light-blue border and, if + * c is a {@link java.awt.Container}, recursively sets all + * elements contained within as drop targets, though only the top level + * container will change borders. + * + * @param c Component on which files will be dropped. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.awt.Component c, final Listener listener) { + this(null, // Logging stream + c, // Drop target + javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2, + defaultBorderColor), // Drag border + true, // Recursive + listener); + } // end constructor + + /** + * Constructor with a default border and the option to recursively set drop + * targets. If your component is a java.awt.Container, then each of + * its children components will also listen for drops, though only the + * parent will change borders. + * + * @param c Component on which files will be dropped. + * @param recursive Recursively set children as drop targets. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.awt.Component c, final boolean recursive, + final Listener listener) { + this(null, // Logging stream + c, // Drop target + javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2, + defaultBorderColor), // Drag border + recursive, // Recursive + listener); + } // end constructor + + /** + * Constructor with a default border and debugging optionally turned on. + * With Debugging turned on, more status messages will be displayed to + * out. A common way to use this constructor is with + * System.out or System.err. A null value for the + * parameter out will result in no debugging output. + * + * @param out PrintStream to record debugging info or null for no debugging. + * @param out + * @param c Component on which files will be dropped. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.io.PrintStream out, final java.awt.Component c, + final Listener listener) { + this(out, // Logging stream + c, // Drop target + javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2, + defaultBorderColor), false, // Recursive + listener); + } // end constructor + + /** + * Constructor with a default border, debugging optionally turned on and the + * option to recursively set drop targets. If your component is a + * java.awt.Container, then each of its children components will + * also listen for drops, though only the parent will change borders. With + * Debugging turned on, more status messages will be displayed to + * out. A common way to use this constructor is with + * System.out or System.err. A null value for the + * parameter out will result in no debugging output. + * + * @param out PrintStream to record debugging info or null for no debugging. + * @param out + * @param c Component on which files will be dropped. + * @param recursive Recursively set children as drop targets. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.io.PrintStream out, final java.awt.Component c, + final boolean recursive, final Listener listener) { + this(out, // Logging stream + c, // Drop target + javax.swing.BorderFactory.createMatteBorder(2, 2, 2, 2, + defaultBorderColor), // Drag border + recursive, // Recursive + listener); + } // end constructor + + /** + * Constructor with a specified border + * + * @param c Component on which files will be dropped. + * @param dragBorder Border to use on JComponent when dragging + * occurs. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.awt.Component c, + final javax.swing.border.Border dragBorder, final Listener listener) { + this(null, // Logging stream + c, // Drop target + dragBorder, // Drag border + false, // Recursive + listener); + } // end constructor + + /** + * Constructor with a specified border and the option to recursively set + * drop targets. If your component is a java.awt.Container, then + * each of its children components will also listen for drops, though only + * the parent will change borders. + * + * @param c Component on which files will be dropped. + * @param dragBorder Border to use on JComponent when dragging + * occurs. + * @param recursive Recursively set children as drop targets. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.awt.Component c, + final javax.swing.border.Border dragBorder, + final boolean recursive, final Listener listener) { + this(null, c, dragBorder, recursive, listener); + } // end constructor + + /** + * Constructor with a specified border and debugging optionally turned on. + * With Debugging turned on, more status messages will be displayed to + * out. A common way to use this constructor is with + * System.out or System.err. A null value for the + * parameter out will result in no debugging output. + * + * @param out PrintStream to record debugging info or null for no debugging. + * @param c Component on which files will be dropped. + * @param dragBorder Border to use on JComponent when dragging + * occurs. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.io.PrintStream out, final java.awt.Component c, + final javax.swing.border.Border dragBorder, final Listener listener) { + this(out, // Logging stream + c, // Drop target + dragBorder, // Drag border + false, // Recursive + listener); + } // end constructor + + /** + * Full constructor with a specified border and debugging optionally turned + * on. With Debugging turned on, more status messages will be displayed to + * out. A common way to use this constructor is with + * System.out or System.err. A null value for the + * parameter out will result in no debugging output. + * + * @param out PrintStream to record debugging info or null for no debugging. + * @param c Component on which files will be dropped. + * @param dragBorder Border to use on JComponent when dragging + * occurs. + * @param recursive Recursively set children as drop targets. + * @param listener Listens for filesDropped. + * @since 1.0 + */ + public FileDrop(final java.io.PrintStream out, final java.awt.Component c, + final javax.swing.border.Border dragBorder, + final boolean recursive, final Listener listener) { + + if (supportsDnD()) { // Make a drop listener + dropListener = new java.awt.dnd.DropTargetListener() { + public void dragEnter(final java.awt.dnd.DropTargetDragEvent evt) { + log(out, "FileDrop: dragEnter event."); + + // Is this an acceptable drag event? + if (isDragOk(out, evt)) { + // If it's a Swing component, set its border + if (c instanceof javax.swing.JComponent) { + final javax.swing.JComponent jc = (javax.swing.JComponent) c; + normalBorder = jc.getBorder(); + log(out, "FileDrop: normal border saved."); + jc.setBorder(dragBorder); + log(out, "FileDrop: drag border set."); + } // end if: JComponent + + // Acknowledge that it's okay to enter + // evt.acceptDrag( + // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE ); + evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_COPY); + log(out, "FileDrop: event accepted."); + } // end if: drag ok + else { // Reject the drag event + evt.rejectDrag(); + log(out, "FileDrop: event rejected."); + } // end else: drag not ok + } // end dragEnter + + public void dragOver(final java.awt.dnd.DropTargetDragEvent evt) { // This + // is + // called + // continually + // as + // long + // as + // the + // mouse + // is + // over + // the + // drag + // target. + } // end dragOver + + public void drop(final java.awt.dnd.DropTargetDropEvent evt) { + log(out, "FileDrop: drop event."); + try { // Get whatever was dropped + final java.awt.datatransfer.Transferable tr = evt + .getTransferable(); + + // Is it a file list? + if (tr.isDataFlavorSupported(java.awt.datatransfer.DataFlavor.javaFileListFlavor)) { + // Say we'll take it. + // evt.acceptDrop ( + // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE ); + evt.acceptDrop(java.awt.dnd.DnDConstants.ACTION_COPY); + log(out, "FileDrop: file list accepted."); + + // Get a useful list + final java.util.List fileList = (java.util.List) tr + .getTransferData(java.awt.datatransfer.DataFlavor.javaFileListFlavor); + final java.util.Iterator iterator = fileList.iterator(); + + // Convert list to array + final java.io.File[] filesTemp = new java.io.File[fileList + .size()]; + fileList.toArray(filesTemp); + final java.io.File[] files = filesTemp; + + // Alert listener to drop. + if (listener != null) { + listener.filesDropped(files); + } + + // Mark that drop is completed. + evt.getDropTargetContext().dropComplete(true); + log(out, "FileDrop: drop complete."); + } // end if: file list + else // this section will check for a reader flavor. + { + // Thanks, Nathan! + // BEGIN 2007-09-12 Nathan Blomquist -- Linux + // (KDE/Gnome) support added. + final DataFlavor[] flavors = tr + .getTransferDataFlavors(); + boolean handled = false; + for (int zz = 0; zz < flavors.length; zz++) { + if (flavors[zz].isRepresentationClassReader()) { + // Say we'll take it. + // evt.acceptDrop ( + // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE + // ); + evt.acceptDrop(java.awt.dnd.DnDConstants.ACTION_COPY); + log(out, "FileDrop: reader accepted."); + + final Reader reader = flavors[zz] + .getReaderForText(tr); + + final BufferedReader br = new BufferedReader( + reader); + + if (listener != null) { + listener.filesDropped(createFileArray( + br, out)); + } + + // Mark that drop is completed. + evt.getDropTargetContext().dropComplete( + true); + log(out, "FileDrop: drop complete."); + handled = true; + break; + } + } + if (!handled) { + log(out, + "FileDrop: not a file list or reader - abort."); + evt.rejectDrop(); + } + // END 2007-09-12 Nathan Blomquist -- Linux + // (KDE/Gnome) support added. + } // end else: not a file list + } // end try + catch (final java.io.IOException io) { + log(out, "FileDrop: IOException - abort:"); + io.printStackTrace(out); + evt.rejectDrop(); + } // end catch IOException + catch (final java.awt.datatransfer.UnsupportedFlavorException ufe) { + log(out, + "FileDrop: UnsupportedFlavorException - abort:"); + ufe.printStackTrace(out); + evt.rejectDrop(); + } // end catch: UnsupportedFlavorException + finally { + // If it's a Swing component, reset its border + if (c instanceof javax.swing.JComponent) { + final javax.swing.JComponent jc = (javax.swing.JComponent) c; + jc.setBorder(normalBorder); + log(out, "FileDrop: normal border restored."); + } // end if: JComponent + } // end finally + } // end drop + + public void dragExit(final java.awt.dnd.DropTargetEvent evt) { + log(out, "FileDrop: dragExit event."); + // If it's a Swing component, reset its border + if (c instanceof javax.swing.JComponent) { + final javax.swing.JComponent jc = (javax.swing.JComponent) c; + jc.setBorder(normalBorder); + log(out, "FileDrop: normal border restored."); + } // end if: JComponent + } // end dragExit + + public void dropActionChanged( + final java.awt.dnd.DropTargetDragEvent evt) { + log(out, "FileDrop: dropActionChanged event."); + // Is this an acceptable drag event? + if (isDragOk(out, evt)) { // evt.acceptDrag( + // java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE + // ); + evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_COPY); + log(out, "FileDrop: event accepted."); + } // end if: drag ok + else { + evt.rejectDrag(); + log(out, "FileDrop: event rejected."); + } // end else: drag not ok + } // end dropActionChanged + }; // end DropTargetListener + + // Make the component (and possibly children) drop targets + makeDropTarget(out, c, recursive); + } // end if: supports dnd + else { + log(out, "FileDrop: Drag and drop is not supported with this JVM"); + } // end else: does not support DnD + } // end constructor + + private static boolean supportsDnD() { // Static Boolean + if (supportsDnD == null) { + boolean support = false; + try { + final Class arbitraryDndClass = Class + .forName("java.awt.dnd.DnDConstants"); + support = true; + } // end try + catch (final Exception e) { + support = false; + } // end catch + supportsDnD = new Boolean(support); + } // end if: first time through + return supportsDnD.booleanValue(); + } // end supportsDnD + + // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added. + private static String ZERO_CHAR_STRING = "" + (char) 0; + + private static File[] createFileArray(final BufferedReader bReader, + final PrintStream out) { + try { + final java.util.List list = new java.util.ArrayList(); + java.lang.String line = null; + while ((line = bReader.readLine()) != null) { + try { + // kde seems to append a 0 char to the end of the reader + if (ZERO_CHAR_STRING.equals(line)) { + continue; + } + + final java.io.File file = new java.io.File( + new java.net.URI(line)); + list.add(file); + } catch (final Exception ex) { + log(out, "Error with " + line + ": " + ex.getMessage()); + } + } + + return (java.io.File[]) list.toArray(new File[list.size()]); + } catch (final IOException ex) { + log(out, "FileDrop: IOException"); + } + return new File[0]; + } + + // END 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support added. + + private void makeDropTarget(final java.io.PrintStream out, + final java.awt.Component c, final boolean recursive) { + // Make drop target + final java.awt.dnd.DropTarget dt = new java.awt.dnd.DropTarget(); + try { + dt.addDropTargetListener(dropListener); + } // end try + catch (final java.util.TooManyListenersException e) { + e.printStackTrace(); + log(out, + "FileDrop: Drop will not work due to previous error. Do you have another listener attached?"); + } // end catch + + // Listen for hierarchy changes and remove the drop target when the + // parent gets cleared out. + c.addHierarchyListener(new java.awt.event.HierarchyListener() { + public void hierarchyChanged(final java.awt.event.HierarchyEvent evt) { + log(out, "FileDrop: Hierarchy changed."); + final java.awt.Component parent = c.getParent(); + if (parent == null) { + c.setDropTarget(null); + log(out, "FileDrop: Drop target cleared from component."); + } // end if: null parent + else { + new java.awt.dnd.DropTarget(c, dropListener); + log(out, "FileDrop: Drop target added to component."); + } // end else: parent not null + } // end hierarchyChanged + }); // end hierarchy listener + if (c.getParent() != null) { + new java.awt.dnd.DropTarget(c, dropListener); + } + + if (recursive && (c instanceof java.awt.Container)) { + // Get the container + final java.awt.Container cont = (java.awt.Container) c; + + // Get it's components + final java.awt.Component[] comps = cont.getComponents(); + + // Set it's components as listeners also + for (int i = 0; i < comps.length; i++) { + makeDropTarget(out, comps[i], recursive); + } + } // end if: recursively set components as listener + } // end dropListener + + /** Determine if the dragged data is a file list. */ + private boolean isDragOk(final java.io.PrintStream out, + final java.awt.dnd.DropTargetDragEvent evt) { + boolean ok = false; + + // Get data flavors being dragged + final java.awt.datatransfer.DataFlavor[] flavors = evt + .getCurrentDataFlavors(); + + // See if any of the flavors are a file list + int i = 0; + while (!ok && i < flavors.length) { + // BEGIN 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support + // added. + // Is the flavor a file list? + final DataFlavor curFlavor = flavors[i]; + if (curFlavor + .equals(java.awt.datatransfer.DataFlavor.javaFileListFlavor) + || curFlavor.isRepresentationClassReader()) { + ok = true; + } + // END 2007-09-12 Nathan Blomquist -- Linux (KDE/Gnome) support + // added. + i++; + } // end while: through flavors + + // If logging is enabled, show data flavors + if (out != null) { + if (flavors.length == 0) { + log(out, "FileDrop: no data flavors."); + } + for (i = 0; i < flavors.length; i++) { + log(out, flavors[i].toString()); + } + } // end if: logging enabled + + return ok; + } // end isDragOk + + /** Outputs message to out if it's not null. */ + private static void log(final java.io.PrintStream out, final String message) { // Log + // message + // if + // requested + if (out != null) { + out.println(message); + } + } // end log + + /** + * Removes the drag-and-drop hooks from the component and optionally from + * the all children. You should call this if you add and remove components + * after you've set up the drag-and-drop. This will recursively unregister + * all components contained within c if c is a + * {@link java.awt.Container}. + * + * @param c The component to unregister as a drop target + * @since 1.0 + */ + public static boolean remove(final java.awt.Component c) { + return remove(null, c, true); + } // end remove + + /** + * Removes the drag-and-drop hooks from the component and optionally from + * the all children. You should call this if you add and remove components + * after you've set up the drag-and-drop. + * + * @param out Optional {@link java.io.PrintStream} for logging drag and drop + * messages + * @param c The component to unregister + * @param recursive Recursively unregister components within a container + * @since 1.0 + */ + public static boolean remove(final java.io.PrintStream out, + final java.awt.Component c, final boolean recursive) { // Make sure + // we support + // dnd. + if (supportsDnD()) { + log(out, "FileDrop: Removing drag-and-drop hooks."); + c.setDropTarget(null); + if (recursive && (c instanceof java.awt.Container)) { + final java.awt.Component[] comps = ((java.awt.Container) c) + .getComponents(); + for (int i = 0; i < comps.length; i++) { + remove(out, comps[i], recursive); + } + return true; + } // end if: recursive + else + return false; + } // end if: supports DnD + else + return false; + } // end remove + + /* ******** I N N E R I N T E R F A C E L I S T E N E R ******** */ + + /** + * Implement this inner interface to listen for when files are dropped. For + * example your class declaration may begin like this:
+     *      public class MyClass implements FileDrop.Listener
+     *      ...
+     *      public void filesDropped( java.io.File[] files )
+     *      {
+     *          ...
+     *      }   // end filesDropped
+     *      ...
+     * 
+ * + * @since 1.1 + */ + public static interface Listener { + + /** + * This method is called when files have been successfully dropped. + * + * @param files An array of Files that were dropped. + * @since 1.0 + */ + public abstract void filesDropped(java.io.File[] files); + + } // end inner-interface Listener + + /* ******** I N N E R C L A S S ******** */ + + /** + * This is the event that is passed to the + * {@link FileDropListener#filesDropped filesDropped(...)} method in your + * {@link FileDropListener} when files are dropped onto a registered drop + * target. + * + *

+ * I'm releasing this code into the Public Domain. Enjoy. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 1.2 + */ + public static class Event extends java.util.EventObject { + + private static final long serialVersionUID = -2175361562828864378L; + private final java.io.File[] files; + + /** + * Constructs an {@link Event} with the array of files that were dropped + * and the {@link FileDrop} that initiated the event. + * + * @param files The array of files that were dropped + * @source The event source + * @since 1.1 + */ + public Event(final java.io.File[] files, final Object source) { + super(source); + this.files = files; + } // end constructor + + /** + * Returns an array of files that were dropped on a registered drop + * target. + * + * @return array of files that were dropped + * @since 1.1 + */ + public java.io.File[] getFiles() { + return files; + } // end getFiles + + } // end inner class Event + + /* ******** I N N E R C L A S S ******** */ + + /** + * At last an easy way to encapsulate your custom objects for dragging and + * dropping in your Java programs! When you need to create a + * {@link java.awt.datatransfer.Transferable} object, use this class to wrap + * your object. For example: + * + *
+     * 
+     *      ...
+     *      MyCoolClass myObj = new MyCoolClass();
+     *      Transferable xfer = new TransferableObject( myObj );
+     *      ...
+     * 
+     * 
+ * + * Or if you need to know when the data was actually dropped, like when + * you're moving data out of a list, say, you can use the + * {@link TransferableObject.Fetcher} inner class to return your object Just + * in Time. For example: + * + *
+     * 
+     *      ...
+     *      final MyCoolClass myObj = new MyCoolClass();
+     * 
+     *      TransferableObject.Fetcher fetcher = new TransferableObject.Fetcher()
+     *      {   public Object getObject(){ return myObj; }
+     *      }; // end fetcher
+     * 
+     *      Transferable xfer = new TransferableObject( fetcher );
+     *      ...
+     * 
+     * 
+ * + * The {@link java.awt.datatransfer.DataFlavor} associated with + * {@link TransferableObject} has the representation class + * net.iharder.dnd.TransferableObject.class and MIME type + * application/x-net.iharder.dnd.TransferableObject. This data + * flavor is accessible via the static {@link #DATA_FLAVOR} property. + * + * + *

+ * I'm releasing this code into the Public Domain. Enjoy. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 1.2 + */ + public static class TransferableObject implements + java.awt.datatransfer.Transferable { + /** + * The MIME type for {@link #DATA_FLAVOR} is + * application/x-net.iharder.dnd.TransferableObject. + * + * @since 1.1 + */ + public final static String MIME_TYPE = "application/x-net.iharder.dnd.TransferableObject"; + + /** + * The default {@link java.awt.datatransfer.DataFlavor} for + * {@link TransferableObject} has the representation class + * net.iharder.dnd.TransferableObject.class and the MIME type + * application/x-net.iharder.dnd.TransferableObject. + * + * @since 1.1 + */ + public final static java.awt.datatransfer.DataFlavor DATA_FLAVOR = new java.awt.datatransfer.DataFlavor( + FileDrop.TransferableObject.class, MIME_TYPE); + + private Fetcher fetcher; + private Object data; + + private java.awt.datatransfer.DataFlavor customFlavor; + + /** + * Creates a new {@link TransferableObject} that wraps data. + * Along with the {@link #DATA_FLAVOR} associated with this class, this + * creates a custom data flavor with a representation class determined + * from data.getClass() and the MIME type + * application/x-net.iharder.dnd.TransferableObject. + * + * @param data The data to transfer + * @since 1.1 + */ + public TransferableObject(final Object data) { + this.data = data; + this.customFlavor = new java.awt.datatransfer.DataFlavor( + data.getClass(), MIME_TYPE); + } // end constructor + + /** + * Creates a new {@link TransferableObject} that will return the object + * that is returned by fetcher. No custom data flavor is set + * other than the default {@link #DATA_FLAVOR}. + * + * @see Fetcher + * @param fetcher The {@link Fetcher} that will return the data object + * @since 1.1 + */ + public TransferableObject(final Fetcher fetcher) { + this.fetcher = fetcher; + } // end constructor + + /** + * Creates a new {@link TransferableObject} that will return the object + * that is returned by fetcher. Along with the + * {@link #DATA_FLAVOR} associated with this class, this creates a + * custom data flavor with a representation class dataClass + * and the MIME type + * application/x-net.iharder.dnd.TransferableObject. + * + * @see Fetcher + * @param dataClass The {@link java.lang.Class} to use in the custom + * data flavor + * @param fetcher The {@link Fetcher} that will return the data object + * @since 1.1 + */ + public TransferableObject(final Class dataClass, final Fetcher fetcher) { + this.fetcher = fetcher; + this.customFlavor = new java.awt.datatransfer.DataFlavor(dataClass, + MIME_TYPE); + } // end constructor + + /** + * Returns the custom {@link java.awt.datatransfer.DataFlavor} + * associated with the encapsulated object or null if the + * {@link Fetcher} constructor was used without passing a + * {@link java.lang.Class}. + * + * @return The custom data flavor for the encapsulated object + * @since 1.1 + */ + public java.awt.datatransfer.DataFlavor getCustomDataFlavor() { + return customFlavor; + } // end getCustomDataFlavor + + /* ******** T R A N S F E R A B L E M E T H O D S ******** */ + + /** + * Returns a two- or three-element array containing first the custom + * data flavor, if one was created in the constructors, second the + * default {@link #DATA_FLAVOR} associated with + * {@link TransferableObject}, and third the + * {@link java.awt.datatransfer.DataFlavor.stringFlavor}. + * + * @return An array of supported data flavors + * @since 1.1 + */ + public java.awt.datatransfer.DataFlavor[] getTransferDataFlavors() { + if (customFlavor != null) + return new java.awt.datatransfer.DataFlavor[] { customFlavor, + DATA_FLAVOR, + java.awt.datatransfer.DataFlavor.stringFlavor }; // end + // flavors + // array + else + return new java.awt.datatransfer.DataFlavor[] { DATA_FLAVOR, + java.awt.datatransfer.DataFlavor.stringFlavor }; // end + // flavors + // array + } // end getTransferDataFlavors + + /** + * Returns the data encapsulated in this {@link TransferableObject}. If + * the {@link Fetcher} constructor was used, then this is when the + * {@link Fetcher#getObject getObject()} method will be called. If the + * requested data flavor is not supported, then the + * {@link Fetcher#getObject getObject()} method will not be called. + * + * @param flavor The data flavor for the data to return + * @return The dropped data + * @since 1.1 + */ + public Object getTransferData( + final java.awt.datatransfer.DataFlavor flavor) + throws java.awt.datatransfer.UnsupportedFlavorException, + java.io.IOException { + // Native object + if (flavor.equals(DATA_FLAVOR)) + return fetcher == null ? data : fetcher.getObject(); + + // String + if (flavor.equals(java.awt.datatransfer.DataFlavor.stringFlavor)) + return fetcher == null ? data.toString() : fetcher.getObject() + .toString(); + + // We can't do anything else + throw new java.awt.datatransfer.UnsupportedFlavorException(flavor); + } // end getTransferData + + /** + * Returns true if flavor is one of the supported + * flavors. Flavors are supported using the equals(...) + * method. + * + * @param flavor The data flavor to check + * @return Whether or not the flavor is supported + * @since 1.1 + */ + public boolean isDataFlavorSupported( + final java.awt.datatransfer.DataFlavor flavor) { + // Native object + if (flavor.equals(DATA_FLAVOR)) + return true; + + // String + if (flavor.equals(java.awt.datatransfer.DataFlavor.stringFlavor)) + return true; + + // We can't do anything else + return false; + } // end isDataFlavorSupported + + /* ******** I N N E R I N T E R F A C E F E T C H E R ******** */ + + /** + * Instead of passing your data directly to the + * {@link TransferableObject} constructor, you may want to know exactly + * when your data was received in case you need to remove it from its + * source (or do anyting else to it). When the {@link #getTransferData + * getTransferData(...)} method is called on the + * {@link TransferableObject}, the {@link Fetcher}'s {@link #getObject + * getObject()} method will be called. + * + * @author Robert Harder + * @copyright 2001 + * @version 1.1 + * @since 1.1 + */ + public static interface Fetcher { + /** + * Return the object being encapsulated in the + * {@link TransferableObject}. + * + * @return The dropped object + * @since 1.1 + */ + public abstract Object getObject(); + } // end inner interface Fetcher + + } // end class TransferableObject + +} // end class FileDrop diff --git a/src/the/bytecode/club/bytecodeviewer/JarUtils.java b/src/the/bytecode/club/bytecodeviewer/JarUtils.java new file mode 100644 index 00000000..a4aff364 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/JarUtils.java @@ -0,0 +1,135 @@ +package the.bytecode.club.bytecodeviewer; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +/** + * Loading and saving jars + * + * @author Konloch + * @author WaterWolf + * + */ + +public class JarUtils { + + private static JarInputStream jis; + private static JarEntry entry; + public static void put(final File jarFile, final HashMap clazzList) throws IOException { + jis = new JarInputStream(new FileInputStream(jarFile)); + while ((entry = jis.getNextJarEntry()) != null) { + final String name = entry.getName(); + if (!name.endsWith(".class")) { + BytecodeViewer.loadedResources.put(name, getBytes(jis)); + jis.closeEntry(); + continue; + } + + final ClassNode cn = getNode(getBytes(jis)); + clazzList.put(cn.name, cn); + + jis.closeEntry(); + } + jis.close(); + + } + + private static ByteArrayOutputStream baos = null; + private static byte[] buffer = null; + private static int a = 0; + public static byte[] getBytes(final InputStream is) throws IOException { + baos = new ByteArrayOutputStream(); + buffer = new byte[1024]; + a = 0; + while ((a = is.read(buffer)) != -1) { + baos.write(buffer, 0, a); + } + baos.close(); + buffer = null; + return baos.toByteArray(); + } + + private static ClassReader cr = null; + private static ClassNode cn = null; + public static ClassNode getNode(final byte[] bytez) { + cr = new ClassReader(bytez); + cn = new ClassNode(); + cr.accept(cn, ClassReader.EXPAND_FRAMES); + cr = null; + return cn; + } + + public static void saveAsJar(ArrayList nodeList, String path, String manifest) { + try { + JarOutputStream out = new JarOutputStream(new FileOutputStream(path)); + for (ClassNode cn : nodeList) { + ClassWriter cw = new ClassWriter(0); + cn.accept(cw); + + out.putNextEntry(new ZipEntry(cn.name + ".class")); + out.write(cw.toByteArray()); + out.closeEntry(); + } + + out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + out.write((manifest.trim()+"\r\n\r\n").getBytes()); + out.closeEntry(); + + for (Entry entry : BytecodeViewer.loadedResources.entrySet()) { + String filename = entry.getKey(); + if(!filename.startsWith("META-INF")) { + out.putNextEntry(new ZipEntry(filename)); + out.write(entry.getValue()); + out.closeEntry(); + } + } + + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void saveAsJar(ArrayList nodeList, String path) { + try { + JarOutputStream out = new JarOutputStream(new FileOutputStream(path)); + for (ClassNode cn : nodeList) { + ClassWriter cw = new ClassWriter(0); + cn.accept(cw); + + out.putNextEntry(new ZipEntry(cn.name + ".class")); + out.write(cw.toByteArray()); + out.closeEntry(); + } + + for (Entry entry : BytecodeViewer.loadedResources.entrySet()) { + String filename = entry.getKey(); + if(!filename.startsWith("META-INF")) { + out.putNextEntry(new ZipEntry(filename)); + out.write(entry.getValue()); + out.closeEntry(); + } + } + + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/BytecodeDecompiler.java b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/BytecodeDecompiler.java new file mode 100644 index 00000000..ac0d7879 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/BytecodeDecompiler.java @@ -0,0 +1,381 @@ +package the.bytecode.club.bytecodeviewer.decompilers.bytecode; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Iterator; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TryCatchBlockNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.searching.commons.InstructionSearcher; + +/** + * A Bytecode decompiler + * + * @author Konloch + * @author WaterWolf + * + */ + +public class BytecodeDecompiler { + + public static String[] opcodeStrings; + public static String[] typeStrings; + + static { + opcodeStrings = new String[256]; + for (final Field f : Opcodes.class.getFields()) { + try { + final Object oo = f.get(null); + if (oo instanceof Integer) { + final int oi = ((Integer)oo); + if (oi < 256 && oi >= 0) { + opcodeStrings[oi] = f.getName().toLowerCase(); + } + } + } catch (final IllegalArgumentException e) { + e.printStackTrace(); + } catch (final IllegalAccessException e) { + e.printStackTrace(); + } + } + typeStrings = new String[100]; + for (final Field f : AbstractInsnNode.class.getFields()) { + if (!(f.getName().endsWith("_INSN"))) { + continue; + } + try { + final Object oo = f.get(null); + if (oo instanceof Integer) { + final int oi = ((Integer)oo); + if (oi < 256 && oi >= 0) { + typeStrings[oi] = f.getName().toLowerCase(); + } + } + } catch (final IllegalArgumentException e) { + e.printStackTrace(); + } catch (final IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + @SuppressWarnings("unchecked") + public String decompileClassNode(final ClassNode cn) { + final StringBuilder classBuilder = new StringBuilder(); + final ClassStringBuilder cb = new ClassStringBuilder(classBuilder); + + final String cnm = cn.name; + String package_ = null; + String class_ = null; + if (cnm.contains("/")) { + package_ = cnm.substring(0, cnm.lastIndexOf("/")); + class_ = cnm.substring(cnm.lastIndexOf("/")+1); + } + else { + class_ = cnm; + } + + if (package_ != null) { + cb.appendnl("package " + package_ + ";", 2); + } + + cb.append(Modifier.toString(cn.access) + " class " + class_ + " "); + + if (cn.superName != null) { + cb.append("extends " + cn.superName + " "); + } + if (cn.interfaces.size() > 0) { + cb.append("implements "); + final Iterator sit = cn.interfaces.iterator(); + while (sit.hasNext()) { + final String s = sit.next(); + cb.append(s); + if (sit.hasNext()) { + cb.append(", "); + } else { + cb.append(" "); + } + } + } + + cb.appendnl("{"); + cb.increase(); + cb.appendnl(); + + final Iterator fni = cn.fields.iterator(); + + while (fni.hasNext()) { + final FieldNode fn = fni.next(); + + cb.appendnl(Modifier.toString(fn.access) + " " + Type.getType(fn.desc).getClassName() + " " + fn.name + ";"); + + } + + cb.appendnl(); + + final Iterator mni = cn.methods.iterator(); + while (mni.hasNext()) { + final MethodNode mn = mni.next(); + final String mnm = mn.name; + if (!mnm.equals("")) { + cb.append(Modifier.toString(mn.access) + " "); + } + + if (mnm.equals("")) { + cb.append(class_); + } + else if (mnm.equals("")) { + cb.append("static {"); + if (BytecodeViewer.viewer.debugHelpers.isSelected()) + cb.appendnl(" // "); + else + cb.appendnl(); + } + else { + cb.append(Type.getReturnType(mn.desc).getClassName() + " "); + cb.append(mnm); + } + + TypeAndName[] args = new TypeAndName[0]; + + if (!mnm.equals("")) { + cb.append("("); + + // TODO desc + final Type[] argTypes = Type.getArgumentTypes(mn.desc); + args = new TypeAndName[argTypes.length]; + + for (int i = 0;i < argTypes.length; i++) { + final Type type = argTypes[i]; + + final TypeAndName tan = new TypeAndName(); + final String argName = "arg" + i; + + tan.name = argName; + tan.type = type; + + args[i] = tan; + + cb.append(type.getClassName() + " " + argName + (i < argTypes.length-1 ? ", " : "")); + } + + cb.appendnl(") {"); + } + + cb.increase(); + + try { + decompileMethod(cb, args, mn, cn); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + cb.decrease(); + cb.appendnl("}"); + cb.appendnl(); + } + + cb.decrease(); + cb.appendnl("}"); + + + return classBuilder.toString(); + } + + public void decompileMethod(final ClassStringBuilder builder, final TypeAndName[] args, final MethodNode mn, final ClassNode parent) throws UnsupportedEncodingException { + final InstructionSearcher is = new InstructionSearcher(mn); + //AbstractInsnNode next = is.getCurrent(); + + for(Object e : mn.tryCatchBlocks.toArray()) { + TryCatchBlockNode t = (TryCatchBlockNode)e; + String type = t.type; + LabelNode start = t.start; + LabelNode end = t.end; + LabelNode handler = t.handler; + builder.appendnl("trycatch block L" + start.hashCode() + " to L" + end.hashCode() + " handled by L" + handler.hashCode() + " exception type: " + type); + } + + int index = 0; + for(AbstractInsnNode next : mn.instructions.toArray()) { + + if (next.getOpcode() == -1) { + + if(next instanceof LabelNode) { + LabelNode l = (LabelNode)next; + builder.appendnl(index++ + ". L" +l.hashCode()); + } else { + builder.appendnl(index++ + ". nop //actually an unimplement opcode, please contact Konloch"); //lets just set it as nop for now. + } + //next = is.getNext(); + continue; + } + + builder.append(index++ + ". " + opcodeStrings[next.getOpcode()] + " "); + + if (next instanceof FieldInsnNode) { + final FieldInsnNode fin = (FieldInsnNode) next; + builder.append(fin.owner + " " + fin.name + " " + fin.desc); + } + else if (next instanceof MethodInsnNode) { + final MethodInsnNode min = (MethodInsnNode) next; + builder.append(min.owner + " " + min.name + " " + min.desc); + } + else if (next instanceof VarInsnNode) { + final VarInsnNode vin = (VarInsnNode) next; + builder.append(vin.var); + if (BytecodeViewer.viewer.debugHelpers.isSelected()) { + if (vin.var == 0 && !Modifier.isStatic(mn.access)) { + builder.append(" // reference to self"); + } + else { + final int refIndex = vin.var - (Modifier.isStatic(mn.access) ? 0 : 1); + if (refIndex >= 0 && refIndex < args.length-1) { + builder.append(" // reference to " + args[refIndex].name); + } + } + } + } + else if (next instanceof IntInsnNode) { + final IntInsnNode iin = (IntInsnNode) next; + builder.append(iin.operand); + } + else if (next instanceof JumpInsnNode) { + final JumpInsnNode jin = (JumpInsnNode) next; + builder.append(is.computePosition(jin.label)); + switch (next.getOpcode()) { + case Opcodes.IF_ICMPLT: + builder.append(" // if val1 less than val2 jump"); + break; + } + } + else if (next instanceof LdcInsnNode) { + final LdcInsnNode lin = (LdcInsnNode) next; + if(lin.cst instanceof String) { + String s = ((String)lin.cst).replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r").replaceAll("\\\"", "\\\\\""); + if(BytecodeViewer.viewer.chckbxmntmNewCheckItem.isSelected()) + builder.append("\"" + StringEscapeUtils.escapeJava(s) + "\""); + else + builder.append("\"" + s + "\""); + } else { + String s = lin.cst.toString().replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r").replaceAll("\\\"", "\\\\\""); + if(BytecodeViewer.viewer.chckbxmntmNewCheckItem.isSelected()) + builder.append("\"" + StringEscapeUtils.escapeJava(s) + "\""); + else + builder.append("\"" + s + "\""); + } + } + else if (next instanceof IincInsnNode) { + final IincInsnNode iin = (IincInsnNode) next; + builder.append("var " + iin.var + " by " + iin.incr); + } + else if (next instanceof TypeInsnNode) { + final TypeInsnNode tin = (TypeInsnNode) next; + builder.append(tin.desc); + } + else { + /* + switch (next.getOpcode()) { + case Opcodes.IF_ICMPLT: + buffer.append(" // "); + break; + } + */ + } + + if (BytecodeViewer.viewer.debugInstructions.isSelected()) { + builder.append(" // " + typeStrings[next.getType()] + " "); + } + + if (BytecodeViewer.viewer.debugHelpers.isSelected() && + next instanceof JumpInsnNode) + { + final JumpInsnNode jin = (JumpInsnNode) next; + builder.append(" // line " + is.computePosition(jin.label) + " is " + printInstruction(is.computePosition(jin.label), mn, is).trim()); + } + + builder.appendnl(); + } + } + + public static String printInstruction(int line, MethodNode mn, InstructionSearcher is) { + for(int i = 0; i < mn.instructions.size(); i++) { + AbstractInsnNode next = mn.instructions.get(i); + if(line == i) + if(next.getOpcode() != -1) { + return beatifyAbstractInsnNode(next, is); + } + } + return "Unable to find, please contact konloch."; + } + + public static String beatifyAbstractInsnNode(AbstractInsnNode next, InstructionSearcher is) { + String insn = ""; + + if(next.getOpcode() != -1) + insn =opcodeStrings[next.getOpcode()] + " "; + else if(next instanceof LabelNode) { + LabelNode l = (LabelNode)next; + insn = "L" +l.hashCode(); + } + + if (next instanceof FieldInsnNode) { + final FieldInsnNode fin = (FieldInsnNode) next; + insn += fin.owner + " " + fin.name + " " + fin.desc; + } + else if (next instanceof MethodInsnNode) { + final MethodInsnNode min = (MethodInsnNode) next; + insn += min.owner + " " + min.name + " " + min.desc; + } + else if (next instanceof VarInsnNode) { + final VarInsnNode vin = (VarInsnNode) next; + insn += vin.var; + } + else if (next instanceof IntInsnNode) { + final IntInsnNode iin = (IntInsnNode) next; + insn += iin.operand; + } + else if (next instanceof JumpInsnNode) { + final JumpInsnNode jin = (JumpInsnNode) next; + insn += is.computePosition(jin.label); + } + else if (next instanceof LdcInsnNode) { + final LdcInsnNode lin = (LdcInsnNode) next; + if(lin.cst instanceof String) + insn += "\"" + ((String) lin.cst).replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r") + "\""; + else + insn += "\"" + lin.cst + "\""; + } + else if (next instanceof IincInsnNode) { + final IincInsnNode iin = (IincInsnNode) next; + insn += "var " + iin.var + " by " + iin.incr; + } + else if (next instanceof TypeInsnNode) { + final TypeInsnNode tin = (TypeInsnNode) next; + insn += tin.desc; + } + else { + + } + + return insn; + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/ClassStringBuilder.java b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/ClassStringBuilder.java new file mode 100644 index 00000000..59226882 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/ClassStringBuilder.java @@ -0,0 +1,76 @@ +package the.bytecode.club.bytecodeviewer.decompilers.bytecode; + +/** + * The buffer where decompiler classes output generated source + * + * @author Waterwolf + * + */ +public class ClassStringBuilder { + private final StringBuilder builder; + public final IndentationLevel iLevel; + private static final String nl = System.getProperty("line.separator"); + private static final int TAB_SPACES = 4; + private boolean isNewline = true; + + public ClassStringBuilder(final StringBuilder builder) { + this.builder = builder; + this.iLevel = new IndentationLevel(); + } + + public void append(final Object obj) { + if (isNewline) { + for (int i = 0;i < TAB_SPACES*iLevel.indentation; i++) { + builder.append(" "); + } + } + builder.append(obj); + isNewline = false; + } + + public void appendnl(final String s) { + appendnl(s, 1); + } + + public void appendnl() { + appendnl("", 1); + } + + public void appendnl(final String s, final int nlAmount) { + append(s); + for (int i = 0;i < nlAmount; i++) { + builder.append(nl); + } + if (nlAmount > 0) { + isNewline = true; + } + } + + public int increase() { + return iLevel.increase(); + } + + public int decrease() { + return iLevel.decrease(); + } + + public int get() { + return iLevel.get(); + } + + public static class IndentationLevel { + private int indentation = 0; + + public int increase() { + return ++indentation; + } + + public int decrease() { + return --indentation; + } + + public int get() { + return indentation; + } + } +} diff --git a/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/TypeAndName.java b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/TypeAndName.java new file mode 100644 index 00000000..bdf553e2 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/decompilers/bytecode/TypeAndName.java @@ -0,0 +1,14 @@ +package the.bytecode.club.bytecodeviewer.decompilers.bytecode; + +import org.objectweb.asm.Type; + +/** + * Container class for type and name. Used to pass arguments and local variables around + * + * @author Waterwolf + * + */ +public class TypeAndName { + public Type type = null; + public String name = null; +} diff --git a/src/the/bytecode/club/bytecodeviewer/decompilers/java/FernFlowerDecompiler.java b/src/the/bytecode/club/bytecodeviewer/decompilers/java/FernFlowerDecompiler.java new file mode 100644 index 00000000..3e1e2035 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/decompilers/java/FernFlowerDecompiler.java @@ -0,0 +1,152 @@ +package the.bytecode.club.bytecodeviewer.decompilers.java; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.JarUtils; + +/** + * A complete FernFlower launcher with all the options (except 2) + * + * @author Konloch + * @author WaterWolf + * + */ + +public class FernFlowerDecompiler { + + public void decompileToZip(String zipName) { + File tempZip = new File(BytecodeViewer.tempDirectory + "temp.zip"); + if(tempZip.exists()) + tempZip.delete(); + + JarUtils.saveAsJar(BytecodeViewer.getLoadedClasses(), tempZip.getAbsolutePath()); + + de.fernflower.main.decompiler.ConsoleDecompiler.main(new String[] {tempZip.getAbsolutePath(), BytecodeViewer.tempDirectory + "./temp/"}); + File tempZip2 = new File(BytecodeViewer.tempDirectory + "./temp/"+tempZip.getName()); + if(tempZip2.exists()) + tempZip2.renameTo(new File(zipName)); + + tempZip.delete(); + new File(BytecodeViewer.tempDirectory + "./temp/").delete(); + } + + public String decompileClassNode(final ClassNode cn) { + final ClassWriter cw = new ClassWriter(0); + cn.accept(cw); + + String fileStart = BytecodeViewer.tempDirectory + "temp"; + int fileNumber = getClassNumber(fileStart, ".class"); + + final File tempClass = new File(fileStart+fileNumber+".class"); + + try { + final FileOutputStream fos = new FileOutputStream(tempClass); + + fos.write(cw.toByteArray()); + + fos.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + + de.fernflower.main.decompiler.ConsoleDecompiler.main(generateMainMethod(tempClass.getAbsolutePath(), ".")); + + tempClass.delete(); + + final File outputJava = new File("temp"+fileNumber+".java"); + if (outputJava.exists()) { + + final String nl = System.getProperty("line.separator"); + final StringBuffer javaSrc = new StringBuffer(); + + try { + final BufferedReader br = new BufferedReader(new FileReader(outputJava)); + String line; + while ((line = br.readLine()) != null) { + javaSrc.append(line + nl); + } + br.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + + outputJava.delete(); + + return javaSrc.toString(); + } + return "FernFlower error! Send the stacktrace to Konloch at http://the.bytecode.club or konloch@gmail.com"; + } + + File tempF = null; + public int getClassNumber(String start, String ext) { + boolean b = true; + int i = 0; + while(b) { + tempF = new File(start + i + ext); + if(!tempF.exists()) + b = false; + else + i++; + } + return i; + } + + private String[] generateMainMethod(String className, String folder) { + boolean rbr = BytecodeViewer.viewer.rbr.isSelected(); + boolean rsy = BytecodeViewer.viewer.rsy.isSelected(); + boolean din = BytecodeViewer.viewer.din.isSelected(); + boolean dc4 = BytecodeViewer.viewer.dc4.isSelected(); + boolean das = BytecodeViewer.viewer.das.isSelected(); + boolean hes = BytecodeViewer.viewer.hes.isSelected(); + boolean hdc = BytecodeViewer.viewer.hdc.isSelected(); + boolean dgs = BytecodeViewer.viewer.dgs.isSelected(); + boolean ner = BytecodeViewer.viewer.ner.isSelected(); + boolean den = BytecodeViewer.viewer.den.isSelected(); + boolean rgn = BytecodeViewer.viewer.rgn.isSelected(); + boolean bto = BytecodeViewer.viewer.bto.isSelected(); + boolean nns = BytecodeViewer.viewer.nns.isSelected(); + boolean uto = BytecodeViewer.viewer.uto.isSelected(); + boolean udv = BytecodeViewer.viewer.udv.isSelected(); + boolean rer = BytecodeViewer.viewer.rer.isSelected(); + boolean fdi = BytecodeViewer.viewer.fdi.isSelected(); + boolean asc = BytecodeViewer.viewer.asc.isSelected(); + return new String[] { + "-rbr="+r(rbr), + "-rsy="+r(rsy), + "-din="+r(din), + "-dc4="+r(dc4), + "-das="+r(das), + "-hes="+r(hes), + "-hdc="+r(hdc), + "-dgs="+r(dgs), + "-ner="+r(ner), + "-den="+r(den), + "-rgn="+r(rgn), + "-bto="+r(bto), + "-nns="+r(nns), + "-uto="+r(uto), + "-udv="+r(udv), + "-rer="+r(rer), + "-fdi="+r(fdi), + "-asc="+r(asc), + className, + folder}; + } + + private String r(boolean b) { + if(b) { + return "1"; + } else { + return "0"; + } + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/AboutWindow.java b/src/the/bytecode/club/bytecodeviewer/gui/AboutWindow.java new file mode 100644 index 00000000..2bb9946a --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/AboutWindow.java @@ -0,0 +1,28 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import javax.swing.JFrame; +import java.awt.Dimension; +import java.awt.CardLayout; +import javax.swing.JTextArea; +import java.awt.Color; + +public class AboutWindow extends JFrame { + public AboutWindow() { + setSize(new Dimension(403, 407)); + setType(Type.UTILITY); + setTitle("Bytecode Viewer - About"); + getContentPane().setLayout(new CardLayout(0, 0)); + + JTextArea txtrBytecodeViewerIs = new JTextArea(); + txtrBytecodeViewerIs.setDisabledTextColor(Color.BLACK); + txtrBytecodeViewerIs.setWrapStyleWord(true); + getContentPane().add(txtrBytecodeViewerIs, "name_140466526081695"); + txtrBytecodeViewerIs.setText("Bytecode Viewer is an open source program\r\ndeveloped by Konloch (konloch@gmail.com)\r\n\r\nIt uses code from the following:\r\n J-RET by WaterWolf\r\n JHexPane by Sam Koivu\r\n JSyntaxPane by Ayman Al\r\n Commons IO by Apache\r\n ASM by OW2\r\n\r\nLimitations:\r\n Syntax highlighting on files that are\r\nbigger than 10K lines can take a while to\r\nload, you may want to disable the syntax\r\nhighlighting for large files.\r\n\r\nIf you're interested in Java Reverse\r\nEngineering, join The Bytecode Club\r\nhttp://the.bytecode.club"); + txtrBytecodeViewerIs.setEnabled(false); + this.setResizable(false); + this.setLocationRelativeTo(null); + } + + private static final long serialVersionUID = -8230501978224923296L; + +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/ClassViewer.java b/src/the/bytecode/club/bytecodeviewer/gui/ClassViewer.java new file mode 100644 index 00000000..4703fa67 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/ClassViewer.java @@ -0,0 +1,281 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; +import java.util.ArrayList; + +import static javax.swing.ScrollPaneConstants.*; + +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BoxView; +import javax.swing.text.ComponentView; +import javax.swing.text.Element; +import javax.swing.text.IconView; +import javax.swing.text.LabelView; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledEditorKit; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import javax.swing.text.html.ParagraphView; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; + +import com.jhe.hexed.JHexEditor; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.decompilers.bytecode.BytecodeDecompiler; +import the.bytecode.club.bytecodeviewer.decompilers.java.FernFlowerDecompiler; + +/** + * This represents the opened classfile. + * + * @author Konloch + * @author WaterWolf + * + */ + +public class ClassViewer extends JPanel { + + private boolean sourcePane = false, bytecodePane = false, hexPane = false; + + /** + * Whoever wrote this function, THANK YOU! + * @param splitter + * @param proportion + * @return + */ + public static JSplitPane setDividerLocation(final JSplitPane splitter, + final double proportion) { + if (splitter.isShowing()) { + if(splitter.getWidth() > 0 && splitter.getHeight() > 0) { + splitter.setDividerLocation(proportion); + } + else { + splitter.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent ce) { + splitter.removeComponentListener(this); + setDividerLocation(splitter, proportion); + } + }); + } + } + else { + splitter.addHierarchyListener(new HierarchyListener() { + @Override + public void hierarchyChanged(HierarchyEvent e) { + if((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && + splitter.isShowing()) { + splitter.removeHierarchyListener(this); + setDividerLocation(splitter, proportion); + } + } + }); + } + return splitter; + } + + private static final long serialVersionUID = -8650495368920680024L; + ArrayList lnData = new ArrayList(); + String name; + ClassNode cn; + JSplitPane sp; + JSplitPane sp2; + JEditorPane bytecode = new JEditorPane(), decomp = new JEditorPane(); + JScrollPane bcScroll; + + public ClassViewer(final String name, final ClassNode cn) { + sourcePane = BytecodeViewer.viewer.sourcePane.isSelected(); + bytecodePane = BytecodeViewer.viewer.bytecodePane.isSelected(); + hexPane = BytecodeViewer.viewer.hexPane.isSelected(); + boolean bytecodeSyntax = BytecodeViewer.viewer.bycSyntax.isSelected(); + boolean sourcecodeSyntax = BytecodeViewer.viewer.srcSyntax.isSelected(); + this.name = name; + this.cn = cn; + this.setName(name); + this.setLayout(new BorderLayout()); + + final JPanel dcPanel = new JPanel(new BorderLayout()); + final JScrollPane dcScroll = new JScrollPane(decomp); + if(sourcePane) { + dcPanel.add(dcScroll, BorderLayout.CENTER); + } + + final JPanel bcPanel = new JPanel(new BorderLayout()); + if(bytecodePane) { + bcScroll = new JScrollPane(bytecode); + } else { + bcScroll = new JScrollPane(); + } + + bcPanel.add(bcScroll, BorderLayout.CENTER); + + if(bytecodePane && bytecodeSyntax) + bytecode.setContentType("text/java"); + + + if(sourcePane && sourcecodeSyntax) + decomp.setContentType("text/java"); + + this.sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, dcPanel, bcPanel); + final ClassWriter cw = new ClassWriter(0); + cn.accept(cw); + JHexEditor hex = new JHexEditor(cw.toByteArray()); + JScrollPane penis; + if(hexPane) { + penis = new JScrollPane(hex); + } else { + penis = new JScrollPane(); + } + penis.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER); + this.sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp, penis); + this.add(sp2, BorderLayout.CENTER); + + hex.setMaximumSize(new Dimension(0, Integer.MAX_VALUE)); + hex.setSize(0, Integer.MAX_VALUE); + resetDivider(); + BytecodeViewer.viewer.setIcon(true); + bytecode.setText("Decompiling, please wait.."); + decomp.setText("Decompiling, please wait.."); + startPaneUpdater(); + this.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + resetDivider(); + } + }); + } + + public void resetDivider() { + if(!sourcePane) { + sp.setResizeWeight(0); + } else if(!bytecodePane) { + sp.setResizeWeight(1); + } else { + sp.setResizeWeight(0.5); + } + if(hexPane) { + if(!sourcePane && !bytecodePane) + sp2 = setDividerLocation(sp2, 0); + else + sp2 = setDividerLocation(sp2, 0.7); + } else { + sp2 = setDividerLocation(sp2, 1); + } + } + + PaneUpdaterThread t; + public void startPaneUpdater() { + t = new PaneUpdaterThread(bytecode, decomp) { + @Override + public void doShit() { + final BytecodeDecompiler bc_dc = new BytecodeDecompiler(); + final FernFlowerDecompiler ff_dc = new FernFlowerDecompiler(); + + final String b = bc_dc.decompileClassNode(cn); + final String s = ff_dc.decompileClassNode(cn); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if(bytecodePane) + p1.setText(b); + if(sourcePane) + p2.setText(s); + p1.setCaretPosition(0); + p2.setCaretPosition(0); + BytecodeViewer.viewer.setIcon(false); + } + }); + } + + }; + t.start(); + } + + public static class MethodData { + public String name, desc; + public int srcLN, bytecodeLN; + + @Override + public boolean equals(final Object o) { + return equals((MethodData) o); + } + + public boolean equals(final MethodData md) { + return this.name.equals(md.name) && this.desc.equals(md.desc); + } + + public String constructPattern() { + final StringBuffer pattern = new StringBuffer(); + pattern.append(name + " *\\("); + final org.objectweb.asm.Type[] types = org.objectweb.asm.Type + .getArgumentTypes(desc); + pattern.append("(.*)"); + for (int i = 0; i < types.length; i++) { + final Type type = types[i]; + final String clazzName = type.getClassName(); + pattern.append(clazzName.substring(clazzName.lastIndexOf(".") + 1) + + "(.*)"); + } + pattern.append("\\) *\\{"); + return pattern.toString(); + } + } + + class WrapEditorKit extends StyledEditorKit { + private static final long serialVersionUID = 1719109651258205346L; + ViewFactory defaultFactory = new WrapColumnFactory(); + + @Override + public ViewFactory getViewFactory() { + return defaultFactory; + } + } + + class WrapColumnFactory implements ViewFactory { + public View create(final Element elem) { + final String kind = elem.getName(); + if (kind != null) { + if (kind.equals(AbstractDocument.ParagraphElementName)) + return new NoWrapParagraphView(elem); + else if (kind.equals(AbstractDocument.SectionElementName)) + return new BoxView(elem, View.Y_AXIS); + else if (kind.equals(StyleConstants.ComponentElementName)) + return new ComponentView(elem); + else if (kind.equals(StyleConstants.IconElementName)) + return new IconView(elem); + } + + // default to text display + return new LabelView(elem); + } + } + + public class NoWrapParagraphView extends ParagraphView { + public NoWrapParagraphView(final Element elem) { + super(elem); + } + + @Override + public void layout(final int width, final int height) { + super.layout(Short.MAX_VALUE, height); + } + + @Override + public float getMinimumSpan(final int axis) { + return super.getPreferredSpan(axis); + } + } + + +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/ExportJar.java b/src/the/bytecode/club/bytecodeviewer/gui/ExportJar.java new file mode 100644 index 00000000..33aeab32 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/ExportJar.java @@ -0,0 +1,54 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import javax.swing.JFrame; +import java.awt.Dimension; +import javax.swing.JButton; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.JarUtils; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +import javax.swing.BoxLayout; +import javax.swing.JScrollPane; +import javax.swing.JLabel; +import javax.swing.JTextArea; + +public class ExportJar extends JFrame { + public ExportJar(final String jarPath) { + setSize(new Dimension(250, 277)); + setResizable(false); + setTitle("Save As Jar.."); + + JButton btnNewButton = new JButton("Save As Jar.."); + btnNewButton.setMaximumSize(new Dimension(999, 23)); + btnNewButton.setMinimumSize(new Dimension(999, 23)); + btnNewButton.setSize(new Dimension(999, 0)); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + + JScrollPane scrollPane = new JScrollPane(); + getContentPane().add(scrollPane); + + JLabel lblMetainfmanifestmf = new JLabel("META-INF/MANIFEST.MF:"); + scrollPane.setColumnHeaderView(lblMetainfmanifestmf); + + final JTextArea mani = new JTextArea(); + mani.setText("Manifest-Version: 1.0\r\nClass-Path: .\r\nMain-Class: "); + scrollPane.setViewportView(mani); + getContentPane().add(btnNewButton); + + btnNewButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + BytecodeViewer.viewer.setC(true); + JarUtils.saveAsJar(BytecodeViewer.getLoadedClasses(), jarPath, mani.getText()); + BytecodeViewer.viewer.setC(false); + dispose(); + } + }); + + this.setLocationRelativeTo(null); + } + + private static final long serialVersionUID = -2662514582647810868L; +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java b/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java new file mode 100644 index 00000000..c2ffa9ec --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/FileNavigationPane.java @@ -0,0 +1,366 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import java.awt.BorderLayout; +import java.awt.font.FontRenderContext; +import java.awt.geom.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map.Entry; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.*; + +@SuppressWarnings("serial") +public class FileNavigationPane extends VisibleComponent implements FileDrop.Listener { + + FileChangeNotifier fcn; + JCheckBox exact = new JCheckBox("Exact"); + + MyTreeNode treeRoot = new MyTreeNode("Root"); + MyTree tree; + + public FileNavigationPane(final FileChangeNotifier fcn) { + super("ClassNavigation"); + setTitle("Files"); + + this.fcn = fcn; + + getContentPane().setLayout(new BorderLayout()); + + this.tree = new MyTree(treeRoot); + getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER); + + this.tree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(final TreeSelectionEvent arg0) { + final TreePath path = arg0.getPath(); + if (((TreeNode)path.getLastPathComponent()).getChildCount() > 0) + return; + final StringBuffer nameBuffer = new StringBuffer(); + for (int i = 1;i < path.getPathCount(); i++) { + nameBuffer.append(path.getPathComponent(i)); + if (i < path.getPathCount()-1) { + nameBuffer.append("/"); + } + } + final ClassNode cn = BytecodeViewer.getClassNode(nameBuffer.toString()); + if (cn != null) { + openClassFileToWorkSpace(nameBuffer.toString(), cn); + } + } + }); + + final String quickSearchText = "Quick class search"; + + final JTextField quickSearch = new JTextField(quickSearchText); + quickSearch.setForeground(Color.gray); + quickSearch.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(final KeyEvent ke) { + if (ke.getKeyCode() == KeyEvent.VK_ENTER) { + + final String qt = quickSearch.getText(); + quickSearch.setText(""); + + String[] path = null; + + if (qt.contains(".")) { + path = qt.split("\\."); + } + else { + path = new String[] {qt}; + } + + MyTreeNode curNode = treeRoot; + pathLoop: + for (int i = 0;i < path.length; i++) { + final String pathName = path[i]; + final boolean isLast = i == path.length-1; + + for (int c = 0; c < curNode.getChildCount(); c++) { + final MyTreeNode child = (MyTreeNode) curNode.getChildAt(c); + + if(!exact.isSelected()) { + if (((String)child.getUserObject()).toLowerCase().contains(pathName.toLowerCase())) { + curNode = child; + if (isLast) { + final TreePath pathn = new TreePath(curNode.getPath()); + tree.setSelectionPath(pathn); + tree.makeVisible(pathn); + tree.scrollPathToVisible(pathn); + System.out.println("Found! " + curNode); + break pathLoop; + } + continue pathLoop; + } + } else { + if (((String)child.getUserObject()).equals(pathName)) { + curNode = child; + if (isLast) { + final TreePath pathn = new TreePath(curNode.getPath()); + tree.setSelectionPath(pathn); + tree.makeVisible(pathn); + tree.scrollPathToVisible(pathn); + System.out.println("Found! " + curNode); + break pathLoop; + } + continue pathLoop; + } + } + } + + System.out.println("Could not find " + pathName); + break; + } + + } + } + }); + quickSearch.addFocusListener(new FocusListener() { + @Override + public void focusGained(final FocusEvent arg0) { + if (quickSearch.getText().equals(quickSearchText)) { + quickSearch.setText(""); + quickSearch.setForeground(Color.black); + } + } + @Override + public void focusLost(final FocusEvent arg0) { + if (quickSearch.getText().isEmpty()) { + quickSearch.setText(quickSearchText); + quickSearch.setForeground(Color.gray); + } + } + }); + + JPanel p2 = new JPanel(); + p2.setLayout(new BorderLayout()); + p2.add(quickSearch, BorderLayout.NORTH); + p2.add(exact, BorderLayout.SOUTH); + + getContentPane().add(p2, BorderLayout.SOUTH); + + this.setVisible(true); + new FileDrop(this, this); + } + + public void openClassFileToWorkSpace(final String name, final ClassNode node) { + fcn.openClassFile(name, node); + } + + @Override + public void filesDropped(final File[] files) { + if (files.length < 1) + return; + BytecodeViewer.openFiles(files); + } + + public void updateTree() { + treeRoot.removeAllChildren(); + for (final Entry entry : BytecodeViewer.loadedClasses.entrySet()) { + String name = entry.getKey(); + final String[] spl = name.split("\\/"); + if (spl.length < 2) { + treeRoot.add(new MyTreeNode(name)); + } + else { + MyTreeNode parent = treeRoot; + for (final String s : spl) { + 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) { + child = new MyTreeNode(s); + parent.add(child); + } + parent = child; + } + } + } + + + treeRoot.sort(); + tree.expandPath(new TreePath(tree.getModel().getRoot())); + tree.updateUI(); + //expandAll(tree, true); + } + + // If expand is true, expands all nodes in the tree. + // Otherwise, collapses all nodes in the tree. + public void expandAll(final JTree tree, final boolean expand) { + final TreeNode root = (TreeNode) tree.getModel().getRoot(); + + // Traverse tree from root + expandAll(tree, new TreePath(root), expand); + } + + @SuppressWarnings("rawtypes") + private void expandAll(final JTree tree, final TreePath parent, + final boolean expand) { + // Traverse children + final TreeNode node = (TreeNode) parent.getLastPathComponent(); + if (node.getChildCount() >= 0) { + for (final Enumeration e = node.children(); e.hasMoreElements();) { + final TreeNode n = (TreeNode) e.nextElement(); + final TreePath path = parent.pathByAddingChild(n); + expandAll(tree, path, expand); + } + } + + // Expansion or collapse must be done bottom-up + if (expand) { + tree.expandPath(parent); + } else { + tree.collapsePath(parent); + } + } + + public class MyTree extends JTree { + + private static final long serialVersionUID = -2355167326094772096L; + DefaultMutableTreeNode treeRoot; + + public MyTree(final DefaultMutableTreeNode treeRoot) { + super(treeRoot); + this.treeRoot = treeRoot; + } + + StringMetrics m = null; + + @Override + public void paint(final Graphics g) { + super.paint(g); + if(m == null) { + m = new StringMetrics((Graphics2D)g); + } + if (treeRoot.getChildCount() < 1) { + g.setColor(new Color(0, 0, 0, 100)); + g.fillRect(0, 0, getWidth(), getHeight()); + g.setColor(Color.white); + String s = "Drag class/jar here"; + g.drawString(s, ((int)((getWidth()/2)-(m.getWidth(s)/2))), getHeight()/2); + } + } + } + + public class MyTreeNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = -8817777566176729571L; + + public MyTreeNode(final Object o) { + super(o); + } + + @Override + public void insert(final MutableTreeNode newChild, final int childIndex) { + super.insert(newChild, childIndex); + } + + public void sort() { + recursiveSort(this); + } + + @SuppressWarnings("unchecked") + private void recursiveSort(final MyTreeNode node) { + Collections.sort(node.children, nodeComparator); + final Iterator it = node.children.iterator(); + while (it.hasNext()) { + final MyTreeNode nextNode = it.next(); + if (nextNode.getChildCount() > 0) { + recursiveSort(nextNode); + } + } + } + + protected Comparator nodeComparator = new Comparator () { + @Override + public int compare(final MyTreeNode o1, final MyTreeNode o2) { + // To make sure nodes with children are always on top + final int firstOffset = o1.getChildCount() > 0 ? -1000 : 0; + final int secondOffset = o2.getChildCount() > 0 ? 1000 : 0; + return o1.toString().compareToIgnoreCase(o2.toString()) + firstOffset + secondOffset; + } + + @Override + public boolean equals(final Object obj) { + return false; + } + + @Override + public int hashCode() { + final int hash = 7; + return hash; + } + }; + } + + /** + * + * @author http://stackoverflow.com/a/18450804 + * + */ + class StringMetrics { + + Font font; + FontRenderContext context; + + public StringMetrics(Graphics2D g2) { + + font = g2.getFont(); + context = g2.getFontRenderContext(); + } + + Rectangle2D getBounds(String message) { + + return font.getStringBounds(message, context); + } + + double getWidth(String message) { + + Rectangle2D bounds = getBounds(message); + return bounds.getWidth(); + } + + double getHeight(String message) { + + Rectangle2D bounds = getBounds(message); + return bounds.getHeight(); + } + + } + + public void resetWorkspace() { + treeRoot.removeAllChildren(); + tree.repaint(); + tree.updateUI(); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java b/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java new file mode 100644 index 00000000..ced5664a --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java @@ -0,0 +1,584 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.BoxLayout; +import javax.swing.JMenuBar; +import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; + +import javax.swing.JScrollPane; +import javax.swing.filechooser.FileFilter; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JSeparator; +import javax.swing.JCheckBoxMenuItem; + +import org.apache.commons.codec.binary.Base64; +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.FileChangeNotifier; +import the.bytecode.club.bytecodeviewer.JarUtils; +import the.bytecode.club.bytecodeviewer.decompilers.java.FernFlowerDecompiler; +import the.bytecode.club.bytecodeviewer.plugins.AllatoriStringDecrypter; +import the.bytecode.club.bytecodeviewer.plugins.PluginManager; +import the.bytecode.club.bytecodeviewer.plugins.ShowAllStrings; +import the.bytecode.club.bytecodeviewer.plugins.ShowMainMethods; +import the.bytecode.club.bytecodeviewer.plugins.ZKMStringDecrypter; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.ArrayList; + + +public class MainViewerGUI extends JFrame implements FileChangeNotifier { + + private static final long serialVersionUID = 1851409230530948543L; + public JCheckBoxMenuItem debugHelpers = new JCheckBoxMenuItem("Debug Helpers"); + public JCheckBoxMenuItem debugInstructions = new JCheckBoxMenuItem("Debug Instructions"); + private JSplitPane sp1; + private JSplitPane sp2; + static ArrayList rfComps = new ArrayList(); + public JCheckBoxMenuItem rbr = new JCheckBoxMenuItem("Hide bridge methods"); + public JCheckBoxMenuItem rsy = new JCheckBoxMenuItem("Hide synthetic class members"); + public JCheckBoxMenuItem din = new JCheckBoxMenuItem("Decompile inner classes"); + public JCheckBoxMenuItem dc4 = new JCheckBoxMenuItem("Collapse 1.4 class references"); + public JCheckBoxMenuItem das = new JCheckBoxMenuItem("Decompile assertions"); + public JCheckBoxMenuItem hes = new JCheckBoxMenuItem("Hide empty super invocation"); + public JCheckBoxMenuItem hdc = new JCheckBoxMenuItem("Hide empty default constructor"); + public JCheckBoxMenuItem dgs = new JCheckBoxMenuItem("Decompile generic signatures"); + public JCheckBoxMenuItem ner = new JCheckBoxMenuItem("Assume return not throwing exceptions"); + public JCheckBoxMenuItem den = new JCheckBoxMenuItem("Decompile enumerations"); + public JCheckBoxMenuItem rgn = new JCheckBoxMenuItem("Remove getClass() invocation"); + public JCheckBoxMenuItem bto = new JCheckBoxMenuItem("Interpret int 1 as boolean true"); + public JCheckBoxMenuItem nns = new JCheckBoxMenuItem("Allow for not set synthetic attribute"); + public JCheckBoxMenuItem uto = new JCheckBoxMenuItem("Consider nameless types as java.lang.Object"); + public JCheckBoxMenuItem udv = new JCheckBoxMenuItem("Reconstruct variable names from debug info"); + public JCheckBoxMenuItem rer = new JCheckBoxMenuItem("Remove empty exception ranges"); + public JCheckBoxMenuItem fdi = new JCheckBoxMenuItem("Deinline finally structures"); + public JCheckBoxMenuItem asc = new JCheckBoxMenuItem("Allow only ASCII characters in strings"); + private final JSeparator separator_2 = new JSeparator(); + public JCheckBoxMenuItem srcSyntax = new JCheckBoxMenuItem("Source Code Syntax"); + public JCheckBoxMenuItem bycSyntax = new JCheckBoxMenuItem("Bytecode Syntax"); + JCheckBoxMenuItem sourcePane = new JCheckBoxMenuItem("Source Pane"); + JCheckBoxMenuItem bytecodePane = new JCheckBoxMenuItem("Bytecode Pane"); + JCheckBoxMenuItem hexPane = new JCheckBoxMenuItem("Hex Pane"); + private final JMenuItem mntmNewWorkspace = new JMenuItem("New Workspace"); + public JMenu mnRecentFiles = new JMenu("Recent Files"); + private final JMenuItem mntmNewMenuItem = new JMenuItem("Save Java Files As.."); + private final JMenuItem mntmAbout = new JMenuItem("About"); + private AboutWindow aboutWindow = new AboutWindow(); + private final JSeparator separator_3 = new JSeparator(); + private final JMenu mnNewMenu_1 = new JMenu("Plugins"); + private final JMenuItem mntmStartExternalPlugin = new JMenuItem("Open Plugin.."); + private final JSeparator separator_4 = new JSeparator(); + public JMenu mnRecentPlugins = new JMenu("Recent Plugins"); + private final JSeparator separator_5 = new JSeparator(); + private final JMenuItem mntmStartZkmString = new JMenuItem("ZKM String Decrypter"); + private final JMenuItem mntmNewMenuItem_1 = new JMenuItem("Malicious Code Scanner"); + private final JMenuItem mntmNewMenuItem_2 = new JMenuItem("Allatori String Decrypter"); + private final JMenuItem mntmShowAllStrings = new JMenuItem("Show All Strings"); + private final JMenuItem mntmShowMainMethods = new JMenuItem("Show Main Methods"); + private final JMenuItem mntmNewMenuItem_3 = new JMenuItem("Save As Jar.."); + private JMenuBar menuBar = new JMenuBar(); + public JCheckBoxMenuItem chckbxmntmNewCheckItem = new JCheckBoxMenuItem("Allow only ASCII characters in strings"); + private final JMenuItem mntmReplaceStrings = new JMenuItem("Replace Strings"); + private final JMenuItem mntmNewMenuItem_4 = new JMenuItem(""); + + public void setC(boolean busy) { + if(busy) { + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + for(Component c : this.getComponents()) + c.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + sp1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + sp2.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + for(VisibleComponent c : rfComps) { + c.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + if(c instanceof WorkPane) { + WorkPane w = (WorkPane)c; + for(Component c2 : w.tabs.getComponents()) + c2.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + } + } else { + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + for(Component c : this.getComponents()) + c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + + sp1.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + sp2.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + + for(VisibleComponent c : rfComps) { + c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if(c instanceof WorkPane) { + WorkPane w = (WorkPane)c; + for(Component c2 : w.tabs.getComponents()) + c2.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + } + } + + public void setIcon(final boolean busy) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if(busy) { + try { + mntmNewMenuItem_4.setIcon(new ImageIcon(getClass().getResource("/resources/1.gif"))); + } catch(NullPointerException e) { + mntmNewMenuItem_4.setIcon(new ImageIcon(b642IMG("R0lGODlhEAALAPQAAP///wAAANra2tDQ0Orq6gcHBwAAAC8vL4KCgmFhYbq6uiMjI0tLS4qKimVlZb6+vicnJwUFBU9PT+bm5tjY2PT09Dk5Odzc3PLy8ra2tqCgoMrKyu7u7gAAAAAAAAAAACH5BAkLAAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAALAAAFLSAgjmRpnqSgCuLKAq5AEIM4zDVw03ve27ifDgfkEYe04kDIDC5zrtYKRa2WQgAh+QQJCwAAACwAAAAAEAALAAAFJGBhGAVgnqhpHIeRvsDawqns0qeN5+y967tYLyicBYE7EYkYAgAh+QQJCwAAACwAAAAAEAALAAAFNiAgjothLOOIJAkiGgxjpGKiKMkbz7SN6zIawJcDwIK9W/HISxGBzdHTuBNOmcJVCyoUlk7CEAAh+QQJCwAAACwAAAAAEAALAAAFNSAgjqQIRRFUAo3jNGIkSdHqPI8Tz3V55zuaDacDyIQ+YrBH+hWPzJFzOQQaeavWi7oqnVIhACH5BAkLAAAALAAAAAAQAAsAAAUyICCOZGme1rJY5kRRk7hI0mJSVUXJtF3iOl7tltsBZsNfUegjAY3I5sgFY55KqdX1GgIAIfkECQsAAAAsAAAAABAACwAABTcgII5kaZ4kcV2EqLJipmnZhWGXaOOitm2aXQ4g7P2Ct2ER4AMul00kj5g0Al8tADY2y6C+4FIIACH5BAkLAAAALAAAAAAQAAsAAAUvICCOZGme5ERRk6iy7qpyHCVStA3gNa/7txxwlwv2isSacYUc+l4tADQGQ1mvpBAAIfkECQsAAAAsAAAAABAACwAABS8gII5kaZ7kRFGTqLLuqnIcJVK0DeA1r/u3HHCXC/aKxJpxhRz6Xi0ANAZDWa+kEAA7"), "")); + } + } else + mntmNewMenuItem_4.setIcon(null); + mntmNewMenuItem_4.updateUI(); + } + }); + } + + /** + * Decodes a Base64 String as a BufferedImage + */ + public BufferedImage b642IMG(String imageString) { + BufferedImage image = null; + byte[] imageByte; + + try { + imageByte = Base64.decodeBase64(imageString); + ByteArrayInputStream bis = new ByteArrayInputStream(imageByte); + image = ImageIO.read(bis); + bis.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return image; + } + + public MainViewerGUI() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + rbr.setSelected(true); + rsy.setSelected(false); + din.setSelected(true); + dc4.setSelected(true); + das.setSelected(true); + hes.setSelected(true); + hdc.setSelected(true); + dgs.setSelected(false); + ner.setSelected(true); + den.setSelected(true); + rgn.setSelected(true); + bto.setSelected(true); + nns.setSelected(true); + uto.setSelected(true); + udv.setSelected(true); + rer.setSelected(true); + fdi.setSelected(true); + asc.setSelected(false); + srcSyntax.setSelected(true); + bycSyntax.setSelected(true); + debugHelpers.setSelected(true); + sourcePane.setSelected(true); + bytecodePane.setSelected(true); + + setJMenuBar(menuBar); + + JMenu mnNewMenu = new JMenu("File"); + menuBar.add(mnNewMenu); + + final JFrame This = this; + mntmNewWorkspace.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + BytecodeViewer.resetWorkSpace(); + } + }); + + JMenuItem mntmLoadJar = new JMenuItem("Add.."); + mntmLoadJar.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + fc.setFileFilter(new JarZipClassFileFilter()); + fc.setFileHidingEnabled(false); + fc.setAcceptAllFileFilterUsed(false); + int returnVal = fc.showOpenDialog(This); + + if (returnVal == JFileChooser.APPROVE_OPTION) + try { + BytecodeViewer.viewer.setC(true); + BytecodeViewer.openFiles(new File[]{fc.getSelectedFile()}); + BytecodeViewer.viewer.setC(false); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + mnNewMenu.add(mntmLoadJar); + + mnNewMenu.add(mntmNewWorkspace); + + JMenuItem mntmSave = new JMenuItem("Save Files As.."); + mntmSave.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + JFileChooser fc = new JFileChooser(); + fc.setFileFilter(new ZipFileFilter()); + fc.setFileHidingEnabled(false); + fc.setAcceptAllFileFilterUsed(false); + int returnVal = fc.showSaveDialog(MainViewerGUI.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + BytecodeViewer.viewer.setC(true); + JarUtils.saveAsJar(BytecodeViewer.getLoadedClasses(), file.getAbsolutePath()); + BytecodeViewer.viewer.setC(false); + } + } + }); + + mnNewMenu.add(separator_3); + mntmNewMenuItem_3.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + fc.setFileFilter(new JarFileFilter()); + fc.setFileHidingEnabled(false); + fc.setAcceptAllFileFilterUsed(false); + int returnVal = fc.showSaveDialog(MainViewerGUI.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + new ExportJar(file.getAbsolutePath()).setVisible(true); + } + } + }); + + mnNewMenu.add(mntmNewMenuItem_3); + mnNewMenu.add(mntmSave); + mntmNewMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + JFileChooser fc = new JFileChooser(); + fc.setFileFilter(new ZipFileFilter()); + fc.setFileHidingEnabled(false); + fc.setAcceptAllFileFilterUsed(false); + int returnVal = fc.showSaveDialog(MainViewerGUI.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + BytecodeViewer.viewer.setC(true); + FernFlowerDecompiler d = new FernFlowerDecompiler(); + d.decompileToZip(file.getAbsolutePath()); + BytecodeViewer.viewer.setC(false); + } + } + }); + + mnNewMenu.add(mntmNewMenuItem); + + JSeparator separator = new JSeparator(); + mnNewMenu.add(separator); + + mnNewMenu.add(mnRecentFiles); + + JSeparator separator_1 = new JSeparator(); + mnNewMenu.add(separator_1); + mntmAbout.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + aboutWindow.setVisible(true); + } + }); + + mnNewMenu.add(mntmAbout); + + JMenuItem mntmExit = new JMenuItem("Exit"); + mnNewMenu.add(mntmExit); + + JMenu mnView = new JMenu("View"); + menuBar.add(mnView); + + mnView.add(sourcePane); + mnView.add(bytecodePane); + mnView.add(hexPane); + + mnView.add(separator_2); + + mnView.add(srcSyntax); + + mnView.add(bycSyntax); + + JMenu mnDecompilerSettings = new JMenu("Java Decompiler"); + menuBar.add(mnDecompilerSettings); + mnDecompilerSettings.add(rbr); + mnDecompilerSettings.add(rsy); + mnDecompilerSettings.add(din); + mnDecompilerSettings.add(dc4); + mnDecompilerSettings.add(das); + mnDecompilerSettings.add(hes); + mnDecompilerSettings.add(hdc); + mnDecompilerSettings.add(dgs); + mnDecompilerSettings.add(ner); + mnDecompilerSettings.add(den); + mnDecompilerSettings.add(rgn); + mnDecompilerSettings.add(bto); + mnDecompilerSettings.add(nns); + mnDecompilerSettings.add(uto); + mnDecompilerSettings.add(udv); + mnDecompilerSettings.add(rer); + mnDecompilerSettings.add(fdi); + mnDecompilerSettings.add(asc); + + JMenu mnBytecodeDecompilerSettings = new JMenu("Bytecode Decompiler"); + menuBar.add(mnBytecodeDecompilerSettings); + + mnBytecodeDecompilerSettings.add(debugHelpers); + + mnBytecodeDecompilerSettings.add(debugInstructions); + + mnBytecodeDecompilerSettings.add(chckbxmntmNewCheckItem); + + menuBar.add(mnNewMenu_1); + mnNewMenu_1.add(mntmStartExternalPlugin); + mnNewMenu_1.add(separator_4); + mnNewMenu_1.add(mnRecentPlugins); + mnNewMenu_1.add(separator_5); + mnNewMenu_1.add(mntmNewMenuItem_1); + mnNewMenu_1.add(mntmShowMainMethods); + mnNewMenu_1.add(mntmShowAllStrings); + mntmReplaceStrings.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if(!BytecodeViewer.loadedClasses.isEmpty()) + new ReplaceStringsOptions().setVisible(true); + else + System.out.println("Plugin not ran, put some classes in first."); + } + }); + + mnNewMenu_1.add(mntmReplaceStrings); + mnNewMenu_1.add(mntmNewMenuItem_2); + mnNewMenu_1.add(mntmStartZkmString); + + menuBar.add(mntmNewMenuItem_4); + + + mntmStartExternalPlugin.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + JFileChooser fc = new JFileChooser(); + fc.setFileFilter(new GroovyPythonRubyFileFilter()); + fc.setFileHidingEnabled(false); + fc.setAcceptAllFileFilterUsed(false); + int returnVal = fc.showOpenDialog(This); + + if (returnVal == JFileChooser.APPROVE_OPTION) + try { + BytecodeViewer.viewer.setC(true); + BytecodeViewer.startPlugin(fc.getSelectedFile()); + BytecodeViewer.viewer.setC(false); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + mntmStartZkmString.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PluginManager.runPlugin(new ZKMStringDecrypter()); + } + }); + mntmNewMenuItem_2.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PluginManager.runPlugin(new AllatoriStringDecrypter()); + } + }); + mntmNewMenuItem_1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if(!BytecodeViewer.loadedClasses.isEmpty()) + new MaliciousCodeScannerOptions().setVisible(true); + else + System.out.println("Plugin not ran, put some classes in first."); + } + }); + mntmShowAllStrings.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PluginManager.runPlugin(new ShowAllStrings()); + } + }); + + mntmShowMainMethods.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PluginManager.runPlugin(new ShowMainMethods()); + } + }); + + setSize(new Dimension(800, 400)); + setTitle("Bytecode Viewer - http://the.bytecode.club - @Konloch"); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.X_AXIS)); + + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setMaximumSize(new Dimension(12000, 32767)); + //scrollPane.setViewportView(tree); + FileNavigationPane cn = new FileNavigationPane(this); + cn.setMinimumSize(new Dimension(200, 50)); + //panel.add(cn); + SearchingPane s = new SearchingPane(this); + s.setPreferredSize(new Dimension(200, 50)); + s.setMinimumSize(new Dimension(200, 50)); + s.setMaximumSize(new Dimension(200, 2147483647)); + //panel.add(s); + sp1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, cn, s); + //panel.add(sp1); + cn.setPreferredSize(new Dimension(200, 50)); + cn.setMaximumSize(new Dimension(200, 2147483647)); + WorkPane cv = new WorkPane(this); + sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp1, cv); + getContentPane().add(sp2); + sp2.setResizeWeight(0.05); + sp1.setResizeWeight(0.5); + rfComps.add(cn); + + rfComps.add(s); + rfComps.add(cv); + this.setLocationRelativeTo(null); + } + + @Override + public void openClassFile(final String name, final ClassNode cn) { + for (final VisibleComponent vc : rfComps) { + vc.openClassFile(name, cn); + } + } + + @SuppressWarnings("unchecked") + public static T getComponent(final Class clazz) { + for (final VisibleComponent vc : rfComps) { + if (vc.getClass() == clazz) + return (T) vc; + } + return null; + } + + public class GroovyPythonRubyFileFilter extends FileFilter { + @Override + public boolean accept(File f) { + if (f.isDirectory()) + return true; + + String extension = getExtension(f); + if (extension != null) + return (extension.equals("gy") || extension.equals("groovy") || + extension.equals("py") || extension.equals("python") || + extension.equals("rb") || extension.equals("ruby")); + + return false; + } + + @Override + public String getDescription() { + return "Groovy, Python or Ruby plugins."; + } + + public String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) + ext = s.substring(i+1).toLowerCase(); + + return ext; + } + } + + public class JarZipClassFileFilter extends FileFilter { + @Override + public boolean accept(File f) { + if (f.isDirectory()) + return true; + + String extension = getExtension(f); + if (extension != null) + return (extension.equals("jar") || extension.equals("zip") || extension.equals("class")); + + return false; + } + + @Override + public String getDescription() { + return "Class Files or Zip/Jar Archives"; + } + + public String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) + ext = s.substring(i+1).toLowerCase(); + + return ext; + } + } + + public class ZipFileFilter extends FileFilter { + @Override + public boolean accept(File f) { + if (f.isDirectory()) + return true; + + String extension = getExtension(f); + if (extension != null) + return (extension.equals("zip")); + + return false; + } + + @Override + public String getDescription() { + return "Zip Archives"; + } + + public String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) + ext = s.substring(i+1).toLowerCase(); + + return ext; + } + } + + public class JarFileFilter extends FileFilter { + @Override + public boolean accept(File f) { + if (f.isDirectory()) + return true; + + String extension = getExtension(f); + if (extension != null) + return (extension.equals("jar")); + + return false; + } + + @Override + public String getDescription() { + return "Jar Archives"; + } + + public String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) + ext = s.substring(i+1).toLowerCase(); + + return ext; + } + } +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/gui/MaliciousCodeScannerOptions.java b/src/the/bytecode/club/bytecodeviewer/gui/MaliciousCodeScannerOptions.java new file mode 100644 index 00000000..e3342a9d --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/MaliciousCodeScannerOptions.java @@ -0,0 +1,78 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import javax.swing.JFrame; + +import java.awt.Dimension; + +import javax.swing.JCheckBox; +import javax.swing.JButton; + +import the.bytecode.club.bytecodeviewer.plugins.MaliciousCodeScanner; +import the.bytecode.club.bytecodeviewer.plugins.PluginManager; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +public class MaliciousCodeScannerOptions extends JFrame { + public MaliciousCodeScannerOptions() { + setSize(new Dimension(250, 277)); + setResizable(false); + setTitle("Malicious Code Scanner Options"); + getContentPane().setLayout(null); + + final JCheckBox chckbxJavalangreflection = new JCheckBox("java/lang/reflection"); + chckbxJavalangreflection.setSelected(true); + chckbxJavalangreflection.setBounds(6, 7, 232, 23); + getContentPane().add(chckbxJavalangreflection); + + final JCheckBox chckbxJavanet = new JCheckBox("java/net"); + chckbxJavanet.setSelected(true); + chckbxJavanet.setBounds(6, 59, 232, 23); + getContentPane().add(chckbxJavanet); + + final JCheckBox chckbxJavaio = new JCheckBox("java/io"); + chckbxJavaio.setBounds(6, 85, 232, 23); + getContentPane().add(chckbxJavaio); + + final JCheckBox chckbxJavalangruntime = new JCheckBox("java/lang/Runtime"); + chckbxJavalangruntime.setSelected(true); + chckbxJavalangruntime.setBounds(6, 33, 232, 23); + getContentPane().add(chckbxJavalangruntime); + + final JCheckBox chckbxLdcContainswww = new JCheckBox("LDC contains 'www.'"); + chckbxLdcContainswww.setSelected(true); + chckbxLdcContainswww.setBounds(6, 111, 232, 23); + getContentPane().add(chckbxLdcContainswww); + + final JCheckBox chckbxLdcContainshttp = new JCheckBox("LDC contains 'http://'"); + chckbxLdcContainshttp.setSelected(true); + chckbxLdcContainshttp.setBounds(6, 137, 232, 23); + getContentPane().add(chckbxLdcContainshttp); + + final JCheckBox chckbxLdcContainshttps = new JCheckBox("LDC contains 'https://'"); + chckbxLdcContainshttps.setSelected(true); + chckbxLdcContainshttps.setBounds(6, 163, 232, 23); + getContentPane().add(chckbxLdcContainshttps); + + final JCheckBox chckbxLdcMatchesIp = new JCheckBox("LDC matches IP regex"); + chckbxLdcMatchesIp.setSelected(true); + chckbxLdcMatchesIp.setBounds(6, 189, 232, 23); + getContentPane().add(chckbxLdcMatchesIp); + + JButton btnNewButton = new JButton("Start Scanning"); + btnNewButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + PluginManager.runPlugin(new MaliciousCodeScanner(chckbxJavalangreflection.isSelected(), + chckbxJavalangruntime.isSelected(), chckbxJavanet.isSelected(), chckbxJavaio.isSelected(), + chckbxLdcContainswww.isSelected(), chckbxLdcContainshttp.isSelected(), chckbxLdcContainshttps.isSelected(), + chckbxLdcMatchesIp.isSelected())); + dispose(); + } + }); + btnNewButton.setBounds(6, 219, 232, 23); + getContentPane().add(btnNewButton); + this.setLocationRelativeTo(null); + } + + private static final long serialVersionUID = -2662514582647810868L; +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/PaneUpdaterThread.java b/src/the/bytecode/club/bytecodeviewer/gui/PaneUpdaterThread.java new file mode 100644 index 00000000..f81a01af --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/PaneUpdaterThread.java @@ -0,0 +1,28 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import javax.swing.JEditorPane; + +/** + * Allows us to run a background thread then update the two JEditorPanes + * + * @author Konloch + * + */ + +public abstract class PaneUpdaterThread extends Thread { + + JEditorPane p1; + JEditorPane p2; + public PaneUpdaterThread(JEditorPane p1, JEditorPane p2) { + this.p1 = p1; + this.p2 = p2; + } + + public abstract void doShit(); + + @Override + public void run() { + doShit(); + } + +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/gui/ReplaceStringsOptions.java b/src/the/bytecode/club/bytecodeviewer/gui/ReplaceStringsOptions.java new file mode 100644 index 00000000..01097727 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/ReplaceStringsOptions.java @@ -0,0 +1,73 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import java.awt.Dimension; +import javax.swing.JFrame; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JTextField; + +import the.bytecode.club.bytecodeviewer.plugins.PluginManager; +import the.bytecode.club.bytecodeviewer.plugins.ReplaceStrings; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import javax.swing.JCheckBox; + +public class ReplaceStringsOptions extends JFrame { + public ReplaceStringsOptions() { + setSize(new Dimension(250, 176)); + setResizable(false); + setTitle("Replace Strings"); + getContentPane().setLayout(null); + + JButton btnNewButton = new JButton("Start Replacing"); + btnNewButton.setBounds(6, 115, 232, 23); + getContentPane().add(btnNewButton); + + JLabel lblNewLabel = new JLabel("Original LDC:"); + lblNewLabel.setBounds(6, 40, 67, 14); + getContentPane().add(lblNewLabel); + + textField = new JTextField(); + textField.setBounds(80, 37, 158, 20); + getContentPane().add(textField); + textField.setColumns(10); + + JLabel lblNewLabel_1 = new JLabel("New LDC:"); + lblNewLabel_1.setBounds(6, 65, 77, 14); + getContentPane().add(lblNewLabel_1); + + textField_1 = new JTextField(); + textField_1.setColumns(10); + textField_1.setBounds(80, 62, 158, 20); + getContentPane().add(textField_1); + + JLabel lblNewLabel_2 = new JLabel("Class:"); + lblNewLabel_2.setBounds(6, 90, 46, 14); + getContentPane().add(lblNewLabel_2); + + textField_2 = new JTextField(); + textField_2.setToolTipText("* will search all classes"); + textField_2.setText("*"); + textField_2.setBounds(80, 87, 158, 20); + getContentPane().add(textField_2); + textField_2.setColumns(10); + + final JCheckBox chckbxNewCheckBox = new JCheckBox("Replace All Contains"); + chckbxNewCheckBox.setToolTipText("If it's unticked, it will check if the string equals, if its ticked it will check if it contains, then replace the original LDC part of the string."); + chckbxNewCheckBox.setBounds(6, 7, 232, 23); + getContentPane().add(chckbxNewCheckBox); + btnNewButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + PluginManager.runPlugin(new ReplaceStrings(textField.getText(), textField_1.getText(), textField_2.getText(), chckbxNewCheckBox.isSelected())); + dispose(); + } + }); + this.setLocationRelativeTo(null); + } + + private static final long serialVersionUID = -2662514582647810868L; + private JTextField textField; + private JTextField textField_1; + private JTextField textField_2; +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/SearchingPane.java b/src/the/bytecode/club/bytecodeviewer/gui/SearchingPane.java new file mode 100644 index 00000000..a062a29f --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/SearchingPane.java @@ -0,0 +1,217 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import the.bytecode.club.bytecodeviewer.*; +import the.bytecode.club.bytecodeviewer.searching.*; + +/** + * A pane dedicating to searching the loaded files. + * + * @author Konloch + * @author WaterWolf + * + */ + +@SuppressWarnings("rawtypes") +public class SearchingPane extends VisibleComponent { + + private static final long serialVersionUID = -1098524689236993932L; + + FileChangeNotifier fcn; + + JCheckBox exact = new JCheckBox("Exact"); + DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode("Root"); + JTree tree; + + SearchType searchType = null; + JComboBox searchRadiusBox; + + public JButton search = new JButton("Search"); + BackgroundSearchThread t = new BackgroundSearchThread(true) { + @Override + public void doSearch() { + // empty + } + + }; + + @SuppressWarnings("unchecked") + public SearchingPane(final FileChangeNotifier fcn) { + super("Search"); + + this.fcn = fcn; + + final JPanel optionPanel = new JPanel(new BorderLayout()); + + final JPanel searchRadiusOpt = new JPanel(new BorderLayout()); + + final JPanel searchOpts = new JPanel(new GridLayout(2, 1)); + + searchRadiusOpt.add(new JLabel("Search from "), BorderLayout.WEST); + + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (final SearchRadius st : SearchRadius.values()) { + model.addElement(st); + } + + searchRadiusBox = new JComboBox(model); + + searchRadiusOpt.add(searchRadiusBox, BorderLayout.CENTER); + + searchOpts.add(searchRadiusOpt); + + model = new DefaultComboBoxModel(); + for (final SearchType st : SearchType.values()) { + model.addElement(st); + } + + final JComboBox typeBox = new JComboBox(model); + final JPanel searchOptPanel = new JPanel(); + + final ItemListener il = new ItemListener() { + @Override + public void itemStateChanged(final ItemEvent arg0) { + searchOptPanel.removeAll(); + searchType = (SearchType) typeBox.getSelectedItem(); + searchOptPanel.add(searchType.details.getPanel()); + + searchOptPanel.revalidate(); + searchOptPanel.repaint(); + } + }; + + typeBox.addItemListener(il); + + typeBox.setSelectedItem(SearchType.LDC); + il.itemStateChanged(null); + + searchOpts.add(typeBox); + + optionPanel.add(searchOpts, BorderLayout.NORTH); + + JPanel p2 = new JPanel(); + p2.setLayout(new BorderLayout()); + p2.add(searchOptPanel, BorderLayout.NORTH); + p2.add(exact, BorderLayout.SOUTH); + + optionPanel.add(p2, BorderLayout.CENTER); + + search.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent arg0) { + treeRoot.removeAllChildren(); + searchType = (SearchType) typeBox.getSelectedItem(); + final SearchRadius radius = (SearchRadius) searchRadiusBox.getSelectedItem(); + final SearchResultNotifier srn = new SearchResultNotifier() { + @Override + public void notifyOfResult(final ClassNode clazz, + final MethodNode method, final AbstractInsnNode insn) { + treeRoot.add(new DefaultMutableTreeNode(clazz.name + "." + method.name)); + } + }; + if (radius == SearchRadius.All_Classes) { + if(t.finished) { + t = new BackgroundSearchThread() { + @Override + public void doSearch() { + for (ClassNode cln : BytecodeViewer.getLoadedClasses()) + searchType.details.search(cln, srn, exact.isSelected()); + + MainViewerGUI.getComponent(SearchingPane.class).search.setEnabled(true); + MainViewerGUI.getComponent(SearchingPane.class).search.setText("Search"); + tree.expandPath(new TreePath(tree.getModel().getRoot())); + tree.updateUI(); + } + + }; + MainViewerGUI.getComponent(SearchingPane.class).search.setEnabled(false); + MainViewerGUI.getComponent(SearchingPane.class).search.setText("Searching, please wait.."); + t.start(); + } else { //this should really never be called. + BytecodeViewer.showMessage("You currently have a search performing in the background, please wait for that to finish."); + } + } + else if (radius == SearchRadius.Current_Class) { + final ClassViewer cv = MainViewerGUI.getComponent(WorkPane.class).getCurrentClass(); + if (cv != null) { + searchType.details.search(cv.cn, srn, exact.isSelected()); + } + } + } + }); + + optionPanel.add(search, BorderLayout.SOUTH); + + this.tree = new JTree(treeRoot); + + getContentPane().setLayout(new BorderLayout()); + + getContentPane().add(optionPanel, BorderLayout.NORTH); + getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER); + + this.tree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(final TreeSelectionEvent arg0) { + final TreePath path = arg0.getPath(); + if ( ((TreeNode)path.getLastPathComponent()).getChildCount() > 0) + return; + final String clazzName = path.getLastPathComponent().toString(); + final ClassNode fN = BytecodeViewer.getClassNode(clazzName); + if (fN != null) { + MainViewerGUI.getComponent(FileNavigationPane.class).openClassFileToWorkSpace(clazzName, fN); + } + } + }); + + this.setVisible(true); + + } + + public enum SearchType { + LDC (new LDCSearch()), + Regex (new RegexSearch()), + MethodCall (new MethodCallSearch()), + FieldCall (new FieldCallSearch()); + + public final SearchTypeDetails details; + + SearchType(final SearchTypeDetails details) { + this.details = details; + } + } + + public enum SearchRadius { + All_Classes, + Current_Class; + } + + public void resetWorkspace() { + treeRoot.removeAllChildren(); + tree.updateUI(); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/TabbedPane.java b/src/the/bytecode/club/bytecodeviewer/gui/TabbedPane.java new file mode 100644 index 00000000..0d154cf4 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/TabbedPane.java @@ -0,0 +1,186 @@ +package the.bytecode.club.bytecodeviewer.gui; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.plaf.basic.BasicButtonUI; + +/** + * Component to be used as tabComponent; + * Contains a JLabel to show the text and + * a JButton to close the tab it belongs to + * + * @author Konloch + * @author WaterWolf + * + */ +public class TabbedPane extends JPanel { + + private static final long serialVersionUID = -4774885688297538774L; + private final JTabbedPane pane; + final JButton button = new TabButton(); + + public TabbedPane(final JTabbedPane pane) { + //unset default FlowLayout' gaps + super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + if (pane == null) + throw new NullPointerException("TabbedPane is null"); + this.pane = pane; + setOpaque(false); + + //make JLabel read titles from JTabbedPane + final JLabel label = new JLabel() { + private static final long serialVersionUID = -5511025206527893360L; + + @Override + public String getText() { + final int i = pane.indexOfTabComponent(TabbedPane.this); + if (i != -1) + return pane.getTitleAt(i); + return null; + } + }; + + add(label); + //add more space between the label and the button + label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); + //tab button + add(button); + //add more space to the top of the component + setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); + pane.addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent arg0) { + } + @Override + public void mouseEntered(MouseEvent arg0) { + } + @Override + public void mouseExited(MouseEvent arg0) { + } + @Override + public void mousePressed(MouseEvent arg0) { + } + @Override + public void mouseReleased(MouseEvent e) { + //final Component component = e.getComponent(); + // if(component instanceof JTabbedPane) { + if(e.getModifiers() == 8) { + for(Component c : pane.getComponents()) { + if(c.getMousePosition() != null && c instanceof JPanel) { + System.out.println("gotten here..."); + /*BytecodeViewer.viewer.getComponent(WorkPane.class).tabs.remove(component); + final int i = BytecodeViewer.viewer.getComponent(WorkPane.class).tabs.indexOfTabComponent(c); + if (i != -1) + BytecodeViewer.viewer.getComponent(WorkPane.class).tabs.remove(i); + BytecodeViewer.viewer.getComponent(WorkPane.class).tabs.updateUI(); + BytecodeViewer.viewer.getComponent(WorkPane.class).tabs.repaint(); + *////if(c.getComponentAt((int)c.getMousePosition().getX(), (int)c.getMousePosition().getY())button.) + // button.doClick(); + } + + //System.out.println(c.getMousePosition() + ":" + e.getX()); + //System.out.println(c.getWidth() + ":" + e.getX()); + //if( e.getX() >= && + // e.getY()) + // button.doClick(); + } + } + } + //} + + }); + } + + private class TabButton extends JButton implements ActionListener { + private static final long serialVersionUID = -4492967978286454159L; + + public TabButton() { + final int size = 17; + setPreferredSize(new Dimension(size, size)); + setToolTipText("Close this tab"); + //Make the button looks the same for all Laf's + setUI(new BasicButtonUI()); + //Make it transparent + setContentAreaFilled(false); + //No need to be focusable + setFocusable(false); + setBorder(BorderFactory.createEtchedBorder()); + setBorderPainted(false); + //Making nice rollover effect + //we use the same listener for all buttons + addMouseListener(buttonMouseListener); + setRolloverEnabled(true); + //Close the proper tab by clicking the button + addActionListener(this); + } + + public void actionPerformed(final ActionEvent e) { + final int i = pane.indexOfTabComponent(TabbedPane.this); + if (i != -1) { + pane.remove(i); + } + } + + //we don't want to update UI for this button + @Override + public void updateUI() { + } + + //paint the cross + @Override + protected void paintComponent(final Graphics g) { + super.paintComponent(g); + final Graphics2D g2 = (Graphics2D) g.create(); + //shift the image for pressed buttons + if (getModel().isPressed()) { + g2.translate(1, 1); + } + g2.setStroke(new BasicStroke(2)); + g2.setColor(Color.BLACK); + if (getModel().isRollover()) { + g2.setColor(Color.MAGENTA); + } + final int delta = 6; + g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1); + g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1); + g2.dispose(); + } + } + + private final static MouseListener buttonMouseListener = new MouseAdapter() { + @Override + public void mouseEntered(final MouseEvent e) { + final Component component = e.getComponent(); + if (component instanceof AbstractButton) { + final AbstractButton button = (AbstractButton) component; + button.setBorderPainted(true); + } + } + + @Override + public void mouseExited(final MouseEvent e) { + final Component component = e.getComponent(); + if (component instanceof AbstractButton) { + final AbstractButton button = (AbstractButton) component; + button.setBorderPainted(false); + } + } + }; + +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/gui/VisibleComponent.java b/src/the/bytecode/club/bytecodeviewer/gui/VisibleComponent.java new file mode 100644 index 00000000..7f1f4fc5 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/VisibleComponent.java @@ -0,0 +1,35 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import javax.swing.JInternalFrame; + +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.FileChangeNotifier; + +/** + * Used to represent all the panes inside of Bytecode Viewer, this is temp code + * that was included from porting in J-RET, this needs to be re-written. + * + * @author Konloch + * @author WaterWolf + * + */ + +public abstract class VisibleComponent extends JInternalFrame implements FileChangeNotifier { + + private static final long serialVersionUID = -6453413772343643526L; + + public VisibleComponent(final String title) { + super(title, false, false, false, false); + this.setFrameIcon(null); + } + + @SuppressWarnings("unused") + private VisibleComponent() { //because we want to enforce the title argument + + } + + @Override + public void openClassFile(final String name, final ClassNode cn) {} + +} diff --git a/src/the/bytecode/club/bytecodeviewer/gui/WorkPane.java b/src/the/bytecode/club/bytecodeviewer/gui/WorkPane.java new file mode 100644 index 00000000..28781190 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/gui/WorkPane.java @@ -0,0 +1,156 @@ +package the.bytecode.club.bytecodeviewer.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.lang.reflect.Field; +import java.util.HashMap; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import jsyntaxpane.DefaultSyntaxKit; + +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.FileChangeNotifier; + +/** + * The pane that contains all of the classes as tabs. + * + * @author Konloch + * @author WaterWolf + * + */ + +public class WorkPane extends VisibleComponent implements ActionListener { + + private static final long serialVersionUID = 6542337997679487946L; + + + FileChangeNotifier fcn; + JTabbedPane tabs; + + JPanel buttonPanel; + JButton refreshClass; + + HashMap workingOn = new HashMap(); + + public static int SyntaxFontHeight = 12; + + public WorkPane(final FileChangeNotifier fcn) { + super("WorkPanel"); + setTitle("Work Space"); + + DefaultSyntaxKit.initKit(); + Font defFont = null; + try { + final Field defFontField = DefaultSyntaxKit.class.getDeclaredField("DEFAULT_FONT"); + defFontField.setAccessible(true); + defFont = (Font) defFontField.get(null); + } catch (final Exception e) { + e.printStackTrace(); + } + SyntaxFontHeight = defFont.getSize(); + + this.tabs = new JTabbedPane(); + this.fcn = fcn; + + getContentPane().setLayout(new BorderLayout()); + + getContentPane().add(tabs, BorderLayout.CENTER); + + buttonPanel = new JPanel(new FlowLayout()); + + refreshClass = new JButton("Refresh class"); + refreshClass.addActionListener(this); + + buttonPanel.add(refreshClass); + + buttonPanel.setVisible(false); + getContentPane().add(buttonPanel, BorderLayout.SOUTH); + + tabs.addContainerListener(new ContainerListener() { + + @Override + public void componentAdded(final ContainerEvent e) { + } + + @Override + public void componentRemoved(final ContainerEvent e) { + final Component c = e.getChild(); + if (c instanceof ClassViewer) { + workingOn.remove(((ClassViewer)c).name); + } + } + + }); + tabs.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(final ChangeEvent arg0) { + buttonPanel.setVisible(tabs.getSelectedIndex() != -1); + } + }); + + this.setVisible(true); + + } + + int tabCount = 0; + + public void addWorkingFile(final String name, final ClassNode cn) { + if(!BytecodeViewer.viewer.hexPane.isSelected() && + !BytecodeViewer.viewer.sourcePane.isSelected() && + !BytecodeViewer.viewer.bytecodePane.isSelected()) { + BytecodeViewer.showMessage("You currently have no viewing panes selected."); + return; + } + if (!workingOn.containsKey(name)) { + final Component tabComp = new ClassViewer(name, cn); + tabs.add(tabComp); + final int tabCount = tabs.indexOfComponent(tabComp); + workingOn.put(name, tabCount); + tabs.setTabComponentAt(tabCount, new TabbedPane(tabs)); + tabs.setSelectedIndex(tabCount); + } else { + tabs.setSelectedIndex(workingOn.get(name)); + } + } + + @Override + public void openClassFile(final String name, final ClassNode cn) { + addWorkingFile(name, cn); + } + + public ClassViewer getCurrentClass() { + return (ClassViewer) tabs.getSelectedComponent(); + } + + @Override + public void actionPerformed(final ActionEvent arg0) { + final JButton src = (JButton) arg0.getSource(); + if (src == refreshClass) { + final Component tabComp = tabs.getSelectedComponent(); + if (tabComp != null) { + BytecodeViewer.viewer.setC(true); + ((ClassViewer)tabComp).startPaneUpdater(); + BytecodeViewer.viewer.setC(false); + } + } + } + + public void resetWorkspace() { + tabs.removeAll(); + tabs.updateUI(); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/AllatoriStringDecrypter.java b/src/the/bytecode/club/bytecodeviewer/plugins/AllatoriStringDecrypter.java new file mode 100644 index 00000000..a83147bb --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/AllatoriStringDecrypter.java @@ -0,0 +1,23 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.ClassNode; + +/** + * Coming soon. + * + * @author Konloch + * + */ + +public class AllatoriStringDecrypter extends Plugin { + + @Override + public void execute(ArrayList classNodeList) { + for(ClassNode classNode : classNodeList) { + + } + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/MaliciousCodeScanner.java b/src/the/bytecode/club/bytecodeviewer/plugins/MaliciousCodeScanner.java new file mode 100644 index 00000000..57e7525a --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/MaliciousCodeScanner.java @@ -0,0 +1,88 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * The idea/core was based off of J-RET's Malicious Code Searcher + * I improved it, and added more stuff to search for. + * + * @author Konloch + * @author WaterWolf + * + */ + +public class MaliciousCodeScanner extends Plugin { + + public boolean + ORE, + ONE, + ORU, + OIO, + LWW, + LHT, + LHS, + LIP; + + public MaliciousCodeScanner(boolean reflect, boolean runtime, boolean net, boolean io, + boolean www, boolean http, boolean https, boolean ip) { + ORE = reflect; + ONE = net; + ORU = runtime; + OIO = io; + LWW = www; + LHT = http; + LHS = https; + LIP = ip; + } + + @Override + public void execute(ArrayList classNodeList) { + PluginConsole frame = new PluginConsole("Malicious Code Scanner"); + BytecodeViewer.viewer.setC(true); + for(ClassNode classNode : classNodeList) { + for(Object o : classNode.methods.toArray()) { + MethodNode m = (MethodNode) o; + + InsnList iList = m.instructions; + for(AbstractInsnNode a : iList.toArray()) { + if (a instanceof MethodInsnNode) { + final MethodInsnNode min = (MethodInsnNode) a; + if ((ORE && min.owner.startsWith("java/lang/reflect")) || + (ONE && min.owner.startsWith("java/net")) || + (ORU && min.owner.equals("java/lang/Runtime")) || + (OIO && min.owner.startsWith("java/io"))) + { + frame.appendText("Found Method call to " + min.owner + "." + min.name + "(" + min.desc + ") at " + classNode.name + "." +m.name+"("+m.desc+")"); + } + } + if (a instanceof LdcInsnNode) { + if(((LdcInsnNode)a).cst instanceof String) { + final String s = (String) ((LdcInsnNode)a).cst; + if ((LWW && s.contains("www.")) || + (LHT && s.contains("http://")) || + (LHS && s.contains("https://")) || + (ORE && s.contains("java/lang/Runtime")) || + (ORE && s.contains("java.lang.Runtime")) || + (LIP && s.matches("\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"))) + { + frame.appendText("Found LDC \"" + s + "\" at " + classNode.name + "." +m.name+"("+m.desc+")"); + } + } + } + } + } + } + BytecodeViewer.viewer.setC(false); + frame.setVisible(true); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/Plugin.java b/src/the/bytecode/club/bytecodeviewer/plugins/Plugin.java new file mode 100644 index 00000000..6286a3f9 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/Plugin.java @@ -0,0 +1,37 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.ClassNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * A simple plugin class, it will run the plugin in a background thread. + * + * @author Konloch + * + */ + +public abstract class Plugin extends Thread { + + @Override + public void run() { + BytecodeViewer.viewer.setIcon(true); + try { + if(!BytecodeViewer.getLoadedClasses().isEmpty()) + execute(BytecodeViewer.getLoadedClasses()); + else + System.out.println("Plugin not ran, put some classes in first."); + } catch(Exception e) { + e.printStackTrace(); + } finally { + finished = true; + BytecodeViewer.viewer.setIcon(false); + } + } + + public boolean finished = false; + public abstract void execute(ArrayList classNodeList); + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/PluginConsole.java b/src/the/bytecode/club/bytecodeviewer/plugins/PluginConsole.java new file mode 100644 index 00000000..d0e6c1e1 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/PluginConsole.java @@ -0,0 +1,36 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import javax.swing.JFrame; +import java.awt.Dimension; +import javax.swing.JScrollPane; +import java.awt.BorderLayout; +import javax.swing.JTextArea; + +/** + * A simple console GUI. + * + * @author Konloch + * + */ + +public class PluginConsole extends JFrame { + JTextArea textArea = new JTextArea(); + public PluginConsole(String pluginName) { + setTitle("Bytecode Viewer - Plugin Console - " + pluginName); + setSize(new Dimension(542, 316)); + + JScrollPane scrollPane = new JScrollPane(); + getContentPane().add(scrollPane, BorderLayout.CENTER); + + scrollPane.setViewportView(textArea); + this.setLocationRelativeTo(null); + } + + public void appendText(String t) { + textArea.setText((textArea.getText().isEmpty() ? "" : textArea.getText()+"\r\n")+t); + textArea.setCaretPosition(textArea.getLineCount()); + } + + private static final long serialVersionUID = -6556940545421437508L; + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/PluginManager.java b/src/the/bytecode/club/bytecodeviewer/plugins/PluginManager.java new file mode 100644 index 00000000..ad72c968 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/PluginManager.java @@ -0,0 +1,106 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.io.File; +import java.io.FileReader; +import java.io.Reader; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * Supports loading of groovy, python or ruby scripts. + * + * Only allows one plugin to be running at once. + * + * @author Konloch + * + */ + +public class PluginManager { + + private static Plugin pluginInstance; + + public static void runPlugin(Plugin newPluginInstance) { + if(pluginInstance == null || pluginInstance.finished) { + pluginInstance = newPluginInstance; + pluginInstance.start(); //start the thread + } else if(!pluginInstance.finished) { + BytecodeViewer.showMessage("There is currently another plugin running right now, please wait for that to finish executing."); + } + } + + public static void runPlugin(File f) throws Exception { + Plugin p = null; + if(f.getName().endsWith(".gy") || f.getName().endsWith(".groovy")) { + p = loadGroovyScript(f); + } + if(f.getName().endsWith(".py") || f.getName().endsWith(".python")) { + p = loadPythonScript(f); + } + if(f.getName().endsWith(".rb") || f.getName().endsWith(".ruby")) { + p = loadRubyScript(f); + } + if(p != null) { + runPlugin(p); + } + } + + /** + * Loads a groovy file as a Script + * @param file + * @return + * @throws Exception + */ + private static Plugin loadGroovyScript(File file) throws Exception { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("groovy"); + + if(engine == null) + throw new Exception("Cannot find Groovy script engine! Please contact Konloch."); + + Reader reader = new FileReader(file); + engine.eval(reader); + + return (Plugin)engine.eval("new " + file.getName().replace(".gy", "").replace(".groovy", "") + "();"); + } + + /** + * Loads a python file as a Script + * @param file + * @return + * @throws Exception + */ + private static Plugin loadPythonScript(File file) throws Exception { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("python"); + + if(engine == null) + throw new Exception("Cannot find Jython script engine! Please contact Konloch."); + + Reader reader = new FileReader(file); + engine.eval(reader); + + return (Plugin)engine.eval(file.getName().replace(".py", "").replace(".python", "") + "()"); + } + + /** + * Loads a ruby file as a Script + * @param file + * @return + * @throws Exception + */ + private static Plugin loadRubyScript(File file) throws Exception { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("jruby"); + + if(engine == null) + throw new Exception("Cannot find jRuby script engine! Please contact Konloch."); + + Reader reader = new FileReader(file); + engine.eval(reader); + + return (Plugin)engine.eval(file.getName().replace(".rb", "").replace(".ruby", "") + ".new"); + } +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/ReplaceStrings.java b/src/the/bytecode/club/bytecodeviewer/plugins/ReplaceStrings.java new file mode 100644 index 00000000..9d117404 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/ReplaceStrings.java @@ -0,0 +1,116 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * Replaces all string and string[] instances with whatever. + * + * @author Konloch + * + */ + +public class ReplaceStrings extends Plugin { + + PluginConsole frame = new PluginConsole("Replace Strings"); + String originalLDC; + String newLDC; + String className; + boolean contains; + + public ReplaceStrings(String originalLDC, String newLDC, String className, boolean contains) { + this.originalLDC = originalLDC; + this.newLDC = newLDC; + this.className = className; + this.contains = contains; + } + + @Override + public void execute(ArrayList classNodeList) { + BytecodeViewer.viewer.setC(true); + + if(!className.equals("*")) { + for(ClassNode classNode : classNodeList) { + if(classNode.name.equals(className)) + scanClassNode(classNode); + } + } else { + for(ClassNode classNode : classNodeList) { + scanClassNode(classNode); + } + } + + BytecodeViewer.viewer.setC(false); + frame.setVisible(true); + } + + + public void scanClassNode(ClassNode classNode) { + for(Object o : classNode.fields.toArray()) { + FieldNode f = (FieldNode) o; + Object v = f.value; + if(v instanceof String) { + String s = (String)v; + if(contains) { + if(s.contains(originalLDC)) + f.value = ((String)f.value).replaceAll(originalLDC, newLDC); + } else { + if(s.equals(originalLDC)) + f.value = newLDC; + } + } + if(v instanceof String[]) { + for(int i = 0; i < ((String[])v).length; i++) { + String s = ((String[])v)[i]; + if(contains) { + if(s.contains(originalLDC)) { + f.value = ((String[])f.value)[i].replaceAll(originalLDC, newLDC); + String ugh = s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r"); + frame.appendText(classNode.name + "." +f.name+""+f.desc+" -> \"" + ugh + "\" replaced with \"" + s.replaceAll(originalLDC, newLDC) + "\""); + } + } else { + if(s.equals(originalLDC)) { + ((String[])f.value)[i] = newLDC; + String ugh = s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r"); + frame.appendText(classNode.name + "." +f.name+""+f.desc+" -> \"" + ugh + "\" replaced with \"" + newLDC + "\""); + } + } + } + } + } + + for(Object o : classNode.methods.toArray()) { + MethodNode m = (MethodNode) o; + + InsnList iList = m.instructions; + for(AbstractInsnNode a : iList.toArray()) { + if (a instanceof LdcInsnNode) { + if(((LdcInsnNode)a).cst instanceof String) { + final String s = (String) ((LdcInsnNode)a).cst; + if(contains) { + if(s.contains(originalLDC)) { + ((LdcInsnNode)a).cst = ((String)((LdcInsnNode)a).cst).replaceAll(originalLDC, newLDC); + String ugh = s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r"); + frame.appendText(classNode.name + "." +m.name+""+m.desc+" -> \"" + ugh + "\" replaced with \"" + s.replaceAll(originalLDC, newLDC) + "\""); + } + } else { + if(s.equals(originalLDC)) { + ((LdcInsnNode)a).cst = newLDC; + String ugh = s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r"); + frame.appendText(classNode.name + "." +m.name+""+m.desc+" -> \"" + ugh + "\" replaced with \"" + newLDC + "\""); + } + } + } + } + } + } + } +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/ShowAllStrings.java b/src/the/bytecode/club/bytecodeviewer/plugins/ShowAllStrings.java new file mode 100644 index 00000000..1f70c0be --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/ShowAllStrings.java @@ -0,0 +1,64 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * Simply shows all the non-empty strings in every single class + * + * @author Konloch + * + */ + +public class ShowAllStrings extends Plugin { + + @Override + public void execute(ArrayList classNodeList) { + PluginConsole frame = new PluginConsole("Show All Strings"); + BytecodeViewer.viewer.setC(true); + for(ClassNode classNode : classNodeList) { + for(Object o : classNode.fields.toArray()) { + FieldNode f = (FieldNode) o; + Object v = f.value; + if(v instanceof String) { + String s = (String)v; + if(!s.isEmpty()) + frame.appendText(classNode.name + "." +f.name+""+f.desc+" -> \"" + s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r") + "\""); + } + if(v instanceof String[]) { + for(int i = 0; i < ((String[])v).length; i++) { + String s = ((String[])v)[i]; + if(!s.isEmpty()) + frame.appendText(classNode.name + "." +f.name+""+f.desc+"["+i+"] -> \"" + s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r") + "\""); + } + } + } + + for(Object o : classNode.methods.toArray()) { + MethodNode m = (MethodNode) o; + + InsnList iList = m.instructions; + for(AbstractInsnNode a : iList.toArray()) { + if (a instanceof LdcInsnNode) { + if(((LdcInsnNode)a).cst instanceof String) { + final String s = (String) ((LdcInsnNode)a).cst; + if(!s.isEmpty()) + frame.appendText(classNode.name + "." +m.name+""+m.desc+" -> \"" + s.replaceAll("\\n", "\\\\n").replaceAll("\\r", "\\\\r") + "\""); + } + } + } + } + } + BytecodeViewer.viewer.setC(false); + frame.setVisible(true); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/ShowMainMethods.java b/src/the/bytecode/club/bytecodeviewer/plugins/ShowMainMethods.java new file mode 100644 index 00000000..a0f7010b --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/ShowMainMethods.java @@ -0,0 +1,35 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * Simply shows all classes that have a public static void main(String[]) + * + * @author Konloch + * + */ + +public class ShowMainMethods extends Plugin { + + @Override + public void execute(ArrayList classNodeList) { + PluginConsole frame = new PluginConsole("Show Main Methods"); + BytecodeViewer.viewer.setC(true); + for(ClassNode classNode : classNodeList) { + for(Object o : classNode.methods.toArray()) { + MethodNode m = (MethodNode) o; + + if(m.name.equals("main") && m.desc.equals("([Ljava/lang/String;)V")) + frame.appendText(classNode.name + "." +m.name+""+m.desc); + } + } + BytecodeViewer.viewer.setC(false); + frame.setVisible(true); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/plugins/ZKMStringDecrypter.java b/src/the/bytecode/club/bytecodeviewer/plugins/ZKMStringDecrypter.java new file mode 100644 index 00000000..180d3d65 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/plugins/ZKMStringDecrypter.java @@ -0,0 +1,23 @@ +package the.bytecode.club.bytecodeviewer.plugins; + +import java.util.ArrayList; + +import org.objectweb.asm.tree.ClassNode; + +/** + * Coming soon. + * + * @author Konloch + * + */ + +public class ZKMStringDecrypter extends Plugin { + + @Override + public void execute(ArrayList classNodeList) { + for(ClassNode classNode : classNodeList) { + + } + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/BackgroundSearchThread.java b/src/the/bytecode/club/bytecodeviewer/searching/BackgroundSearchThread.java new file mode 100644 index 00000000..d2203363 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/BackgroundSearchThread.java @@ -0,0 +1,34 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import the.bytecode.club.bytecodeviewer.BytecodeViewer; + +/** + * A simple class to make searching run in a background thread. + * + * @author Konloch + * + */ + +public abstract class BackgroundSearchThread extends Thread { + + public BackgroundSearchThread() { + + } + + public BackgroundSearchThread(boolean finished) { + this.finished = finished; + } + + public boolean finished = false; + + public abstract void doSearch(); + + @Override + public void run() { + BytecodeViewer.viewer.setIcon(true); + doSearch(); + finished = true; + BytecodeViewer.viewer.setIcon(false); + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/FieldCallSearch.java b/src/the/bytecode/club/bytecodeviewer/searching/FieldCallSearch.java new file mode 100644 index 00000000..6463ddd4 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/FieldCallSearch.java @@ -0,0 +1,100 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import java.awt.GridLayout; +import java.util.Iterator; +import java.util.ListIterator; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; + +/** + * Field call searching + * + * @author Water Wolf + * + */ + +public class FieldCallSearch implements SearchTypeDetails { + + JTextField mOwner = new JTextField(""), mName = new JTextField(""), mDesc = new JTextField(""); + JPanel myPanel = null; + + @Override + public JPanel getPanel() { + if (myPanel == null) { + myPanel = new JPanel(new GridLayout(3, 2)); + myPanel.add(new JLabel("Owner: ")); + myPanel.add(mOwner); + myPanel.add(new JLabel("Name: ")); + myPanel.add(mName); + myPanel.add(new JLabel("Desc: ")); + myPanel.add(mDesc); + } + + return myPanel; + } + @Override + public void search(final ClassNode node, final SearchResultNotifier srn, boolean exact) { + @SuppressWarnings("unchecked") + final Iterator methods = node.methods.iterator(); + String owner = mOwner.getText(); + if (owner.isEmpty()) { + owner = null; + } + String name = mName.getText(); + if (name.isEmpty()) { + name = null; + } + String desc = mDesc.getText(); + if (desc.isEmpty()) { + desc = null; + } + while (methods.hasNext()) { + final MethodNode method = methods.next(); + + final InsnList insnlist = method.instructions; + @SuppressWarnings("unchecked") + final ListIterator instructions = insnlist.iterator(); + while (instructions.hasNext()) { + final AbstractInsnNode insnNode = instructions.next(); + if (insnNode instanceof FieldInsnNode) { + final FieldInsnNode min = (FieldInsnNode) insnNode; + if(name == null && owner == null && desc == null) + continue; + if(exact) { + if (name != null && !name.equals(min.name)) { + continue; + } + if (owner != null && !owner.equals(min.owner)) { + continue; + } + if (desc != null && !desc.equals(min.desc)) { + continue; + } + srn.notifyOfResult(node, method, insnNode); + } else { + + if (name != null && !name.contains(min.name)) { + continue; + } + if (owner != null && !owner.contains(min.owner)) { + continue; + } + if (desc != null && !desc.contains(min.desc)) { + continue; + } + srn.notifyOfResult(node, method, insnNode); + } + } + } + + } + } +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/searching/LDCSearch.java b/src/the/bytecode/club/bytecodeviewer/searching/LDCSearch.java new file mode 100644 index 00000000..bf62fac3 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/LDCSearch.java @@ -0,0 +1,66 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import java.awt.GridLayout; +import java.util.Iterator; +import java.util.ListIterator; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * LDC Searching + * + * @author WaterWolf + * + */ + +public class LDCSearch implements SearchTypeDetails { + + JTextField searchText = new JTextField(""); + JPanel myPanel = null; + + @Override + public JPanel getPanel() { + if (myPanel == null) { + myPanel = new JPanel(new GridLayout(1, 2)); + myPanel.add(new JLabel("Search String: ")); + myPanel.add(searchText); + } + + return myPanel; + } + @SuppressWarnings("unchecked") + @Override + public void search(final ClassNode node, final SearchResultNotifier srn, boolean exact) { + final Iterator methods = node.methods.iterator(); + final String srchText = searchText.getText(); + if(srchText.isEmpty()) + return; + while (methods.hasNext()) { + final MethodNode method = methods.next(); + + final InsnList insnlist = method.instructions; + final ListIterator instructions = insnlist.iterator(); + while (instructions.hasNext()) { + final AbstractInsnNode insnNode = instructions.next(); + if (insnNode instanceof LdcInsnNode) { + final Object ldcObject = ((LdcInsnNode) insnNode).cst; + final String ldcString = ldcObject.toString(); + if ((exact && ldcString.equals(srchText)) || + (!exact && ldcString.contains(srchText))) + { + srn.notifyOfResult(node, method, insnNode); + } + } + } + + } + } +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/searching/MethodCallSearch.java b/src/the/bytecode/club/bytecodeviewer/searching/MethodCallSearch.java new file mode 100644 index 00000000..b018f8e4 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/MethodCallSearch.java @@ -0,0 +1,99 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import java.awt.GridLayout; +import java.util.Iterator; +import java.util.ListIterator; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Method call searching + * + * @author WaterWolf + * + */ + +public class MethodCallSearch implements SearchTypeDetails { + + JTextField mOwner = new JTextField(""), mName = new JTextField(""), mDesc = new JTextField(""); + JPanel myPanel = null; + + @Override + public JPanel getPanel() { + if (myPanel == null) { + myPanel = new JPanel(new GridLayout(3, 2)); + myPanel.add(new JLabel("Owner: ")); + myPanel.add(mOwner); + myPanel.add(new JLabel("Name: ")); + myPanel.add(mName); + myPanel.add(new JLabel("Desc: ")); + myPanel.add(mDesc); + } + + return myPanel; + } + @SuppressWarnings("unchecked") + @Override + public void search(final ClassNode node, final SearchResultNotifier srn, boolean exact) { + final Iterator methods = node.methods.iterator(); + String owner = mOwner.getText(); + if (owner.isEmpty()) { + owner = null; + } + String name = mName.getText(); + if (name.isEmpty()) { + name = null; + } + String desc = mDesc.getText(); + if (desc.isEmpty()) { + desc = null; + } + + while (methods.hasNext()) { + final MethodNode method = methods.next(); + + final InsnList insnlist = method.instructions; + final ListIterator instructions = insnlist.iterator(); + while (instructions.hasNext()) { + final AbstractInsnNode insnNode = instructions.next(); + if (insnNode instanceof MethodInsnNode) { + final MethodInsnNode min = (MethodInsnNode) insnNode; + if(name == null && owner == null && desc == null) + continue; + if(exact) { + if (name != null && !name.equals(min.name)) { + continue; + } + if (owner != null && !owner.equals(min.owner)) { + continue; + } + if (desc != null && !desc.equals(min.desc)) { + continue; + } + srn.notifyOfResult(node, method, insnNode); + } else { + if (name != null && !name.contains(min.name)) { + continue; + } + if (owner != null && !owner.contains(min.owner)) { + continue; + } + if (desc != null && !desc.contains(min.desc)) { + continue; + } + srn.notifyOfResult(node, method, insnNode); + } + } + } + + } + } +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/searching/RegexInsnFinder.java b/src/the/bytecode/club/bytecodeviewer/searching/RegexInsnFinder.java new file mode 100644 index 00000000..f80a94ba --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/RegexInsnFinder.java @@ -0,0 +1,381 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import java.rmi.UnexpectedException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.MultiANewArrayInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * An instruction finder that finds regex patterns in a method's instruction + * list and returns an array with the found instructions. + * + * @author Frédéric Hannes + * + */ + +public class RegexInsnFinder { + + private static String[] opcodes = new String[] { "NOP", "ACONST_NULL", + "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", + "ICONST_4", "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0", + "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1", "BIPUSH", "SIPUSH", + "LDC", "LDC_W", "LDC2_W", "ILOAD", "LLOAD", "FLOAD", "DLOAD", + "ALOAD", "ILOAD_0", "ILOAD_1", "ILOAD_2", "ILOAD_3", "LLOAD_0", + "LLOAD_1", "LLOAD_2", "LLOAD_3", "FLOAD_0", "FLOAD_1", "FLOAD_2", + "FLOAD_3", "DLOAD_0", "DLOAD_1", "DLOAD_2", "DLOAD_3", "ALOAD_0", + "ALOAD_1", "ALOAD_2", "ALOAD_3", "IALOAD", "LALOAD", "FALOAD", + "DALOAD", "AALOAD", "BALOAD", "CALOAD", "SALOAD", "ISTORE", + "LSTORE", "FSTORE", "DSTORE", "ASTORE", "ISTORE_0", "ISTORE_1", + "ISTORE_2", "ISTORE_3", "LSTORE_0", "LSTORE_1", "LSTORE_2", + "LSTORE_3", "FSTORE_0", "FSTORE_1", "FSTORE_2", "FSTORE_3", + "DSTORE_0", "DSTORE_1", "DSTORE_2", "DSTORE_3", "ASTORE_0", + "ASTORE_1", "ASTORE_2", "ASTORE_3", "IASTORE", "LASTORE", + "FASTORE", "DASTORE", "AASTORE", "BASTORE", "CASTORE", "SASTORE", + "POP", "POP2", "DUP", "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", + "DUP2_X2", "SWAP", "IADD", "LADD", "FADD", "DADD", "ISUB", "LSUB", + "FSUB", "DSUB", "IMUL", "LMUL", "FMUL", "DMUL", "IDIV", "LDIV", + "FDIV", "DDIV", "IREM", "LREM", "FREM", "DREM", "INEG", "LNEG", + "FNEG", "DNEG", "ISHL", "LSHL", "ISHR", "LSHR", "IUSHR", "LUSHR", + "IAND", "LAND", "IOR", "LOR", "IXOR", "LXOR", "IINC", "I2L", "I2F", + "I2D", "L2I", "L2F", "L2D", "F2I", "F2L", "F2D", "D2I", "D2L", + "D2F", "I2B", "I2C", "I2S", "LCMP", "FCMPL", "FCMPG", "DCMPL", + "DCMPG", "IFEQ", "IFNE", "IFLT", "IFGE", "IFGT", "IFLE", + "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT", + "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE", "GOTO", "JSR", "RET", + "TABLESWITCH", "LOOKUPSWITCH", "IRETURN", "LRETURN", "FRETURN", + "DRETURN", "ARETURN", "RETURN", "GETSTATIC", "PUTSTATIC", + "GETFIELD", "PUTFIELD", "INVOKEVIRTUAL", "INVOKESPECIAL", + "INVOKESTATIC", "INVOKEINTERFACE", "INVOKEDYNAMIC", "NEW", + "NEWARRAY", "ANEWARRAY", "ARRAYLENGTH", "ATHROW", "CHECKCAST", + "INSTANCEOF", "MONITORENTER", "MONITOREXIT", "WIDE", + "MULTIANEWARRAY", "IFNULL", "IFNONNULL", "GOTO_W", "JSR_W" }; + + private static String[] opcodesVar = new String[] { "ILOAD", "LLOAD", + "FLOAD", "DLOAD", "ALOAD", "ISTORE", "LSTORE", "FSTORE", "DSTORE", + "ASTORE", "RET" }; + private static String opcodeVars = buildRegexItems(opcodesVar); + + private static String[] opcodesInt = new String[] { "BIPUSH", "SIPUSH", + "NEWARRAY" }; + private static String opcodesInts = buildRegexItems(opcodesInt); + + private static String[] opcodesField = new String[] { "GETSTATIC", + "PUTSTATIC", "GETFIELD", "PUTFIELD" }; + private static String opcodesFields = buildRegexItems(opcodesField); + + private static String[] opcodesMethod = new String[] { "INVOKEVIRTUAL", + "INVOKESPECIAL", "INVOKESTATIC", "INVOKEINTERFACE", "INVOKEDYNAMIC" }; + private static String opcodesMethods = buildRegexItems(opcodesMethod); + + private static String[] opcodesType = new String[] { "NEW", "ANEWARRAY", + "ARRAYLENGTH", "CHECKCAST", "INSTANCEOF" }; + private static String opcodesTypes = buildRegexItems(opcodesType); + + private static String[] opcodesIf = new String[] { "IFEQ", "IFNE", "IFLT", + "IFGE", "IFGT", "IFLE", "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT", + "IF_ICMPGE", "IF_ICMPGT", "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE" }; + private static String opcodesIfs = buildRegexItems(opcodesIf, false, false); + + private static String[] opcodesAny = new String[] { "NOP", "ACONST_NULL", + "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", + "ICONST_4", "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0", + "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1", "BIPUSH", "SIPUSH", + "LDC", "LDC_W", "LDC2_W", "ILOAD", "LLOAD", "FLOAD", "DLOAD", + "ALOAD", "IALOAD", "LALOAD", "FALOAD", "DALOAD", "AALOAD", + "BALOAD", "CALOAD", "SALOAD", "ISTORE", "LSTORE", "FSTORE", + "DSTORE", "ASTORE", "IASTORE", "LASTORE", "FASTORE", "DASTORE", + "AASTORE", "BASTORE", "CASTORE", "SASTORE", "POP", "POP2", "DUP", + "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", "DUP2_X2", "SWAP", "IADD", + "LADD", "FADD", "DADD", "ISUB", "LSUB", "FSUB", "DSUB", "IMUL", + "LMUL", "FMUL", "DMUL", "IDIV", "LDIV", "FDIV", "DDIV", "IREM", + "LREM", "FREM", "DREM", "INEG", "LNEG", "FNEG", "DNEG", "ISHL", + "LSHL", "ISHR", "LSHR", "IUSHR", "LUSHR", "IAND", "LAND", "IOR", + "LOR", "IXOR", "LXOR", "IINC", "I2L", "I2F", "I2D", "L2I", "L2F", + "L2D", "F2I", "F2L", "F2D", "D2I", "D2L", "D2F", "I2B", "I2C", + "I2S", "LCMP", "FCMPL", "FCMPG", "DCMPL", "DCMPG", "IFEQ", "IFNE", + "IFLT", "IFGE", "IFGT", "IFLE", "IF_ICMPEQ", "IF_ICMPNE", + "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT", "IF_ICMPLE", "IF_ACMPEQ", + "IF_ACMPNE", "GOTO", "JSR", "RET", "TABLESWITCH", "LOOKUPSWITCH", + "IRETURN", "LRETURN", "FRETURN", "DRETURN", "ARETURN", "RETURN", + "GETSTATIC", "PUTSTATIC", "GETFIELD", "PUTFIELD", "INVOKEVIRTUAL", + "INVOKESPECIAL", "INVOKESTATIC", "INVOKEINTERFACE", + "INVOKEDYNAMIC", "NEW", "NEWARRAY", "ANEWARRAY", "ARRAYLENGTH", + "ATHROW", "CHECKCAST", "INSTANCEOF", "MONITORENTER", "MONITOREXIT", + "MULTIANEWARRAY", "IFNULL", "IFNONNULL" }; + private static String opcodesAnys = buildRegexItems(opcodesAny, false, false); + + private static String buildRegexItems(final String[] items, final boolean capture, final boolean stdRepl) { + if (items.length == 0) + return "()"; + String result = (stdRepl ? "\\b" : "") + "(" + (capture ? "" : "?:") + items[0]; + for (int i = 1; i < items.length; i++) { + result += "|" + items[i]; + } + result += ")"; + return result; + } + + private static String buildRegexItems(final String[] items) { + return buildRegexItems(items, true, true); + } + + private static String processRegex(final String regex) { + String result = regex.trim(); + result = result.replaceAll("\\bANYINSN *", opcodesAnys); + result = result.replaceAll(opcodesInts + "\\\\\\{\\s*(\\d+)\\s*\\\\\\} *", + "$1\\\\{$2\\\\} "); + result = result.replaceAll(opcodesInts + " *", "$1\\\\{\\\\d+\\\\} "); + result = result.replaceAll("\\bLDC\\\\\\{(.*?)\\\\\\}(? il = new ArrayList(); + + final Iterator iIt = insnList.iterator(); + while (iIt.hasNext()) { + final AbstractInsnNode node = iIt.next(); + if (node.getOpcode() >= 0) { + il.add(node); + } + } + return il.toArray(new AbstractInsnNode[il.size()]); + } + + /** + * Refreshes the internal instruction list when you have made changes to the method. + */ + public void refresh() { + origInstructions = cleanInsn(mn.instructions); + final List il = new ArrayList(); + for (final AbstractInsnNode ain : mn.instructions.toArray()) + if (ain.getOpcode() >= 0) { + il.add(ain); + } + instructions = il.toArray(new AbstractInsnNode[il.size()]); + offsets = new int[instructions.length]; + insnString = ""; + for (int i = 0; i < instructions.length; i++) { + offsets[i] = -1; + final AbstractInsnNode ain = instructions[i]; + if (ain.getOpcode() >= 0) { + if (ain.getOpcode() >= opcodes.length) { + try { + throw new UnexpectedException( + "Unknown opcode encountered: " + + ain.getOpcode()); + } catch (final UnexpectedException e) { + e.printStackTrace(); + } + } + offsets[i] = insnString.length(); + insnString += opcodes[ain.getOpcode()]; + switch (ain.getType()) { + case AbstractInsnNode.INT_INSN: + final IntInsnNode iin = (IntInsnNode) ain; + insnString += "{" + iin.operand + "}"; + break; + case AbstractInsnNode.LDC_INSN: + final LdcInsnNode lin = (LdcInsnNode) ain; + insnString += "{" + lin.cst.toString().replace("}", "\\}") + + "}"; + break; + case AbstractInsnNode.VAR_INSN: + final VarInsnNode vin = (VarInsnNode) ain; + insnString += "_" + vin.var; + break; + case AbstractInsnNode.IINC_INSN: + final IincInsnNode iiin = (IincInsnNode) ain; + insnString += "{" + iiin.var + "," + iiin.incr + "}"; + break; + case AbstractInsnNode.FIELD_INSN: + final FieldInsnNode fin = (FieldInsnNode) ain; + insnString += "{" + fin.desc + "," + fin.owner + "," + + fin.name + "}"; + break; + case AbstractInsnNode.METHOD_INSN: + final MethodInsnNode min = (MethodInsnNode) ain; + insnString += "{" + min.desc + "," + min.owner + "," + + min.name + "}"; + break; + case AbstractInsnNode.TYPE_INSN: + final TypeInsnNode tin = (TypeInsnNode) ain; + insnString += "{" + tin.desc + "}"; + break; + case AbstractInsnNode.MULTIANEWARRAY_INSN: + final MultiANewArrayInsnNode manain = (MultiANewArrayInsnNode) ain; + insnString += "{" + manain.dims + "," + manain.desc + "}"; + break; + } + insnString += " "; + } + } + } + + public void setMethod(final ClassNode ci, final MethodNode mi) { + this.mn = mi; + refresh(); + } + + private AbstractInsnNode[] makeResult(final int start, final int end) { + int startIndex = 0; + int endIndex = -1; + for (int i = 0; i < offsets.length - 1; i++) { + final int offset = offsets[i]; + if (offset == start) { + startIndex = i; + } + if ((offset < end) && (offsets[i + 1] >= end)) { + endIndex = i; + break; + } + } + if (endIndex == -1) { + endIndex = offsets.length - 1; + } + final int length = endIndex - startIndex + 1; + final AbstractInsnNode[] result = new AbstractInsnNode[length]; + System.arraycopy(origInstructions, startIndex, result, 0, length); + return result; + } + + /** + * Searches for a regex in the instruction list and returns the first match. + * @param regex the regular expression + * @return the matching instructions + */ + public AbstractInsnNode[] find(final String regex) { + try { + final Matcher regexMatcher = Pattern.compile(processRegex(regex), + Pattern.MULTILINE).matcher(insnString); + if (regexMatcher.find()) + return makeResult(regexMatcher.start(), regexMatcher.end()); + } catch (final PatternSyntaxException ex) { + ex.printStackTrace(); + } + return new AbstractInsnNode[0]; + } + + /** + * Searches a regex in an instruction list and returns all matches. + * @param regex the regular expression + * @return a list with all sets of matching instructions + */ + public List findAll(final String regex) { + final List results = new ArrayList(); + try { + final Matcher regexMatcher = Pattern.compile(processRegex(regex), + Pattern.MULTILINE).matcher(insnString); + while (regexMatcher.find()) { + results.add(makeResult(regexMatcher.start(), regexMatcher.end())); + } + } catch (final PatternSyntaxException ex) { + ex.printStackTrace(); + } + return results; + } + + /** + * Searches for a regex in the instruction list and returns all groups for the first match. + * @param regex the regular expression + * @return the groups with matching instructions + */ + public AbstractInsnNode[][] findGroups(final String regex) { + try { + final Matcher regexMatcher = Pattern.compile(processRegex(regex), + Pattern.MULTILINE).matcher(insnString); + if (regexMatcher.find()) { + final AbstractInsnNode[][] result = new AbstractInsnNode[regexMatcher.groupCount() + 1][0]; + for (int i = 0; i <= regexMatcher.groupCount(); i++) { + result[i] = makeResult(regexMatcher.start(i), regexMatcher.end(i)); + } + return result; + } + } catch (final PatternSyntaxException ex) { + ex.printStackTrace(); + } + return new AbstractInsnNode[0][0]; + } + + /** + * Searches for a regex in the instruction list and returns all groups for all matches. + * @param regex the regular expression + * @return a list with all sets of groups with matching instructions + */ + public List findAllGroups(final String regex) { + final List results = new ArrayList(); + try { + final Matcher regexMatcher = Pattern.compile(processRegex(regex), + Pattern.MULTILINE).matcher(insnString); + if (regexMatcher.find()) { + final AbstractInsnNode[][] result = new AbstractInsnNode[regexMatcher.groupCount() + 1][0]; + for (int i = 0; i <= regexMatcher.groupCount(); i++) { + result[i] = makeResult(regexMatcher.start(i), regexMatcher.end(i)); + } + results.add(result); + } + } catch (final PatternSyntaxException ex) { + ex.printStackTrace(); + } + return results; + } + +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/RegexSearch.java b/src/the/bytecode/club/bytecodeviewer/searching/RegexSearch.java new file mode 100644 index 00000000..10529bbf --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/RegexSearch.java @@ -0,0 +1,61 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import java.awt.GridLayout; +import java.util.Iterator; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Regex Searching + * + * @author WaterWolf + * + */ + +public class RegexSearch implements SearchTypeDetails { + + JTextField searchText = new JTextField(""); + JPanel myPanel = null; + + private static RegexInsnFinder regexFinder; + + @Override + public JPanel getPanel() { + if (myPanel == null) { + myPanel = new JPanel(new GridLayout(1, 2)); + myPanel.add(new JLabel("Search Regex: ")); + myPanel.add(searchText); + } + + return myPanel; + } + + @SuppressWarnings("unchecked") + @Override + public void search(final ClassNode node, final SearchResultNotifier srn, boolean exact) { + final Iterator methods = node.methods.iterator(); + final String srchText = searchText.getText(); + if(srchText.isEmpty()) + return; + while (methods.hasNext()) { + final MethodNode method = methods.next(); + + if (regexFinder == null) { + regexFinder = new RegexInsnFinder(node, method); + } + else { + regexFinder.setMethod(node, method); + } + + if (regexFinder.find(srchText).length > 0) { + srn.notifyOfResult(node, method, null); + } + + } + } +} \ No newline at end of file diff --git a/src/the/bytecode/club/bytecodeviewer/searching/SearchResultNotifier.java b/src/the/bytecode/club/bytecodeviewer/searching/SearchResultNotifier.java new file mode 100644 index 00000000..dbd878eb --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/SearchResultNotifier.java @@ -0,0 +1,17 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Used to update the search pane that there's been a result found. + * + * @author WaterWolf + * + */ + +public interface SearchResultNotifier { + public void notifyOfResult(ClassNode clazz, MethodNode method, + AbstractInsnNode insn); +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/SearchTypeDetails.java b/src/the/bytecode/club/bytecodeviewer/searching/SearchTypeDetails.java new file mode 100644 index 00000000..082da071 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/SearchTypeDetails.java @@ -0,0 +1,18 @@ +package the.bytecode.club.bytecodeviewer.searching; + +import javax.swing.JPanel; + +import org.objectweb.asm.tree.ClassNode; + +/** + * Search type details + * + * @author WaterWolf + * + */ + +public interface SearchTypeDetails { + public JPanel getPanel(); + + public void search(ClassNode node, SearchResultNotifier srn, boolean exact); +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/commons/AnalyzerFactory.java b/src/the/bytecode/club/bytecodeviewer/searching/commons/AnalyzerFactory.java new file mode 100644 index 00000000..3075b28d --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/commons/AnalyzerFactory.java @@ -0,0 +1,489 @@ +package the.bytecode.club.bytecodeviewer.searching.commons; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.IntInsnNode; + +/** + * + * Class containing bytecode search conditions and couple of helper methods to make them + * + * @author Waterwolf + * + **/ +public class AnalyzerFactory implements Opcodes { + public static InsnAnalyzer makeOpcodeCond(final int opcode) { + return new InsnAnalyzer() { + @Override + public boolean accept(final AbstractInsnNode node) { + return node.getOpcode() == opcode; + } + }; + } + public static InsnAnalyzer makeFieldCond(final int opcode, + final String type) { + return new InsnAnalyzer() { + @Override + public boolean accept(final AbstractInsnNode node) { + if (node instanceof FieldInsnNode) + return node.getOpcode() == opcode && ((FieldInsnNode) node).desc.equals(type); + else + return false; + } + }; + } + public static InsnAnalyzer makeFieldOwnerCond(final int opcode, + final String owner) { + return new InsnAnalyzer() { + @Override + public boolean accept(final AbstractInsnNode node) { + if (node instanceof FieldInsnNode) + return node.getOpcode() == opcode && ((FieldInsnNode) node).owner.equals(owner); + else + return false; + } + }; + } + public static InsnAnalyzer makeFieldRegexCond(final int opcode, + final String regex) { + return new InsnAnalyzer() { + @Override + public boolean accept(final AbstractInsnNode node) { + if (node instanceof FieldInsnNode) + return node.getOpcode() == opcode && ((FieldInsnNode) node).desc.matches(regex); + else + return false; + } + }; + } + + public static InsnAnalyzer makeIntCond(final int opcode, + final int value) { + return new InsnAnalyzer() { + @Override + public boolean accept(final AbstractInsnNode node) { + if (node instanceof IntInsnNode) + return node.getOpcode() == opcode && ((IntInsnNode) node).operand == value; + else + return false; + } + }; + } + + /** + * An instruction condition for a GETFIELD instruction with signature Z. + */ + public final static InsnAnalyzer GETFIELD_Z = + makeFieldCond(GETFIELD, "Z"); + /** + * An instruction condition for a PUTFIELD instruction with signature Z. + */ + public final static InsnAnalyzer PUTFIELD_Z = + makeFieldCond(PUTFIELD, "Z"); + /** + * An instruction condition for a GETSTATIC instruction with signature Z. + */ + public final static InsnAnalyzer GETSTATIC_Z = + makeFieldCond(GETSTATIC, "Z"); + /** + * An instruction condition for a PUTSTATIC instruction with signature Z. + */ + public final static InsnAnalyzer PUTSTATIC_Z = + makeFieldCond(PUTSTATIC, "Z"); + + /** + * An instruction condition for a GETFIELD instruction with signature [Z. + */ + public final static InsnAnalyzer GETFIELD_ZA = + makeFieldCond(GETFIELD, "[Z"); + /** + * An instruction condition for a PUTFIELD instruction with signature [Z. + */ + public final static InsnAnalyzer PUTFIELD_ZA = + makeFieldCond(PUTFIELD, "[Z"); + /** + * An instruction condition for a GETSTATIC instruction with signature [Z. + */ + public final static InsnAnalyzer GETSTATIC_ZA = + makeFieldCond(GETSTATIC, "[Z"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [Z. + */ + public final static InsnAnalyzer PUTSTATIC_ZA = + makeFieldCond(PUTSTATIC, "[Z"); + + /** + * An instruction condition for a GETFIELD instruction with signature [[Z. + */ + public final static InsnAnalyzer GETFIELD_ZAA = + makeFieldCond(GETFIELD, "[[Z"); + /** + * An instruction condition for a PUTFIELD instruction with signature [[Z. + */ + public final static InsnAnalyzer PUTFIELD_ZAA = + makeFieldCond(PUTFIELD, "[[Z"); + /** + * An instruction condition for a GETSTATIC instruction with signature [[Z. + */ + public final static InsnAnalyzer GETSTATIC_ZAA = + makeFieldCond(GETSTATIC, "[[Z"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [[Z. + */ + public final static InsnAnalyzer PUTSTATIC_ZAA = + makeFieldCond(PUTSTATIC, "[[Z"); + + /** + * An instruction condition for a GETFIELD instruction with signature B. + */ + public final static InsnAnalyzer GETFIELD_B = + makeFieldCond(GETFIELD, "B"); + /** + * An instruction condition for a PUTFIELD instruction with signature B. + */ + public final static InsnAnalyzer PUTFIELD_B = + makeFieldCond(PUTFIELD, "B"); + /** + * An instruction condition for a GETSTATIC instruction with signature B. + */ + public final static InsnAnalyzer GETSTATIC_B = + makeFieldCond(GETSTATIC, "B"); + /** + * An instruction condition for a PUTSTATIC instruction with signature B. + */ + public final static InsnAnalyzer PUTSTATIC_B = + makeFieldCond(PUTSTATIC, "B"); + + /** + * An instruction condition for a GETFIELD instruction with signature [B. + */ + public final static InsnAnalyzer GETFIELD_BA = + makeFieldCond(GETFIELD, "[B"); + /** + * An instruction condition for a PUTFIELD instruction with signature [B. + */ + public final static InsnAnalyzer PUTFIELD_BA = + makeFieldCond(PUTFIELD, "[B"); + /** + * An instruction condition for a GETSTATIC instruction with signature [B. + */ + public final static InsnAnalyzer GETSTATIC_BA = + makeFieldCond(GETSTATIC, "[B"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [B. + */ + public final static InsnAnalyzer PUTSTATIC_BA = + makeFieldCond(PUTSTATIC, "[B"); + + /** + * An instruction condition for a GETFIELD instruction with signature [[B. + */ + public final static InsnAnalyzer GETFIELD_BAA = + makeFieldCond(GETFIELD, "[[B"); + + /** + * An instruction condition for a PUTFIELD instruction with signature [[B. + */ + public final static InsnAnalyzer PUTFIELD_BAA = + makeFieldCond(PUTFIELD, "[[B"); + /** + * An instruction condition for a GETSTATIC instruction with signature [[B. + */ + public final static InsnAnalyzer GETSTATIC_BAA = + makeFieldCond(GETSTATIC, "[[B"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [[B. + */ + public final static InsnAnalyzer PUTSTATIC_BAA = + makeFieldCond(PUTSTATIC, "[[B"); + + /** + * An instruction condition for a GETFIELD instruction with signature C. + */ + public final static InsnAnalyzer GETFIELD_C = + makeFieldCond(GETFIELD, "C"); + /** + * An instruction condition for a PUTFIELD instruction with signature C. + */ + public final static InsnAnalyzer PUTFIELD_C = + makeFieldCond(PUTFIELD, "C"); + /** + * An instruction condition for a GETSTATIC instruction with signature C. + */ + public final static InsnAnalyzer GETSTATIC_C = + makeFieldCond(GETSTATIC, "C"); + /** + * An instruction condition for a PUTSTATIC instruction with signature C. + */ + public final static InsnAnalyzer PUTSTATIC_C = + makeFieldCond(PUTSTATIC, "C"); + + /** + * An instruction condition for a GETFIELD instruction with signature [C. + */ + public final static InsnAnalyzer GETFIELD_CA = + makeFieldCond(GETFIELD, "[C"); + /** + * An instruction condition for a PUTFIELD instruction with signature [C. + */ + public final static InsnAnalyzer PUTFIELD_CA = + makeFieldCond(PUTFIELD, "[C"); + /** + * An instruction condition for a GETSTATIC instruction with signature [C. + */ + public final static InsnAnalyzer GETSTATIC_CA = + makeFieldCond(GETSTATIC, "[C"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [C. + */ + public final static InsnAnalyzer PUTSTATIC_CA = + makeFieldCond(PUTSTATIC, "[C"); + + /** + * An instruction condition for a GETFIELD instruction with signature [[C. + */ + public final static InsnAnalyzer GETFIELD_CAA = + makeFieldCond(GETFIELD, "[[C"); + /** + * An instruction condition for a PUTFIELD instruction with signature [[C. + */ + public final static InsnAnalyzer PUTFIELD_CAA = + makeFieldCond(PUTFIELD, "[[C"); + /** + * An instruction condition for a GETSTATIC instruction with signature [[C. + */ + public final static InsnAnalyzer GETSTATIC_CAA = + makeFieldCond(GETSTATIC, "[[C"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [[C. + */ + public final static InsnAnalyzer PUTSTATIC_CAA = + makeFieldCond(PUTSTATIC, "[[C"); + + /** + * An instruction condition for a GETFIELD instruction with signature S. + */ + public final static InsnAnalyzer GETFIELD_S = + makeFieldCond(GETFIELD, "S"); + /** + * An instruction condition for a PUTFIELD instruction with signature S. + */ + public final static InsnAnalyzer PUTFIELD_S = + makeFieldCond(PUTFIELD, "S"); + /** + * An instruction condition for a GETSTATIC instruction with signature S. + */ + public final static InsnAnalyzer GETSTATIC_S = + makeFieldCond(GETSTATIC, "S"); + /** + * An instruction condition for a PUTSTATIC instruction with signature S. + */ + public final static InsnAnalyzer PUTSTATIC_S = + makeFieldCond(PUTSTATIC, "S"); + + /** + * An instruction condition for a GETFIELD instruction with signature [S. + */ + public final static InsnAnalyzer GETFIELD_SA = + makeFieldCond(GETFIELD, "[S"); + /** + * An instruction condition for a PUTFIELD instruction with signature [S. + */ + public final static InsnAnalyzer PUTFIELD_SA = + makeFieldCond(PUTFIELD, "[S"); + /** + * An instruction condition for a GETSTATIC instruction with signature [S. + */ + public final static InsnAnalyzer GETSTATIC_SA = + makeFieldCond(GETSTATIC, "[S"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [S. + */ + public final static InsnAnalyzer PUTSTATIC_SA = + makeFieldCond(PUTSTATIC, "[S"); + + /** + * An instruction condition for a GETFIELD instruction with signature [[S. + */ + public final static InsnAnalyzer GETFIELD_SAA = + makeFieldCond(GETFIELD, "[[S"); + /** + * An instruction condition for a PUTFIELD instruction with signature [[S. + */ + public final static InsnAnalyzer PUTFIELD_SAA = + makeFieldCond(PUTFIELD, "[[S"); + /** + * An instruction condition for a GETSTATIC instruction with signature [[S. + */ + public final static InsnAnalyzer GETSTATIC_SAA = + makeFieldCond(GETSTATIC, "[[S"); + /** + * An instruction condition for a PUTSTATIC instruction with signature [[S. + */ + public final static InsnAnalyzer PUTSTATIC_SAA = + makeFieldCond(PUTSTATIC, "[[S"); + + public final static InsnAnalyzer GETFIELD_I = + makeFieldCond(GETFIELD, "I"); + public final static InsnAnalyzer PUTFIELD_I = + makeFieldCond(PUTFIELD, "I"); + public final static InsnAnalyzer GETSTATIC_I = + makeFieldCond(GETSTATIC, "I"); + public final static InsnAnalyzer PUTSTATIC_I = + makeFieldCond(PUTSTATIC, "I"); + + public final static InsnAnalyzer GETFIELD_IA = + makeFieldCond(GETFIELD, "[I"); + public final static InsnAnalyzer PUTFIELD_IA = + makeFieldCond(PUTFIELD, "[I"); + public final static InsnAnalyzer GETSTATIC_IA = + makeFieldCond(GETSTATIC, "[I"); + public final static InsnAnalyzer PUTSTATIC_IA = + makeFieldCond(PUTSTATIC, "[I"); + + public final static InsnAnalyzer GETFIELD_IAA = + makeFieldCond(GETFIELD, "[[I"); + public final static InsnAnalyzer PUTFIELD_IAA = + makeFieldCond(PUTFIELD, "[[I"); + public final static InsnAnalyzer GETSTATIC_IAA = + makeFieldCond(GETSTATIC, "[[I"); + public final static InsnAnalyzer PUTSTATIC_IAA = + makeFieldCond(PUTSTATIC, "[[I"); + + public final static InsnAnalyzer GETFIELD_J = + makeFieldCond(GETFIELD, "J"); + public final static InsnAnalyzer PUTFIELD_J = + makeFieldCond(PUTFIELD, "J"); + public final static InsnAnalyzer GETSTATIC_J = + makeFieldCond(GETSTATIC, "J"); + public final static InsnAnalyzer PUTSTATIC_J = + makeFieldCond(PUTSTATIC, "J"); + + public final static InsnAnalyzer GETFIELD_JA = + makeFieldCond(GETFIELD, "[J"); + public final static InsnAnalyzer PUTFIELD_JA = + makeFieldCond(PUTFIELD, "[J"); + public final static InsnAnalyzer GETSTATIC_JA = + makeFieldCond(GETSTATIC, "[J"); + public final static InsnAnalyzer PUTSTATIC_JA = + makeFieldCond(PUTSTATIC, "[J"); + + public final static InsnAnalyzer GETFIELD_JAA = + makeFieldCond(GETFIELD, "[[J"); + public final static InsnAnalyzer PUTFIELD_JAA = + makeFieldCond(PUTFIELD, "[[J"); + public final static InsnAnalyzer GETSTATIC_JAA = + makeFieldCond(GETSTATIC, "[[J"); + public final static InsnAnalyzer PUTSTATIC_JAA = + makeFieldCond(PUTSTATIC, "[[J"); + + public final static InsnAnalyzer GETFIELD_F = + makeFieldCond(GETFIELD, "F"); + public final static InsnAnalyzer PUTFIELD_F = + makeFieldCond(PUTFIELD, "F"); + public final static InsnAnalyzer GETSTATIC_F = + makeFieldCond(GETSTATIC, "F"); + public final static InsnAnalyzer PUTSTATIC_F = + makeFieldCond(PUTSTATIC, "F"); + + public final static InsnAnalyzer GETFIELD_FA = + makeFieldCond(GETFIELD, "[F"); + public final static InsnAnalyzer PUTFIELD_FA = + makeFieldCond(PUTFIELD, "[F"); + public final static InsnAnalyzer GETSTATIC_FA = + makeFieldCond(GETSTATIC, "[F"); + public final static InsnAnalyzer PUTSTATIC_FA = + makeFieldCond(PUTSTATIC, "[F"); + + public final static InsnAnalyzer GETFIELD_FAA = + makeFieldCond(GETFIELD, "[[F"); + public final static InsnAnalyzer PUTFIELD_FAA = + makeFieldCond(PUTFIELD, "[[F"); + public final static InsnAnalyzer GETSTATIC_FAA = + makeFieldCond(GETSTATIC, "[[F"); + public final static InsnAnalyzer PUTSTATIC_FAA = + makeFieldCond(PUTSTATIC, "[[F"); + + public final static InsnAnalyzer GETFIELD_D = + makeFieldCond(GETFIELD, "D"); + public final static InsnAnalyzer PUTFIELD_D = + makeFieldCond(PUTFIELD, "D"); + public final static InsnAnalyzer GETSTATIC_D = + makeFieldCond(GETSTATIC, "D"); + public final static InsnAnalyzer PUTSTATIC_D = + makeFieldCond(PUTSTATIC, "D"); + + public final static InsnAnalyzer GETFIELD_DA = + makeFieldCond(GETFIELD, "[D"); + public final static InsnAnalyzer PUTFIELD_DA = + makeFieldCond(PUTFIELD, "[D"); + public final static InsnAnalyzer GETSTATIC_DA = + makeFieldCond(GETSTATIC, "[D"); + public final static InsnAnalyzer PUTSTATIC_DA = + makeFieldCond(PUTSTATIC, "[D"); + + public final static InsnAnalyzer GETFIELD_DAA = + makeFieldCond(GETFIELD, "[[D"); + public final static InsnAnalyzer PUTFIELD_DAA = + makeFieldCond(PUTFIELD, "[[D"); + public final static InsnAnalyzer GETSTATIC_DAA = + makeFieldCond(GETSTATIC, "[[D"); + public final static InsnAnalyzer PUTSTATIC_DAA = + makeFieldCond(PUTSTATIC, "[[D"); + + public final static InsnAnalyzer GETFIELD_L = + makeFieldRegexCond(GETFIELD, "L.*;"); + public final static InsnAnalyzer PUTFIELD_L = + makeFieldRegexCond(PUTFIELD, "L.*;"); + public final static InsnAnalyzer GETSTATIC_L = + makeFieldRegexCond(GETSTATIC, "L.*;"); + public final static InsnAnalyzer PUTSTATIC_L = + makeFieldRegexCond(PUTSTATIC, "L.*;"); + + public final static InsnAnalyzer GETFIELD_LA = + makeFieldRegexCond(GETFIELD, "\\[L.*;"); + public final static InsnAnalyzer PUTFIELD_LA = + makeFieldRegexCond(PUTFIELD, "\\[L.*;"); + public final static InsnAnalyzer GETSTATIC_LA = + makeFieldRegexCond(GETSTATIC, "\\[L.*;"); + public final static InsnAnalyzer PUTSTATIC_LA = + makeFieldRegexCond(PUTSTATIC, "\\[L.*;"); + + public final static InsnAnalyzer GETFIELD_LAA = + makeFieldRegexCond(GETFIELD, "\\[\\[L.*;"); + public final static InsnAnalyzer PUTFIELD_LAA = + makeFieldRegexCond(PUTFIELD, "\\[\\[L.*;"); + public final static InsnAnalyzer GETSTATIC_LAA = + makeFieldRegexCond(GETSTATIC, "\\[\\[L.*;"); + public final static InsnAnalyzer PUTSTATIC_LAA = + makeFieldRegexCond(PUTSTATIC, "\\[\\[L.*;"); + + public final static InsnAnalyzer GETFIELD_String = + makeFieldCond(GETFIELD, "Ljava/lang/String;"); + public final static InsnAnalyzer PUTFIELD_String = + makeFieldCond(PUTFIELD, "Ljava/lang/String;"); + public final static InsnAnalyzer GETSTATIC_String = + makeFieldCond(GETSTATIC, "Ljava/lang/String;"); + public final static InsnAnalyzer PUTSTATIC_String = + makeFieldCond(PUTSTATIC, "Ljava/lang/String;"); + + public final static InsnAnalyzer GETFIELD_StringA = + makeFieldCond(GETFIELD, "[Ljava/lang/String;"); + public final static InsnAnalyzer PUTFIELD_StringA = + makeFieldCond(PUTFIELD, "[Ljava/lang/String;"); + public final static InsnAnalyzer GETSTATIC_StringA = + makeFieldCond(GETSTATIC, "[Ljava/lang/String;"); + public final static InsnAnalyzer PUTSTATIC_StringA = + makeFieldCond(PUTSTATIC, "[Ljava/lang/String;"); + + public final static InsnAnalyzer GETFIELD_StringAA = + makeFieldCond(GETFIELD, "[[Ljava/lang/String;"); + public final static InsnAnalyzer PUTFIELD_StringAA = + makeFieldCond(PUTFIELD, "[[Ljava/lang/String;"); + public final static InsnAnalyzer GETSTATIC_StringAA = + makeFieldCond(GETSTATIC, "[[Ljava/lang/String;"); + public final static InsnAnalyzer PUTSTATIC_StringAA = + makeFieldCond(PUTSTATIC, "[[Ljava/lang/String;"); + +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/commons/InsnAnalyzer.java b/src/the/bytecode/club/bytecodeviewer/searching/commons/InsnAnalyzer.java new file mode 100644 index 00000000..8fed605a --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/commons/InsnAnalyzer.java @@ -0,0 +1,14 @@ +package the.bytecode.club.bytecodeviewer.searching.commons; + +import org.objectweb.asm.tree.AbstractInsnNode; + +/** + * + * Bytecode instruction search baseclass + * + * @author Waterwolf + * + */ +public interface InsnAnalyzer { + public boolean accept(AbstractInsnNode node); +} diff --git a/src/the/bytecode/club/bytecodeviewer/searching/commons/InstructionSearcher.java b/src/the/bytecode/club/bytecodeviewer/searching/commons/InstructionSearcher.java new file mode 100644 index 00000000..9a71ac84 --- /dev/null +++ b/src/the/bytecode/club/bytecodeviewer/searching/commons/InstructionSearcher.java @@ -0,0 +1,171 @@ +package the.bytecode.club.bytecodeviewer.searching.commons; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Class that searches bytecode instructions using given search conditions. + * + * @author WaterWolf + * @author Matthew Bovard + * + */ +public class InstructionSearcher { + private final InsnList list; + private AbstractInsnNode current; + + public InstructionSearcher(final MethodNode m) { + this.list = m.instructions; + this.current = list.getFirst(); + } + + public AbstractInsnNode getCurrent() { + return current; + } + + public void setCurrent(final AbstractInsnNode in) { + current = in; + } + + public AbstractInsnNode getNext(final int opcode) { + return getNext(AnalyzerFactory.makeOpcodeCond(opcode)); + } + + public AbstractInsnNode getNext(final InsnAnalyzer analyzer) { + while (current != null) { + if (analyzer.accept(current)) { + final AbstractInsnNode old = current; + current = current.getNext(); + return old; + } + current = current.getNext(); + } + return null; + } + + public AbstractInsnNode getNext() { + if (current == null) + return null; + current = current.getNext(); + while (current != null && current.getOpcode() == -1) { + current = current.getNext(); + } + return current; + } + + public AbstractInsnNode getPrevious(final InsnAnalyzer analyzer) { + while (current != null) { + if (analyzer.accept(current)) { + final AbstractInsnNode old = current; + current = current.getPrevious(); + return old; + } + current = current.getPrevious(); + } + return null; + } + + public AbstractInsnNode getPrevious(final int opcode) { + return getPrevious(AnalyzerFactory.makeOpcodeCond(opcode)); + } + + public AbstractInsnNode getPrevious() { + current = current.getPrevious(); + while (current.getOpcode() == -1) { + current = current.getPrevious(); + } + return current; + } + + public LdcInsnNode getNextLDC(final Object cst) { + AbstractInsnNode in; + while ((in = getNext(Opcodes.LDC)) != null) { + final LdcInsnNode ln = (LdcInsnNode) in; + if (ln.cst.equals(cst)) return ln; + } + return null; + } + + public LdcInsnNode getPreviousLDC(final Object cst) { + AbstractInsnNode in; + while ((in = getPrevious(Opcodes.LDC)) != null) { + final LdcInsnNode ln = (LdcInsnNode) in; + if (ln.cst.equals(cst)) + return ln; + } + return null; + } + + public IntInsnNode getNextInt(final int opcode, final int i) { + return (IntInsnNode) this.getNext(AnalyzerFactory.makeIntCond(opcode, i)); + } + + public IntInsnNode getPreviousInt(final int opcode, final int i) { + return (IntInsnNode) this.getPrevious(AnalyzerFactory.makeIntCond(opcode, i)); + } + + /** + * @param opcode One of Opcodes.BIPUSH/Opcodes.SIPUSH + * @param value Value to look for + * @return + */ + public IntInsnNode getNextPush(final int opcode, final int value) { + AbstractInsnNode in; + while ((in = getNext(opcode)) != null) { + final IntInsnNode iin = (IntInsnNode) in; + if (iin.operand == value) return iin; + } + return null; + } + + public List analyze(final int opcode) { + reset(); + final List list = new ArrayList(); + AbstractInsnNode in; + while ((in = getNext(opcode)) != null) { + list.add(in); + } + return list; + } + + public int getIndex() { + return list.indexOf(current); + } + + public void setIndex(final int index) { + current = list.get(index); + } + + /** + * Resets us back to the first instruction + */ + public void reset() { + current = list.getFirst(); + } + + public void resetToEnd() { + current = list.getLast(); + } + + public void insert(final AbstractInsnNode location, final AbstractInsnNode insn) { + this.list.insert(location, insn); + } + + public int computePosition(final AbstractInsnNode node) { + AbstractInsnNode poller = list.getFirst(); + int index = 0; + while ((poller = poller.getNext()) != null) { + if (poller.equals(node)) + return index; + index++; + } + return -1; + } +} \ No newline at end of file