From ab02e6e7c3292ba3ad8342a3210bf1f748dc15f9 Mon Sep 17 00:00:00 2001 From: Shaun Dang Date: Fri, 12 Mar 2021 21:44:42 +0800 Subject: [PATCH] feat(gui): add Quark-Engine integration (#1119) (PR #1135) --- .../src/main/java/jadx/gui/ui/MainWindow.java | 17 ++ .../main/java/jadx/gui/ui/QuarkDialog.java | 232 ++++++++++++++++++ .../main/java/jadx/gui/ui/QuarkReport.java | 105 ++++++++ .../src/main/java/jadx/gui/ui/TabbedPane.java | 3 + .../main/resources/icons-16/icon_quark.png | Bin 0 -> 1086 bytes 5 files changed, 357 insertions(+) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java create mode 100644 jadx-gui/src/main/resources/icons-16/icon_quark.png diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 49f8f8e4..12998f84 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -143,6 +143,7 @@ public class MainWindow extends JFrame { private static final ImageIcon ICON_COMMENT_SEARCH = UiUtils.openIcon("table_edit"); private static final ImageIcon ICON_BACK = UiUtils.openIcon("icon_back"); private static final ImageIcon ICON_FORWARD = UiUtils.openIcon("icon_forward"); + private static final ImageIcon ICON_QUARK = UiUtils.openIcon("icon_quark"); private static final ImageIcon ICON_PREF = UiUtils.openIcon("wrench"); private static final ImageIcon ICON_DEOBF = UiUtils.openIcon("lock_edit"); private static final ImageIcon ICON_LOG = UiUtils.openIcon("report"); @@ -662,6 +663,8 @@ public class MainWindow extends JFrame { } } else if (obj instanceof ApkSignature) { tabbedPane.showSimpleNode((JNode) obj); + } else if (obj instanceof QuarkReport) { + tabbedPane.showSimpleNode((JNode) obj); } else if (obj instanceof JNode) { tabbedPane.codeJump(new JumpPosition((JNode) obj)); } @@ -914,6 +917,14 @@ public class MainWindow extends JFrame { forwardAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("nav.forward")); forwardAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK)); + Action quarkAction = new AbstractAction("Quark Engine", ICON_QUARK) { + @Override + public void actionPerformed(ActionEvent e) { + new QuarkDialog(MainWindow.this).setVisible(true); + } + }; + quarkAction.putValue(Action.SHORT_DESCRIPTION, "Quark Engine"); + JMenu file = new JMenu(NLS.str("menu.file")); file.setMnemonic(KeyEvent.VK_F); file.add(openAction); @@ -998,6 +1009,8 @@ public class MainWindow extends JFrame { toolbar.addSeparator(); toolbar.add(prefsAction); toolbar.addSeparator(); + toolbar.add(quarkAction); + toolbar.addSeparator(); toolbar.add(Box.createHorizontalGlue()); toolbar.add(updateLink); @@ -1249,6 +1262,10 @@ public class MainWindow extends JFrame { return progressPane; } + public JRoot getTreeRoot() { + return treeRoot; + } + private class RecentProjectsMenuListener implements MenuListener { private final JMenu menu; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java new file mode 100644 index 00000000..c3e41f55 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/QuarkDialog.java @@ -0,0 +1,232 @@ +package jadx.gui.ui; + +import java.awt.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + +import jadx.gui.settings.JadxSettings; +import jadx.gui.treemodel.JRoot; +import jadx.gui.utils.NLS; +import jadx.gui.utils.logs.LogCollector; + +class QuarkDialog extends JDialog { + + private static final long serialVersionUID = 4855753773520368215L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkDialog.class); + + private File quarkReportFile; + + private final transient JadxSettings settings; + private final transient MainWindow mainWindow; + private JProgressBar progressBar; + private JPanel progressPane; + + private JComboBox selectFile; + + private final List files; + private ArrayList analyzeFile = new ArrayList(); + + 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 + return; + } + initUI(); + settings.loadWindowPos(this); + } + + private boolean prepareAnalysis() { + + String[] exts = 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); + + if (!Arrays.stream(exts).anyMatch(extension::equals)) { + LOG.warn("Quark: Current file can't be analysis ", fileName); + continue; + } + analyzeFile.add(filePath); + } + return true; + } + String fileName = this.files.get(0).toString(); + int dotIndex = fileName.lastIndexOf('.'); + String extension = (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); + if (!Arrays.stream(exts).anyMatch(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() { + + JLabel description = new JLabel("Analyzing apk using Quark-Engine"); + JLabel selectApkText = new JLabel("Select Apk/Dex"); + description.setAlignmentX(0.5f); + + selectFile = new JComboBox(filesToStringArr()); + + JPanel textPane = new JPanel(); + + textPane.add(description); + + 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); + + 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()); + 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); + contentPane.add(centerPane); + contentPane.add(buttonPane, BorderLayout.PAGE_END); + + setTitle("Quark Engine"); + pack(); + setSize(200, 125); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setModalityType(ModalityType.MODELESS); + setLocationRelativeTo(null); + } + + private void analyzeAPK() { + LoadTask task = new LoadTask(); + task.execute(); + } + + private void loadReportFile() { + try { + JsonObject quarkReport = (JsonObject) JsonParser.parseReader(new FileReader(quarkReportFile.getAbsolutePath().toString())); + + JRoot root = mainWindow.getCacheObject().getJRoot(); + + QuarkReport quarkNode = QuarkReport.analysisAPK(quarkReport); + + root.update(); + root.add(quarkNode); + + mainWindow.reloadTree(); + + } catch (JsonIOException | JsonSyntaxException | FileNotFoundException e) { + LOG.error("Quark: Load report failed: ", e); + } + + } + + private void close() { + dispose(); + } + + @Override + public void dispose() { + LogCollector.getInstance().resetListener(); + settings.saveWindowPos(this); + super.dispose(); + } + + private class LoadTask extends SwingWorker { + public LoadTask() { + progressPane.setVisible(true); + } + + @Override + public Void doInBackground() { + try { + + quarkReportFile = File.createTempFile("QuarkReport-", ".json"); + + String outputPath = quarkReportFile.getAbsolutePath().toString(); + String apkName = selectFile.getSelectedItem().toString(); + String apkPath = null; + for (Path path : files) { + if (path.getFileName().toString().equals(apkName)) { + apkPath = path.toString(); + } + } + String cmd = "quark -a " + apkPath + " -s -o " + outputPath; + Runtime run = Runtime.getRuntime(); + Process process = run.exec(cmd); + + BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream())); + String output = ""; + LOG.debug("Quark analyzing..."); + while ((output = buf.readLine()) != null) { + LOG.debug(output); + } + + } catch (IOException e) { + LOG.error("Quark failed: ", e); + dispose(); + } + return null; + } + + @Override + public void done() { + loadReportFile(); + dispose(); + } + } + +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java b/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java new file mode 100644 index 00000000..23e196cd --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/QuarkReport.java @@ -0,0 +1,105 @@ +package jadx.gui.ui; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.utils.UiUtils; + +public class QuarkReport extends JNode { + + private static final long serialVersionUID = -766800957202637021L; + + private static final Logger LOG = LoggerFactory.getLogger(QuarkReport.class); + + private static final ImageIcon REPORT_ICON = UiUtils.openIcon("report"); + + private String content; + private String apkFileName; + + private JsonObject reportData; + + public static QuarkReport analysisAPK(JsonObject data) { + return new QuarkReport(data); + } + + public QuarkReport(JsonObject data) { + this.reportData = data; + this.apkFileName = data.get("apk_filename").getAsString(); + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return REPORT_ICON; + } + + @Override + public String makeString() { + return "Quark analysis report"; + } + + @Override + public String getContent() { + if (content != null) { + return this.content; + } + try { + + JsonArray crimes = (JsonArray) this.reportData.get("crimes"); + + StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); + + builder.append("

Quark Analysis Report

"); + builder.append("

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

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

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

");
+			builder.escape(ExceptionUtils.getStackTrace(e));
+			builder.append("
"); + return builder.toString(); + } + + return this.content; + } + +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index ae5e0452..18104e51 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -305,6 +305,9 @@ public class TabbedPane extends JTabbedPane { if (node instanceof ApkSignature) { return new HtmlPanel(this, node); } + if (node instanceof QuarkReport) { + return new HtmlPanel(this, node); + } return new ClassCodeContentPanel(this, node); } diff --git a/jadx-gui/src/main/resources/icons-16/icon_quark.png b/jadx-gui/src/main/resources/icons-16/icon_quark.png new file mode 100644 index 0000000000000000000000000000000000000000..1524100e29c56643a8e0ddf833dde727c9c35742 GIT binary patch literal 1086 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|T2doC(|mmy zw18|523AHP24;{FAY@>aVqgWc85q16rQz%#Mh&PMCI*J~Oa>OHnkXO*0v4nJa0`PlBg3pY5xV%QuQiw3qZOUY$~jP%-qzHM1_jnoV;SI3R@+x3M(KRB&@Hb09I0xZL1XF8=&Bv zUzDm~s%N5Spk&9TprBw=l#*r@P?Wt5Z@Sn2DRmzV368|&p4rRy77T3YHG z80i}s=>k>g7FXt#Bv$C=6)QswftllyTAW;zSx}OhpQivaH!&%{w8U0P31kr*K-^i9 znTD__uNdkrpa=CqGWv#k2KsQbfm&@qqE`MznW;dVLFU^T+JIG}h(YbK(Fa+Mo$B-hQW zGpE-EPJVT;@#Ob=XMXQ9H_l%iW5Uz5=z-lbq4)`Fy;x`Zh^>2lPwSb5p3+2vM;{9_ zR&gz2>U-QFwQbwwJ*wX;PrUwL{q1$*1B39Xso~kCQFh0!lrVj*iZw{>n`r&zcg+pY zrGdif(l?hiEXx%6*=~M${fD9~p>4U&icJQqLYGbozqfu;gWk1XmJ1>i8vD-iwKyq0 zG*UD4`55+j-43qc_@u)pnA4sg++-=&)%*Rjn!^0afxA>bJc(ZGyWlHB_=C@Bg+V%9 zTkd>b=G(xTbN#gaDM=->-(_tZ#Q&W2$QBOxTQ~8ua1nQp+r=ZVpQ;?D@o8%9glV)qy zMBHD%%+Bl}Hq<|E0t7xN9K|7zOj9_!I_Y=!T4uLGM_=>InVw(YcwUZpt; zXRb=A?E7cp4AY+_&%OU+y~dYg;&)D;kKt?k$lmDSz%xPA;@n!pW5!s(W9{o1VllsV}*IdZNREYoJWz>FVdQ&MBb@0P9P9KL7v# literal 0 HcmV?d00001 -- GitLab