From 1d50ba8ccd7551d238e57da975849d79333c2ebb Mon Sep 17 00:00:00 2001 From: GraxCode Date: Sat, 25 Apr 2020 11:08:56 +0200 Subject: [PATCH] add bytecode viewer --- build.gradle | 3 +- .../{CodeTracker.java => CodeRewriter.java} | 10 +- ...eHandler.java => ICRReferenceHandler.java} | 2 +- .../cleanup/remove/RemoveUnnecessary.java | 11 +- src/me/nov/threadtear/io/Conversion.java | 10 ++ .../threadtear/swing/frame/BytecodeFrame.java | 43 +++++++ .../nov/threadtear/swing/list/ClassList.java | 30 ++--- .../threadtear/swing/panel/BytecodePanel.java | 119 ++++++++++++++++++ .../swing/textarea/BytecodeTextArea.java | 26 ++++ .../swing/textarea/DecompilerTextArea.java | 1 - src/res/bytecode.svg | 1 + src/res/toggle.svg | 1 - 12 files changed, 228 insertions(+), 29 deletions(-) rename src/me/nov/threadtear/analysis/full/{CodeTracker.java => CodeRewriter.java} (93%) rename src/me/nov/threadtear/analysis/full/{ICodeReferenceHandler.java => ICRReferenceHandler.java} (87%) create mode 100644 src/me/nov/threadtear/swing/frame/BytecodeFrame.java create mode 100644 src/me/nov/threadtear/swing/panel/BytecodePanel.java create mode 100644 src/me/nov/threadtear/swing/textarea/BytecodeTextArea.java create mode 100644 src/res/bytecode.svg delete mode 100644 src/res/toggle.svg diff --git a/build.gradle b/build.gradle index b238a8d..9bf2800 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'eclipse' } -version = '2.1.1' +version = '2.2.0' sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -30,6 +30,7 @@ dependencies { compile 'org.ow2.asm:asm:8.0.1' compile 'org.ow2.asm:asm-tree:8.0.1' compile 'org.ow2.asm:asm-analysis:8.0.1' + compile 'org.ow2.asm:asm-util:8.0.1' compile 'org.benf:cfr:0.149' compile 'com.fifesoft:rsyntaxtextarea:3.1.1' diff --git a/src/me/nov/threadtear/analysis/full/CodeTracker.java b/src/me/nov/threadtear/analysis/full/CodeRewriter.java similarity index 93% rename from src/me/nov/threadtear/analysis/full/CodeTracker.java rename to src/me/nov/threadtear/analysis/full/CodeRewriter.java index de8a4c5..156149f 100644 --- a/src/me/nov/threadtear/analysis/full/CodeTracker.java +++ b/src/me/nov/threadtear/analysis/full/CodeRewriter.java @@ -27,21 +27,21 @@ import me.nov.threadtear.analysis.full.value.values.UnaryOpValue; import me.nov.threadtear.analysis.full.value.values.UnknownInstructionValue; -public class CodeTracker extends Interpreter implements Opcodes { +public class CodeRewriter extends Interpreter implements Opcodes { SuperInterpreter basic = new SuperInterpreter(); private CodeReferenceValue[] presetArgs; private Type[] desc; - private ICodeReferenceHandler referenceHandler; + private ICRReferenceHandler referenceHandler; - public CodeTracker(ICodeReferenceHandler referenceHandler) { + public CodeRewriter(ICRReferenceHandler referenceHandler) { super(ASM8); this.referenceHandler = referenceHandler; } - public CodeTracker(ICodeReferenceHandler referenceHandler, boolean isStatic, int localVariables, String descr, CodeReferenceValue[] args) { + public CodeRewriter(ICRReferenceHandler referenceHandler, boolean isStatic, int localVariables, String descr, CodeReferenceValue[] args) { super(ASM8); this.referenceHandler = referenceHandler; this.desc = Type.getArgumentTypes(descr); @@ -153,7 +153,7 @@ public CodeReferenceValue newEmptyValue(int local) { @Override public CodeReferenceValue unaryOperation(AbstractInsnNode insn, CodeReferenceValue value) throws AnalyzerException { - //TODO checkcast, instanceof + // TODO checkcast, instanceof BasicValue v = basic.unaryOperation(insn, value.getType()); switch (insn.getOpcode()) { case GETFIELD: diff --git a/src/me/nov/threadtear/analysis/full/ICodeReferenceHandler.java b/src/me/nov/threadtear/analysis/full/ICRReferenceHandler.java similarity index 87% rename from src/me/nov/threadtear/analysis/full/ICodeReferenceHandler.java rename to src/me/nov/threadtear/analysis/full/ICRReferenceHandler.java index c4e7d81..ff10d90 100644 --- a/src/me/nov/threadtear/analysis/full/ICodeReferenceHandler.java +++ b/src/me/nov/threadtear/analysis/full/ICRReferenceHandler.java @@ -6,7 +6,7 @@ import me.nov.threadtear.analysis.full.value.CodeReferenceValue; -public interface ICodeReferenceHandler { +public interface ICRReferenceHandler { Object getFieldValueOrNull(BasicValue v, String owner, String name, String desc); diff --git a/src/me/nov/threadtear/execution/cleanup/remove/RemoveUnnecessary.java b/src/me/nov/threadtear/execution/cleanup/remove/RemoveUnnecessary.java index 87742b2..e21bb6d 100644 --- a/src/me/nov/threadtear/execution/cleanup/remove/RemoveUnnecessary.java +++ b/src/me/nov/threadtear/execution/cleanup/remove/RemoveUnnecessary.java @@ -13,8 +13,8 @@ import org.objectweb.asm.tree.analysis.Frame; import me.nov.threadtear.analysis.full.CodeAnalyzer; -import me.nov.threadtear.analysis.full.CodeTracker; -import me.nov.threadtear.analysis.full.ICodeReferenceHandler; +import me.nov.threadtear.analysis.full.CodeRewriter; +import me.nov.threadtear.analysis.full.ICRReferenceHandler; import me.nov.threadtear.analysis.full.value.CodeReferenceValue; import me.nov.threadtear.execution.Execution; import me.nov.threadtear.execution.ExecutionCategory; @@ -22,7 +22,7 @@ import me.nov.threadtear.util.asm.Access; import me.nov.threadtear.util.asm.Instructions; -public class RemoveUnnecessary extends Execution implements ICodeReferenceHandler { +public class RemoveUnnecessary extends Execution implements ICRReferenceHandler { public RemoveUnnecessary() { super(ExecutionCategory.CLEANING, "Remove unnecessary instructions", @@ -30,8 +30,7 @@ public RemoveUnnecessary() { } /* - * TODO Nothing done here yet, this class should simulate stack and - * simultaneously rewrite the code. + * TODO Nothing done here yet, this class should simulate stack and simultaneously rewrite the code. * * eg. ICONST_4 ICONST_1 IADD INVOKESTATIC ... * @@ -58,7 +57,7 @@ private InsnList simulateAndRewrite(ClassNode cn, MethodNode m) { m.tryCatchBlocks.clear(); if (m.localVariables != null) m.localVariables.clear(); - CodeAnalyzer a = new CodeAnalyzer(new CodeTracker(this, Access.isStatic(m.access), m.maxLocals, m.desc, new CodeReferenceValue[0])); + CodeAnalyzer a = new CodeAnalyzer(new CodeRewriter(this, Access.isStatic(m.access), m.maxLocals, m.desc, new CodeReferenceValue[0])); try { a.analyze(cn.name, m); } catch (AnalyzerException e) { diff --git a/src/me/nov/threadtear/io/Conversion.java b/src/me/nov/threadtear/io/Conversion.java index b68e41d..ef12097 100644 --- a/src/me/nov/threadtear/io/Conversion.java +++ b/src/me/nov/threadtear/io/Conversion.java @@ -1,8 +1,12 @@ package me.nov.threadtear.io; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.util.TraceClassVisitor; public class Conversion { public static byte[] toBytecode(ClassNode cn, boolean useMaxs) { @@ -38,4 +42,10 @@ public static ClassNode toNode(final byte[] bytez) { cr = null; return cn; } + + public static String textify(ClassNode cn) { + StringWriter out = new StringWriter(); + new ClassReader(toBytecode0(cn)).accept(new TraceClassVisitor(new PrintWriter(out)), ClassReader.SKIP_DEBUG); + return out.toString(); + } } diff --git a/src/me/nov/threadtear/swing/frame/BytecodeFrame.java b/src/me/nov/threadtear/swing/frame/BytecodeFrame.java new file mode 100644 index 0000000..1cc8554 --- /dev/null +++ b/src/me/nov/threadtear/swing/frame/BytecodeFrame.java @@ -0,0 +1,43 @@ +package me.nov.threadtear.swing.frame; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.WindowConstants; + +import org.objectweb.asm.tree.ClassNode; + +import com.github.weisj.darklaf.icons.IconLoader; + +import me.nov.threadtear.swing.Utils; +import me.nov.threadtear.swing.panel.BytecodePanel; + +public class BytecodeFrame extends JFrame { + private static final long serialVersionUID = 1L; + + public BytecodeFrame(ClassNode cn) { + setTitle("Bytecode: " + cn.name); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setBounds(100, 100, 1000, 600); + setLayout(new BorderLayout()); + setIconImage(Utils.iconToImage(IconLoader.get().loadSVGIcon("res/bytecode.svg", 64, 64, false))); + setAlwaysOnTop(true); + JPanel cp = new JPanel(new BorderLayout()); + cp.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + cp.add(new BytecodePanel(cn), BorderLayout.CENTER); + this.add(cp, BorderLayout.CENTER); + JPanel buttons = new JPanel(); + buttons.setLayout(new FlowLayout(FlowLayout.RIGHT)); + JButton close = new JButton("Close"); + close.addActionListener(e -> { + dispose(); + }); + buttons.add(close); + getContentPane().add(buttons, BorderLayout.SOUTH); + + } +} diff --git a/src/me/nov/threadtear/swing/list/ClassList.java b/src/me/nov/threadtear/swing/list/ClassList.java index a08e0b4..35faf30 100644 --- a/src/me/nov/threadtear/swing/list/ClassList.java +++ b/src/me/nov/threadtear/swing/list/ClassList.java @@ -15,6 +15,7 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import com.github.weisj.darklaf.icons.IconLoader; @@ -23,6 +24,7 @@ import me.nov.threadtear.io.JarIO; import me.nov.threadtear.swing.Utils; import me.nov.threadtear.swing.dialog.JarAnalysis; +import me.nov.threadtear.swing.frame.BytecodeFrame; import me.nov.threadtear.swing.frame.DecompilerFrame; import me.nov.threadtear.swing.handler.ILoader; import me.nov.threadtear.swing.handler.JarDropHandler; @@ -67,27 +69,27 @@ private JPanel createButtons() { } }); panel.add(decompile); + JButton bytecode = new JButton("Bytecode", IconLoader.get().loadSVGIcon("res/bytecode.svg", false)); + bytecode.addActionListener(l -> { + SortedTreeClassNode tn = (SortedTreeClassNode) tree.getLastSelectedPathComponent(); + if (tn != null && tn.member != null) { + new BytecodeFrame(tn.member.node).setVisible(true); + } + }); + panel.add(bytecode); JButton ignore = new JButton("Ignore", IconLoader.get().loadSVGIcon("res/ignore.svg", false)); ignore.addActionListener(l -> { - SortedTreeClassNode node = (SortedTreeClassNode) tree.getLastSelectedPathComponent(); - if (node != null) { - ignoreChilds(node); - refreshIgnored(); - repaint(); - tree.grabFocus(); + TreePath[] paths = tree.getSelectionPaths(); + for (int i = 0; i < paths.length; i++) { + SortedTreeClassNode tn = (SortedTreeClassNode) paths[i].getLastPathComponent(); + ignoreChilds(tn); } - }); - panel.add(ignore); - JButton toggle = new JButton("Toggle all", IconLoader.get().loadSVGIcon("res/toggle.svg", false)); - toggle.addActionListener(l -> { - ignoreChilds((SortedTreeClassNode) model.getRoot()); refreshIgnored(); repaint(); tree.grabFocus(); }); - - panel.add(toggle); + panel.add(ignore); return panel; } @@ -121,7 +123,7 @@ public ClassTree() { this.setCellRenderer(new ClassTreeCellRenderer()); model = new DefaultTreeModel(new SortedTreeClassNode("")); this.setModel(model); - this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + this.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION); this.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { diff --git a/src/me/nov/threadtear/swing/panel/BytecodePanel.java b/src/me/nov/threadtear/swing/panel/BytecodePanel.java new file mode 100644 index 0000000..d3a62ad --- /dev/null +++ b/src/me/nov/threadtear/swing/panel/BytecodePanel.java @@ -0,0 +1,119 @@ +package me.nov.threadtear.swing.panel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.Objects; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.border.EmptyBorder; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultHighlighter; +import javax.swing.text.Document; +import javax.swing.text.Highlighter; + +import org.fife.ui.rtextarea.RTextScrollPane; +import org.objectweb.asm.tree.ClassNode; + +import com.github.weisj.darklaf.icons.IconLoader; + +import me.nov.threadtear.io.Conversion; +import me.nov.threadtear.swing.textarea.BytecodeTextArea; + +public class BytecodePanel extends JPanel { + private static final long serialVersionUID = 1L; + + private BytecodeTextArea textArea; + + private int searchIndex = -1; + private String lastSearchText = null; + + public BytecodePanel(ClassNode cn) { + this.setLayout(new BorderLayout(4, 4)); + JPanel actionPanel = new JPanel(); + actionPanel.setLayout(new GridBagLayout()); + JButton reload = new JButton(IconLoader.get().loadSVGIcon("res/refresh.svg", false)); + reload.addActionListener(l -> { + textArea.setText(Conversion.textify(cn)); + }); + JTextField search = new JTextField(); + // search.putClientProperty(DarkTextUI.KEY_DEFAULT_TEXT, "Search..."); + search.setPreferredSize(new Dimension(200, reload.getPreferredSize().height)); + search.addActionListener(l -> { + try { + String text = search.getText(); + if (text.isEmpty()) { + textArea.getHighlighter().removeAllHighlights(); + return; + } + String searchText = text.toLowerCase(); + if (!Objects.equals(searchText, lastSearchText)) { + searchIndex = -1; + lastSearchText = searchText; + } + String[] split = textArea.getText().split("\\r?\\n"); + int firstIndex = -1; + boolean first = false; + Label: { + for (int i = 0; i < split.length; i++) { + String line = split[i]; + if (line.toLowerCase().contains(searchText)) { + if (i > searchIndex) { + textArea.setCaretPosition(textArea.getDocument().getDefaultRootElement().getElement(i).getStartOffset()); + searchIndex = i; + break Label; + } else if (!first) { + firstIndex = i; + first = true; + } + } + } + if (first) { + // go back to first line + textArea.setCaretPosition(textArea.getDocument().getDefaultRootElement().getElement(firstIndex).getStartOffset()); + searchIndex = firstIndex; + } + } + hightlightText(searchText); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + actionPanel.add(search); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.EAST; + actionPanel.add(reload, gbc); + JPanel topPanel = new JPanel(); + topPanel.setBorder(new EmptyBorder(1, 5, 0, 1)); + topPanel.setLayout(new BorderLayout()); + topPanel.add(new JLabel("Bytecode of " + cn.name.replace('/', '.') + ""), BorderLayout.WEST); + topPanel.add(actionPanel, BorderLayout.EAST); + this.add(topPanel, BorderLayout.NORTH); + this.textArea = new BytecodeTextArea(); + textArea.setText(Conversion.textify(cn)); + JScrollPane scp = new RTextScrollPane(textArea); + scp.getVerticalScrollBar().setUnitIncrement(16); + scp.setBorder(BorderFactory.createLoweredSoftBevelBorder()); + this.add(scp, BorderLayout.CENTER); + } + + private void hightlightText(String searchText) throws BadLocationException { + Highlighter highlighter = textArea.getHighlighter(); + highlighter.removeAllHighlights(); + Document document = textArea.getDocument(); + String text = document.getText(0, document.getLength()).toLowerCase(); + int pos = text.indexOf(searchText); + while (pos >= 0) { + highlighter.addHighlight(pos, pos + searchText.length(), (Highlighter.HighlightPainter) new DefaultHighlighter.DefaultHighlightPainter(new Color(0x0078d7))); + pos = text.indexOf(searchText, pos + searchText.length()); + } + } +} diff --git a/src/me/nov/threadtear/swing/textarea/BytecodeTextArea.java b/src/me/nov/threadtear/swing/textarea/BytecodeTextArea.java new file mode 100644 index 0000000..f7d99d9 --- /dev/null +++ b/src/me/nov/threadtear/swing/textarea/BytecodeTextArea.java @@ -0,0 +1,26 @@ +package me.nov.threadtear.swing.textarea; + +import java.awt.Font; +import java.io.IOException; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Theme; + +public class BytecodeTextArea extends RSyntaxTextArea { + private static final long serialVersionUID = 1L; + + public BytecodeTextArea() { + this.setSyntaxEditingStyle(SYNTAX_STYLE_CPLUSPLUS); + this.setCodeFoldingEnabled(true); + this.setAntiAliasingEnabled(true); + this.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + this.setEditable(false); + + try { + Theme theme = Theme.load(getClass().getResourceAsStream("/res/rsta-theme.xml")); + theme.apply(this); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/me/nov/threadtear/swing/textarea/DecompilerTextArea.java b/src/me/nov/threadtear/swing/textarea/DecompilerTextArea.java index 530f15a..032c30c 100644 --- a/src/me/nov/threadtear/swing/textarea/DecompilerTextArea.java +++ b/src/me/nov/threadtear/swing/textarea/DecompilerTextArea.java @@ -16,7 +16,6 @@ public DecompilerTextArea() { this.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); this.setEditable(false); - // change theme to java try { Theme theme = Theme.load(getClass().getResourceAsStream("/res/rsta-theme.xml")); theme.apply(this); diff --git a/src/res/bytecode.svg b/src/res/bytecode.svg new file mode 100644 index 0000000..72a564a --- /dev/null +++ b/src/res/bytecode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/res/toggle.svg b/src/res/toggle.svg deleted file mode 100644 index bcade4d..0000000 --- a/src/res/toggle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file