* Add quark installation * add error/warning dialog * change Quark task to background task * fix missing the last line of input stream
This commit is contained in:
@@ -7,18 +7,21 @@ 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.JProgressBar;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -27,6 +30,8 @@ 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;
|
||||
@@ -37,67 +42,51 @@ 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 JProgressBar progressBar;
|
||||
private JPanel progressPane;
|
||||
|
||||
private JComboBox<String> selectFile;
|
||||
private JComboBox<String> fileSelectCombo;
|
||||
|
||||
private final List<Path> files;
|
||||
private ArrayList<Path> analyzeFile = new ArrayList<Path>();
|
||||
private Map<String, Path> choosableFiles = new HashMap<>();
|
||||
|
||||
public QuarkDialog(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.settings = mainWindow.getSettings();
|
||||
this.files = mainWindow.getWrapper().getOpenPaths();
|
||||
|
||||
if (!prepareAnalysis()) {
|
||||
// The files are unable to analysis by Quark
|
||||
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 boolean prepareAnalysis() {
|
||||
String[] exts = new String[] { "apk", "dex" };
|
||||
private void fileNameExtensionFilter() {
|
||||
String[] extensions = new String[] { "apk", "dex" };
|
||||
|
||||
if (this.files.size() != 1) {
|
||||
for (Path filePath : this.files) {
|
||||
String fileName = filePath.toString();
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
|
||||
for (Path filePath : this.files) {
|
||||
String fileName = filePath.toString();
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
|
||||
|
||||
if (Arrays.stream(exts).noneMatch(extension::equals)) {
|
||||
LOG.warn("Quark: Current file can't be analysis: {}", fileName);
|
||||
continue;
|
||||
}
|
||||
analyzeFile.add(filePath);
|
||||
if (Arrays.stream(extensions).noneMatch(extension::equals)) {
|
||||
LOG.debug("Quark: {} is not apk nor dex", fileName);
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
choosableFiles.put(fileName, filePath);
|
||||
}
|
||||
String fileName = this.files.get(0).toString();
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
|
||||
if (Arrays.stream(exts).noneMatch(extension::equals)) {
|
||||
LOG.warn("Quark: Current file can't be analysis: {}", fileName);
|
||||
return false;
|
||||
}
|
||||
analyzeFile.add(this.files.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
private String[] filesToStringArr() {
|
||||
String[] arr = new String[files.size()];
|
||||
int index = 0;
|
||||
for (Path file : analyzeFile) {
|
||||
arr[index] = file.getFileName().toString();
|
||||
index++;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
public final void initUI() {
|
||||
@@ -105,7 +94,8 @@ class QuarkDialog extends JDialog {
|
||||
JLabel selectApkText = new JLabel("Select Apk/Dex");
|
||||
description.setAlignmentX(0.5f);
|
||||
|
||||
selectFile = new JComboBox<String>(filesToStringArr());
|
||||
String[] comboFiles = choosableFiles.keySet().toArray(new String[choosableFiles.size()]);
|
||||
fileSelectCombo = new JComboBox<>(comboFiles);
|
||||
|
||||
JPanel textPane = new JPanel();
|
||||
|
||||
@@ -113,30 +103,19 @@ class QuarkDialog extends JDialog {
|
||||
|
||||
JPanel selectApkPanel = new JPanel();
|
||||
selectApkPanel.add(selectApkText);
|
||||
selectApkPanel.add(selectFile);
|
||||
|
||||
progressPane = new JPanel();
|
||||
progressPane.setVisible(false);
|
||||
progressPane.setSize(150, 10);
|
||||
|
||||
progressBar = new JProgressBar(0, 100);
|
||||
progressBar.setSize(150, 10);
|
||||
progressBar.setIndeterminate(true);
|
||||
progressBar.setStringPainted(false);
|
||||
progressPane.add(progressBar);
|
||||
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 -> analyzeAPK());
|
||||
start.addActionListener(event -> mainWindow.getBackgroundExecutor().execute(new QuarkTask()));
|
||||
buttonPane.add(start);
|
||||
buttonPane.add(close);
|
||||
getRootPane().setDefaultButton(close);
|
||||
|
||||
JPanel centerPane = new JPanel();
|
||||
centerPane.add(selectApkPanel);
|
||||
centerPane.add(progressPane);
|
||||
Container contentPane = getContentPane();
|
||||
|
||||
contentPane.add(textPane, BorderLayout.PAGE_START);
|
||||
@@ -154,25 +133,6 @@ class QuarkDialog extends JDialog {
|
||||
UiUtils.addEscapeShortCutToDispose(this);
|
||||
}
|
||||
|
||||
private void analyzeAPK() {
|
||||
LoadTask task = new LoadTask();
|
||||
task.execute();
|
||||
}
|
||||
|
||||
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) {
|
||||
LOG.error("Quark: Load report failed: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
dispose();
|
||||
}
|
||||
@@ -183,52 +143,259 @@ class QuarkDialog extends JDialog {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private class LoadTask extends SwingWorker<Void, Void> {
|
||||
public LoadTask() {
|
||||
progressPane.setVisible(true);
|
||||
private class QuarkTask implements IBackgroundTask {
|
||||
|
||||
private Process quarkProcess;
|
||||
private boolean isVenv = false;
|
||||
|
||||
public QuarkTask() {
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground() {
|
||||
try {
|
||||
quarkReportFile = File.createTempFile("QuarkReport-", ".json");
|
||||
private boolean isPipInstalled() {
|
||||
List<String> cmdList = new ArrayList<>();
|
||||
cmdList.add("pip3");
|
||||
return executeCommand(cmdList);
|
||||
}
|
||||
|
||||
String apkName = (String) selectFile.getSelectedItem();
|
||||
String apkPath = null;
|
||||
for (Path path : files) {
|
||||
if (path.getFileName().toString().equals(apkName)) {
|
||||
apkPath = path.toString();
|
||||
}
|
||||
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<>();
|
||||
cmdList.add("quark");
|
||||
String command = (isVenv) ? getVenvPath(QUARK_CMD).toString() : QUARK_CMD;
|
||||
cmdList.add(command);
|
||||
cmdList.add("-a");
|
||||
cmdList.add(apkPath);
|
||||
cmdList.add("-s");
|
||||
cmdList.add("-o");
|
||||
cmdList.add(quarkReportFile.getAbsolutePath());
|
||||
LOG.debug("Running Quark cmd: {}", String.join(" ", cmdList));
|
||||
Process process = Runtime.getRuntime().exec(cmdList.toArray(new String[0]));
|
||||
try (BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
LOG.debug("Quark analyzing...");
|
||||
while (process.isAlive()) {
|
||||
String output = buf.readLine();
|
||||
if (output != null) {
|
||||
LOG.debug(output);
|
||||
}
|
||||
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("Quark failed: ", e);
|
||||
dispose();
|
||||
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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done() {
|
||||
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();
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user