bcv-vf/src/main/java/the/bytecode/club/bytecodeviewer/BytecodeViewer.java

590 lines
20 KiB
Java
Raw Normal View History

package the.bytecode.club.bytecodeviewer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
2021-06-26 12:59:51 +00:00
2021-06-29 09:41:29 +00:00
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bootloader.Boot;
import the.bytecode.club.bytecodeviewer.api.ClassNodeLoader;
2021-07-07 00:53:29 +00:00
import the.bytecode.club.bytecodeviewer.api.ExceptionUI;
2021-06-26 17:36:26 +00:00
import the.bytecode.club.bytecodeviewer.gui.components.*;
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.TabbedPane;
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ClassViewer;
import the.bytecode.club.bytecodeviewer.gui.MainViewerGUI;
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ResourceViewer;
import the.bytecode.club.bytecodeviewer.obfuscators.mapping.Refactorer;
import the.bytecode.club.bytecodeviewer.plugin.PluginManager;
import the.bytecode.club.bytecodeviewer.util.*;
2021-06-26 15:10:02 +00:00
import the.bytecode.club.bytecodeviewer.resources.importing.ImportResource;
import static the.bytecode.club.bytecodeviewer.Constants.*;
2021-06-29 09:41:29 +00:00
import static the.bytecode.club.bytecodeviewer.Settings.addRecentPlugin;
import static the.bytecode.club.bytecodeviewer.util.MiscUtils.guessLanguage;
/***************************************************************************
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
* Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
/**
* A lightweight Java Reverse Engineering suite, developed by Konloch - http://konloch.me
*
* All you have to do is add a jar or class file into the workspace,
2019-04-17 06:45:15 +00:00
* 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.
*
2019-04-17 06:45:15 +00:00
* 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 anything else you can think of.
*
2019-04-17 06:45:15 +00:00
* You can either use one of the pre-written plugins, or write your own. It supports java 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.
*
2019-04-17 06:45:15 +00:00
* Are you a Java Reverse Engineer? Or maybe you want to learn Java Reverse Engineering?
2021-04-12 20:19:12 +00:00
* Join The Bytecode Club, we're noob friendly, and censorship free.
* http://the.bytecode.club
*
2021-06-26 02:59:43 +00:00
* TODO BUGS:
2021-07-05 04:50:28 +00:00
* + Panes that currently are being opened/decompiled should not be able to be refreshed - Causes a lock
2021-06-28 04:14:20 +00:00
* + View>Visual Settings>Show Class Methods
2021-06-26 05:25:50 +00:00
* + Spam-clicking the refresh button will cause the swing thread to deadlock (Quickly opening resources used to also do this)
2021-06-26 02:59:43 +00:00
* This is caused by the ctrlMouseWheelZoom code, a temporary patch is just removing it worst case
* + Versioning and updating need to be fixed
2021-06-26 05:25:50 +00:00
* + Fix classfile searcher
* + Smali Assembly compile - Needs to be fixed
2021-06-26 02:59:43 +00:00
*
* TODO IN-PROGRESS:
* + While loading an external plugin it should check if its java or JS, if so it should ask if you'd like to run or edit the plugin using the PluginWriter
* + Resource Importer needs to be rewritten to handle resources better
2021-07-05 04:50:28 +00:00
* + Resource Exporter/Save/Decompile As Zip needs to be rewrittern
2021-06-26 05:25:50 +00:00
* + Finish dragging code
* + Finish right-click tab menu detection
* + Fix hook inject for EZ-Injection
2021-06-26 02:59:43 +00:00
*
* TODO FEATURES:
* + CLI Headless needs to be supported
2021-06-26 05:25:50 +00:00
* + Add stackmapframes to bytecode decompiler
* + Add JEB decompiler optionally, requires them to add jeb library jar
* + Add https://github.com/exbin/bined as the replacement Hed Viewer/Editor
* + Make the decompilers launch in a separate process
* + Make it use that global last used inside of export as jar
* + Make zipfile not include the decode shit
* + Make ez-injection plugin console show all sys.out calls
* + Add decompile as zip for krakatau-bytecode, jd-gui and smali for CLI
* + Add decompile all as zip for CLI
2018-01-31 15:41:24 +00:00
*
2021-07-04 08:51:05 +00:00
* TODO IDEAS:
* + App Bundle Support
2021-07-05 04:50:28 +00:00
* + Add the setting to force all non-class resources to be opened with the Hex Viewer
2021-07-04 10:23:14 +00:00
* ^ Optionally a right-click menu open-as would work inside of the resource list
2021-07-04 08:51:05 +00:00
* + Allow class files to be opened without needing the .class extension
* ^ Easiest way to do this is to read the file header CAFEBABE on resource view
* + Look into removing the loaded classes from inside the FileContainer & then generate the ClassNodes on demand
* ^ This has the added benefit of only extracting on decompilation/when needed. It would also mean everything
* could be treated as byte[] file resources instead of juggling between Classes and File resources.
* ^ An added bonus would be you could also support BCEL (along with other bytecode manipulation libraries)
* and add support for https://github.com/ptnkjke/Java-Bytecode-Editor visualizer as a plugin
*
* @author Konloch
2019-04-17 06:45:15 +00:00
* @author The entire BCV community
*/
public class BytecodeViewer
{
2021-07-07 00:23:34 +00:00
public static boolean EXPERIMENTAL_TAB_CODE = false;
public static boolean DEV_MODE = false; //if true error streams as preserved
2021-07-06 23:54:20 +00:00
public static String[] launchArgs;
public static MainViewerGUI viewer = null;
public static ClassNodeLoader loader = new ClassNodeLoader(); //might be insecure due to assholes targeting BCV,
2021-06-29 15:17:42 +00:00
public static SecurityMan sm = new SecurityMan(); //might be insecure due to assholes targeting BCV,
public static Refactorer refactorer = new Refactorer();
public static List<FileContainer> files = new ArrayList<>(); //all of BCV's loaded files/classes/etc
public static List<Process> createdProcesses = new ArrayList<>();
2021-06-29 09:41:29 +00:00
public static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
2021-07-07 00:23:34 +00:00
2021-07-01 09:48:10 +00:00
private static final Thread versionChecker = new Thread(new VersionChecker(), "Version Checker");
2021-07-06 23:53:37 +00:00
private static final Thread pingBack = new Thread(new PingBack(), "Pingback");
private static final Thread installFatJar = new Thread(new InstallFatJar(), "Install Fat-Jar");
2021-07-01 09:48:10 +00:00
private static final Thread bootCheck = new Thread(new BootCheck(), "Boot Check");
2018-01-31 15:41:24 +00:00
/**
* Main startup
*
* @param args files you want to open or CLI
*/
public static void main(String[] args)
{
2021-07-06 23:54:20 +00:00
launchArgs = args;
2021-07-06 23:53:37 +00:00
//welcome message
2021-07-07 00:53:29 +00:00
System.out.print("Bytecode Viewer " + VERSION);
if(FAT_JAR)
System.out.print(" [FatJar]");
System.out.println(" - Created by @Konloch");
System.out.println("https://bytecodeviewer.com - https://the.bytecode.club");
2021-07-06 23:53:37 +00:00
//set the security manager
2018-01-31 15:41:24 +00:00
System.setSecurityManager(sm);
2021-07-06 23:53:37 +00:00
try
{
//precache settings file
2021-06-29 09:41:29 +00:00
SettingsSerializer.preloadSettingsFile();
//setup look and feel
Configuration.lafTheme.setLAF();
2019-04-17 06:45:15 +00:00
if (PREVIEW_COPY && !CommandLineInput.containsCommand(args))
2021-04-12 20:19:12 +00:00
showMessage("WARNING: This is a preview/dev copy, you WON'T be alerted when " + VERSION + " is "
+ "actually out if you use this." + nl +
"Make sure to watch the repo: https://github.com/Konloch/bytecode-viewer for " + VERSION + "'s release");
//set swing specific system properties
System.setProperty("swing.aatext", "true");
//setup swing components
2018-01-31 15:41:24 +00:00
viewer = new MainViewerGUI();
SwingUtilities.updateComponentTreeUI(viewer);
//load settings and set swing components state
2021-06-29 09:41:29 +00:00
SettingsSerializer.loadSettings();
//set translation language
2021-06-29 09:41:29 +00:00
if(!Settings.hasSetLanguageAsSystemLanguage)
MiscUtils.setLanguage(guessLanguage());
//handle CLI
2018-01-31 15:41:24 +00:00
int CLI = CommandLineInput.parseCommandLine(args);
if (CLI == CommandLineInput.STOP)
return;
2021-07-06 23:53:37 +00:00
if (!FAT_JAR)
{
2018-01-31 15:41:24 +00:00
bootCheck.start();
2021-04-12 20:19:12 +00:00
Boot.boot(args, CLI != CommandLineInput.OPEN_FILE);
2021-07-06 23:53:37 +00:00
}
else
2021-06-21 11:32:07 +00:00
installFatJar.start();
2018-01-31 15:41:24 +00:00
if (CLI == CommandLineInput.OPEN_FILE)
2021-06-21 11:32:07 +00:00
BytecodeViewer.boot(false);
2021-07-06 23:53:37 +00:00
else
{
2021-06-21 11:32:07 +00:00
BytecodeViewer.boot(true);
2018-01-31 15:41:24 +00:00
CommandLineInput.executeCommandLine(args);
}
2021-07-06 23:53:37 +00:00
}
catch (Exception e)
{
2018-01-31 15:41:24 +00:00
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e);
}
}
/**
* Boot after all of the libraries have been loaded
*
* @param cli is it running CLI mode or not
*/
public static void boot(boolean cli)
{
2021-07-01 09:06:12 +00:00
cleanupAsync();
Runtime.getRuntime().addShutdownHook(new Thread(() ->
{
2021-04-12 22:31:22 +00:00
for (Process proc : createdProcesses)
proc.destroy();
2021-06-29 09:41:29 +00:00
SettingsSerializer.saveSettings();
2021-04-12 22:31:22 +00:00
cleanup();
2021-07-01 09:48:10 +00:00
}, "Shutdown Hook"));
2018-01-31 15:41:24 +00:00
viewer.calledAfterLoad();
2021-06-29 09:41:29 +00:00
Settings.resetRecentFilesMenu();
2018-01-31 15:41:24 +00:00
//ping back once on first boot to add to global user count
if (!Configuration.pingback)
{
2021-06-21 11:32:07 +00:00
pingBack.start();
Configuration.pingback = true;
2018-01-31 15:41:24 +00:00
}
2021-06-21 22:45:00 +00:00
if (viewer.updateCheck.isSelected())
2018-01-31 15:41:24 +00:00
versionChecker.start();
if (!cli)
viewer.setVisible(true);
System.out.println("Start up took " + ((System.currentTimeMillis() - Configuration.start) / 1000) + " seconds");
2018-01-31 15:41:24 +00:00
if (!cli)
2021-07-06 23:54:20 +00:00
if (launchArgs.length >= 1)
for (String s : launchArgs)
2018-01-31 15:41:24 +00:00
openFiles(new File[]{new File(s)}, true);
}
/**
* Returns the java command it can use to launch the decompilers
*
* @return
*/
2021-07-06 23:53:37 +00:00
public static synchronized String getJavaCommand()
{
sm.stopBlocking();
try
{
2018-01-31 15:41:24 +00:00
ProcessBuilder pb = new ProcessBuilder("java", "-version");
2021-04-12 22:31:22 +00:00
pb.start();
return "java"; //java is set
2021-07-06 23:53:37 +00:00
}
catch (Exception e) //ignore
{
2018-01-31 15:41:24 +00:00
sm.setBlocking();
boolean empty = Configuration.java.isEmpty();
2021-07-06 23:53:37 +00:00
while (empty)
{
showMessage("You need to set your Java path, this requires the JRE to be downloaded." +
nl + "(C:/Program Files/Java/JDK_xx/bin/java.exe)");
viewer.selectJava();
empty = Configuration.java.isEmpty();
2018-01-31 15:41:24 +00:00
}
}
2021-07-06 23:53:37 +00:00
finally
{
sm.setBlocking();
}
return Configuration.java;
2018-01-31 15:41:24 +00:00
}
/**
* Returns the currently opened ClassNode
*
* @return the currently opened ClassNode
*/
public static ClassNode getCurrentlyOpenedClassNode() {
return viewer.workPane.getCurrentViewer().cn;
}
/**
* Returns the ClassNode by the specified name
*
* @param name the class name
* @return the ClassNode instance
*/
2021-07-06 23:53:37 +00:00
public static ClassNode getClassNode(String name)
{
2018-01-31 15:41:24 +00:00
for (FileContainer container : files)
for (ClassNode c : container.classes)
if (c.name.equals(name))
return c;
return null;
}
2021-07-07 00:23:34 +00:00
/**
* Returns the File Container by the specific name
*/
2021-07-06 23:53:37 +00:00
public static FileContainer getFileContainer(String name)
{
2019-04-17 06:45:15 +00:00
for (FileContainer container : files)
if (container.name.equals(name))
return container;
return null;
}
2021-07-07 00:23:34 +00:00
/**
* Returns all of the loaded File Containers
*/
2019-06-01 01:04:07 +00:00
public static List<FileContainer> getFiles() {
return files;
}
2021-07-07 00:23:34 +00:00
/**
* Returns a ClassNode by name specific namefrom a specific File Container
*/
2021-07-06 23:53:37 +00:00
public static ClassNode getClassNode(FileContainer container, String name)
{
2019-04-17 06:45:15 +00:00
for (ClassNode c : container.classes)
if (c.name.equals(name))
return c;
return null;
}
2018-01-31 15:41:24 +00:00
/**
* Grabs the file contents of the loaded resources.
*
* @param name the file name
* @return the file contents as a byte[]
*/
2021-07-06 23:08:08 +00:00
public static byte[] getFileContents(String name)
{
2021-06-26 12:59:51 +00:00
for (FileContainer container : files)
if (container.files.containsKey(name))
return container.files.get(name);
2018-01-31 15:41:24 +00:00
return null;
}
2021-06-26 12:38:58 +00:00
/**
2021-07-07 00:23:34 +00:00
* Grab the byte array from the loaded Class object by getting the resource from the classloader
2021-06-26 12:38:58 +00:00
*/
2021-07-07 00:23:34 +00:00
public static byte[] getClassFileBytes(Class<?> clazz) throws IOException
2021-07-06 23:08:08 +00:00
{
2021-07-07 00:23:34 +00:00
return ClassFileUtils.getClassFileBytes(clazz);
2021-06-26 12:38:58 +00:00
}
2018-01-31 15:41:24 +00:00
/**
* Replaces an old node with a new instance
*
* @param oldNode the old instance
* @param newNode the new instance
*/
public static void updateNode(ClassNode oldNode, ClassNode newNode)
{
2021-07-06 23:08:08 +00:00
for (FileContainer container : files)
2018-01-31 15:41:24 +00:00
if (container.classes.remove(oldNode))
container.classes.add(newNode);
}
/**
* Gets all of the loaded classes as an array list
*
* @return the loaded classes as an array list
*/
2021-07-06 23:08:08 +00:00
public static ArrayList<ClassNode> getLoadedClasses()
{
2021-04-12 22:31:22 +00:00
ArrayList<ClassNode> a = new ArrayList<>();
2018-01-31 15:41:24 +00:00
for (FileContainer container : files)
for (ClassNode c : container.classes)
if (!a.contains(c))
a.add(c);
return a;
}
2021-07-03 19:29:31 +00:00
/**
* Called any time refresh is called to automatically compile all of the compilable panes that're opened.
*/
public static boolean autoCompileSuccessful()
{
if(!BytecodeViewer.viewer.autoCompileOnRefresh.isSelected())
return true;
try {
2021-07-06 00:26:11 +00:00
return compile(false, false);
2021-07-03 19:29:31 +00:00
} catch (NullPointerException ignored) {
return false;
}
}
2018-01-31 15:41:24 +00:00
/**
* Compile all of the compilable panes that're opened.
*
* @param message if it should send a message saying it's compiled sucessfully.
* @return true if no errors, false if it failed to compile.
*/
2021-07-06 23:08:08 +00:00
public static boolean compile(boolean message, boolean successAlert)
{
2021-07-06 22:57:42 +00:00
BytecodeViewer.updateBusyStatus(true);
boolean noErrors = true;
2018-01-31 15:41:24 +00:00
boolean actuallyTried = false;
2021-07-06 00:06:05 +00:00
for (java.awt.Component c : BytecodeViewer.viewer.workPane.getLoadedViewers())
{
if (c instanceof ClassViewer)
{
2018-01-31 15:41:24 +00:00
ClassViewer cv = (ClassViewer) c;
if(noErrors && !cv.resourceViewPanel1.compile())
noErrors = false;
if(noErrors && !cv.resourceViewPanel2.compile())
noErrors = false;
if(noErrors && !cv.resourceViewPanel3.compile())
noErrors = false;
2021-07-06 00:06:05 +00:00
if(cv.resourceViewPanel1.textArea != null && cv.resourceViewPanel1.textArea.isEditable())
2018-01-31 15:41:24 +00:00
actuallyTried = true;
2021-07-06 00:06:05 +00:00
if(cv.resourceViewPanel2.textArea != null && cv.resourceViewPanel2.textArea.isEditable())
2018-01-31 15:41:24 +00:00
actuallyTried = true;
2021-07-06 00:06:05 +00:00
if(cv.resourceViewPanel3.textArea != null && cv.resourceViewPanel3.textArea.isEditable())
2018-01-31 15:41:24 +00:00
actuallyTried = true;
}
}
if (message)
{
2018-01-31 15:41:24 +00:00
if (actuallyTried)
{
2021-07-05 03:37:01 +00:00
if(noErrors && successAlert)
BytecodeViewer.showMessage("Compiled Successfully.");
}
2018-01-31 15:41:24 +00:00
else
{
2018-01-31 15:41:24 +00:00
BytecodeViewer.showMessage("You have no editable panes opened, make one editable and try again.");
}
}
2021-07-06 00:06:05 +00:00
2021-07-06 22:57:42 +00:00
BytecodeViewer.updateBusyStatus(false);
2018-01-31 15:41:24 +00:00
return true;
}
/**
* Opens a file, optional if it should append to the recent files menu
*
* @param files the file(s) you wish to open
* @param recentFiles if it should append to the recent files menu
*/
2021-07-06 23:08:08 +00:00
public static void openFiles(final File[] files, boolean recentFiles)
{
2018-01-31 15:41:24 +00:00
if (recentFiles)
2021-07-01 09:06:12 +00:00
{
2018-01-31 15:41:24 +00:00
for (File f : files)
if (f.exists())
2021-06-29 09:41:29 +00:00
Settings.addRecentFile(f);
2021-07-01 09:06:12 +00:00
SettingsSerializer.saveSettingsAsync();
}
2018-01-31 15:41:24 +00:00
2021-07-06 22:57:42 +00:00
BytecodeViewer.updateBusyStatus(true);
Configuration.needsReDump = true;
2021-07-01 09:48:10 +00:00
Thread t = new Thread(new ImportResource(files), "Import Resource");
2018-01-31 15:41:24 +00:00
t.start();
}
/**
* Starts the specified plugin
*
* @param file the file of the plugin
*/
2021-07-06 23:08:08 +00:00
public static void startPlugin(File file)
{
2018-01-31 15:41:24 +00:00
if (!file.exists())
2021-07-05 04:24:19 +00:00
{
BytecodeViewer.showMessage("The plugin file " + file.getAbsolutePath() + " could not be found.");
Settings.removeRecentPlugin(file);
2018-01-31 15:41:24 +00:00
return;
2021-07-05 04:24:19 +00:00
}
2018-01-31 15:41:24 +00:00
try {
PluginManager.runPlugin(file);
} catch (Throwable e) {
new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e);
}
addRecentPlugin(file);
}
/**
* Send a message to alert the user
*
* @param message the message you need to send
*/
2021-07-06 23:08:08 +00:00
public static void showMessage(String message)
{
2021-07-05 03:34:37 +00:00
BetterJOptionPane.showMessageDialog(viewer, message);
2018-01-31 15:41:24 +00:00
}
2021-07-06 22:57:42 +00:00
/**
* Alerts the user the program is running something in the background
*/
public static void updateBusyStatus(boolean busyStatus)
{
viewer.updateBusyStatus(busyStatus);
}
/**
* Clears all active busy status icons
*/
public static void clearBusyStatus()
{
viewer.clearBusyStatus();
}
2021-07-06 23:53:37 +00:00
/**
* Refreshes the title on all of the opened tabs
*/
public static void refreshAllTabTitles()
{
for(int i = 0; i < BytecodeViewer.viewer.workPane.tabs.getTabCount(); i++)
{
ResourceViewer viewer = ((TabbedPane) BytecodeViewer.viewer.workPane.tabs.getTabComponentAt(i)).resource;
viewer.refreshTitle();
}
}
2018-01-31 15:41:24 +00:00
/**
* Resets the workspace with optional user input required
*
* @param ask if should require user input or not
*/
2021-07-06 22:57:42 +00:00
public static void resetWorkspace(boolean ask)
2021-06-26 18:10:55 +00:00
{
if (ask)
{
MultipleChoiceDialogue dialogue = new MultipleChoiceDialogue("Bytecode Viewer - Reset Workspace",
"Are you sure you want to reset the workspace?" +
"\n\rIt will also reset your file navigator and search.",
new String[]{"Yes", "No"});
2021-07-06 22:57:42 +00:00
2021-06-26 18:10:55 +00:00
if (dialogue.promptChoice() != 0)
2019-04-17 06:45:15 +00:00
return;
2018-01-31 15:41:24 +00:00
}
2021-07-06 22:57:42 +00:00
BCVResourceUtils.resetWorkspace();
2018-01-31 15:41:24 +00:00
}
2021-07-01 09:06:12 +00:00
/**
* Clears the temp directory
*/
public static void cleanupAsync()
{
2021-07-01 09:48:10 +00:00
Thread cleanupThread = new Thread(BytecodeViewer::cleanup, "Cleanup");
2021-07-01 09:06:12 +00:00
cleanupThread.start();
}
2018-01-31 15:41:24 +00:00
/**
* Clears the temp directory
*/
2021-07-01 09:06:12 +00:00
public static void cleanup()
{
2021-04-12 22:31:22 +00:00
File tempF = new File(tempDirectory);
2018-01-31 15:41:24 +00:00
try {
FileUtils.deleteDirectory(tempF);
2021-07-06 22:57:42 +00:00
} catch (Exception ignored) { }
2018-01-31 15:41:24 +00:00
while (!tempF.exists()) // keep making dirs
tempF.mkdir();
}
2021-07-06 23:08:08 +00:00
/**
2021-07-06 23:53:37 +00:00
* because Smali and Baksmali System.exit if it failed
*
* @param i
2021-07-06 23:08:08 +00:00
*/
2021-07-06 23:53:37 +00:00
public static void exit(int i) { }
}