From 49c9bd3d2098aeed3920c433422a98b50011225d Mon Sep 17 00:00:00 2001 From: Johnny Santamaria Date: Fri, 10 Oct 2025 01:32:18 +0000 Subject: [PATCH] feat: Add deobfuscator feature with UI integration, preview, and undo --- pom.xml | 7 +++ .../deobfuscator/Deobfuscator.java | 56 +++++++++++++++++++ .../bytecodeviewer/gui/MainViewerGUI.java | 50 +++++++++++++++++ .../deobfuscator/DeobfuscatorTest.java | 41 ++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/deobfuscator/Deobfuscator.java create mode 100644 src/test/java/the/bytecode/club/bytecodeviewer/deobfuscator/DeobfuscatorTest.java diff --git a/pom.xml b/pom.xml index 43face833..ccdefdc35 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,13 @@ HTTPRequest ${httprequest.version} + + + junit + junit + 4.13.2 + test + org.jetbrains annotations diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/deobfuscator/Deobfuscator.java b/src/main/java/the/bytecode/club/bytecodeviewer/deobfuscator/Deobfuscator.java new file mode 100644 index 000000000..0334abf70 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/deobfuscator/Deobfuscator.java @@ -0,0 +1,56 @@ +package the.bytecode.club.bytecodeviewer.deobfuscator; + +import org.objectweb.asm.tree.ClassNode; +import the.bytecode.club.bytecodeviewer.BytecodeViewer; +import the.bytecode.club.bytecodeviewer.api.ASMResourceUtil; +import java.util.HashMap; +import java.util.Map; + +/** + * Deobfuscator logic for renaming obfuscated class names to semantic names. + * Prefixes: C for Class, AC for Abstract Class, I for Interface. + */ +public class Deobfuscator { + private final Map nameMapping = new HashMap<>(); + private int classCount = 1; + private int abstractClassCount = 1; + private int interfaceCount = 1; + + public void run() { + for (ClassNode cn : BytecodeViewer.getLoadedClasses()) { + String newName = generateNewName(cn); + // Handle inner/anonymous classes + if (cn.name.contains("$")) { + // Split outer and inner + String[] parts = cn.name.split("\\$"); + String outer = parts[0]; + String inner = parts[1]; + String outerNew = nameMapping.getOrDefault(outer, generateNewName(cn)); + // If anonymous (numeric), keep numeric + if (inner.matches("\\d+")) { + newName = outerNew + "$" + inner; + } else { + newName = outerNew + "$" + inner; + } + } + // Optionally handle package renaming (preserve for now) + nameMapping.put(cn.name, newName); + ASMResourceUtil.renameClassNode(cn.name, newName); + } + } + + String generateNewName(ClassNode cn) { + int access = cn.access; + if ((access & org.objectweb.asm.Opcodes.ACC_INTERFACE) != 0) { + return "I" + String.format("%03d", interfaceCount++); + } else if ((access & org.objectweb.asm.Opcodes.ACC_ABSTRACT) != 0) { + return "AC" + String.format("%03d", abstractClassCount++); + } else { + return "C" + String.format("%03d", classCount++); + } + } + + public Map getNameMapping() { + return nameMapping; + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java index 8a6b84fa7..a06fd1790 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/MainViewerGUI.java @@ -682,6 +682,56 @@ public void buildPluginMenu() pluginsMainMenu.add(stackFramesRemover); pluginsMainMenu.add(changeClassFileVersions); + // Deobfuscator integration + JMenuItem deobfuscateClasses = new JMenuItem("Deobfuscate Classes"); + pluginsMainMenu.add(deobfuscateClasses); + + JMenuItem previewDeobfuscation = new JMenuItem("Preview Deobfuscation Mapping"); + pluginsMainMenu.add(previewDeobfuscation); + + JMenuItem undoDeobfuscation = new JMenuItem("Undo Deobfuscation"); + pluginsMainMenu.add(undoDeobfuscation); + + // Store last mapping for undo + final the.bytecode.club.bytecodeviewer.deobfuscator.Deobfuscator[] lastDeobfuscator = new the.bytecode.club.bytecodeviewer.deobfuscator.Deobfuscator[1]; + + deobfuscateClasses.addActionListener(e -> { + the.bytecode.club.bytecodeviewer.deobfuscator.Deobfuscator deobfuscator = new the.bytecode.club.bytecodeviewer.deobfuscator.Deobfuscator(); + deobfuscator.run(); + lastDeobfuscator[0] = deobfuscator; + JOptionPane.showMessageDialog(this, "Deobfuscation complete.", "Deobfuscator", JOptionPane.INFORMATION_MESSAGE); + BytecodeViewer.refreshAllTabs(); + }); + + previewDeobfuscation.addActionListener(e -> { + the.bytecode.club.bytecodeviewer.deobfuscator.Deobfuscator deobfuscator = new the.bytecode.club.bytecodeviewer.deobfuscator.Deobfuscator(); + // Generate mapping without renaming + StringBuilder sb = new StringBuilder(); + for (org.objectweb.asm.tree.ClassNode cn : the.bytecode.club.bytecodeviewer.BytecodeViewer.getLoadedClasses()) { + String newName = deobfuscator.generateNewName(cn); + sb.append(cn.name).append(" -> ").append(newName).append("\n"); + } + JTextArea textArea = new JTextArea(sb.toString()); + textArea.setEditable(false); + JScrollPane scrollPane = new JScrollPane(textArea); + scrollPane.setPreferredSize(new Dimension(500, 400)); + JOptionPane.showMessageDialog(this, scrollPane, "Preview Deobfuscation Mapping", JOptionPane.INFORMATION_MESSAGE); + }); + + undoDeobfuscation.addActionListener(e -> { + if (lastDeobfuscator[0] == null) { + JOptionPane.showMessageDialog(this, "No deobfuscation to undo.", "Undo Deobfuscation", JOptionPane.WARNING_MESSAGE); + return; + } + // Reverse mapping + for (java.util.Map.Entry entry : lastDeobfuscator[0].getNameMapping().entrySet()) { + the.bytecode.club.bytecodeviewer.api.ASMResourceUtil.renameClassNode(entry.getValue(), entry.getKey()); + } + lastDeobfuscator[0] = null; + JOptionPane.showMessageDialog(this, "Deobfuscation undone.", "Undo Deobfuscation", JOptionPane.INFORMATION_MESSAGE); + BytecodeViewer.refreshAllTabs(); + }); + //allatori is disabled since they are just placeholders //ZKM and ZStringArray decrypter are disabled until deobfuscation has been extended //mnNewMenu_1.add(mntmNewMenuItem_2); diff --git a/src/test/java/the/bytecode/club/bytecodeviewer/deobfuscator/DeobfuscatorTest.java b/src/test/java/the/bytecode/club/bytecodeviewer/deobfuscator/DeobfuscatorTest.java new file mode 100644 index 000000000..99d56194b --- /dev/null +++ b/src/test/java/the/bytecode/club/bytecodeviewer/deobfuscator/DeobfuscatorTest.java @@ -0,0 +1,41 @@ +package the.bytecode.club.bytecodeviewer.deobfuscator; + +import org.junit.Test; +import org.objectweb.asm.tree.ClassNode; +import java.util.ArrayList; +import java.util.List; +import static org.junit.Assert.*; + +public class DeobfuscatorTest { + @Test + public void testGenerateNewName() { + Deobfuscator deobfuscator = new Deobfuscator(); + List nodes = new ArrayList<>(); + // Concrete class + ClassNode classNode = new ClassNode(); + classNode.name = "a"; + classNode.access = 0; // No abstract/interface + nodes.add(classNode); + // Abstract class + ClassNode abstractNode = new ClassNode(); + abstractNode.name = "b"; + abstractNode.access = org.objectweb.asm.Opcodes.ACC_ABSTRACT; + nodes.add(abstractNode); + // Interface + ClassNode interfaceNode = new ClassNode(); + interfaceNode.name = "c"; + interfaceNode.access = org.objectweb.asm.Opcodes.ACC_INTERFACE; + nodes.add(interfaceNode); + // Simulate BytecodeViewer.getLoadedClasses() + for (ClassNode cn : nodes) { + String newName = deobfuscator.generateNewName(cn); + if (cn.access == 0) { + assertTrue(newName.startsWith("C")); + } else if ((cn.access & org.objectweb.asm.Opcodes.ACC_ABSTRACT) != 0) { + assertTrue(newName.startsWith("AC")); + } else if ((cn.access & org.objectweb.asm.Opcodes.ACC_INTERFACE) != 0) { + assertTrue(newName.startsWith("I")); + } + } + } +}