From b5720bd14e9e9673a60f9aa8e8b1390efef39288 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 2 Jul 2021 18:13:28 +0100 Subject: [PATCH] fix(gui): improve Quark tasks scheduling and report viewer (#1119) --- .../src/main/java/jadx/api/JavaClass.java | 9 + .../jadx/gui/jobs/BackgroundExecutor.java | 6 +- .../jadx/gui/plugins/quark/QuarkDialog.java | 114 +++++ .../jadx/gui/plugins/quark/QuarkManager.java | 221 ++++++++++ .../gui/plugins/quark/QuarkReportData.java | 48 +++ .../gui/plugins/quark/QuarkReportNode.java | 79 ++++ .../gui/plugins/quark/QuarkReportPanel.java | 318 ++++++++++++++ .../java/jadx/gui/treemodel/ApkSignature.java | 10 + .../main/java/jadx/gui/treemodel/JClass.java | 8 + .../main/java/jadx/gui/treemodel/JMethod.java | 8 + .../main/java/jadx/gui/treemodel/JNode.java | 7 + .../java/jadx/gui/treemodel/JResource.java | 15 + .../main/java/jadx/gui/treemodel/JRoot.java | 27 +- .../java/jadx/gui/ui/CertificatePanel.java | 30 -- .../main/java/jadx/gui/ui/ContentPanel.java | 2 - .../src/main/java/jadx/gui/ui/ImagePanel.java | 2 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 83 ++-- .../main/java/jadx/gui/ui/QuarkDialog.java | 401 ------------------ .../main/java/jadx/gui/ui/QuarkReport.java | 105 ----- .../main/java/jadx/gui/ui/RenameDialog.java | 6 +- .../src/main/java/jadx/gui/ui/TabbedPane.java | 48 +-- 21 files changed, 921 insertions(+), 626 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkDialog.java create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index bcea6283..98025579 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -223,6 +223,15 @@ public final class JavaClass implements JavaNode { return methods; } + @Nullable + public JavaMethod searchMethodByShortId(String shortId) { + MethodNode methodNode = cls.searchMethodByShortId(shortId); + if (methodNode == null) { + return null; + } + return new JavaMethod(this, methodNode); + } + @Override public int getDecompiledLine() { return cls.getDecompiledLine(); diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java index 61f3f5d9..826c341b 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java @@ -66,6 +66,10 @@ public class BackgroundExecutor { execute(new SimpleTask(title, Collections.singletonList(backgroundRunnable), onFinishUiRunnable)); } + public void execute(String title, Runnable backgroundRunnable) { + execute(new SimpleTask(title, Collections.singletonList(backgroundRunnable), null)); + } + private ThreadPoolExecutor makeTaskQueueExecutor() { return (ThreadPoolExecutor) Executors.newFixedThreadPool(1); } @@ -125,7 +129,7 @@ public class BackgroundExecutor { return cancelStatus; } setProgress(calcProgress(executor.getCompletedTaskCount())); - Thread.sleep(1000); + Thread.sleep(500); } } catch (InterruptedException e) { LOG.debug("Task wait interrupted"); diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkDialog.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkDialog.java new file mode 100644 index 00000000..700a5fd3 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkDialog.java @@ -0,0 +1,114 @@ +package jadx.gui.plugins.quark; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.List; +import java.util.stream.Collectors; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.WindowConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.gui.settings.JadxSettings; +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.UiUtils; + +public class QuarkDialog extends JDialog { + private static final long serialVersionUID = 4855753773520368215L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkDialog.class); + + private final transient JadxSettings settings; + private final transient MainWindow mainWindow; + private final List files; + + private JComboBox fileSelectCombo; + + public QuarkDialog(MainWindow mainWindow) { + this.mainWindow = mainWindow; + this.settings = mainWindow.getSettings(); + this.files = filterOpenFiles(mainWindow); + if (files.isEmpty()) { + UiUtils.errorMessage(mainWindow, "Quark is unable to analyze loaded files"); + LOG.error("Quark: The files cannot be analyzed: {}", mainWindow.getWrapper().getOpenPaths()); + return; + } + initUI(); + } + + private List filterOpenFiles(MainWindow mainWindow) { + PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.{apk,dex}"); + return mainWindow.getWrapper().getOpenPaths() + .stream() + .filter(matcher::matches) + .collect(Collectors.toList()); + } + + private void initUI() { + JLabel description = new JLabel("Analyzing apk using Quark-Engine"); + JLabel selectApkText = new JLabel("Select Apk/Dex"); + description.setAlignmentX(0.5f); + + fileSelectCombo = new JComboBox<>(files.toArray(new Path[0])); + fileSelectCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(value.getFileName().toString())); + + JPanel textPane = new JPanel(); + textPane.add(description); + + JPanel selectApkPanel = new JPanel(); + selectApkPanel.add(selectApkText); + selectApkPanel.add(fileSelectCombo); + + JPanel buttonPane = new JPanel(); + JButton start = new JButton("Start"); + JButton close = new JButton("Close"); + close.addActionListener(event -> close()); + start.addActionListener(event -> startQuarkTasks()); + buttonPane.add(start); + buttonPane.add(close); + getRootPane().setDefaultButton(close); + + JPanel centerPane = new JPanel(); + centerPane.add(selectApkPanel); + Container contentPane = getContentPane(); + + contentPane.add(textPane, BorderLayout.PAGE_START); + contentPane.add(centerPane); + contentPane.add(buttonPane, BorderLayout.PAGE_END); + + setTitle("Quark Engine"); + pack(); + if (!mainWindow.getSettings().loadWindowPos(this)) { + setSize(300, 140); + } + setLocationRelativeTo(null); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setModalityType(ModalityType.APPLICATION_MODAL); + UiUtils.addEscapeShortCutToDispose(this); + } + + private void startQuarkTasks() { + Path apkFile = (Path) fileSelectCombo.getSelectedItem(); + new QuarkManager(mainWindow, apkFile).start(); + close(); + } + + private void close() { + dispose(); + } + + @Override + public void dispose() { + settings.saveWindowPos(this); + super.dispose(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java new file mode 100644 index 00000000..573b2b68 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java @@ -0,0 +1,221 @@ +package jadx.gui.plugins.quark; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JOptionPane; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.jobs.BackgroundExecutor; +import jadx.gui.treemodel.JRoot; +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.SystemInfo; +import jadx.gui.utils.UiUtils; + +public class QuarkManager { + private static final Logger LOG = LoggerFactory.getLogger(QuarkManager.class); + + private static final Path QUARK_DIR_PATH = Paths.get(System.getProperty("user.home"), ".quark-engine"); + private static final Path VENV_PATH = QUARK_DIR_PATH.resolve("quark_venv"); + private static final int LARGE_APK_SIZE = 30; + + private final MainWindow mainWindow; + private final Path apkPath; + + private boolean useVEnv; + private boolean installComplete; + private Path reportFile; + + public QuarkManager(MainWindow mainWindow, Path apkPath) { + this.mainWindow = mainWindow; + this.apkPath = apkPath; + } + + public void start() { + if (!checkFileSize(LARGE_APK_SIZE)) { + int result = JOptionPane.showConfirmDialog(mainWindow, + "The selected file size is too large (over 30M) that may take a long time to analyze, do you want to continue", + "Quark: Warning", JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.NO_OPTION) { + return; + } + } + BackgroundExecutor executor = mainWindow.getBackgroundExecutor(); + executor.execute("Quark install", this::checkInstall, + installStatus -> executor.execute("Quark analysis", this::startAnalysis, analysisStatus -> loadReport())); + } + + private void checkInstall() { + try { + if (checkCommand("quark")) { + useVEnv = false; + installComplete = true; + return; + } + useVEnv = true; + if (checkVEnvCommand("quark")) { + installComplete = true; + installQuark(); // upgrade quark + return; + } + int result = JOptionPane.showConfirmDialog(mainWindow, + "Quark is not installed, do you want to install it from PyPI?", "Warning", + JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.NO_OPTION) { + installComplete = false; + return; + } + createVirtualEnv(); + installQuark(); + installComplete = true; + } catch (Exception e) { + UiUtils.errorMessage(mainWindow, e.getMessage()); + LOG.error("Failed to install quark", e); + installComplete = false; + } + } + + private void startAnalysis() { + if (!installComplete) { + return; + } + try { + updateQuarkRules(); + reportFile = Files.createTempFile("QuarkReport-", ".json").toAbsolutePath(); + List cmd = new ArrayList<>(); + cmd.add(getCommand("quark")); + cmd.add("-a"); + cmd.add(apkPath.toString()); + cmd.add("-o"); + cmd.add(reportFile.toString()); + runCommand(cmd); + } catch (Exception e) { + UiUtils.errorMessage(mainWindow, "Failed to execute Quark"); + LOG.error("Failed to execute Quark", e); + } + } + + private void loadReport() { + try { + QuarkReportNode quarkNode = new QuarkReportNode(reportFile); + JRoot root = mainWindow.getCacheObject().getJRoot(); + root.replaceCustomNode(quarkNode); + root.update(); + mainWindow.reloadTree(); + mainWindow.getTabbedPane().showNode(quarkNode); + } catch (Exception e) { + UiUtils.errorMessage(mainWindow, "Failed to load Quark report."); + LOG.error("Failed to load Quark report.", e); + } + } + + private void createVirtualEnv() { + if (Files.exists(getVenvPath("activate"))) { + return; + } + File directory = QUARK_DIR_PATH.toFile(); + if (!directory.isDirectory()) { + if (!directory.mkdirs()) { + throw new JadxRuntimeException("Failed create directory: " + directory); + } + } + List cmd = new ArrayList<>(); + if (SystemInfo.IS_WINDOWS) { + cmd.add("python"); + cmd.add("-m"); + cmd.add("venv"); + } else { + cmd.add("virtualenv"); + } + cmd.add(VENV_PATH.toString()); + try { + runCommand(cmd); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to create virtual environment", e); + } + } + + private void installQuark() { + List cmd = new ArrayList<>(); + cmd.add(getCommand("pip3")); + cmd.add("install"); + cmd.add("quark-engine"); + cmd.add("--upgrade"); + try { + runCommand(cmd); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to install quark-engine", e); + } + } + + private void updateQuarkRules() { + List cmd = new ArrayList<>(); + cmd.add(getCommand("freshquark")); + try { + runCommand(cmd); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to update quark rules", e); + } + } + + public boolean checkFileSize(int sizeThreshold) { + try { + int fileSize = (int) Files.size(apkPath) / 1024 / 1024; + if (fileSize > sizeThreshold) { + return false; + } + } catch (Exception e) { + LOG.error("Failed to calculate file: {}", e.getMessage(), e); + return false; + } + return true; + } + + private String getCommand(String cmd) { + if (useVEnv) { + return getVenvPath(cmd).toAbsolutePath().toString(); + } + return cmd; + } + + private boolean checkVEnvCommand(String cmd) { + Path venvPath = getVenvPath(cmd); + return checkCommand(venvPath.toAbsolutePath().toString()); + } + + private Path getVenvPath(String cmd) { + if (SystemInfo.IS_WINDOWS) { + return VENV_PATH.resolve("Scripts").resolve(cmd + ".exe"); + } + return VENV_PATH.resolve("bin").resolve(cmd); + } + + private void runCommand(List cmd) throws Exception { + LOG.debug("Running command: {}", String.join(" ", cmd)); + Process process = Runtime.getRuntime().exec(cmd.toArray(new String[0])); + try (BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + buf.lines().forEach(msg -> LOG.debug("# {}", msg)); + } finally { + process.waitFor(); + } + } + + private boolean checkCommand(String... cmd) { + try { + Process process = Runtime.getRuntime().exec(cmd); + process.waitFor(); + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportData.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportData.java new file mode 100644 index 00000000..f3f1e5f0 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportData.java @@ -0,0 +1,48 @@ +package jadx.gui.plugins.quark; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +import jadx.core.utils.Utils; + +@SuppressWarnings("MemberName") +public class QuarkReportData { + public static class Crime { + public String crime; + public String confidence; + public List permissions; + + List native_api; + List combination; + List> register; + } + + public static class Method { + @SerializedName("class") + String cls; + String method; + String descriptor; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Utils.cleanObjectName(cls)).append(".").append(method); + if (descriptor != null) { + sb.append(descriptor); + } + return sb.toString(); + } + } + + public static class InvokePlace { + List first; + List second; + } + + String apk_filename; + String threat_level; + int total_score; + List crimes; +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java new file mode 100644 index 00000000..10e4a4d7 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java @@ -0,0 +1,79 @@ +package jadx.gui.plugins.quark; + +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.HtmlPanel; +import jadx.gui.ui.TabbedPane; +import jadx.gui.utils.UiUtils; + +public class QuarkReportNode extends JNode { + + private static final long serialVersionUID = -766800957202637021L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkReportNode.class); + + private static final Gson GSON = new GsonBuilder().create(); + + private static final ImageIcon ICON = UiUtils.openIcon("icon_quark"); + private final Path apkFile; + + private String errorContent; + + public QuarkReportNode(Path apkFile) { + this.apkFile = apkFile; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return ICON; + } + + @Override + public String makeString() { + return "Quark analysis report"; + } + + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + QuarkReportData data; + try { + data = GSON.fromJson(Files.newBufferedReader(apkFile), QuarkReportData.class); + } catch (Exception e) { + LOG.error("Quark report parse error", e); + StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); + builder.append("

"); + builder.escape("Quark analysis failed!"); + builder.append("

");
+			builder.escape(ExceptionUtils.getStackTrace(e));
+			builder.append("
"); + errorContent = builder.toString(); + return new HtmlPanel(tabbedPane, this); + } + return new QuarkReportPanel(tabbedPane, this, data); + } + + @Override + public String getContent() { + return this.errorContent; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java new file mode 100644 index 00000000..bde656f1 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java @@ -0,0 +1,318 @@ +package jadx.gui.plugins.quark; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.IdentityHashMap; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.beust.jcommander.Strings; + +import jadx.api.JavaClass; +import jadx.api.JavaMethod; +import jadx.core.utils.Utils; +import jadx.gui.JadxWrapper; +import jadx.gui.jobs.BackgroundExecutor; +import jadx.gui.treemodel.JMethod; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.TabbedPane; +import jadx.gui.utils.JNodeCache; + +public class QuarkReportPanel extends ContentPanel { + private static final long serialVersionUID = -242266836695889206L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkReportPanel.class); + + private final QuarkReportData data; + private final JNodeCache nodeCache; + + private JEditorPane header; + private JTree tree; + private DefaultMutableTreeNode treeRoot; + private Font font; + private Font boldFont; + private CachingTreeCellRenderer cellRenderer; + + protected QuarkReportPanel(TabbedPane panel, QuarkReportNode node, QuarkReportData data) { + super(panel, node); + this.data = data; + this.nodeCache = panel.getMainWindow().getCacheObject().getNodeCache(); + prepareData(); + initUI(); + loadSettings(); + } + + private void prepareData() { + data.crimes.sort(Comparator.comparingInt(c -> -Integer.parseInt(c.confidence.replace("%", "")))); + } + + private void initUI() { + setLayout(new BorderLayout()); + + header = new JEditorPane(); + header.setContentType("text/html"); + header.setEditable(false); + header.setText(buildHeader()); + + cellRenderer = new CachingTreeCellRenderer(); + treeRoot = new TextTreeNode("Potential Malicious Activities:").bold(); + tree = buildTree(); + for (QuarkReportData.Crime crime : data.crimes) { + treeRoot.add(new CrimeTreeNode(crime)); + } + tree.expandRow(0); + tree.expandRow(1); + + JScrollPane tableScroll = new JScrollPane(tree); + tableScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(header, BorderLayout.PAGE_START); + mainPanel.add(tableScroll, BorderLayout.CENTER); + + add(mainPanel); + } + + private JTree buildTree() { + JTree tree = new JTree(treeRoot); + tree.setLayout(new BorderLayout()); + tree.setBorder(BorderFactory.createEmptyBorder()); + tree.setShowsRootHandles(false); + tree.setScrollsOnExpand(false); + tree.setSelectionModel(null); + tree.setCellRenderer(cellRenderer); + tree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (SwingUtilities.isLeftMouseButton(event)) { + Object node = getNodeUnderMouse(tree, event); + if (node instanceof MethodTreeNode) { + JMethod method = ((MethodTreeNode) node).getJMethod(); + BackgroundExecutor executor = tabbedPane.getMainWindow().getBackgroundExecutor(); + executor.execute("Decompiling class", + () -> tabbedPane.codeJump(method), + status -> tabbedPane.codeJump(method) // TODO: fix bug with incorrect jump on just decompiled code + ); + } + } + } + }); + tree.addTreeExpansionListener(new TreeExpansionListener() { + @Override + public void treeExpanded(TreeExpansionEvent event) { + TreePath path = event.getPath(); + Object leaf = path.getLastPathComponent(); + if (leaf instanceof CrimeTreeNode) { + CrimeTreeNode node = (CrimeTreeNode) leaf; + Enumeration children = node.children(); + while (children.hasMoreElements()) { + TreeNode child = children.nextElement(); + tree.expandPath(path.pathByAddingChild(child)); + } + } + } + + @Override + public void treeCollapsed(TreeExpansionEvent event) { + } + }); + return tree; + } + + private String buildHeader() { + StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); + builder.append("

Quark Analysis Report

"); + builder.append("

"); + builder.append("File: ").append(data.apk_filename); + builder.append("
"); + builder.append("Treat level: ").append(data.threat_level); + builder.append("
"); + builder.append("Total score: ").append(Integer.toString(data.total_score)); + builder.append("

"); + return builder.toString(); + } + + @Override + public void loadSettings() { + Font settingsFont = getTabbedPane().getMainWindow().getSettings().getFont(); + this.font = settingsFont.deriveFont(settingsFont.getSize2D() + 1.f); + this.boldFont = font.deriveFont(Font.BOLD); + header.setFont(font); + tree.setFont(font); + cellRenderer.clearCache(); + } + + private static Object getNodeUnderMouse(JTree tree, MouseEvent mouseEvent) { + TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()); + return path != null ? path.getLastPathComponent() : null; + } + + private static class CachingTreeCellRenderer implements TreeCellRenderer { + private final Map cache = new IdentityHashMap<>(); + + @Override + public Component getTreeCellRendererComponent(JTree tr, Object value, boolean selected, + boolean expanded, boolean leaf, int row, boolean focus) { + return cache.computeIfAbsent((BaseTreeNode) value, BaseTreeNode::render); + } + + public void clearCache() { + cache.clear(); + } + } + + private abstract static class BaseTreeNode extends DefaultMutableTreeNode { + private static final long serialVersionUID = 7197501219150495889L; + + public BaseTreeNode(Object userObject) { + super(userObject); + } + + public abstract Component render(); + } + + private class TextTreeNode extends BaseTreeNode { + private static final long serialVersionUID = 6763410122501083453L; + + private boolean bold; + + public TextTreeNode(String text) { + super(text); + } + + public TextTreeNode bold() { + bold = true; + return this; + } + + @Override + public Component render() { + JLabel label = new JLabel(((String) getUserObject())); + label.setFont(bold ? boldFont : font); + label.setIcon(null); + label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + return label; + } + } + + private class CrimeTreeNode extends TextTreeNode { + private static final long serialVersionUID = -1464310215237483911L; + + private final QuarkReportData.Crime crime; + + public CrimeTreeNode(QuarkReportData.Crime crime) { + super(crime.crime); + this.crime = crime; + bold(); + addDetails(); + } + + private void addDetails() { + add(new TextTreeNode("Confidence: " + crime.confidence)); + if (Utils.notEmpty(crime.permissions)) { + add(new TextTreeNode("Permissions: " + Strings.join(", ", crime.permissions))); + } + if (Utils.notEmpty(crime.combination)) { + TextTreeNode node = new TextTreeNode("Native API"); + for (QuarkReportData.Method method : crime.combination) { + node.add(new TextTreeNode(method.toString())); + } + add(node); + } else { + if (Utils.notEmpty(crime.native_api)) { + TextTreeNode node = new TextTreeNode("Native API"); + for (QuarkReportData.Method method : crime.native_api) { + node.add(new TextTreeNode(method.toString())); + } + add(node); + } + } + if (Utils.notEmpty(crime.register)) { + TextTreeNode node = new TextTreeNode("Invocations"); + for (Map invokeMap : crime.register) { + invokeMap.forEach((key, value) -> node.add(resolveMethod(key))); + } + add(node); + } + } + + @Override + public String toString() { + return crime.crime; + } + } + + public MutableTreeNode resolveMethod(String descr) { + try { + String[] parts = descr.split(" ", 3); + String cls = Utils.cleanObjectName(parts[0].replace('$', '.')); + String mth = parts[1] + parts[2].replace(" ", ""); + MainWindow mainWindow = getTabbedPane().getMainWindow(); + JadxWrapper wrapper = mainWindow.getWrapper(); + JavaClass javaClass = wrapper.searchJavaClassByRawName(cls); + if (javaClass == null) { + return new TextTreeNode(cls + "." + mth); + } + JavaMethod javaMethod = javaClass.searchMethodByShortId(mth); + if (javaMethod == null) { + return new TextTreeNode(javaClass.getFullName() + "." + mth); + } + return new MethodTreeNode(javaMethod); + } catch (Exception e) { + LOG.error("Failed to parse method descriptor string", e); + return new TextTreeNode(descr); + } + } + + private class MethodTreeNode extends BaseTreeNode { + private static final long serialVersionUID = 4350343915220068508L; + + private final JavaMethod mth; + private final JMethod jnode; + + public MethodTreeNode(JavaMethod mth) { + super(mth); + this.mth = mth; + this.jnode = (JMethod) nodeCache.makeFrom(mth); + } + + public JMethod getJMethod() { + return jnode; + } + + @Override + public Component render() { + JLabel label = new JLabel(mth.toString()); + label.setFont(font); + label.setIcon(jnode.getIcon()); + label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + return label; + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java index 429902b6..d36c6fcd 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java @@ -10,6 +10,7 @@ import javax.swing.ImageIcon; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.text.StringEscapeUtils; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +19,9 @@ import com.android.apksig.ApkVerifier; import jadx.api.ResourceFile; import jadx.api.ResourceType; import jadx.gui.JadxWrapper; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.HtmlPanel; +import jadx.gui.ui.TabbedPane; import jadx.gui.utils.CertificateManager; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -32,6 +36,7 @@ public class ApkSignature extends JNode { private final transient File openFile; private String content; + @Nullable public static ApkSignature getApkSignature(JadxWrapper wrapper) { // Only show the ApkSignature node if an AndroidManifest.xml is present. // Without a manifest the Google ApkVerifier refuses to work. @@ -70,6 +75,11 @@ public class ApkSignature extends JNode { return "APK signature"; } + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + return new HtmlPanel(tabbedPane, this); + } + @Override public String getContent() { if (content != null) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index e591c545..e1a5043f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -12,6 +12,9 @@ import jadx.api.JavaMethod; import jadx.api.JavaNode; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -100,6 +103,11 @@ public class JClass extends JLoadableNode { return cls.getCode(); } + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + return new ClassCodeContentPanel(tabbedPane, this); + } + @Override public String getSmali() { return cls.getSmali(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index 9c41d5cb..8c58e81e 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -12,6 +12,9 @@ import jadx.api.JavaNode; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.utils.OverlayIcon; import jadx.gui.utils.UiUtils; @@ -62,6 +65,11 @@ public class JMethod extends JNode { return mth.getDecompiledLine(); } + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + return new ClassCodeContentPanel(tabbedPane, this); + } + @Override public Icon getIcon() { AccessInfo accessFlags = mth.getAccessFlags(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 9c21d449..226f0164 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -9,6 +9,8 @@ import org.jetbrains.annotations.Nullable; import jadx.api.ICodeInfo; import jadx.api.JadxDecompiler; import jadx.api.JavaNode; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.TabbedPane; public abstract class JNode extends DefaultMutableTreeNode { @@ -31,6 +33,11 @@ public abstract class JNode extends DefaultMutableTreeNode { return null; } + @Nullable + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + return null; + } + public String getSmali() { return null; } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index d6d693e1..0f769973 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -21,6 +21,10 @@ import jadx.api.ResourcesLoader; import jadx.api.impl.SimpleCodeInfo; import jadx.core.utils.Utils; import jadx.core.xmlgen.ResContainer; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.ImagePanel; +import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.codearea.CodeContentPanel; import jadx.gui.utils.NLS; import jadx.gui.utils.OverlayIcon; import jadx.gui.utils.UiUtils; @@ -106,6 +110,17 @@ public class JResource extends JLoadableNode implements Comparable { return content; } + @Override + public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) { + if (resFile == null) { + return null; + } + if (resFile.getType() == ResourceType.IMG) { + return new ImagePanel(tabbedPane, this); + } + return new CodeContentPanel(tabbedPane, this); + } + @Override public synchronized String getContent() { if (loaded) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java index a6950ffc..550b4f0f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -2,12 +2,16 @@ package jadx.gui.treemodel; import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.regex.Pattern; -import javax.swing.*; +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import org.jetbrains.annotations.Nullable; import jadx.api.ResourceFile; import jadx.gui.JadxWrapper; @@ -24,6 +28,8 @@ public class JRoot extends JNode { private transient boolean flatPackages = false; + private final List customNodes = new ArrayList<>(); + public JRoot(JadxWrapper wrapper) { this.wrapper = wrapper; } @@ -37,10 +43,8 @@ public class JRoot extends JNode { jRes.update(); add(jRes); } - - ApkSignature signature = ApkSignature.getApkSignature(wrapper); - if (signature != null) { - add(signature); + for (JNode customNode : customNodes) { + add(customNode); } } @@ -108,6 +112,19 @@ public class JRoot extends JNode { } } + public void replaceCustomNode(@Nullable JNode node) { + if (node == null) { + return; + } + Class nodeCls = node.getClass(); + customNodes.removeIf(n -> n.getClass().equals(nodeCls)); + customNodes.add(node); + } + + public List getCustomNodes() { + return customNodes; + } + @Override public Icon getIcon() { return ROOT_ICON; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java deleted file mode 100644 index 122ca32c..00000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java +++ /dev/null @@ -1,30 +0,0 @@ -package jadx.gui.ui; - -import java.awt.*; - -import javax.swing.*; - -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; - -import jadx.gui.treemodel.JNode; -import jadx.gui.ui.codearea.CodeArea; - -public final class CertificatePanel extends ContentPanel { - private static final long serialVersionUID = 8566591625905036877L; - - private final RSyntaxTextArea textArea; - - CertificatePanel(TabbedPane panel, JNode jnode) { - super(panel, jnode); - setLayout(new BorderLayout()); - textArea = new RSyntaxTextArea(jnode.getContent()); - loadSettings(); - JScrollPane sp = new JScrollPane(textArea); - add(sp); - } - - @Override - public void loadSettings() { - CodeArea.loadCommonSettings(getTabbedPane().getMainWindow(), textArea); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java index 13357f12..d012e1bc 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java @@ -34,8 +34,6 @@ public abstract class ContentPanel extends JPanel { * selected entry inside the APK file. * * If null is returned no tool tip will be displayed. - * - * @return */ @Nullable public String getTabTooltip() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java index e50e87d6..61177134 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java @@ -22,7 +22,7 @@ import jadx.gui.ui.codearea.AbstractCodeArea; public class ImagePanel extends ContentPanel { private static final long serialVersionUID = 4071356367073142688L; - ImagePanel(TabbedPane panel, JResource res) { + public ImagePanel(TabbedPane panel, JResource res) { super(panel, res); setLayout(new BorderLayout()); try { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 99625748..50326301 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -93,6 +93,7 @@ import jadx.gui.jobs.DecompileTask; import jadx.gui.jobs.ExportTask; import jadx.gui.jobs.IndexService; import jadx.gui.jobs.TaskStatus; +import jadx.gui.plugins.quark.QuarkDialog; import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsWindow; @@ -125,7 +126,6 @@ import static jadx.gui.utils.FileUtils.fileNamesToPaths; import static jadx.gui.utils.FileUtils.toPaths; import static javax.swing.KeyStroke.getKeyStroke; -@SuppressWarnings("serial") public class MainWindow extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); @@ -379,33 +379,43 @@ public class MainWindow extends JFrame { } void open(List paths, Runnable onFinish) { - if (paths.size() == 1 - && paths.get(0).getFileName().toString().toLowerCase(Locale.ROOT).endsWith(JadxProject.PROJECT_EXTENSION)) { - openProject(paths.get(0)); - onFinish.run(); - } else { - project.setFilePath(paths); - clearTree(); - BreakpointManager.saveAndExit(); - if (paths.isEmpty()) { + if (paths.size() == 1) { + Path singleFile = paths.get(0); + if (singleFile.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(JadxProject.PROJECT_EXTENSION)) { + openProject(singleFile); + onFinish.run(); return; } - backgroundExecutor.execute(NLS.str("progress.load"), - () -> wrapper.openFile(paths), - (status) -> { - if (status == TaskStatus.CANCEL_BY_MEMORY) { - showHeapUsageBar(); - UiUtils.errorMessage(this, NLS.str("message.memoryLow")); - return; - } - deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); - initTree(); - update(); - runInitialBackgroundJobs(); - BreakpointManager.init(paths.get(0).getParent()); - onFinish.run(); - }); } + project.setFilePath(paths); + clearTree(); + BreakpointManager.saveAndExit(); + if (paths.isEmpty()) { + return; + } + backgroundExecutor.execute(NLS.str("progress.load"), + () -> wrapper.openFile(paths), + status -> { + if (status == TaskStatus.CANCEL_BY_MEMORY) { + showHeapUsageBar(); + UiUtils.errorMessage(this, NLS.str("message.memoryLow")); + return; + } + onOpen(paths); + onFinish.run(); + }); + } + + private void onOpen(List paths) { + deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); + initTree(); + update(); + runInitialBackgroundJobs(); + BreakpointManager.init(paths.get(0).getParent()); + } + + private void addTreeCustomNodes() { + treeRoot.replaceCustomNode(ApkSignature.getApkSignature(wrapper)); } private boolean ensureProjectIsSaved() { @@ -592,11 +602,12 @@ public class MainWindow extends JFrame { public void initTree() { treeRoot = new JRoot(wrapper); + cacheObject.setJRoot(treeRoot); treeRoot.setFlatPackages(isFlattenPackage); treeModel.setRoot(treeRoot); + addTreeCustomNodes(); treeRoot.update(); reloadTree(); - cacheObject.setJRoot(treeRoot); cacheObject.setJadxSettings(settings); } @@ -677,16 +688,14 @@ public class MainWindow extends JFrame { JResource res = (JResource) obj; ResourceFile resFile = res.getResFile(); if (resFile != null && JResource.isSupportedForView(resFile.getType())) { - tabbedPane.showResource(res); + tabbedPane.showNode(res); } - } else if (obj instanceof ApkSignature) { - tabbedPane.showSimpleNode((JNode) obj); - } else if (obj instanceof QuarkReport) { - tabbedPane.showSimpleNode((JNode) obj); } else if (obj instanceof JNode) { JNode node = (JNode) obj; if (node.getRootClass() != null) { tabbedPane.codeJump(new JumpPosition(node)); + } else { + tabbedPane.showNode(node); } } } catch (Exception e) { @@ -991,10 +1000,12 @@ public class MainWindow extends JFrame { JMenu tools = new JMenu(NLS.str("menu.tools")); tools.setMnemonic(KeyEvent.VK_T); tools.add(deobfMenuItem); - tools.add(logAction); + tools.add(quarkAction); + tools.add(openDeviceAction); JMenu help = new JMenu(NLS.str("menu.help")); help.setMnemonic(KeyEvent.VK_H); + help.add(logAction); help.add(aboutAction); JMenuBar menuBar = new JMenuBar(); @@ -1034,15 +1045,13 @@ public class MainWindow extends JFrame { toolbar.add(forwardAction); toolbar.addSeparator(); toolbar.add(deobfToggleBtn); + toolbar.add(quarkAction); + toolbar.add(openDeviceAction); toolbar.addSeparator(); toolbar.add(logAction); toolbar.addSeparator(); toolbar.add(prefsAction); toolbar.addSeparator(); - toolbar.add(quarkAction); - toolbar.addSeparator(); - toolbar.add(openDeviceAction); - toolbar.addSeparator(); toolbar.add(Box.createHorizontalGlue()); toolbar.add(updateLink); @@ -1193,7 +1202,7 @@ public class MainWindow extends JFrame { pathList.add(name); path = path.getParentPath(); } - return pathList.toArray(new String[pathList.size()]); + return pathList.toArray(new String[0]); } public static void getExpandedPaths(JTree tree, TreePath path, List list) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java deleted file mode 100644 index edfdae35..00000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java +++ /dev/null @@ -1,401 +0,0 @@ -package jadx.gui.ui; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.WindowConstants; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import jadx.gui.jobs.IBackgroundTask; -import jadx.gui.jobs.TaskStatus; -import jadx.gui.settings.JadxSettings; -import jadx.gui.treemodel.JRoot; -import jadx.gui.utils.NLS; -import jadx.gui.utils.UiUtils; - -class QuarkDialog extends JDialog { - - private static final long serialVersionUID = 4855753773520368215L; - - private static final Logger LOG = LoggerFactory.getLogger(QuarkDialog.class); - private static final String QUARK_CMD_LOG_MESSAGE = "Running Quark cmd: {}"; - private static final String QUARK_INTERRUPT_MESSAGE = "Quark process interrupted: {}"; - private static final String QUARK_FAILED_MESSAGE = "Failed to execute Quark."; - private static final String QUARK_CMD = "quark"; - private static final int LARGE_APK_SIZE = 30; - private static final Path QUARK_DIR_PATH = Paths.get(System.getProperty("user.home"), ".quark-engine"); - - private Path venvPath = Paths.get(QUARK_DIR_PATH.toString(), "quark_venv"); - private File quarkReportFile; - - private final transient JadxSettings settings; - private final transient MainWindow mainWindow; - - private JComboBox fileSelectCombo; - - private final List files; - private Map choosableFiles = new HashMap<>(); - - public QuarkDialog(MainWindow mainWindow) { - this.mainWindow = mainWindow; - this.settings = mainWindow.getSettings(); - this.files = mainWindow.getWrapper().getOpenPaths(); - fileNameExtensionFilter(); - if (choosableFiles.isEmpty()) { - UiUtils.errorMessage(mainWindow, "Quark is unable to analyze the selected file."); - LOG.error("Quark: The files cannot be analyze. {}", files); - return; - } - initUI(); - } - - private void fileNameExtensionFilter() { - String[] extensions = new String[] { "apk", "dex" }; - - for (Path filePath : this.files) { - String fileName = filePath.toString(); - int dotIndex = fileName.lastIndexOf('.'); - String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); - - if (Arrays.stream(extensions).noneMatch(extension::equals)) { - LOG.debug("Quark: {} is not apk nor dex", fileName); - continue; - } - choosableFiles.put(fileName, filePath); - } - } - - public final void initUI() { - JLabel description = new JLabel("Analyzing apk using Quark-Engine"); - JLabel selectApkText = new JLabel("Select Apk/Dex"); - description.setAlignmentX(0.5f); - - String[] comboFiles = choosableFiles.keySet().toArray(new String[choosableFiles.size()]); - fileSelectCombo = new JComboBox<>(comboFiles); - - JPanel textPane = new JPanel(); - - textPane.add(description); - - JPanel selectApkPanel = new JPanel(); - selectApkPanel.add(selectApkText); - selectApkPanel.add(fileSelectCombo); - - JPanel buttonPane = new JPanel(); - JButton start = new JButton("Start"); - JButton close = new JButton(NLS.str("tabs.close")); - close.addActionListener(event -> close()); - start.addActionListener(event -> mainWindow.getBackgroundExecutor().execute(new QuarkTask())); - buttonPane.add(start); - buttonPane.add(close); - getRootPane().setDefaultButton(close); - - JPanel centerPane = new JPanel(); - centerPane.add(selectApkPanel); - Container contentPane = getContentPane(); - - contentPane.add(textPane, BorderLayout.PAGE_START); - contentPane.add(centerPane); - contentPane.add(buttonPane, BorderLayout.PAGE_END); - - setTitle("Quark Engine"); - pack(); - if (!mainWindow.getSettings().loadWindowPos(this)) { - setSize(300, 140); - } - setLocationRelativeTo(null); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setModalityType(ModalityType.MODELESS); - UiUtils.addEscapeShortCutToDispose(this); - } - - private void close() { - dispose(); - } - - @Override - public void dispose() { - settings.saveWindowPos(this); - super.dispose(); - } - - private class QuarkTask implements IBackgroundTask { - - private Process quarkProcess; - private boolean isVenv = false; - - public QuarkTask() { - dispose(); - } - - private boolean isPipInstalled() { - List cmdList = new ArrayList<>(); - cmdList.add("pip3"); - return executeCommand(cmdList); - } - - private boolean isQuarkInstalled() { - List cmdList = new ArrayList<>(); - cmdList.add(QUARK_CMD); - if (executeCommand(cmdList)) { - return true; - } - - isVenv = true; - cmdList = new ArrayList<>(); - cmdList.add(getVenvPath(QUARK_CMD).toString()); - return executeCommand(cmdList); - } - - private void createVirtualenv() { - - // Check if venv exist - if (Files.exists(getVenvPath("activate"))) { - return; - } - - List cmdList = new ArrayList<>(); - - if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) { - cmdList.add("python"); - cmdList.add("-m"); - cmdList.add("venv"); - } else { - cmdList.add("virtualenv"); - } - - cmdList.add(venvPath.toString()); - try { - LOG.debug(QUARK_CMD_LOG_MESSAGE, cmdList); - Process process = Runtime.getRuntime().exec(cmdList.toArray(new String[0])); - process.waitFor(); - } catch (InterruptedException e) { - LOG.error(QUARK_INTERRUPT_MESSAGE, e.getMessage(), e); - Thread.currentThread().interrupt(); - } catch (Exception e) { - UiUtils.errorMessage(mainWindow, "Failed to create virtual environment."); - LOG.error("Failed to create virtual environment: {}", e.getMessage(), e); - } - } - - private boolean installQuark() { - List cmdList = new ArrayList<>(); - String command = (isVenv) ? getVenvPath("pip3").toString() : "pip3"; - cmdList.add(command); - cmdList.add("install"); - cmdList.add("quark-engine"); - cmdList.add("--upgrade"); - try { - LOG.debug(QUARK_CMD_LOG_MESSAGE, cmdList); - Process process = Runtime.getRuntime().exec(cmdList.toArray(new String[0])); - process.waitFor(); - - if (!isQuarkInstalled()) { - return false; - } - } catch (InterruptedException e) { - LOG.error(QUARK_INTERRUPT_MESSAGE, e.getMessage(), e); - Thread.currentThread().interrupt(); - } catch (Exception e) { - UiUtils.errorMessage(mainWindow, "Failed to install quark-engine."); - LOG.error("Failed to execute pip install command: {}", String.join(" ", cmdList), e); - return false; - } - return true; - } - - private void updateQuarkRules() { - List cmdList = new ArrayList<>(); - String command = (isVenv) ? getVenvPath("freshquark").toString() : "freshquark"; - cmdList.add(command); - executeCommand(cmdList); - } - - private boolean analyzeAPK() { - try { - updateQuarkRules(); - quarkReportFile = File.createTempFile("QuarkReport-", ".json"); - String apkName = (String) fileSelectCombo.getSelectedItem(); - String apkPath = choosableFiles.get(apkName).toString(); - - List cmdList = new ArrayList<>(); - String command = (isVenv) ? getVenvPath(QUARK_CMD).toString() : QUARK_CMD; - cmdList.add(command); - cmdList.add("-a"); - cmdList.add(apkPath); - cmdList.add("-o"); - cmdList.add(quarkReportFile.getAbsolutePath()); - LOG.debug(QUARK_CMD_LOG_MESSAGE, cmdList); - quarkProcess = Runtime.getRuntime().exec(cmdList.toArray(new String[0])); - - try (BufferedReader buf = new BufferedReader(new InputStreamReader(quarkProcess.getInputStream()))) { - String output = null; - while ((output = buf.readLine()) != null) { - LOG.debug(output); - } - } - } catch (Exception e) { - LOG.error("Failed to execute Quark: {}", e.getMessage(), e); - return false; - } - return true; - } - - private boolean executeCommand(List cmdList) { - try { - LOG.debug(QUARK_CMD_LOG_MESSAGE, cmdList); - Process process = Runtime.getRuntime().exec(cmdList.toArray(new String[0])); - process.waitFor(); - } catch (InterruptedException e) { - LOG.error(QUARK_INTERRUPT_MESSAGE, e.getMessage(), e); - Thread.currentThread().interrupt(); - } catch (Exception e) { - LOG.error("Failed to execute command: {}", String.join(" ", cmdList), e); - return false; - } - return true; - } - - public boolean checkFileSize(int sizeThreshold) { - String apkName = (String) fileSelectCombo.getSelectedItem(); - - try { - int fileSize = (int) Files.size(choosableFiles.get(apkName)) / 1024 / 1024; - if (fileSize > sizeThreshold) { - return false; - } - } catch (Exception e) { - LOG.error("Failed to calculate file: {}", e.getMessage(), e); - return false; - } - return true; - } - - private void loadReportFile() { - try (Reader reader = new FileReader(quarkReportFile)) { - JsonObject quarkReport = (JsonObject) JsonParser.parseReader(reader); - QuarkReport quarkNode = QuarkReport.analysisAPK(quarkReport); - JRoot root = mainWindow.getCacheObject().getJRoot(); - root.update(); - root.add(quarkNode); - mainWindow.reloadTree(); - } catch (Exception e) { - UiUtils.errorMessage(mainWindow, "Failed to load Quark report."); - LOG.error("Failed to load Quark report.", e); - } - } - - private Path getVenvPath(String cmd) { - String os = System.getProperty("os.name").toLowerCase(); - if (os.indexOf("win") >= 0) { - return Paths.get(venvPath.toString(), "Scripts", String.format("%s.exe", cmd)); - } else { - return Paths.get(venvPath.toString(), "bin", cmd); - } - } - - @Override - public String getTitle() { - return "Quark:"; - } - - @Override - public boolean canBeCanceled() { - return true; - } - - @Override - public List scheduleJobs() { - List jobs = new ArrayList<>(); - - // mkdir `$HOME/.quark-engine/` - File directory = new File(QUARK_DIR_PATH.toString()); - if (!directory.isDirectory()) { - directory.mkdirs(); - } - - if (!checkFileSize(LARGE_APK_SIZE)) { - int result = JOptionPane.showConfirmDialog(mainWindow, - "The selected file size is too large (over 30M) that may take a long time to analyze, do you want to continue", - "Quark: Warning", JOptionPane.YES_NO_OPTION); - if (result == JOptionPane.NO_OPTION) { - return jobs; - } - } - - jobs.add(() -> { - if (!isPipInstalled()) { - UiUtils.errorMessage(mainWindow, "Pip is not installed."); - LOG.error("Pip is not installed"); - mainWindow.cancelBackgroundJobs(); - } - }); - - jobs.add(() -> { - mainWindow.getProgressPane().setLabel("Check Quark installed"); - if (!isQuarkInstalled()) { - LOG.warn("Quark is not installed, do you want to install it from PyPI?"); - int result = JOptionPane.showConfirmDialog(mainWindow, - "Quark is not installed, do you want to install it from PyPI?", "Warning", - JOptionPane.YES_NO_OPTION); - - if (result == JOptionPane.YES_OPTION) { - mainWindow.getProgressPane().setLabel("Installing Quark"); - createVirtualenv(); - if (!installQuark()) { - UiUtils.errorMessage(mainWindow, "Failed to install quark-engine."); - mainWindow.cancelBackgroundJobs(); - } - } - if (result == JOptionPane.NO_OPTION) { - mainWindow.cancelBackgroundJobs(); - } - } - }); - - jobs.add(() -> { - mainWindow.getProgressPane().setLabel("Analyzing"); - if (!analyzeAPK()) { - UiUtils.errorMessage(mainWindow, "Quark: Failed to analyze apk."); - mainWindow.cancelBackgroundJobs(); - } - }); - - return jobs; - } - - @Override - public void onFinish(TaskStatus status, long skipped) { - - if (quarkProcess.exitValue() != 0) { - LOG.error(QUARK_FAILED_MESSAGE); - return; - } - loadReportFile(); - } - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java b/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java deleted file mode 100644 index 23e196cd..00000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java +++ /dev/null @@ -1,105 +0,0 @@ -package jadx.gui.ui; - -import javax.swing.Icon; -import javax.swing.ImageIcon; - -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JNode; -import jadx.gui.utils.UiUtils; - -public class QuarkReport extends JNode { - - private static final long serialVersionUID = -766800957202637021L; - - private static final Logger LOG = LoggerFactory.getLogger(QuarkReport.class); - - private static final ImageIcon REPORT_ICON = UiUtils.openIcon("report"); - - private String content; - private String apkFileName; - - private JsonObject reportData; - - public static QuarkReport analysisAPK(JsonObject data) { - return new QuarkReport(data); - } - - public QuarkReport(JsonObject data) { - this.reportData = data; - this.apkFileName = data.get("apk_filename").getAsString(); - } - - @Override - public JClass getJParent() { - return null; - } - - @Override - public Icon getIcon() { - return REPORT_ICON; - } - - @Override - public String makeString() { - return "Quark analysis report"; - } - - @Override - public String getContent() { - if (content != null) { - return this.content; - } - try { - - JsonArray crimes = (JsonArray) this.reportData.get("crimes"); - - StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); - - builder.append("

Quark Analysis Report

"); - builder.append("

"); - builder.append("File name: "); - builder.append(apkFileName); - builder.append("

"); - builder.append(""); - builder.append(""); - builder.append(""); - builder.append(""); - - for (Object obj : crimes) { - JsonObject crime = (JsonObject) obj; - String crimeDes = crime.get("crime").getAsString(); - String confidence = crime.get("confidence").getAsString(); - - builder.append(""); - } - - builder.append("
Potential Malicious ActivitiesConfidence
"); - builder.append(crimeDes); - builder.append(""); - builder.append(confidence); - builder.append("
"); - this.content = builder.toString(); - - } catch (Exception e) { - LOG.error(e.getMessage(), e); - StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); - builder.append("

"); - builder.escape("Quark analysis failed!"); - builder.append("

");
-			builder.escape(ExceptionUtils.getStackTrace(e));
-			builder.append("
"); - return builder.toString(); - } - - return this.content; - } - -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index ad735693..bd640a28 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -222,11 +222,9 @@ public class RenameDialog extends JDialog { UiUtils.errorMessage(this, NLS.str("message.memoryLow")); } if (node instanceof JPackage) { - // reinit tree - mainWindow.initTree(); - } else { - mainWindow.reloadTree(); + mainWindow.getTreeRoot().update(); } + mainWindow.reloadTree(); }); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index a6edb694..201406d2 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -20,19 +20,14 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.api.ResourceFile; -import jadx.api.ResourceType; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.gui.treemodel.ApkSignature; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; -import jadx.gui.treemodel.JResource; -import jadx.gui.ui.codearea.*; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.ClassCodeContentPanel; -import jadx.gui.ui.codearea.CodeContentPanel; +import jadx.gui.ui.codearea.SmaliArea; import jadx.gui.utils.JumpManager; import jadx.gui.utils.JumpPosition; @@ -203,20 +198,16 @@ public class TabbedPane extends JTabbedPane { }); } - public void showResource(JResource res) { - final ContentPanel contentPanel = getContentPanel(res); + public void showNode(JNode node) { + final ContentPanel contentPanel = getContentPanel(node); if (contentPanel == null) { return; } SwingUtilities.invokeLater(() -> setSelectedComponent(contentPanel)); } - public void showSimpleNode(JNode node) { - final ContentPanel contentPanel = getContentPanel(node); - if (contentPanel == null) { - return; - } - SwingUtilities.invokeLater(() -> setSelectedComponent(contentPanel)); + public void codeJump(JNode node) { + codeJump(new JumpPosition(node)); } public void codeJump(JumpPosition pos) { @@ -292,7 +283,7 @@ public class TabbedPane extends JTabbedPane { private ContentPanel getContentPanel(JNode node) { ContentPanel panel = openTabs.get(node); if (panel == null) { - panel = makeContentPanel(node); + panel = node.getContentPanel(this); if (panel == null) { return null; } @@ -311,29 +302,6 @@ public class TabbedPane extends JTabbedPane { } } - @Nullable - private ContentPanel makeContentPanel(JNode node) { - if (node instanceof JResource) { - JResource res = (JResource) node; - ResourceFile resFile = res.getResFile(); - if (resFile != null) { - if (resFile.getType() == ResourceType.IMG) { - return new ImagePanel(this, res); - } - return new CodeContentPanel(this, node); - } else { - return null; - } - } - if (node instanceof ApkSignature) { - return new HtmlPanel(this, node); - } - if (node instanceof QuarkReport) { - return new HtmlPanel(this, node); - } - return new ClassCodeContentPanel(this, node); - } - @Nullable ContentPanel getSelectedCodePanel() { return (ContentPanel) getSelectedComponent(); @@ -418,7 +386,7 @@ public class TabbedPane extends JTabbedPane { pane.addFocusListener(listener); return; } - throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane); + // throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane); } static void focusOnCodePanel(ContentPanel pane) { @@ -444,7 +412,7 @@ public class TabbedPane extends JTabbedPane { SwingUtilities.invokeLater(((ImagePanel) pane)::requestFocusInWindow); return; } - throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane); + // throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane); } } } -- GitLab