fix(gui): improve Quark tasks scheduling and report viewer (#1119)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<Path> files;
|
||||
|
||||
private JComboBox<Path> 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<Path> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> permissions;
|
||||
|
||||
List<Method> native_api;
|
||||
List<Method> combination;
|
||||
List<Map<String, InvokePlace>> 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<String> first;
|
||||
List<String> second;
|
||||
}
|
||||
|
||||
String apk_filename;
|
||||
String threat_level;
|
||||
int total_score;
|
||||
List<Crime> crimes;
|
||||
}
|
||||
@@ -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("<h2>");
|
||||
builder.escape("Quark analysis failed!");
|
||||
builder.append("</h2><pre>");
|
||||
builder.escape(ExceptionUtils.getStackTrace(e));
|
||||
builder.append("</pre>");
|
||||
errorContent = builder.toString();
|
||||
return new HtmlPanel(tabbedPane, this);
|
||||
}
|
||||
return new QuarkReportPanel(tabbedPane, this, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
return this.errorContent;
|
||||
}
|
||||
}
|
||||
@@ -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<TreeNode> 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("<h1>Quark Analysis Report</h1>");
|
||||
builder.append("<h3>");
|
||||
builder.append("File: ").append(data.apk_filename);
|
||||
builder.append("<br>");
|
||||
builder.append("Treat level: ").append(data.threat_level);
|
||||
builder.append("<br>");
|
||||
builder.append("Total score: ").append(Integer.toString(data.total_score));
|
||||
builder.append("</h3>");
|
||||
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<BaseTreeNode, Component> 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<String, QuarkReportData.InvokePlace> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<JResource> {
|
||||
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) {
|
||||
|
||||
@@ -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<JNode> 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<JNode> getCustomNodes() {
|
||||
return customNodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ROOT_ICON;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -34,8 +34,6 @@ public abstract class ContentPanel extends JPanel {
|
||||
* selected entry inside the APK file.
|
||||
*
|
||||
* If <code>null</code> is returned no tool tip will be displayed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getTabTooltip() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Path> 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<Path> 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<TreePath> list) {
|
||||
|
||||
@@ -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<String> fileSelectCombo;
|
||||
|
||||
private final List<Path> files;
|
||||
private Map<String, Path> 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<String> cmdList = new ArrayList<>();
|
||||
cmdList.add("pip3");
|
||||
return executeCommand(cmdList);
|
||||
}
|
||||
|
||||
private boolean isQuarkInstalled() {
|
||||
List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<Runnable> scheduleJobs() {
|
||||
List<Runnable> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("<h1>Quark Analysis Report</h1>");
|
||||
builder.append("<h3>");
|
||||
builder.append("File name: ");
|
||||
builder.append(apkFileName);
|
||||
builder.append("</h3>");
|
||||
builder.append("<table><thead><tr>");
|
||||
builder.append("<th>Potential Malicious Activities</th>");
|
||||
builder.append("<th>Confidence</th>");
|
||||
builder.append("</tr></thead><tbody>");
|
||||
|
||||
for (Object obj : crimes) {
|
||||
JsonObject crime = (JsonObject) obj;
|
||||
String crimeDes = crime.get("crime").getAsString();
|
||||
String confidence = crime.get("confidence").getAsString();
|
||||
|
||||
builder.append("<tr><td>");
|
||||
builder.append(crimeDes);
|
||||
builder.append("</td><td>");
|
||||
builder.append(confidence);
|
||||
builder.append("</td></tr>");
|
||||
}
|
||||
|
||||
builder.append("</tbody></table>");
|
||||
this.content = builder.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
|
||||
builder.append("<h1>");
|
||||
builder.escape("Quark analysis failed!");
|
||||
builder.append("</h1><pre>");
|
||||
builder.escape(ExceptionUtils.getStackTrace(e));
|
||||
builder.append("</pre>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
return this.content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user