提交 b5720bd1 编写于 作者: S Skylot

fix(gui): improve Quark tasks scheduling and report viewer (#1119)

上级 cc99409a
......@@ -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");
......
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();
}
}
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;
}
}
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;
}
package jadx.gui.ui;
package jadx.gui.plugins.quark;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.swing.Icon;
import javax.swing.ImageIcon;
......@@ -8,33 +11,31 @@ 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 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 QuarkReport extends JNode {
public class QuarkReportNode 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 static final Logger LOG = LoggerFactory.getLogger(QuarkReportNode.class);
private String content;
private String apkFileName;
private static final Gson GSON = new GsonBuilder().create();
private JsonObject reportData;
private static final ImageIcon ICON = UiUtils.openIcon("icon_quark");
private final Path apkFile;
public static QuarkReport analysisAPK(JsonObject data) {
return new QuarkReport(data);
}
private String errorContent;
public QuarkReport(JsonObject data) {
this.reportData = data;
this.apkFileName = data.get("apk_filename").getAsString();
public QuarkReportNode(Path apkFile) {
this.apkFile = apkFile;
}
@Override
......@@ -44,7 +45,7 @@ public class QuarkReport extends JNode {
@Override
public Icon getIcon() {
return REPORT_ICON;
return ICON;
}
@Override
......@@ -53,53 +54,26 @@ public class QuarkReport extends JNode {
}
@Override
public String getContent() {
if (content != null) {
return this.content;
}
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
QuarkReportData data;
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();
data = GSON.fromJson(Files.newBufferedReader(apkFile), QuarkReportData.class);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
LOG.error("Quark report parse error", e);
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
builder.append("<h1>");
builder.append("<h2>");
builder.escape("Quark analysis failed!");
builder.append("</h1><pre>");
builder.append("</h2><pre>");
builder.escape(ExceptionUtils.getStackTrace(e));
builder.append("</pre>");
return builder.toString();
errorContent = builder.toString();
return new HtmlPanel(tabbedPane, this);
}
return this.content;
return new QuarkReportPanel(tabbedPane, this, data);
}
@Override
public String getContent() {
return this.errorContent;
}
}
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;
......
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,11 +379,14 @@ 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));
if (paths.size() == 1) {
Path singleFile = paths.get(0);
if (singleFile.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(JadxProject.PROJECT_EXTENSION)) {
openProject(singleFile);
onFinish.run();
} else {
return;
}
}
project.setFilePath(paths);
clearTree();
BreakpointManager.saveAndExit();
......@@ -392,20 +395,27 @@ public class MainWindow extends JFrame {
}
backgroundExecutor.execute(NLS.str("progress.load"),
() -> wrapper.openFile(paths),
(status) -> {
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());
onFinish.run();
});
}
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) {
......
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();
}
}
}
......@@ -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);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册