Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@
<artifactId>HTTPRequest</artifactId>
<version>${httprequest.version}</version>
</dependency>
<!-- JUnit for unit testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> getNameMapping() {
return nameMapping;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ClassNode> 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"));
}
}
}
}