package the.bytecode.club.bytecodeviewer; import me.konloch.kontainer.io.HTTPRequest; import org.apache.commons.io.FileUtils; import org.objectweb.asm.tree.ClassNode; import the.bytecode.club.bytecodeviewer.api.ClassNodeLoader; import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.gui.*; import the.bytecode.club.bytecodeviewer.obfuscators.mapping.Refactorer; import the.bytecode.club.bytecodeviewer.plugin.PluginManager; import javax.swing.*; import javax.swing.filechooser.FileFilter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.*; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /*************************************************************************** * 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 * <p/> * Are you a Java Reverse Engineer? Or maybe you want to learn Java Reverse Engineering? * Join The Bytecode Club, we're noob friendly, and censorship free. * <p/> * http://the.bytecode.club * <p/> * 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. * <p/> * 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 * 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. * <p/> * TODO: * <p/> * 3.0.0: (RETIREMENT PARTY, WOHOOO) * Add obfuscation: * - Add integer boxing and other obfuscation methods contra implemented * - Insert unadded/debug opcodes to try to fuck up decompilers * - ClassAnylyzterAdapter * Add the jump/save mark system Ida Pro has. * Add class annotations to bytecode decompiler. * EVERYTHING BUG FREE, CHECK 100% * bytecode editor that works by editing per method instead of entire class, methods are in a pane like the file navigator * Make the tabs menu and middle mouse button click work on the tab itself not just the close button. * <p/> * before 3.0.0: * EVERYTHING ON THE FUCKING GITHUB ISSUES LOL * make it use that global last used inside of export as jar * Spiffy up the plugin console with hilighted lines * Take https://github.com/ptnkjke/Java-Bytecode-Editor visualize * make zipfile not include the decode shit * add stackmapframes to bytecode decompiler * add stackmapframes remover? * make ez-injection plugin console show all sys.out calls * add JEB decompiler optionally, requires them to add jeb library jar externally and disable update check ? * add decompile as zip for krakatau-bytecode, jd-gui and smali for CLI * fix hook inject for EZ-Injection * fix classfile searcher * make the decompilers launch in a separate process? * <p/> * -----2.9.9-----: * 08/01/2015 - Fixed a pingback concurrency exception issue. * 08/03/2015 - Fixed a typo for FernFlower decompiler. * 08/03/2015 - Fixed an issue with Krakatau Decompiler as zip. * 08/07/2015 - "Fixed" an issue with Enjarify and latest PyPy3 bin. * 08/07/2015 - FernFlower & CFR Decompiler now launch in their own process with the 'slimjar' version. * 08/07/2015 - Switched the ClassViewer up slightly so it utilizes the event dispatch thread. * 08/07/2015 - Fixed? CFIDE's Bytecode Decompiler on TableSwitchs * 01/04/2015 - Afffsdd fixed the same filename from 2 containers. * 01/04/2015 - Afffsdd fixed a typo. * 01/04/2015 - Afffsdd Added an option to show the containing file name. * * @author Konloch */ public class BytecodeViewer { /*per version*/ public static final String version = "3.0.0"; public static final String krakatauVersion = "8"; public static final String enjarifyVersion = "2"; public static final boolean previewCopy = false; /* Constants */ public static final String fs = System.getProperty("file.separator"); public static final String nl = System.getProperty("line.separator"); public static final File dataDir = new File(System.getProperty("user.home") + fs + ".Bytecode-Viewer"); public static final File filesFile = new File(dataDir, "recentfiles.bcv"); public static final File pluginsFile = new File(dataDir, "recentplugins.bcv"); public static final File settingsFile = new File(dataDir, "settings.bcv"); public static final File krakatauDirectory = new File(dataDir + fs + "krakatau_" + krakatauVersion + fs + "Krakatau-master"); public static final File enjarifyDirectory = new File(dataDir + fs + "enjarify_" + enjarifyVersion + fs + "enjarify-master"); @Deprecated public static final File tempDir = new File(dataDir, "bcv_temp"); private static final long start = System.currentTimeMillis(); /*the rest*/ public static MainViewerGUI viewer = null; public static ClassNodeLoader loader = new ClassNodeLoader(); // TODO MAKE SECURE BECAUSE THIS IS INSECURE public static SecurityMan sm = new SecurityMan(); // TODO MAKE SECURE BECAUSE THIS IS INSECURE public static ArrayList<FileContainer> files = new ArrayList<FileContainer>(); //all of BCV's loaded files/classes/etc private static int maxRecentFiles = 25; private static List<String> recentFiles = new ArrayList<>(); private static List<String> recentPlugins = new ArrayList<>(); public static boolean runningObfuscation = false; public static String lastDirectory = ""; public static ArrayList<Process> createdProcesses = new ArrayList<Process>(); public static Refactorer refactorer = new Refactorer(); public static boolean pingback = false; public static boolean deleteForiegnLibraries = true; private BytecodeViewer() { } /** * Main startup * * @param args files you want to open or CLI */ public static void main(String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { new ExceptionUI(e); } try { System.setSecurityManager(sm); System.out.println("https://the.bytecode.club - Created by @Konloch and @samczsun - Bytecode Viewer " + version); CommandLineInput input = new CommandLineInput(args); if (previewCopy && !input.containsCommand()) 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"); if (!filesFile.exists() && !filesFile.createNewFile()) { throw new RuntimeException("Could not create recent files file"); } if (!pluginsFile.exists() && !pluginsFile.createNewFile()) { throw new RuntimeException("Could not create recent plugins file"); } recentFiles.addAll(FileUtils.readLines(filesFile, "UTF-8")); recentPlugins.addAll(FileUtils.readLines(pluginsFile, "UTF-8")); int CLI = input.parseCommandLine(); if (CLI == CommandLineInput.STOP) return; if (CLI == CommandLineInput.OPEN_FILE) { viewer = new MainViewerGUI(); Settings.loadGUI(); Boot.boot(); BytecodeViewer.BOOT(args, false); } else { BytecodeViewer.BOOT(args, true); input.executeCommandLine(); } } catch (Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } } /** * The version checker thread */ private static final Thread versionChecker = new Thread() { @Override public void run() { try { HTTPRequest r = new HTTPRequest(new URL("https://raw.githubusercontent.com/Konloch/bytecode-viewer/master/VERSION")); final String version = r.readSingle(); try { int simplemaths = Integer.parseInt(version.replace(".", "")); int simplemaths2 = Integer.parseInt(BytecodeViewer.version.replace(".", "")); if (simplemaths2 > simplemaths) return; //developer version } catch (Exception e) { } if (!BytecodeViewer.version.equals(version)) { r = new HTTPRequest(new URL("https://raw.githubusercontent.com/Konloch/bytecode-viewer/master/README.txt")); String[] readme = r.read(); String changelog = "Unable to load change log, please try again later." + nl; boolean trigger = false; boolean finalTrigger = false; for (String st : readme) { if (st.equals("--- " + BytecodeViewer.version + " ---:")) { changelog = ""; trigger = true; } else if (trigger) { if (st.startsWith("--- ")) finalTrigger = true; if (finalTrigger) changelog += st + nl; } } JOptionPane pane = new JOptionPane("Your version: " + BytecodeViewer.version + ", latest version: " + version + nl + nl + "Changes since your version:" + nl + changelog + nl + "What would you like to do?"); Object[] options = new String[]{"Open The Download Page", "Download The Updated Jar", "Do Nothing"}; pane.setOptions(options); JDialog dialog = pane.createDialog(BytecodeViewer.viewer, "Bytecode Viewer - Outdated Version"); dialog.setVisible(true); 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) { if (Desktop.isDesktopSupported()) { Desktop.getDesktop().browse(new URI("https://github.com/Konloch/bytecode-viewer/releases")); } else { showMessage("Cannot open the page, please manually type it." + nl + "https://github.com/Konloch/bytecode-viewer/releases"); } } if (result == 1) { JFileChooser fc = new JFileChooser(); try { fc.setCurrentDirectory(new File(".").getAbsoluteFile()); //set the current working directory } catch (Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() || MiscUtils.extension(f.getAbsolutePath()).equals("zip"); } @Override public String getDescription() { return "Zip Archives"; } }); fc.setFileHidingEnabled(false); fc.setAcceptAllFileFilterUsed(false); int returnVal = fc.showSaveDialog(viewer); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); if (!file.getAbsolutePath().endsWith(".zip")) file = new File(file.getAbsolutePath() + ".zip"); if (file.exists()) { pane = new JOptionPane("The file " + file + " exists, would you like to overwrite it?"); options = new String[]{"Yes", "No"}; pane.setOptions(options); dialog = pane.createDialog(BytecodeViewer.viewer, "Bytecode Viewer - Overwrite File"); dialog.setVisible(true); obj = pane.getValue(); result = -1; for (int k = 0; k < options.length; k++) if (options[k].equals(obj)) result = k; if (result != 0) return; file.delete(); } final File finalFile = file; Thread downloadThread = new Thread() { @Override public void run() { try { InputStream is = new URL("https://github.com/Konloch/bytecode-viewer/releases/download/v" + version + "/BytecodeViewer." + version + ".zip").openConnection().getInputStream(); FileOutputStream fos = new FileOutputStream(finalFile); try { System.out.println("Downloading from https://github.com/Konloch/bytecode-viewer/releases/download/v" + version + "/BytecodeViewer." + version + ".zip"); byte[] buffer = new byte[8192]; int len; int downloaded = 0; boolean flag = false; showMessage("Downloading the jar in the background, when it's finished you will be alerted with another message box." + nl + nl + "Expect this to take several minutes."); while ((len = is.read(buffer)) > 0) { fos.write(buffer, 0, len); fos.flush(); downloaded += 8192; int mbs = downloaded / 1048576; if (mbs % 5 == 0 && mbs != 0) { if (!flag) System.out.println("Downloaded " + mbs + "MBs so far"); flag = true; } else flag = false; } } finally { try { if (is != null) { is.close(); } } finally { if (fos != null) { fos.flush(); fos.close(); } } } System.out.println("Download finished!"); showMessage("Download successful! You can find the updated program at " + finalFile.getAbsolutePath()); } catch (FileNotFoundException e) { showMessage("Unable to download, the zip file has not been uploaded yet, please try again in about 10 minutes."); } catch (Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } } }; downloadThread.start(); } } } } catch (Exception e) { e.printStackTrace(); } } }; /** * Pings back to bytecodeviewer.com to be added into the total running statistics */ private static final Thread PingBack = new Thread() { @Override public void run() { try { new HTTPRequest(new URL("https://bytecodeviewer.com/add.php")).read(); } catch (Exception e) { pingback = false; } } }; public static void pingback() { JOptionPane pane = new JOptionPane("Would you like to 'pingback' to https://bytecodeviewer.com to be counted in the global users for BCV?"); Object[] options = new String[]{"Yes", "No"}; pane.setOptions(options); JDialog dialog = pane.createDialog(BytecodeViewer.viewer, "Bytecode Viewer - Optional Pingback"); dialog.setVisible(true); 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) { try { if (!PingBack.isAlive()) PingBack.start(); } catch (Exception e) { 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(String[] args, boolean cli) { cleanup(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { for (Process proc : createdProcesses) proc.destroy(); try { FileUtils.writeLines(filesFile, recentFiles); FileUtils.writeLines(pluginsFile, recentPlugins); } catch (IOException e) { new ExceptionUI(e); } Settings.saveGUI(); cleanup(); } }); viewer.calledAfterLoad(); resetRecentFilesMenu(); if (!pingback) { PingBack.start(); pingback = true; } if (viewer.chckbxmntmNewCheckItem_12.isSelected()) versionChecker.start(); if (!cli) viewer.setVisible(true); System.out.println("Start up took " + ((System.currentTimeMillis() - start) / 1000) + " seconds"); if (!cli) if (args.length >= 1) for (String s : args) { openFiles(new File[]{new File(s)}, true); } } /** * because Smali and Baksmali System.exit if it failed * * @param i */ public static void exit(int i) { } /** * 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 containerName name of the FileContainer that this class is in * @param name the class name * @return the ClassNode instance */ public static ClassNode getClassNode(String containerName, String name) { for (FileContainer container : files) { if (container.name.equals(containerName) && container.getData().containsKey(name + ".class")) { return container.getClassNode(name); } } return null; } public static byte[] getClassBytes(String containerName, String name) { for (FileContainer container : files) { if (container.name.equals(containerName) && container.getData().containsKey(name)) { return container.getData().get(name); } } return null; } /** * Grabs the file contents of the loaded resources. * * @param name the file name * @return the file contents as a byte[] */ public static byte[] getFileContents(String name) { for (FileContainer container : files) { HashMap<String, byte[]> files = container.files; if (files.containsKey(name)) return files.get(name); } return null; } /** * 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) { for (FileContainer container : files) { if (container.remove(oldNode)) container.add(newNode); } } /** * Gets all of the loaded classes as an array list * * @return the loaded classes as an array list */ public static ArrayList<ClassNode> getLoadedClasses() { ArrayList<ClassNode> a = new ArrayList<ClassNode>(); for (FileContainer container : files) for (ClassNode c : container.values()) if (!a.contains(c)) a.add(c); return a; } public static ArrayList<ClassNode> loadAllClasses() { ArrayList<ClassNode> a = new ArrayList<ClassNode>(); for (FileContainer container : files) { for (String s : container.files.keySet()) { ClassNode loaded = container.getClassNode(s.substring(0, s.length() - 6)); if (loaded != null) { a.add(loaded); } } } return a; } public static Map<String, byte[]> getLoadedBytes() { Map<String, byte[]> data = new HashMap<>(); for (FileContainer container : files) { data.putAll(container.getData()); } return data; } /** * 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. */ public static boolean compile(boolean message) { BytecodeViewer.viewer.setIcon(true); boolean actuallyTried = false; for (java.awt.Component c : BytecodeViewer.viewer.workPane.getLoadedViewers()) { if (c instanceof ClassViewer) { ClassViewer cv = (ClassViewer) c; boolean valid = false; for (int i = 0; i < cv.panels.size(); i++) { if (cv.smalis.get(i) != null && cv.smalis.get(i).isEditable()) { valid = true; } } if (valid) { actuallyTried = true; Object smali[] = cv.getSmali(); if (smali != null) { ClassNode origNode = (ClassNode) smali[0]; String smaliText = (String) smali[1]; byte[] smaliCompiled = the.bytecode.club.bytecodeviewer.compilers.Compiler.smali.compile(smaliText, origNode.name); if (smaliCompiled != null) { ClassNode newNode = JarUtils.getNode(smaliCompiled); BytecodeViewer.updateNode(origNode, newNode); } else { BytecodeViewer.showMessage("There has been an error with assembling your Smali code, please check this. Class: " + origNode.name); BytecodeViewer.viewer.setIcon(false); return false; } } } valid = false; for (int i = 0; i < cv.panels.size(); i++) { if (cv.krakataus.get(i) != null && cv.krakataus.get(i).isEditable()) { valid = true; } } if (valid) { actuallyTried = true; Object krakatau[] = cv.getKrakatau(); if (krakatau != null) { ClassNode origNode = (ClassNode) krakatau[0]; String krakatauText = (String) krakatau[1]; byte[] krakatauCompiled = the.bytecode.club.bytecodeviewer.compilers.Compiler.krakatau.compile(krakatauText, origNode.name); if (krakatauCompiled != null) { ClassNode newNode = JarUtils.getNode(krakatauCompiled); BytecodeViewer.updateNode(origNode, newNode); } else { BytecodeViewer.showMessage("There has been an error with assembling your Krakatau Bytecode, please check this. Class: " + origNode.name); BytecodeViewer.viewer.setIcon(false); return false; } } } valid = false; for (int i = 0; i < cv.panels.size(); i++) { if (cv.javas.get(i) != null && cv.javas.get(i).isEditable()) { valid = true; } } if (valid) { actuallyTried = true; Object java[] = cv.getJava(); if (java != null) { ClassNode origNode = (ClassNode) java[0]; String javaText = (String) java[1]; SystemErrConsole errConsole = new SystemErrConsole("Java Compile Issues"); errConsole.setText("Error compiling class: " + origNode.name + nl + "Keep in mind most decompilers cannot produce compilable classes" + nl + nl); byte[] javaCompiled = the.bytecode.club.bytecodeviewer.compilers.Compiler.java.compile(javaText, origNode.name); if (javaCompiled != null) { ClassNode newNode = JarUtils.getNode(javaCompiled); BytecodeViewer.updateNode(origNode, newNode); errConsole.finished(); } else { errConsole.pretty(); errConsole.setVisible(true); errConsole.finished(); BytecodeViewer.viewer.setIcon(false); return false; } } } } } if (message) if (actuallyTried) BytecodeViewer.showMessage("Compiled Successfully."); else BytecodeViewer.showMessage("You have no editable panes opened, make one editable and try again."); BytecodeViewer.viewer.setIcon(false); return true; } private static boolean update = 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 */ public static void openFiles(final File[] files, boolean recentFiles) { if (recentFiles) for (File f : files) if (f.exists()) BytecodeViewer.addRecentFile(f); BytecodeViewer.viewer.setIcon(true); update = true; Thread t = new Thread() { @Override public void run() { try { for (final File f : files) { final String fn = f.getName(); if (!f.exists()) { update = false; showMessage("The file " + f.getAbsolutePath() + " could not be found."); } else { if (f.isDirectory()) { FileContainer container = new FileContainer(f); HashMap<String, byte[]> files = new HashMap<String, byte[]>(); boolean finished = false; ArrayList<File> totalFiles = new ArrayList<File>(); totalFiles.add(f); String dir = f.getAbsolutePath();//f.getAbsolutePath().substring(0, f.getAbsolutePath().length()-f.getName().length()); while (!finished) { boolean added = false; for (int i = 0; i < totalFiles.size(); i++) { File child = totalFiles.get(i); if (child.listFiles() != null) for (File rocket : child.listFiles()) if (!totalFiles.contains(rocket)) { totalFiles.add(rocket); added = true; } } if (!added) { for (File child : totalFiles) if (child.isFile()) { String fileName = child.getAbsolutePath().substring(dir.length() + 1, child.getAbsolutePath().length()).replaceAll("\\\\", "\\/"); files.put(fileName, Files.readAllBytes(Paths.get(child.getAbsolutePath()))); } finished = true; } } container.files = files; BytecodeViewer.files.add(container); } else { if (fn.endsWith(".jar") || fn.endsWith(".zip")) { try { JarUtils.put(f); } catch (final Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); update = false; } } else if (fn.endsWith(".class")) { try { byte[] bytes = JarUtils.getBytes(new FileInputStream(f)); String cafebabe = String.format("%02X%02X%02X%02X", bytes[0], bytes[1], bytes[2], bytes[3]); if (cafebabe.toLowerCase().equals("cafebabe")) { final ClassNode cn = JarUtils.getNode(bytes); FileContainer container = new FileContainer(f); container.files.put(cn.name + ".class", bytes); container.add(cn); BytecodeViewer.files.add(container); } else { showMessage(fn + ": Header does not start with CAFEBABE, ignoring."); update = false; } } catch (final Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); update = false; } } else if (fn.endsWith(".apk")) { try { BytecodeViewer.viewer.setIcon(true); FileContainer container = new FileContainer(f); if (viewer.decodeAPKResources.isSelected()) { File decodedResources = new File(tempDir, MiscUtils.randomString(32) + ".apk"); APKTool.decodeResources(f, decodedResources); container.files = JarUtils.loadResources(decodedResources); } container.files.putAll(JarUtils.loadResources(f)); String name = getRandomizedName() + ".jar"; File output = new File(tempDir, name); if (BytecodeViewer.viewer.apkConversionGroup.isSelected(BytecodeViewer.viewer.apkConversionDex.getModel())) Dex2Jar.dex2Jar(f, output); else if (BytecodeViewer.viewer.apkConversionGroup.isSelected(BytecodeViewer.viewer.apkConversionEnjarify.getModel())) Enjarify.apk2Jar(f, output); for (ClassNode classNode : JarUtils.loadClasses(output)) { container.add(classNode); } BytecodeViewer.viewer.setIcon(false); BytecodeViewer.files.add(container); } catch (final Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } return; } else if (fn.endsWith(".dex")) { try { BytecodeViewer.viewer.setIcon(true); FileContainer container = new FileContainer(f); String name = getRandomizedName() + ".jar"; File output = new File(tempDir, name); if (BytecodeViewer.viewer.apkConversionGroup.isSelected(BytecodeViewer.viewer.apkConversionDex.getModel())) Dex2Jar.dex2Jar(f, output); else if (BytecodeViewer.viewer.apkConversionGroup.isSelected(BytecodeViewer.viewer.apkConversionEnjarify.getModel())) Enjarify.apk2Jar(f, output); for (ClassNode classNode : JarUtils.loadClasses(output)) { container.add(classNode); } BytecodeViewer.viewer.setIcon(false); BytecodeViewer.files.add(container); } catch (final Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } return; } else { HashMap<String, byte[]> files = new HashMap<String, byte[]>(); byte[] bytes = JarUtils.getBytes(new FileInputStream(f)); files.put(f.getName(), bytes); FileContainer container = new FileContainer(f); container.files = files; BytecodeViewer.files.add(container); } } } } } catch (final Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } finally { BytecodeViewer.viewer.setIcon(false); if (update) try { MainViewerGUI.getComponent(FileNavigationPane.class).updateTree(); } catch (java.lang.NullPointerException e) { } } } }; t.start(); } /** * Starts the specified plugin * * @param file the file of the plugin */ public static void startPlugin(File file) { if (!file.exists()) return; 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 */ public static void showMessage(String message) { JOptionPane.showMessageDialog(viewer, message); } /** * Resets the workspace with optional user input required * * @param ask if should require user input or not */ public static void resetWorkSpace(boolean ask) { if (!ask) { files.clear(); MainViewerGUI.getComponent(FileNavigationPane.class).resetWorkspace(); MainViewerGUI.getComponent(WorkPane.class).resetWorkspace(); MainViewerGUI.getComponent(SearchingPane.class).resetWorkspace(); the.bytecode.club.bytecodeviewer.api.BytecodeViewer.getClassNodeLoader().clear(); } else { 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.setVisible(true); 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) { files.clear(); MainViewerGUI.getComponent(FileNavigationPane.class).resetWorkspace(); MainViewerGUI.getComponent(WorkPane.class).resetWorkspace(); MainViewerGUI.getComponent(SearchingPane.class).resetWorkspace(); the.bytecode.club.bytecodeviewer.api.BytecodeViewer.getClassNodeLoader().clear(); } } } private static ArrayList<String> killList = new ArrayList<String>(); /** * Add the recent file * * @param f the recent file */ 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()); resetRecentFilesMenu(); } private static ArrayList<String> killList2 = new ArrayList<String>(); /** * Add to the recent plugin list * * @param f the plugin file */ 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()); resetRecentFilesMenu(); } /** * resets the recent files menu */ public static void resetRecentFilesMenu() { viewer.mnRecentFiles.removeAll(); for (String s : recentFiles) if (!s.isEmpty()) { JMenuItem m = new JMenuItem(s); m.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JMenuItem m = (JMenuItem) e.getSource(); openFiles(new File[]{new File(m.getText())}, true); } }); viewer.mnRecentFiles.add(m); } viewer.mnRecentPlugins.removeAll(); for (String s : recentPlugins) if (!s.isEmpty()) { JMenuItem m = new JMenuItem(s); m.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JMenuItem m = (JMenuItem) e.getSource(); startPlugin(new File(m.getText())); } }); viewer.mnRecentPlugins.add(m); } } /** * Clears the temp directory */ public static void cleanup() { try { FileUtils.cleanDirectory(tempDir); } catch (Exception e) { } } public static ArrayList<String> createdRandomizedNames = new ArrayList<String>(); /** * Ensures it will only return a uniquely generated names, contains a dupe checker to be sure * * @return the unique randomized name of 25 characters. */ public static String getRandomizedName() { boolean generated = false; String name = ""; while (!generated) { String randomizedName = MiscUtils.randomString(25); if (!createdRandomizedNames.contains(randomizedName)) { createdRandomizedNames.add(randomizedName); name = randomizedName; generated = true; } } return name; } /** * Returns the BCV directory * * @return the static BCV directory */ public static String getBCVDirectory() { while (!dataDir.exists()) dataDir.mkdirs(); if (!dataDir.isHidden() && isWindows()) hideFile(dataDir); return dataDir.getAbsolutePath(); } /** * Checks if the OS contains 'win' * * @return true if the os.name property contains 'win' */ private static boolean isWindows() { return System.getProperty("os.name").toLowerCase().contains("win"); } /** * Runs the windows command to hide files * * @param f file you want hidden */ private static void hideFile(File f) { sm.stopBlocking(); try { // Hide file by running attrib system command (on Windows) Runtime.getRuntime().exec("attrib +H " + f.getAbsolutePath()); } catch (Exception e) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e); } sm.setBlocking(); } private static long last = System.currentTimeMillis(); /** * Checks the hotkeys * * @param e */ public static void checkHotKey(KeyEvent e) { if (System.currentTimeMillis() - last <= (4000)) return; if ((e.getKeyCode() == KeyEvent.VK_O) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { last = System.currentTimeMillis(); JFileChooser fc = new JFileChooser(); try { fc.setSelectedFile(new File(BytecodeViewer.lastDirectory)); } catch (Exception e2) { } fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) return true; String extension = MiscUtils.extension(f.getAbsolutePath()); if (extension != null) if (extension.equals("jar") || extension.equals("zip") || extension.equals("class") || extension.equals("apk") || extension.equals("dex")) return true; return false; } @Override public String getDescription() { return "APKs, DEX, Class Files or Zip/Jar Archives"; } }); fc.setFileHidingEnabled(false); fc.setAcceptAllFileFilterUsed(false); int returnVal = fc.showOpenDialog(BytecodeViewer.viewer); if (returnVal == JFileChooser.APPROVE_OPTION) { BytecodeViewer.lastDirectory = fc.getSelectedFile().getAbsolutePath(); try { BytecodeViewer.viewer.setIcon(true); BytecodeViewer.openFiles(new File[]{fc.getSelectedFile()}, true); BytecodeViewer.viewer.setIcon(false); } catch (Exception e1) { new the.bytecode.club.bytecodeviewer.api.ExceptionUI(e1); } } } else if ((e.getKeyCode() == KeyEvent.VK_N) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { last = System.currentTimeMillis(); BytecodeViewer.resetWorkSpace(true); } else if ((e.getKeyCode() == KeyEvent.VK_T) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { last = System.currentTimeMillis(); Thread t = new Thread() { public void run() { BytecodeViewer.compile(true); } }; t.start(); } else if ((e.getKeyCode() == KeyEvent.VK_R) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { last = System.currentTimeMillis(); if (BytecodeViewer.getLoadedClasses().isEmpty()) { BytecodeViewer.showMessage("First open a class, jar, zip, apk or dex file."); return; } new RunOptions().setVisible(true); } else if ((e.getKeyCode() == KeyEvent.VK_S) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { last = System.currentTimeMillis(); if (BytecodeViewer.getLoadedClasses().isEmpty()) { BytecodeViewer.showMessage("First open a class, jar, zip, apk or dex file."); return; } Thread t = new Thread() { public void run() { if (viewer.autoCompileSmali.isSelected() && !BytecodeViewer.compile(false)) return; JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() || MiscUtils.extension(f.getAbsolutePath()).equals("zip"); } @Override public String getDescription() { return "Zip Archives"; } }); fc.setFileHidingEnabled(false); fc.setAcceptAllFileFilterUsed(false); int returnVal = fc.showSaveDialog(viewer); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); if (!file.getAbsolutePath().endsWith(".zip")) file = new File(file.getAbsolutePath() + ".zip"); if (file.exists()) { JOptionPane pane = new JOptionPane("Are you sure you wish to overwrite this existing file?"); Object[] options = new String[]{"Yes", "No"}; pane.setOptions(options); JDialog dialog = pane.createDialog(BytecodeViewer.viewer, "Bytecode Viewer - Overwrite File"); dialog.setVisible(true); 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) { file.delete(); } else { return; } } final File file2 = file; BytecodeViewer.viewer.setIcon(true); Thread t = new Thread() { @Override public void run() { JarUtils.saveAsJar(BytecodeViewer.getLoadedBytes(), file2.getAbsolutePath()); BytecodeViewer.viewer.setIcon(false); } }; t.start(); } } }; t.start(); } else if ((e.getKeyCode() == KeyEvent.VK_W) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { last = System.currentTimeMillis(); if (viewer.workPane.getCurrentViewer() != null) viewer.workPane.tabs.remove(viewer.workPane.getCurrentViewer()); } } }