getOptimizedStreamEntries() {
312 | return this.optimizedStreamEntries;
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/MainFrame.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui;
19 |
20 | import com.github.struppigel.gui.pedetails.FileContentPreviewPanel;
21 | import com.github.struppigel.gui.pedetails.PEDetailsPanel;
22 | import com.github.struppigel.gui.utils.PELoadWorker;
23 | import com.github.struppigel.gui.utils.PortexSwingUtils;
24 | import com.github.struppigel.gui.utils.WorkerKiller;
25 | import com.github.struppigel.gui.utils.WriteSettingsWorker;
26 | import com.github.struppigel.settings.LookAndFeelSetting;
27 | import com.github.struppigel.settings.PortexSettings;
28 | import com.github.struppigel.settings.PortexSettingsKey;
29 | import org.apache.logging.log4j.LogManager;
30 | import org.apache.logging.log4j.Logger;
31 |
32 | import javax.swing.*;
33 | import java.awt.*;
34 | import java.awt.datatransfer.DataFlavor;
35 | import java.awt.datatransfer.UnsupportedFlavorException;
36 | import java.awt.dnd.DnDConstants;
37 | import java.awt.dnd.DropTarget;
38 | import java.awt.dnd.DropTargetDropEvent;
39 | import java.awt.event.ItemEvent;
40 | import java.io.BufferedReader;
41 | import java.io.File;
42 | import java.io.IOException;
43 | import java.io.InputStreamReader;
44 | import java.net.*;
45 | import java.nio.charset.StandardCharsets;
46 | import java.util.List;
47 | import java.util.Scanner;
48 | import java.util.concurrent.ExecutionException;
49 |
50 | import static javax.swing.SwingUtilities.invokeLater;
51 | import static javax.swing.SwingWorker.StateValue.DONE;
52 |
53 | /**
54 | * The main frame that sets everything in place and holds the main menu.
55 | */
56 | public class MainFrame extends JFrame {
57 | private static final Logger LOGGER = LogManager.getLogger();
58 | private final JLabel filePathLabel = new JLabel();
59 | private final VisualizerPanel visualizerPanel = new VisualizerPanel();;
60 | private final PEDetailsPanel peDetailsPanel;
61 | private final PortexSettings settings;
62 | private FullPEData pedata = null;
63 | private PEComponentTree peComponentTree;
64 | ;
65 | private JFrame progressBarFrame;
66 | private JProgressBar progressBar;
67 | private final static String versionURL = "https://github.com/struppigel/PortexAnalyzerGUI/raw/main/resources/upd_version.txt";
68 | private final static String currVersion = "/upd_version.txt";
69 | private final static String releasePage = "https://github.com/struppigel/PortexAnalyzerGUI/releases";
70 | private final JLabel progressText = new JLabel("Loading ...");
71 | private final JPanel cardPanel = new JPanel(new CardLayout());
72 | private FileContentPreviewPanel fileContent = new FileContentPreviewPanel();
73 |
74 | public MainFrame(PortexSettings settings) {
75 | super("Portex Analyzer v. " + AboutFrame.version);
76 |
77 | this.settings = settings;
78 | peDetailsPanel = new PEDetailsPanel(cardPanel, this, settings, fileContent);
79 | peComponentTree = new PEComponentTree(peDetailsPanel);
80 |
81 | initGUI();
82 | initDropTargets();
83 | checkForUpdate();
84 | setToHex(settings.valueEquals(PortexSettingsKey.VALUES_AS_HEX, "1"));
85 | }
86 |
87 | private void checkForUpdate() {
88 | // only update if setting does not prevent it
89 | if(settings.valueEquals(PortexSettingsKey.DISABLE_UPDATE,"1")) {
90 | return;
91 | }
92 | UpdateWorker updater = new UpdateWorker();
93 | updater.execute();
94 | }
95 |
96 | public void refreshSelection() {
97 | peComponentTree.refreshSelection();
98 | }
99 |
100 | private static class UpdateWorker extends SwingWorker {
101 | @Override
102 | protected Boolean doInBackground() {
103 | try {
104 | URL githubURL = new URL(versionURL);
105 | try (InputStreamReader is = new InputStreamReader(getClass().getResourceAsStream(currVersion), StandardCharsets.UTF_8);
106 | BufferedReader versionIn = new BufferedReader(is);
107 | Scanner s = new Scanner(githubURL.openStream())) {
108 |
109 | int versionHere = Integer.parseInt(versionIn.readLine().trim());
110 | int githubVersion = s.nextInt();
111 |
112 | if (versionHere < githubVersion) {
113 | return true;
114 | }
115 | }
116 | } catch (UnknownHostException e) {
117 | LOGGER.info("unknown host or no internet connection: " + e.getMessage());
118 | } catch (IOException | NumberFormatException e) {
119 | LOGGER.error(e);
120 | e.printStackTrace();
121 | }
122 | return false;
123 | }
124 |
125 | protected void done() {
126 | try {
127 | if (get()) {
128 | LOGGER.debug("update requested");
129 | String message = "A new version is available. Do you want to download it?";
130 | int response = JOptionPane.showConfirmDialog(null,
131 | message,
132 | "Update available",
133 | JOptionPane.YES_NO_OPTION);
134 | if (response == JOptionPane.YES_OPTION) {
135 | openWebpage(new URL(releasePage));
136 | }
137 | } else {
138 | LOGGER.debug("no update necessary");
139 | }
140 | } catch (InterruptedException | ExecutionException | MalformedURLException e) {
141 | LOGGER.error(e);
142 | }
143 | }
144 | }
145 |
146 | private static boolean openWebpage(URI uri) {
147 | Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
148 | if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
149 | try {
150 | desktop.browse(uri);
151 | return true;
152 | } catch (IOException e) {
153 | LOGGER.error(e);
154 | e.printStackTrace();
155 | }
156 | }
157 | return false;
158 | }
159 |
160 | private static boolean openWebpage(URL url) {
161 | try {
162 | return openWebpage(url.toURI());
163 | } catch (URISyntaxException e) {
164 | LOGGER.error(e);
165 | e.printStackTrace();
166 | }
167 | return false;
168 | }
169 |
170 | private void initDropTargets() {
171 | // file drag and drop support
172 | this.setDropTarget(new FileDropper());
173 | peDetailsPanel.setDropTarget(new FileDropper());
174 | visualizerPanel.setDropTarget(new FileDropper());
175 | filePathLabel.setDropTarget(new FileDropper());
176 | }
177 |
178 | private void loadFile(File file) {
179 | PELoadWorker worker = new PELoadWorker(file, this, progressText);
180 | worker.addPropertyChangeListener(evt -> {
181 | String name = evt.getPropertyName();
182 | if (name.equals("progress")) {
183 | int progress = (Integer) evt.getNewValue();
184 | progressBar.setValue(progress);
185 | progressBar.setString(progress + " %");
186 | progressBarFrame.toFront();
187 | progressBarFrame.setAlwaysOnTop(true);
188 | } else if (name.equals("state")) {
189 | SwingWorker.StateValue state = (SwingWorker.StateValue) evt
190 | .getNewValue();
191 | if (state == DONE) {
192 | progressBarFrame.setVisible(false);
193 | }
194 | }
195 | });
196 | progressBarFrame.setVisible(true);
197 | WorkerKiller.getInstance().cancelAndDeleteWorkers();
198 | WorkerKiller.getInstance().addWorker(worker); // need to add this in case a user loads another PE during load process
199 | worker.execute();
200 | }
201 |
202 | public void setPeData(FullPEData data) {
203 | try {
204 | this.pedata = data;
205 | visualizerPanel.visualizePE(pedata.getFile());
206 | peDetailsPanel.setPeData(pedata);
207 | peComponentTree.setPeData(pedata);
208 | peComponentTree.setSelectionRow(0);
209 | fileContent.setPeData(pedata);
210 | } catch (IOException e) {
211 | String message = "Could not load PE file! Reason: " + e.getMessage();
212 | LOGGER.error(message);
213 | e.printStackTrace();
214 | JOptionPane.showMessageDialog(this,
215 | message,
216 | "Unable to load",
217 | JOptionPane.ERROR_MESSAGE);
218 | }
219 | }
220 |
221 | private void initGUI() {
222 | // Main frame settings
223 | setSize(1280, 768);
224 | setLocationRelativeTo(null);
225 | setVisible(true);
226 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
227 |
228 | // Init main panel that holds everything
229 | JPanel panel = new JPanel();
230 | panel.setLayout(new BorderLayout());
231 |
232 | // init Card Panel on the right
233 | this.cardPanel.add(visualizerPanel, "VISUALIZER");
234 | this.cardPanel.add(fileContent, "FILE_CONTENT");
235 | if(settings.valueEquals(PortexSettingsKey.CONTENT_PREVIEW, "1")) {
236 | ((CardLayout) cardPanel.getLayout()).show(cardPanel, "FILE_CONTENT");
237 | } else {
238 | ((CardLayout) cardPanel.getLayout()).show(cardPanel, "VISUALIZER");
239 | }
240 | // Add all other components
241 | panel.add(peDetailsPanel, BorderLayout.CENTER);
242 | panel.add(peComponentTree, BorderLayout.LINE_START);
243 | panel.add(cardPanel, BorderLayout.LINE_END);
244 | this.add(panel, BorderLayout.CENTER);
245 |
246 | initToolbar();
247 | initMenu();
248 | initProgressBar();
249 | }
250 |
251 | private void initToolbar() {
252 | JToolBar toolBar = new JToolBar();
253 |
254 | ImageIcon ico = new ImageIcon(getClass().getResource("/icons8-hexadecimal-24.png"));
255 | JToggleButton hexButton = new JToggleButton(ico);
256 | hexButton.setSelected(settings.valueEquals(PortexSettingsKey.VALUES_AS_HEX, "1"));
257 | hexButton.addItemListener(e -> {
258 | int state = e.getStateChange();
259 | if (state == ItemEvent.SELECTED) {
260 | setToHex(true);
261 | settings.put(PortexSettingsKey.VALUES_AS_HEX, "1");
262 | } else if (state == ItemEvent.DESELECTED) {
263 | setToHex(false);
264 | settings.put(PortexSettingsKey.VALUES_AS_HEX, "0");
265 | }
266 | new WriteSettingsWorker(settings).execute();
267 | });
268 | ImageIcon imgIco = new ImageIcon(getClass().getResource("/icons8-image-24.png"));
269 | JToggleButton imgButton = new JToggleButton(imgIco);
270 | imgButton.setSelected(settings.valueEquals(PortexSettingsKey.CONTENT_PREVIEW, "0"));
271 | imgButton.addItemListener(e -> {
272 | int state = e.getStateChange();
273 | if(state == ItemEvent.SELECTED){
274 | ((CardLayout) cardPanel.getLayout()).show(cardPanel, "VISUALIZER");
275 | settings.put(PortexSettingsKey.CONTENT_PREVIEW, "0");
276 | } else {
277 | ((CardLayout) cardPanel.getLayout()).show(cardPanel, "FILE_CONTENT");
278 | settings.put(PortexSettingsKey.CONTENT_PREVIEW, "1");
279 | }
280 | cardPanel.repaint();
281 | fileContent.repaint();
282 | new WriteSettingsWorker(settings).execute();
283 | });
284 |
285 | toolBar.add(hexButton);
286 | toolBar.add(imgButton);
287 | toolBar.add(Box.createHorizontalGlue());
288 | toolBar.add(filePathLabel);
289 | toolBar.add(Box.createHorizontalGlue());
290 | toolBar.setOpaque(true);
291 |
292 | add(toolBar, BorderLayout.PAGE_START);
293 |
294 | toolBar.repaint();
295 | }
296 |
297 | private void setToHex(boolean hexEnabled) {
298 | peDetailsPanel.setHexEnabled(hexEnabled);
299 | }
300 |
301 | private void initProgressBar() {
302 | this.progressBarFrame = new JFrame("Loading PE file");
303 | JPanel panel = new JPanel();
304 | this.progressBar = new JProgressBar();
305 | progressBar.setPreferredSize(new Dimension(250, 25));
306 | progressBar.setIndeterminate(false);
307 | progressBar.setStringPainted(true);
308 | progressBar.setMaximum(100);
309 |
310 | panel.setLayout(new GridLayout(0, 1));
311 | panel.add(progressBar);
312 |
313 | // this panel makes sure the text is in the middle
314 | JPanel middleText = new JPanel();
315 | middleText.add(progressText);
316 | panel.add(middleText);
317 |
318 | progressBarFrame.add(panel);
319 | progressBarFrame.pack();
320 | progressBarFrame.setSize(400, 100);
321 | progressBarFrame.setLocationRelativeTo(null);
322 | progressBarFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
323 | }
324 |
325 | private void initMenu() {
326 | JMenuBar menuBar = new JMenuBar();
327 | JMenu fileMenu = createFileMenu();
328 | JMenu help = createHelpMenu();
329 | JMenu settingsMenu = createSettingsMenu();
330 |
331 | menuBar.add(fileMenu);
332 | menuBar.add(settingsMenu);
333 | menuBar.add(help);
334 |
335 | this.setJMenuBar(menuBar);
336 | }
337 |
338 | private JMenu createSettingsMenu() {
339 | JMenu settingsMenu = new JMenu("Settings");
340 | JCheckBoxMenuItem disableYaraWarn = new JCheckBoxMenuItem("Disable Yara warnings");
341 | JCheckBoxMenuItem disableUpdateCheck = new JCheckBoxMenuItem("Disable update check");
342 | JCheckBoxMenuItem systemTheme = new JCheckBoxMenuItem("Use system theme");
343 | disableUpdateCheck.setState(settings.valueEquals(PortexSettingsKey.DISABLE_UPDATE, "1"));
344 | disableYaraWarn.setState(settings.valueEquals(PortexSettingsKey.DISABLE_YARA_WARNINGS, "1"));
345 | systemTheme.setState(settings.valueEquals(PortexSettingsKey.LOOK_AND_FEEL, LookAndFeelSetting.SYSTEM.toString()));
346 |
347 | settingsMenu.add(disableYaraWarn);
348 | settingsMenu.add(disableUpdateCheck);
349 | settingsMenu.add(systemTheme);
350 |
351 | disableYaraWarn.addActionListener(e -> {
352 | if(disableYaraWarn.getState()) {
353 | settings.put(PortexSettingsKey.DISABLE_YARA_WARNINGS, "1");
354 | } else {
355 | settings.put(PortexSettingsKey.DISABLE_YARA_WARNINGS, "0");
356 | }
357 | try {
358 | settings.writeSettings();
359 | } catch (IOException ex) {
360 | LOGGER.error(e);
361 | }
362 | });
363 |
364 | disableUpdateCheck.addActionListener(e -> {
365 | if(disableUpdateCheck.getState()) {
366 | settings.put(PortexSettingsKey.DISABLE_UPDATE, "1");
367 | } else {
368 | settings.put(PortexSettingsKey.DISABLE_UPDATE, "0");
369 | }
370 | try {
371 | settings.writeSettings();
372 | } catch (IOException ex) {
373 | LOGGER.error(e);
374 | }
375 | });
376 |
377 | systemTheme.addActionListener(e -> {
378 | JOptionPane.showMessageDialog(this,
379 | "Please restart PortexAnalyzerGUI for the new theme!",
380 | "Theme changed",
381 | JOptionPane.INFORMATION_MESSAGE);
382 | if(systemTheme.getState()) {
383 | settings.put(PortexSettingsKey.LOOK_AND_FEEL, LookAndFeelSetting.SYSTEM.toString());
384 | } else {
385 | settings.put(PortexSettingsKey.LOOK_AND_FEEL, LookAndFeelSetting.PORTEX.toString());
386 | }
387 | try {
388 | settings.writeSettings();
389 | } catch (IOException ex) {
390 | LOGGER.error(e);
391 | }
392 | });
393 | return settingsMenu;
394 | }
395 |
396 | private JMenu createHelpMenu() {
397 | JMenu help = new JMenu("Help");
398 | JMenuItem about = new JMenuItem("About");
399 | help.add(about);
400 | about.addActionListener(arg0 -> invokeLater(() -> {
401 | AboutFrame aFrame = new AboutFrame();
402 | aFrame.setVisible(true);
403 | }));
404 | return help;
405 | }
406 |
407 | private JMenu createFileMenu() {
408 | // open file
409 | JMenu fileMenu = new JMenu("File");
410 | JMenuItem open = new JMenuItem("Open...", new ImageIcon(getClass().getResource("/laptop-bug-icon.png")));
411 | fileMenu.add(open);
412 | open.addActionListener(arg0 -> {
413 | String path = PortexSwingUtils.getOpenFileNameFromUser(this);
414 | if (path != null) {
415 | filePathLabel.setText(path);
416 | loadFile(new File(path));
417 | }
418 | });
419 |
420 | // close application
421 | JMenuItem exit = new JMenuItem("Exit", new ImageIcon(getClass().getResource("/moon-icon.png")));
422 | fileMenu.add(exit);
423 | exit.addActionListener(arg0 -> dispose());
424 | return fileMenu;
425 | }
426 |
427 | private class FileDropper extends DropTarget {
428 | public synchronized void drop(DropTargetDropEvent evt) {
429 | try {
430 | evt.acceptDrop(DnDConstants.ACTION_COPY);
431 | List droppedFiles = (List)
432 | evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
433 | if (droppedFiles.size() > 0) {
434 | File file = droppedFiles.get(0);
435 | filePathLabel.setText(file.getAbsolutePath());
436 | loadFile(file);
437 | }
438 | } catch (IOException | UnsupportedFlavorException ex) {
439 | String message = "Could not load PE file from dropper! Reason: " + ex.getMessage();
440 | LOGGER.error(message);
441 | ex.printStackTrace();
442 | JOptionPane.showMessageDialog(null,
443 | message,
444 | "Unable to load",
445 | JOptionPane.ERROR_MESSAGE);
446 | }
447 | }
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/PEComponentTree.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui;
19 |
20 | import com.github.struppigel.gui.pedetails.PEDetailsPanel;
21 | import org.apache.logging.log4j.LogManager;
22 | import org.apache.logging.log4j.Logger;
23 |
24 | import javax.swing.*;
25 | import javax.swing.tree.DefaultMutableTreeNode;
26 | import javax.swing.tree.DefaultTreeCellRenderer;
27 | import javax.swing.tree.DefaultTreeModel;
28 | import javax.swing.tree.TreePath;
29 | import java.awt.*;
30 | import java.util.*;
31 | import java.util.List;
32 | import java.util.stream.Collectors;
33 |
34 | /**
35 | * The tree on the left side
36 | */
37 | public class PEComponentTree extends JPanel {
38 |
39 | private static final Logger LOGGER = LogManager.getLogger();
40 | private static final String DOS_STUB_TEXT = "MS DOS Stub";
41 | private static final String COFF_FILE_HEADER_TEXT = "COFF File Header";
42 | private static final String OPTIONAL_HEADER_TEXT = "Optional Header";
43 | private static final String STANDARD_FIELDS_TEXT = "Standard Fields";
44 | private static final String WINDOWS_FIELDS_TEXT = "Windows Fields";
45 | private static final String DATA_DIRECTORY_TEXT = "Data Directory";
46 | private static final String SECTION_TABLE_TEXT = "Section Table";
47 | private static final String PE_HEADERS_TEXT = "PE Headers";
48 | private static final String OVERLAY_TEXT = "Overlay";
49 | private static final String RICH_TEXT = "Rich Header";
50 | private static final String MANIFEST_TEXT = "Manifests";
51 | private static final String RESOURCES_TEXT = "Resources";
52 | private static final String RT_STRING_TEXT = "String Table";
53 | private static final String VERSION_INFO_TEXT = "Version Info";
54 | private static final String IMPORTS_TEXT = "Imports";
55 |
56 | private static final String DELAY_LOAD_IMPORTS_TEXT = "Delay Load Imports";
57 | private static final String BOUND_IMPORTS_TEXT = "Bound Imports";
58 | private static final String EXPORTS_TEXT = "Exports";
59 | private static final String DEBUG_TEXT = "Debug";
60 | private static final String ANOMALY_TEXT = "Anomalies";
61 | private static final String HASHES_TEXT = "Hashes";
62 | private static final String VISUALIZATION_TEXT = "Visualization";
63 | private static final String PE_FORMAT_TEXT = "PE Format";
64 | private static final String ICONS_TEXT = "Icons";
65 | private static final String SIGNATURES_TEXT = "Signatures";
66 | private static final String DOT_NET_TEXT = ".NET Headers";
67 | private static final String DOT_NET_METADATA_ROOT_TEXT = "Metadata Root";
68 |
69 | private static final String DOT_NET_STREAM_HEADERS_TEXT = "Stream Headers";
70 |
71 | private static final String DOT_NET_OPTIMIZED_STREAM_TEXT = "#~";
72 |
73 | private List clrTableNames = new ArrayList<>();
74 |
75 | private final PEDetailsPanel peDetailsPanel;
76 | private FullPEData peData = null;
77 | private JTree peTree;
78 |
79 | public PEComponentTree(PEDetailsPanel peDetailsPanel) {
80 | this.peDetailsPanel = peDetailsPanel;
81 | initTree();
82 | }
83 |
84 | public void setPeData(FullPEData peData) {
85 | this.peData = peData;
86 | updateTree();
87 | }
88 |
89 | private void updateTree() {
90 | LOGGER.debug("Updating tree");
91 | DefaultMutableTreeNode root = (DefaultMutableTreeNode) peTree.getModel().getRoot();
92 | root.removeAllChildren();
93 | // create the child nodes
94 | // PE related
95 | // DOS/PE Headers
96 | DefaultMutableTreeNode pe = new DefaultMutableTreeNode(PE_FORMAT_TEXT);
97 | DefaultMutableTreeNode dosStub = new DefaultMutableTreeNode(DOS_STUB_TEXT);
98 | DefaultMutableTreeNode rich = new DefaultMutableTreeNode(RICH_TEXT);
99 | DefaultMutableTreeNode coff = new DefaultMutableTreeNode(COFF_FILE_HEADER_TEXT);
100 | DefaultMutableTreeNode optional = new DefaultMutableTreeNode(OPTIONAL_HEADER_TEXT);
101 | DefaultMutableTreeNode sections = new DefaultMutableTreeNode(SECTION_TABLE_TEXT);
102 | DefaultMutableTreeNode overlay = new DefaultMutableTreeNode(OVERLAY_TEXT);
103 | // OptionalHeader
104 | DefaultMutableTreeNode standard = new DefaultMutableTreeNode(STANDARD_FIELDS_TEXT);
105 | DefaultMutableTreeNode windows = new DefaultMutableTreeNode(WINDOWS_FIELDS_TEXT);
106 | DefaultMutableTreeNode datadir = new DefaultMutableTreeNode(DATA_DIRECTORY_TEXT);
107 | // Resources
108 | DefaultMutableTreeNode resources = new DefaultMutableTreeNode(RESOURCES_TEXT);
109 | DefaultMutableTreeNode manifest = new DefaultMutableTreeNode(MANIFEST_TEXT);
110 | DefaultMutableTreeNode version = new DefaultMutableTreeNode(VERSION_INFO_TEXT);
111 | DefaultMutableTreeNode icons = new DefaultMutableTreeNode(ICONS_TEXT);
112 | DefaultMutableTreeNode rtstrings = new DefaultMutableTreeNode(RT_STRING_TEXT);
113 | // Data directories
114 | DefaultMutableTreeNode imports = new DefaultMutableTreeNode(IMPORTS_TEXT);
115 | DefaultMutableTreeNode delayLoad = new DefaultMutableTreeNode(DELAY_LOAD_IMPORTS_TEXT);
116 | DefaultMutableTreeNode bound = new DefaultMutableTreeNode(BOUND_IMPORTS_TEXT);
117 | DefaultMutableTreeNode exports = new DefaultMutableTreeNode(EXPORTS_TEXT);
118 | DefaultMutableTreeNode debug = new DefaultMutableTreeNode(DEBUG_TEXT);
119 |
120 | // .NET related
121 | DefaultMutableTreeNode dotNet = new DefaultMutableTreeNode(DOT_NET_TEXT);
122 | DefaultMutableTreeNode dotNetRoot = new DefaultMutableTreeNode(DOT_NET_METADATA_ROOT_TEXT);
123 | DefaultMutableTreeNode dotNetStreams = new DefaultMutableTreeNode(DOT_NET_STREAM_HEADERS_TEXT);
124 | DefaultMutableTreeNode dotNetOptStream = new DefaultMutableTreeNode(DOT_NET_OPTIMIZED_STREAM_TEXT);
125 |
126 | // Non-PE
127 | DefaultMutableTreeNode anomaly = new DefaultMutableTreeNode(ANOMALY_TEXT);
128 | DefaultMutableTreeNode hashes = new DefaultMutableTreeNode(HASHES_TEXT);
129 | DefaultMutableTreeNode vis = new DefaultMutableTreeNode(VISUALIZATION_TEXT);
130 | DefaultMutableTreeNode signatures = new DefaultMutableTreeNode(SIGNATURES_TEXT);
131 |
132 | // adding the sub nodes for optional header
133 | optional.add(standard);
134 | optional.add(windows);
135 | optional.add(datadir);
136 |
137 | // add the child nodes to the root node
138 | root.add(pe);
139 | pe.add(dosStub);
140 | if (peData.getPeData().maybeGetRichHeader().isPresent()) {
141 | pe.add(rich);
142 | }
143 | pe.add(coff);
144 | pe.add(optional);
145 | pe.add(sections);
146 | if (peData.hasResources()) {
147 | pe.add(resources);
148 | if (peData.hasManifest()) {
149 | resources.add(manifest);
150 | }
151 | if(peData.hasVersionInfo()) {
152 | resources.add(version);
153 | }
154 | if(peData.hasIcons()){
155 | resources.add(icons);
156 | }
157 | if(peData.hasRTStrings()) {
158 | resources.add(rtstrings);
159 | }
160 | }
161 |
162 | if(peData.hasImports()){
163 | pe.add(imports);
164 | }
165 |
166 | if(peData.hasDelayLoadImports()){
167 | pe.add(delayLoad);
168 | }
169 |
170 | if(peData.hasBoundImportEntries()) {
171 | pe.add(bound);
172 | }
173 |
174 | if(peData.hasExports()){
175 | pe.add(exports);
176 | }
177 |
178 | if(peData.hasDebugInfo()) {
179 | pe.add(debug);
180 | }
181 |
182 | if (peData.overlayExists()) {
183 | pe.add(overlay);
184 | LOGGER.debug("Overlay added to root node of tree");
185 | }
186 |
187 | if(peData.isDotNet()) {
188 | root.add(dotNet);
189 | dotNet.add(dotNetRoot);
190 | dotNet.add(dotNetStreams);
191 | if(peData.hasOptimizedStream()) {
192 | dotNetStreams.add(dotNetOptStream);
193 | this.clrTableNames = peData.getClrTables().keySet().stream().sorted().collect(Collectors.toList());
194 | for (String name : clrTableNames) {
195 | DefaultMutableTreeNode node = new DefaultMutableTreeNode(name);
196 | dotNetOptStream.add(node);
197 | }
198 | }
199 | }
200 |
201 | root.add(anomaly);
202 | root.add(hashes);
203 | root.add(vis);
204 | root.add(signatures);
205 |
206 | // no root
207 | peTree.setRootVisible(false);
208 | // reload the tree model to actually show the update
209 | DefaultTreeModel model = (DefaultTreeModel) peTree.getModel();
210 | model.reload();
211 | // expand the tree per default except for .NET CLR tables
212 | setTreeExpandedState(peTree, true);
213 | setNodeExpandedState(peTree, dotNetOptStream, false);
214 | }
215 |
216 | // this method is from https://www.logicbig.com/tutorials/java-swing/jtree-expand-collapse-all-nodes.html
217 | private static void setTreeExpandedState(JTree tree, boolean expanded) {
218 | DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot();
219 | setNodeExpandedState(tree, node, expanded);
220 | }
221 |
222 | // this method is from https://www.logicbig.com/tutorials/java-swing/jtree-expand-collapse-all-nodes.html
223 | private static void setNodeExpandedState(JTree tree, DefaultMutableTreeNode node, boolean expanded) {
224 | List list = Collections.list(node.children());
225 | for (DefaultMutableTreeNode treeNode : list) {
226 | setNodeExpandedState(tree, treeNode, expanded);
227 | }
228 | if (!expanded && node.isRoot()) {
229 | return;
230 | }
231 | TreePath path = new TreePath(node.getPath());
232 | if (expanded) {
233 | tree.expandPath(path);
234 | } else {
235 | tree.collapsePath(path);
236 | }
237 | }
238 |
239 | public void setSelectionRow(int i) {
240 | peTree.setSelectionRow(i);
241 | }
242 |
243 | private void initTree() {
244 | //create the root node
245 | DefaultMutableTreeNode root = new DefaultMutableTreeNode("PE Format");
246 |
247 | //create the tree by passing in the root node
248 | this.peTree = new JTree(root);
249 |
250 | DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) peTree.getCellRenderer();
251 | Icon leafIcon = new ImageIcon(getClass().getResource("/document-red-icon.png"));
252 | Icon openIcon = new ImageIcon(getClass().getResource("/Places-folder-red-icon.png"));
253 | Icon closeIcon = new ImageIcon(getClass().getResource("/folder-red-icon.png"));
254 | renderer.setLeafIcon(leafIcon);
255 | renderer.setOpenIcon(openIcon);
256 | renderer.setOpenIcon(closeIcon);
257 |
258 | // set scrollbar
259 | JScrollPane scrollPane = new JScrollPane(peTree);
260 | this.setLayout(new BorderLayout());
261 | this.add(scrollPane, BorderLayout.CENTER);
262 |
263 | peTree.setRootVisible(false);
264 | peTree.addTreeSelectionListener(e -> selectionChanged(e.getNewLeadSelectionPath()));
265 | this.setVisible(true);
266 | }
267 |
268 | private void selectionChanged(TreePath path) {
269 | if (path == null)
270 | return; // this happens when a selected node was removed, e.g., new file with no overlay loaded
271 | String node = path.getLastPathComponent().toString();
272 | LOGGER.debug("Tree selection changed to " + node);
273 | switch (node) {
274 | case DOS_STUB_TEXT:
275 | peDetailsPanel.showDosStub();
276 | return;
277 | case RICH_TEXT:
278 | peDetailsPanel.showRichHeader();
279 | return;
280 | case COFF_FILE_HEADER_TEXT:
281 | peDetailsPanel.showCoffFileHeader();
282 | return;
283 | case OPTIONAL_HEADER_TEXT:
284 | peDetailsPanel.showOptionalHeader();
285 | return;
286 | case STANDARD_FIELDS_TEXT:
287 | peDetailsPanel.showStandardFieldsTable();
288 | return;
289 | case WINDOWS_FIELDS_TEXT:
290 | peDetailsPanel.showWindowsFieldsTable();
291 | return;
292 | case DATA_DIRECTORY_TEXT:
293 | peDetailsPanel.showDataDirectoryTable();
294 | return;
295 | case SECTION_TABLE_TEXT:
296 | peDetailsPanel.showSectionTable();
297 | return;
298 | case PE_HEADERS_TEXT:
299 | peDetailsPanel.showPEHeaders();
300 | return;
301 | case OVERLAY_TEXT:
302 | peDetailsPanel.showOverlay();
303 | return;
304 | case MANIFEST_TEXT:
305 | peDetailsPanel.showManifests();
306 | return;
307 | case RESOURCES_TEXT:
308 | peDetailsPanel.showResources();
309 | return;
310 | case VERSION_INFO_TEXT:
311 | peDetailsPanel.showVersionInfo();
312 | return;
313 | case IMPORTS_TEXT:
314 | peDetailsPanel.showImports();
315 | return;
316 | case DELAY_LOAD_IMPORTS_TEXT:
317 | peDetailsPanel.showDelayLoadImports();
318 | return;
319 | case BOUND_IMPORTS_TEXT:
320 | peDetailsPanel.showBoundImports();
321 | return;
322 | case EXPORTS_TEXT:
323 | peDetailsPanel.showExports();
324 | return;
325 | case DEBUG_TEXT:
326 | peDetailsPanel.showDebugInfo();
327 | return;
328 | case ANOMALY_TEXT:
329 | peDetailsPanel.showAnomalies();
330 | return;
331 | case HASHES_TEXT:
332 | peDetailsPanel.showHashes();
333 | return;
334 | case VISUALIZATION_TEXT:
335 | peDetailsPanel.showVisualization();
336 | return;
337 | case PE_FORMAT_TEXT:
338 | peDetailsPanel.showPEFormat();
339 | return;
340 | case ICONS_TEXT:
341 | peDetailsPanel.showIcons();
342 | return;
343 | case SIGNATURES_TEXT:
344 | peDetailsPanel.showSignatures();
345 | return;
346 | case RT_STRING_TEXT:
347 | peDetailsPanel.showRTStrings();
348 | return;
349 | case DOT_NET_METADATA_ROOT_TEXT:
350 | peDetailsPanel.showDotNetMetadataRoot();
351 | return;
352 | case DOT_NET_STREAM_HEADERS_TEXT:
353 | peDetailsPanel.showDotNetStreamHeaders();
354 | return;
355 | case DOT_NET_OPTIMIZED_STREAM_TEXT:
356 | peDetailsPanel.showOptimizedStream();
357 | return;
358 | }
359 | for(String name : clrTableNames){
360 | if(node.equals(name)) {
361 | peDetailsPanel.showClrTable(name);
362 | return;
363 | }
364 | }
365 | }
366 |
367 | public void refreshSelection() {
368 | TreePath[] paths = peTree.getSelectionModel().getSelectionPaths();
369 | if(paths.length > 0) {
370 | // trigger selection change on same element to refresh the view
371 | selectionChanged(paths[0]);
372 | }
373 | }
374 | }
375 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/PEFieldsTable.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui;
19 |
20 | import com.github.struppigel.gui.pedetails.FileContentPreviewPanel;
21 | import org.apache.logging.log4j.LogManager;
22 | import org.apache.logging.log4j.Logger;
23 |
24 | import javax.swing.*;
25 | import javax.swing.table.DefaultTableCellRenderer;
26 | import javax.swing.table.DefaultTableModel;
27 | import java.awt.*;
28 | import java.util.Vector;
29 |
30 | public class PEFieldsTable extends JTable {
31 |
32 | private static final Logger LOGGER = LogManager.getLogger();
33 | private final boolean enableHex;
34 | private int previewOffsetColumn;
35 |
36 | public PEFieldsTable(boolean enableHex) {
37 | this.enableHex = enableHex;
38 | initTable();
39 | }
40 |
41 | public void setPreviewOffsetColumn(int column, FileContentPreviewPanel previewPanel) {
42 | this.previewOffsetColumn = column;
43 | initOffsetListener(this, previewPanel);
44 | }
45 |
46 | private void initTable() {
47 | // set PETableModel for proper sorting of integers and longs
48 | DefaultTableModel model = new PETableModel();
49 | setModel(model);
50 |
51 | // show long and int as hexadecimal string
52 | setDefaultRenderer(Long.class, new HexValueRenderer(enableHex));
53 | setDefaultRenderer(Integer.class, new HexValueRenderer(enableHex));
54 |
55 | setPreferredScrollableViewportSize(new Dimension(500, 70));
56 | setFillsViewportHeight(true);
57 | setAutoCreateRowSorter(true);
58 | setDefaultEditor(Object.class, null); // make not editable
59 | }
60 |
61 | private void initOffsetListener(JTable table, FileContentPreviewPanel previewPanel) {
62 | ListSelectionModel model = table.getSelectionModel();
63 | model.addListSelectionListener(e -> {
64 | Long offset = getOffsetForSelectedRow(table);
65 | if(offset == null) return;
66 | LOGGER.info(offset + " offset selected");
67 | previewPanel.showContentAtOffset(offset);
68 | });
69 | }
70 |
71 | private Long getOffsetForSelectedRow(JTable table) {
72 | DefaultTableModel model = (DefaultTableModel) table.getModel();
73 | int row = getSelectedRow(table);
74 | if (row == -1) return null;
75 | Vector vRow = (Vector) model.getDataVector().elementAt(row);
76 | Object offset = vRow.elementAt(previewOffsetColumn);
77 | return (Long) offset;
78 | }
79 |
80 | /**
81 | * Return a vector of the data in the currently selected row. Returns null if no row selected
82 | *
83 | * @param table
84 | * @return vector of the data in the currently selected row or null if nothing selected
85 | */
86 | private int getSelectedRow(JTable table) {
87 | int rowIndex = table.getSelectedRow();
88 | if (rowIndex >= 0) {
89 | return table.convertRowIndexToModel(rowIndex);
90 |
91 | }
92 | return -1;
93 | }
94 |
95 | public static class PETableModel extends DefaultTableModel {
96 |
97 | @Override
98 | public Class> getColumnClass(int columnIndex) {
99 | if (this.getColumnCount() < columnIndex || this.getRowCount() == 0) {
100 | return Object.class;
101 | }
102 | Class clazz = getValueAt(0, columnIndex).getClass();
103 | return clazz;
104 | }
105 | }
106 |
107 | public static class HexValueRenderer extends DefaultTableCellRenderer {
108 |
109 | private boolean enableHex;
110 |
111 | public HexValueRenderer(boolean enableHex){
112 | this.enableHex = enableHex;
113 | }
114 | @Override
115 | public void setValue(Object value){
116 | if(value == null) {
117 | return;
118 | }
119 | if(value.getClass() == Long.class) {
120 | Long lvalue = (Long) value;
121 | if(enableHex) {
122 | setText(toHex(lvalue));
123 | } else {
124 | setText(lvalue.toString());
125 | }
126 | }
127 | if(value.getClass() == Integer.class) {
128 | Integer ivalue = (Integer) value;
129 | if(enableHex) {
130 | setText(toHex(ivalue));
131 | } else {
132 | setText(ivalue.toString());
133 | }
134 | }
135 | }
136 | }
137 |
138 | private static String toHex(Long num) {
139 | return "0x" + Long.toHexString(num);
140 | }
141 |
142 | private static String toHex(Integer num) {
143 | return "0x" + Integer.toHexString(num);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/Starter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui;
19 |
20 | import com.github.struppigel.settings.LookAndFeelSetting;
21 | import com.github.struppigel.settings.PortexSettings;
22 | import com.github.struppigel.settings.PortexSettingsKey;
23 | import org.apache.logging.log4j.LogManager;
24 | import org.apache.logging.log4j.Logger;
25 |
26 | import javax.swing.*;
27 | import javax.swing.UIManager.LookAndFeelInfo;
28 | import java.awt.*;
29 |
30 | /**
31 | * Sets the look and feel and starts the GUI
32 | */
33 | public class Starter {
34 | private static final Logger LOGGER = LogManager.getLogger();
35 |
36 | public static void main(String[] args) {
37 |
38 | LOGGER.debug("starting program");
39 | PortexSettings s = new PortexSettings();
40 | if(s.valueEquals(PortexSettingsKey.LOOK_AND_FEEL, LookAndFeelSetting.PORTEX.toString())) {
41 | setPortexLookAndFeel();
42 | } else {
43 | setSystemLookAndFeel();
44 | }
45 | initMainFrame(s);
46 | }
47 |
48 | private static void initMainFrame(PortexSettings s) {
49 | SwingUtilities.invokeLater(() -> new MainFrame(s));
50 | }
51 |
52 | private static void setSystemLookAndFeel() {
53 | try {
54 | // Set System L&F
55 | UIManager.setLookAndFeel(
56 | UIManager.getSystemLookAndFeelClassName());
57 | UIManager.put("Table.alternateRowColor", new Color(240,240,240));
58 | }
59 | catch (ClassNotFoundException | InstantiationException
60 | | IllegalAccessException
61 | | UnsupportedLookAndFeelException e) {
62 | LOGGER.error(e.getMessage());
63 | }
64 | }
65 |
66 | private static void setPortexLookAndFeel() {
67 | UIManager.put("nimbusBase", new Color(15, 0, 0));
68 | UIManager.put("nimbusBlueGrey", new Color(170, 0, 0));
69 | UIManager.put("control", Color.black);
70 |
71 | // UIManager.put("ToggleButton.disabled", Color.yellow);
72 | //UIManager.put("ToggleButton.foreground", Color.yellow);
73 | //UIManager.put("ToolBar.opaque", true);
74 | //UIManager.put("ToolBar.background", new Color(100, 0, 0));
75 | //UIManager.put("ToolBar.disabled", Color.green);
76 | //UIManager.put("ToolBar.foreground", Color.red);
77 |
78 | UIManager.put("text", Color.white);
79 |
80 | UIManager.put("nimbusSelectionBackground", Color.gray);
81 | UIManager.put("nimbusSelectedText", Color.white);
82 | UIManager.put("textHighlight", Color.lightGray);
83 | UIManager.put("nimbusFocus", new Color(0xBA3A1E));
84 | UIManager.put("nimbusSelection", new Color(170, 0, 0));
85 | UIManager.put("textBackground", Color.darkGray);
86 | UIManager.put("nimbusLightBackground", Color.black);
87 | UIManager.put("Table.alternateRowColor", new Color(20,20,20));
88 | /*
89 | UIManager.put("ToolBar.background", Color.blue);
90 | UIManager.put("ToolBar.foreground", Color.blue);
91 | UIManager.put("ToolBar.disabled", Color.blue);
92 | UIManager.put("ToolBar.opaque", false);
93 | */
94 |
95 | for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
96 | if ("Nimbus".equals(info.getName())) {
97 | try {
98 | UIManager.setLookAndFeel(info.getClassName());
99 | } catch (ClassNotFoundException | InstantiationException
100 | | IllegalAccessException
101 | | UnsupportedLookAndFeelException e) {
102 | LOGGER.error(e.getMessage());
103 | }
104 | break;
105 | }
106 | }
107 | }
108 |
109 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/VisualizerPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui;
19 |
20 | import com.github.struppigel.tools.visualizer.ColorableItem;
21 | import com.github.struppigel.tools.visualizer.ImageUtil;
22 | import com.github.struppigel.tools.visualizer.Visualizer;
23 | import com.github.struppigel.tools.visualizer.VisualizerBuilder;
24 | import com.github.struppigel.gui.utils.WorkerKiller;
25 | import org.apache.logging.log4j.LogManager;
26 | import org.apache.logging.log4j.Logger;
27 |
28 | import javax.swing.*;
29 | import java.awt.*;
30 | import java.awt.event.ActionEvent;
31 | import java.awt.event.ActionListener;
32 | import java.awt.event.ComponentAdapter;
33 | import java.awt.event.ComponentEvent;
34 | import java.awt.image.BufferedImage;
35 | import java.io.File;
36 | import java.io.IOException;
37 | import java.util.concurrent.ExecutionException;
38 |
39 | /**
40 | * Panel with byteplot, entropy and PE structure image
41 | */
42 | public class VisualizerPanel extends JPanel {
43 |
44 | private static final Logger LOGGER = LogManager.getLogger();
45 |
46 | private javax.swing.Timer waitingTimer;
47 |
48 | private final JLabel visLabel = new JLabel();
49 | private File pefile;
50 |
51 | private final static int RESIZE_DELAY = 1000;
52 | private JProgressBar scanRunningBar = new JProgressBar();
53 |
54 | private boolean enableLegend = false;
55 | private boolean enableByteplot = true;
56 | private boolean enableEntropy = true;
57 |
58 | private int imageWidth = 50;
59 | private BufferedImage image;
60 |
61 | public VisualizerPanel() {
62 | super();
63 | initPanel();
64 | }
65 |
66 | public VisualizerPanel(boolean enableLegend, boolean enableByteplot, boolean enableEntropy, int imageWidth) {
67 | super();
68 | this.enableByteplot = enableByteplot;
69 | this.enableEntropy = enableEntropy;
70 | this.enableLegend = enableLegend;
71 | this.imageWidth = imageWidth;
72 | initPanel();
73 | }
74 |
75 | private void initPanel(){
76 | this.removeAll();
77 | setLayout(new BorderLayout());
78 | add(visLabel, BorderLayout.CENTER);
79 | addComponentListener(new ResizeListener());
80 |
81 | /*JToolBar toolBar = new JToolBar();
82 | ImageIcon ico = new ImageIcon(getClass().getResource("/science-icon.png"));
83 | toolBar.add(new JToggleButton(ico));
84 |
85 | add(toolBar, BorderLayout.PAGE_START);*/
86 | }
87 |
88 | public void visualizePE(File pefile) throws IOException {
89 | this.pefile = pefile;
90 | SwingWorker worker = new VisualizerWorker(getHeight(), imageWidth, pefile, enableEntropy, enableByteplot, enableLegend);
91 | WorkerKiller.getInstance().addWorker(worker);
92 | worker.execute();
93 | initProgressBar();
94 | }
95 |
96 | private void initProgressBar() {
97 | this.removeAll();
98 | this.add(scanRunningBar);
99 | this.setLayout(new FlowLayout());
100 | scanRunningBar.setIndeterminate(true);
101 | scanRunningBar.setVisible(true);
102 | this.repaint();
103 | }
104 |
105 | public BufferedImage getImage() {
106 | return image;
107 | }
108 |
109 | private class VisualizerWorker extends SwingWorker {
110 |
111 | private final int maxHeight;
112 | private final File file;
113 | private final boolean showEntropy;
114 | private final boolean showByteplot;
115 | private final boolean showLegend;
116 | private final int width;
117 |
118 | private int pixelSize = 4;
119 |
120 | public VisualizerWorker(int height, int width, File file, boolean showEntropy, boolean showByteplot, boolean showLegend) {
121 | this.maxHeight = height;
122 | this.width = width;
123 | this.file = file;
124 | this.showEntropy = showEntropy;
125 | this.showByteplot = showByteplot;
126 | this.showLegend = showLegend;
127 | }
128 |
129 | private int height(int nrBytes) {
130 | double nrOfPixels = file.length() / (double) nrBytes;
131 | double pixelsPerRow = width / (double) pixelSize;
132 | double pixelsPerCol = nrOfPixels / pixelsPerRow;
133 | return (int) Math.ceil(pixelsPerCol * pixelSize);
134 | }
135 |
136 | @Override
137 | protected BufferedImage doInBackground() throws Exception {
138 | if(pefile == null) return null;
139 | // bps
140 | int res = 1;
141 | while(height(res) > maxHeight) res *= 2;
142 | int bytesPerPixel = res;
143 |
144 | Visualizer visualizer = new VisualizerBuilder()
145 | .setFileWidth(width)
146 | .setPixelSize(pixelSize)
147 | .setBytesPerPixel(bytesPerPixel, file.length())
148 | .setColor(ColorableItem.ENTROPY, Color.cyan)
149 | .build();
150 |
151 | BufferedImage peImage = visualizer.createImage(file);
152 |
153 | if(showEntropy){
154 | BufferedImage entropyImg = visualizer.createEntropyImage(file);
155 | peImage = ImageUtil.appendImages(entropyImg, peImage);
156 | }
157 | if(showByteplot) {
158 | int bytePlotPixelSize = (width * height(bytesPerPixel) > file.length()) ? pixelSize : 1;
159 | Visualizer vi2 = new VisualizerBuilder() // more fine grained bytePlot, hence new visualizer
160 | .setPixelSize(bytePlotPixelSize)
161 | .setFileWidth(width)
162 | .setHeight(height(bytesPerPixel))
163 | .build();
164 | BufferedImage bytePlot = vi2.createBytePlot(file);
165 | peImage = ImageUtil.appendImages(bytePlot, peImage);
166 | }
167 | if(showLegend) {
168 | BufferedImage legendImage = visualizer.createLegendImage(showByteplot, showEntropy, true);
169 | peImage = ImageUtil.appendImages(peImage, legendImage);
170 | }
171 | return peImage;
172 | }
173 |
174 | @Override
175 | protected void done() {
176 | if(isCancelled()){return;}
177 | try {
178 | image = get();
179 | if(image == null) return;
180 | initPanel();
181 | visLabel.setIcon(new ImageIcon(image));
182 | visLabel.repaint();
183 | } catch (InterruptedException e) {
184 | String message = "Problem with visualizer! Reason: " + e.getMessage();
185 | LOGGER.error(message);
186 | e.printStackTrace(); // no dialog necessary
187 | } catch (ExecutionException e) {
188 | LOGGER.error(e);
189 | e.printStackTrace();
190 | }
191 | }
192 | }
193 |
194 | private class ResizeListener extends ComponentAdapter implements ActionListener {
195 | public void componentResized(ComponentEvent e) {
196 | if(waitingTimer == null) {
197 | try {
198 | waitingTimer = new Timer(RESIZE_DELAY, this);
199 | waitingTimer.start();
200 | } catch (Exception ex) {
201 | LOGGER.error("Visualization update failed " + ex.getMessage());
202 | throw new RuntimeException(ex);
203 | }
204 | } else {
205 | waitingTimer.restart();
206 | }
207 | }
208 |
209 | public void actionPerformed(ActionEvent ae)
210 | {
211 | /* Timer finished? */
212 | if (ae.getSource()==waitingTimer)
213 | {
214 | /* Stop timer */
215 | waitingTimer.stop();
216 | waitingTimer = null;
217 | /* Resize */
218 | applyResize();
219 | }
220 | }
221 |
222 | private void applyResize() {
223 | SwingWorker worker = new VisualizerWorker(getHeight(), imageWidth, pefile, enableEntropy, enableByteplot, enableLegend);
224 | WorkerKiller.getInstance().addWorker(worker);
225 | worker.execute();
226 | }
227 | }
228 |
229 | public void setEnableLegend(boolean enableLegend) {
230 | this.enableLegend = enableLegend;
231 | }
232 |
233 | public void setEnableByteplot(boolean enableByteplot) {
234 | this.enableByteplot = enableByteplot;
235 | }
236 |
237 | public void setEnableEntropy(boolean enableEntropy) {
238 | this.enableEntropy = enableEntropy;
239 | }
240 |
241 | public void setImageWidth(int imageWidth) {
242 | this.imageWidth = imageWidth;
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/FileContentPreviewPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails;
19 |
20 | import com.github.struppigel.parser.IOUtil;
21 | import com.github.struppigel.gui.FullPEData;
22 | import org.apache.logging.log4j.LogManager;
23 | import org.apache.logging.log4j.Logger;
24 |
25 | import javax.swing.*;
26 | import java.awt.*;
27 | import java.io.IOException;
28 | import java.io.RandomAccessFile;
29 | import java.util.concurrent.ExecutionException;
30 |
31 | public class FileContentPreviewPanel extends JPanel {
32 |
33 | private static final Logger LOGGER = LogManager.getLogger();
34 | private static final int PREVIEW_SIZE = 0x2000;
35 | private JTextArea contentDisplay = new JTextArea("Offset: 0");
36 |
37 | private FullPEData pedata;
38 | private boolean isHexEnabled = true;
39 |
40 | public FileContentPreviewPanel() {
41 | contentDisplay.setLineWrap(true);
42 | this.setLayout(new BorderLayout());
43 | this.add(contentDisplay, BorderLayout.CENTER);
44 | Font font = new Font("Courier New", Font.PLAIN, 12);
45 | contentDisplay.setFont(font);
46 | }
47 |
48 | public void setPeData(FullPEData data) {
49 | this.pedata = data;
50 | showContentAtOffset(0);
51 | }
52 |
53 | public void showContentAtOffset(long offset) {
54 | if (pedata == null) {return;}
55 |
56 | (new SwingWorker() {
57 |
58 | @Override
59 | protected String doInBackground() {
60 | byte[] content = prepareContentString(readContentAtOffset(offset));
61 | String contentStr = new String(content);
62 | return contentStr;
63 | }
64 |
65 | protected void done() {
66 | try {
67 | String contentStr = get();
68 | String offsetStr = isHexEnabled ? "Offset: 0x" + Long.toHexString(offset) : "Offset: " + offset;
69 | contentDisplay.setText(offsetStr + "\n" + contentStr);
70 | contentDisplay.repaint();
71 | } catch (InterruptedException | ExecutionException e) {
72 | LOGGER.error(e);
73 | }
74 | }
75 | }).execute();
76 | }
77 |
78 | private byte[] prepareContentString(byte[] arr) {
79 | for(int i = 0; i < arr.length; i++) {
80 | if(arr[i] < 32 || arr[i] == 127) {
81 | arr[i] = 0x2e; // '.'
82 | }
83 | }
84 | return arr;
85 | }
86 |
87 | private byte[] readContentAtOffset(long offset) {
88 | try (RandomAccessFile raf = new RandomAccessFile(pedata.getFile(), "r")) {
89 | return IOUtil.loadBytesSafely(offset, PREVIEW_SIZE, raf);
90 | } catch (IOException e) {
91 | LOGGER.error(e);
92 | }
93 | return new byte[0];
94 | }
95 |
96 | public void setHexEnabled(boolean hexEnabled) {
97 | this.isHexEnabled = hexEnabled;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/IconPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails;
19 |
20 | import com.github.struppigel.parser.PEData;
21 | import com.github.struppigel.parser.sections.rsrc.icon.IcoFile;
22 | import com.github.struppigel.parser.sections.rsrc.icon.IconParser;
23 | import com.github.struppigel.gui.FullPEData;
24 | import com.github.struppigel.gui.utils.WorkerKiller;
25 | import net.ifok.image.image4j.codec.ico.ICODecoder;
26 | import org.apache.logging.log4j.LogManager;
27 | import org.apache.logging.log4j.Logger;
28 |
29 | import javax.swing.*;
30 | import java.awt.*;
31 | import java.awt.image.BufferedImage;
32 | import java.io.IOException;
33 | import java.util.ArrayList;
34 | import java.util.List;
35 | import java.util.concurrent.ExecutionException;
36 |
37 | public class IconPanel extends JPanel {
38 | private static final Logger LOGGER = LogManager.getLogger();
39 | private FullPEData peData;
40 |
41 | private List icons;
42 |
43 | public void setPeData(FullPEData peData) {
44 | this.peData = peData;
45 | SwingWorker worker = new IconUpdateWorker(peData.getPeData());
46 | WorkerKiller.getInstance().addWorker(worker);
47 | worker.execute();
48 | }
49 |
50 | public List getIcons() {
51 | return icons;
52 | }
53 |
54 | private class IconUpdateWorker extends SwingWorker, Void> {
55 | private final PEData data;
56 |
57 | public IconUpdateWorker(PEData data) {
58 | this.data = data;
59 | }
60 |
61 | @Override
62 | protected List doInBackground() throws IOException {
63 | List icons = IconParser.extractIcons(data);
64 | List images = new ArrayList<>();
65 | for (IcoFile icon : icons) {
66 | try {
67 | List result = ICODecoder.read(icon.getInputStream());
68 | images.addAll(result);
69 | } catch (IOException e) {
70 | LOGGER.error(e);
71 | }
72 | }
73 | return images;
74 | }
75 |
76 | @Override
77 | protected void done() {
78 | if (isCancelled()) {
79 | return;
80 | }
81 | try {
82 | // get images
83 | icons = get();
84 |
85 | // init Swing components in the icon panel
86 | GridLayout grid = new GridLayout(0, 2);
87 | JPanel gridPanel = new JPanel(grid);
88 | //gridPanel.setPreferredSize(new Dimension(getWidth(), getHeight()));
89 | IconPanel.this.removeAll();
90 | IconPanel.this.add(gridPanel);
91 |
92 | // add all icons to the grid
93 | for (BufferedImage image : icons) {
94 | JLabel iconLabel = new JLabel(new ImageIcon(image));
95 | gridPanel.add(iconLabel);
96 | }
97 | } catch (InterruptedException | ExecutionException e) {
98 | e.printStackTrace();
99 | LOGGER.error(e);
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/SectionsTabbedPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails;
19 |
20 | import com.github.struppigel.parser.sections.*;
21 | import com.github.struppigel.gui.FullPEData;
22 | import com.github.struppigel.gui.PEFieldsTable;
23 | import org.apache.logging.log4j.LogManager;
24 | import org.apache.logging.log4j.Logger;
25 |
26 | import javax.swing.*;
27 | import javax.swing.table.DefaultTableModel;
28 | import java.awt.*;
29 | import java.util.ArrayList;
30 | import java.util.List;
31 | import java.util.stream.Collectors;
32 | import java.util.stream.Stream;
33 |
34 | import static com.github.struppigel.parser.sections.SectionHeaderKey.*;
35 | import static java.lang.Math.min;
36 |
37 | /**
38 | * There can be many sections, so this panel adds tabs at the top.
39 | */
40 | public class SectionsTabbedPanel extends JPanel {
41 |
42 | private static final Logger LOGGER = LogManager.getLogger();
43 | private FullPEData peData;
44 | private final List tables = new ArrayList<>();
45 | private final JTabbedPane tabbedPane = new JTabbedPane();
46 |
47 | private static final int SECTIONS_PER_TABLE = 4;
48 | private static final int TABLES_PER_TAB = 2;
49 | private static final int SECTION_NR_MAX = 200;
50 | private boolean hexEnabled = true;
51 |
52 | public SectionsTabbedPanel() {
53 | this.setLayout(new GridLayout(0,1));
54 | add(tabbedPane);
55 | }
56 |
57 | private void cleanUpTabsAndTables() {
58 | tabbedPane.removeAll();
59 | tables.clear();
60 | //tabs.clear(); // this will make everything non-working, but why?
61 | LOGGER.debug("Tabs and tables cleared");
62 | }
63 |
64 | public void setPeData(FullPEData peData){
65 | LOGGER.debug("PEData for tabbed Pane changed");
66 | this.peData = peData;
67 | initializeContent();
68 | }
69 |
70 | public void initializeContent() {
71 | LOGGER.debug("Init tabs for section tables");
72 | cleanUpTabsAndTables();
73 |
74 | SectionTable sectionTable = peData.getPeData().getSectionTable();
75 | List sections = sectionTable.getSectionHeaders();
76 | List tabs = initTabs(sections);
77 |
78 | if (peData == null) {
79 | LOGGER.error("PE Data is null!");
80 | return;
81 | }
82 | initTables(tabs, sections);
83 |
84 | LOGGER.debug("Section table shown");
85 | refreshPanel(tabs);
86 | }
87 |
88 | private void initTables(List tabs, List sections) {
89 | // we need that to calculate how many tabs and tables
90 | // init counters
91 | int tableCount = 0;
92 | int tabIndex = 0;
93 | // get first tab
94 | JPanel currTab = tabs.get(tabIndex);
95 | // a section group will contain SECTIONS_PER_TABLE sections max
96 | List sectionGroup = new ArrayList<>();
97 |
98 | // we want to add every section
99 | for (SectionHeader currSec : sections) {
100 | // obtain current section header and add to section group
101 | sectionGroup.add(currSec);
102 | // check if enough sections to create a table
103 | if (sectionGroup.size() == SECTIONS_PER_TABLE) {
104 | // create table with the sections and add table to current tab
105 | addSingleTableForSections(sectionGroup, currTab);
106 | // increment table counter
107 | tableCount++;
108 | // we added the sections to a table, so empty the list
109 | sectionGroup.clear();
110 | // check if we need to grab the next tab for the next table
111 | if (tableCount % TABLES_PER_TAB == 0) {
112 | // increment tab index (which is equal to tab count - 1)
113 | tabIndex++;
114 | // bounds check, in case we are already done with everything the index will be out of bounds
115 | if (tabIndex < tabs.size()) {
116 | currTab = tabs.get(tabIndex);
117 | }
118 | }
119 | }
120 | }
121 | // add remaining tables
122 | if(sectionGroup.size() > 0) {
123 | addSingleTableForSections(sectionGroup, currTab);
124 | tableCount++;
125 | }
126 | LOGGER.debug("Added " + tableCount + " tables and " + tabIndex + " tabs");
127 | }
128 |
129 | private List initTabs(List sections) {
130 | int secNr = min(sections.size(), SECTION_NR_MAX);
131 | int nrOfTabs = new Double(Math.ceil(secNr/(double)(TABLES_PER_TAB * SECTIONS_PER_TABLE))).intValue();
132 | LOGGER.debug("Number of tabs to create: " + nrOfTabs);
133 | if(nrOfTabs == 0){ nrOfTabs = 1;}
134 | List tabs = new ArrayList<>();
135 | for (int i = 0; i < nrOfTabs; i++) {
136 | JPanel tab = new JPanel();
137 | tab.setLayout(new GridLayout(0, 1));
138 | tabbedPane.addTab((i + 1) + "", tab);
139 | tabs.add(tab);
140 | }
141 | return tabs;
142 | }
143 |
144 | private void refreshPanel(List tabs) {
145 | LOGGER.debug("Refreshing tabs");
146 | for(JTable tbl : tables) {
147 | tbl.revalidate();
148 | tbl.repaint();
149 | }
150 | for(JPanel tab : tabs) {
151 | tab.revalidate();
152 | tab.repaint();
153 | }
154 | revalidate();
155 | repaint();
156 | tabbedPane.revalidate();
157 | tabbedPane.repaint();
158 | }
159 |
160 | private void addSingleTableForSections(List sections, JPanel tab) {
161 | LOGGER.info("Setting hex enabled to " + hexEnabled);
162 | JTable table = new PEFieldsTable(hexEnabled);
163 | DefaultTableModel model = new PEFieldsTable.PETableModel();
164 |
165 | createTableHeaderForSections(sections, model);
166 | createRowsForSections(sections, model);
167 |
168 | table.setModel(model);
169 | addTable(table, tab);
170 | }
171 |
172 | private void addTable(JTable table, JPanel tab) {
173 | tables.add(table);
174 | JScrollPane sPane = new JScrollPane(table);
175 | tab.add(sPane);
176 | }
177 |
178 | private void createRowsForSections(List sections, DefaultTableModel model) {
179 | // Section tables should only use string based sorting because there are mixed data types --> keep String[] type for rows
180 | List rows = new ArrayList<>();
181 | SectionLoader loader = new SectionLoader(peData.getPeData());
182 | boolean lowAlign = peData.getPeData().getOptionalHeader().isLowAlignmentMode();
183 |
184 | // collect entropy
185 | Stream entropyRow = sections.stream().map(s -> String.format("%1.2f", peData.getEntropyForSection(s.getNumber())));
186 | addStreamToRow(entropyRow, rows, "Entropy");
187 |
188 | // collect Pointer To Raw Data
189 | Stream ptrToRawRow = sections.stream().map(s -> toHexIfEnabled(s.get(SectionHeaderKey.POINTER_TO_RAW_DATA)));
190 | addStreamToRow(ptrToRawRow, rows, "Pointer To Raw Data");
191 |
192 | // collect aligned Pointer To Raw Data
193 |
194 | Stream alignedPtrRaw = sections.stream().map(s -> toHexIfEnabled(s.getAlignedPointerToRaw(lowAlign)));
195 | addStreamToRow(alignedPtrRaw, rows, "-> Aligned (act. start)");
196 |
197 | // collect Size of Raw Data
198 | Stream sizeRawRow = sections.stream().map(s -> toHexIfEnabled(s.get(SIZE_OF_RAW_DATA)));
199 | addStreamToRow(sizeRawRow, rows, "Size Of Raw Data");
200 |
201 | // collect actual read size
202 | Stream readSizeRow = sections.stream().map(s -> s.get(SIZE_OF_RAW_DATA) != loader.getReadSize(s) ? toHexIfEnabled(loader.getReadSize(s)) : "");
203 | addStreamToRow(readSizeRow, rows, "-> Actual Read Size");
204 |
205 | // collect Physical End
206 | Stream endRow = sections.stream().map(s -> toHexIfEnabled(loader.getReadSize(s) + s.getAlignedPointerToRaw(lowAlign)));
207 | addStreamToRow(endRow, rows, "-> Physical End");
208 |
209 | // collect VA
210 | Stream vaRow = sections.stream().map(s -> toHexIfEnabled(s.get(VIRTUAL_ADDRESS)));
211 | addStreamToRow(vaRow, rows, "Virtual Address");
212 |
213 | // collect alligned VA
214 | Stream vaAlignedRow = sections.stream().map(s -> s.get(VIRTUAL_ADDRESS) != s.getAlignedVirtualAddress(lowAlign) ? toHexIfEnabled(s.getAlignedVirtualAddress(lowAlign)) : "");
215 | addStreamToRow(vaAlignedRow, rows, "-> Aligned");
216 |
217 | // collect Virtual Size
218 | Stream vSizeRow = sections.stream().map(s -> toHexIfEnabled(s.get(VIRTUAL_SIZE)));
219 | addStreamToRow(vSizeRow, rows, "Virtual Size");
220 | // collect Actual Virtual Size
221 | Stream actSizeRow = sections.stream().map(s -> s.get(VIRTUAL_SIZE) != loader.getActualVirtSize(s) ? toHexIfEnabled(loader.getActualVirtSize(s)) : "");
222 | addStreamToRow(actSizeRow, rows, "-> Actual Virtual Size");
223 | // collect Virtual End
224 | Stream veRow = sections.stream().map(s -> toHexIfEnabled(s.getAlignedVirtualAddress(lowAlign) + loader.getActualVirtSize(s)));
225 | addStreamToRow(veRow, rows, "-> Virtual End");
226 | // collect Pointer To Relocations
227 | Stream relocRow = sections.stream().map(s -> toHexIfEnabled(s.get(POINTER_TO_RELOCATIONS)));
228 | addStreamToRow(relocRow, rows, "Pointer To Relocations");
229 | // collect Number Of Relocations
230 | Stream numreRow = sections.stream().map(s -> toHexIfEnabled(s.get(NUMBER_OF_RELOCATIONS)));
231 | addStreamToRow(numreRow, rows, "Number Of Relocations");
232 | // collect Pointer To Line Numbers
233 | Stream linRow = sections.stream().map(s -> toHexIfEnabled(s.get(POINTER_TO_LINE_NUMBERS)));
234 | addStreamToRow(linRow, rows, "Pointer To Line Numbers");
235 | // collect Number Of Line Numbers
236 | Stream numLinRow = sections.stream().map(s -> toHexIfEnabled(s.get(NUMBER_OF_LINE_NUMBERS)));
237 | addStreamToRow(numLinRow, rows, "Number Of Line Numbers");
238 |
239 | for(SectionCharacteristic ch : SectionCharacteristic.values()) {
240 | Stream secCharRow = sections.stream().map(s -> s.getCharacteristics().contains(ch) ? "x" : "");
241 | addStreamToRow(secCharRow, rows, ch.shortName());
242 | }
243 |
244 | // add all rows to model
245 | for(String[] row : rows) {
246 | model.addRow(row);
247 | }
248 | }
249 |
250 | private void addStreamToRow(Stream aStream, List rows, String title) {
251 | List list = aStream.collect(Collectors.toList());
252 | boolean hasContent = false;
253 | for(String l : list) {
254 | if (!l.equals("")) {
255 | hasContent = true;
256 | break;
257 | }
258 | }
259 | if(hasContent) {
260 | list.add(0, title);
261 | rows.add(list.toArray(new String[0]));
262 | }
263 | }
264 |
265 | private String toHexIfEnabled(Long num) {
266 | if(hexEnabled) {
267 | return "0x" + Long.toHexString(num);
268 | }
269 | return num.toString();
270 | }
271 |
272 | private void createTableHeaderForSections(List sections, DefaultTableModel model) {
273 | List names = sections.stream().map(h -> h.getNumber() + ". " + h.getName()).collect(Collectors.toList());
274 | names.add(0,"");
275 | String[] tableHeader = names.toArray(new String[0]);
276 | model.setColumnIdentifiers(tableHeader);
277 | }
278 |
279 | public void setHexEnabled(boolean hexEnabled) {
280 | this.hexEnabled = hexEnabled;
281 | if(peData == null) {return;}
282 | initializeContent();
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/TabbedPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2023 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails;
19 |
20 | import com.github.struppigel.parser.StandardField;
21 | import com.github.struppigel.gui.PEFieldsTable;
22 | import com.github.struppigel.gui.utils.TableContent;
23 | import org.apache.logging.log4j.LogManager;
24 | import org.apache.logging.log4j.Logger;
25 |
26 | import javax.swing.*;
27 | import javax.swing.table.DefaultTableModel;
28 | import java.awt.*;
29 | import java.util.ArrayList;
30 | import java.util.Arrays;
31 | import java.util.List;
32 |
33 | public class TabbedPanel extends JPanel {
34 |
35 | private static final Logger LOGGER = LogManager.getLogger();
36 | private final List tables = new ArrayList<>();
37 | private final JTabbedPane tabbedPane = new JTabbedPane();
38 | private final FileContentPreviewPanel previewPanel;
39 | private List tableHeader = new ArrayList<>();
40 | private List contents = new ArrayList<>();
41 | private boolean hexEnabled = true;
42 |
43 | public TabbedPanel(FileContentPreviewPanel previewPanel) {
44 | LOGGER.debug("Tabbed Panel constructor");
45 | this.previewPanel = previewPanel;
46 | this.setLayout(new GridLayout(0,1));
47 | add(tabbedPane);
48 | }
49 |
50 | public void setContent(List contents, String[] tableHeader) {
51 | this.contents = contents;
52 | this.tableHeader = Arrays.asList(tableHeader);
53 | initializeContent();
54 | }
55 |
56 | private void cleanUpTabsAndTables() {
57 | tabbedPane.removeAll();
58 | tables.clear();
59 | LOGGER.debug("Tabs and tables cleared");
60 | }
61 |
62 | public void initializeContent() {
63 | LOGGER.debug("Init tabs for tables");
64 | cleanUpTabsAndTables();
65 | List tabs = initTabsAndTables();
66 | refreshPanel(tabs);
67 | }
68 |
69 | private void refreshPanel(List tabs) {
70 | LOGGER.debug("Refreshing tabs");
71 | for(JTable tbl : tables) {
72 | tbl.revalidate();
73 | tbl.repaint();
74 | }
75 | for(JPanel tab : tabs) {
76 | tab.revalidate();
77 | tab.repaint();
78 | }
79 | revalidate();
80 | repaint();
81 | tabbedPane.revalidate();
82 | tabbedPane.repaint();
83 | }
84 |
85 | private String toHexIfEnabled(Long num) {
86 | if(hexEnabled) {
87 | return "0x" + Long.toHexString(num);
88 | }
89 | return num.toString();
90 | }
91 |
92 | public void setHexEnabled(boolean hexEnabled) {
93 | this.hexEnabled = hexEnabled;
94 | initializeContent();
95 | }
96 |
97 | private List initTabsAndTables() {
98 | List tabs = new ArrayList<>();
99 | for(TableContent content : contents) {
100 | String title = content.getTitle();
101 | JPanel tab = createTabWithTitle(title);
102 | tabs.add(tab);
103 | addSingleTableForContent(content, tab);
104 | }
105 | return tabs;
106 | }
107 |
108 | private JPanel createTabWithTitle(String title) {
109 | JPanel tab = new JPanel();
110 | tab.setLayout(new GridLayout(0, 1));
111 | tabbedPane.addTab(title, tab);
112 | return tab;
113 | }
114 |
115 | private void addSingleTableForContent(TableContent content, JPanel tab) {
116 | LOGGER.info("Setting hex enabled to " + hexEnabled);
117 | PEFieldsTable table = new PEFieldsTable(hexEnabled);
118 | DefaultTableModel model = new PEFieldsTable.PETableModel();
119 | table.setPreviewOffsetColumn(2, previewPanel);
120 | // add table header
121 | String[] header = tableHeader.toArray(new String[0]);
122 | model.setColumnIdentifiers(header);
123 | createRows(content, model);
124 |
125 | table.setModel(model);
126 | addTableAndDescription(table, tab, content.getDescription());
127 | }
128 | private void createRows(TableContent content, DefaultTableModel model) {
129 | for(StandardField field : content) {
130 | Object[] row = {field.getDescription(), field.getValue(), field.getOffset()};
131 | model.addRow(row);
132 | }
133 | }
134 |
135 | private void addTableAndDescription(JTable table, JPanel tab, String description) {
136 | tables.add(table);
137 | if(description.length() > 0) {
138 | tab.add(new JScrollPane(new JTextArea(description)));
139 | }
140 | JScrollPane sPane = new JScrollPane(table);
141 | tab.add(sPane);
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/EmptyRuleMatch.java:
--------------------------------------------------------------------------------
1 | package com.github.struppigel.gui.pedetails.signatures;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class EmptyRuleMatch implements RuleMatch {
7 |
8 | private static final String message = "no matches found";
9 | @Override
10 | public Object[] toSummaryRow() {
11 | Object[] row = {message,"",""};
12 | return row;
13 | }
14 |
15 | // {"Rule name" , "Pattern name", "Pattern content", "Offset", "Location"};
16 | @Override
17 | public List toPatternRows() {
18 | List row = new ArrayList<>();
19 | Object[] obj = {message, "","","",""};
20 | row.add(obj);
21 | return row;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/PEidRuleMatch.java:
--------------------------------------------------------------------------------
1 | package com.github.struppigel.gui.pedetails.signatures;
2 |
3 | import java.util.List;
4 | import java.util.stream.Collectors;
5 |
6 | public class PEidRuleMatch implements RuleMatch {
7 | final String ruleName;
8 | private final List patterns;
9 | private final String scanMode;
10 | private final String scannerName;
11 |
12 |
13 | public PEidRuleMatch(String ruleName, String scanMode, List patterns, String scannerName) {
14 | this.ruleName = ruleName;
15 | this.patterns = patterns;
16 | this.scanMode = scanMode;
17 | this.scannerName = scannerName;
18 | }
19 |
20 | // {"Source", "Match name", "Scan mode"};
21 | public Object[] toSummaryRow() {
22 | Object[] row = {scannerName, ruleName, scanMode};
23 | return row;
24 | }
25 | // {"Rule name", "Pattern", "Content", "Offset"}
26 | public List toPatternRows() {
27 | return patterns.stream().map(p -> p.toPatternRow(ruleName)).collect(Collectors.toList());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/PEidScanner.java:
--------------------------------------------------------------------------------
1 | package com.github.struppigel.gui.pedetails.signatures;
2 |
3 | import com.github.struppigel.parser.sections.rsrc.Resource;
4 | import com.github.struppigel.tools.Overlay;
5 | import com.github.struppigel.tools.sigscanner.FileTypeScanner;
6 | import com.github.struppigel.tools.sigscanner.SignatureScanner;
7 | import com.github.struppigel.gui.FullPEData;
8 | import org.apache.logging.log4j.LogManager;
9 | import org.apache.logging.log4j.Logger;
10 |
11 | import javax.swing.*;
12 | import java.io.IOException;
13 | import java.util.ArrayList;
14 | import java.util.HashSet;
15 | import java.util.List;
16 | import java.util.Set;
17 | import java.util.concurrent.ExecutionException;
18 | import java.util.stream.Collectors;
19 |
20 | public class PEidScanner extends SwingWorker, Void> {
21 | private static final Logger LOGGER = LogManager.getLogger();
22 | private final SignaturesPanel signaturesPanel;
23 | private final FullPEData pedata;
24 |
25 | public PEidScanner(SignaturesPanel signaturesPanel, FullPEData data) {
26 | this.signaturesPanel = signaturesPanel;
27 | this.pedata = data;
28 | }
29 |
30 | @Override
31 | protected List doInBackground() {
32 | List result = entryPointScan(); // epOnly
33 | result.addAll(overlayScan()); // Overlay
34 | result.addAll(resourceScan()); // Resource
35 | return result;
36 | }
37 |
38 | private List entryPointScan() {
39 | List result;
40 | List matches = SignatureScanner.newInstance().scanAll(pedata.getFile(), true);
41 | result = toPeidRuleMatches(toPatternMatches(matches), "Entry Point", "PEiD");
42 | return result;
43 | }
44 |
45 | private List overlayScan() {
46 | Overlay overlay = pedata.getOverlay();
47 | List result = new ArrayList<>();
48 | try {
49 | long offset = overlay.getOffset();
50 | List overlayMatches = new SignatureScanner(SignatureScanner.loadOverlaySigs()).scanAt(pedata.getFile(), offset);
51 | List oMatch = toPeidRuleMatches(toPatternMatches(overlayMatches), "Overlay", "Filetype");
52 | result.addAll(oMatch);
53 | } catch (IOException e) {
54 | LOGGER.error("something went wrong while scanning the overlay " + e);
55 | }
56 | return result;
57 | }
58 |
59 | private List resourceScan() {
60 | List resMatches = new ArrayList<>();
61 | for (Resource r : pedata.getResources()) {
62 | long resOffset = r.rawBytesLocation().from();
63 | List filetypes = FileTypeScanner.apply(pedata.getFile()).scanAt(resOffset);
64 | resMatches.addAll(toPatternMatches(filetypes));
65 | }
66 | return toPeidRuleMatches(resMatches, "Resource", "Filetype");
67 | }
68 |
69 |
70 | private List toPatternMatches(List matches){
71 | return matches.stream().map(m -> toPatternMatch(m)).collect(Collectors.toList());
72 | }
73 |
74 | private PatternMatch toPatternMatch(SignatureScanner.SignatureMatch m) {
75 | String rulename = m.signature().name();
76 | if (rulename.startsWith("[")) {
77 | rulename = rulename.substring(1, rulename.length() - 1);
78 | }
79 | String pattern = m.signature().signatureString();
80 | long offset = m.address();
81 | return new PatternMatch(offset, rulename, pattern);
82 | }
83 |
84 | private List toPeidRuleMatches(List patterns, String scanMode, String scannerName) {
85 | List result = new ArrayList<>();
86 | // create unique list of all rule names
87 | Set uniqueRulenames = new HashSet<>();
88 | for(PatternMatch pattern : patterns) {
89 | uniqueRulenames.add(pattern.patternName);
90 | }
91 | // for every unique rule name get all patterns
92 | for(String rule : uniqueRulenames) {
93 | List rulePatterns = patterns.stream().filter(p -> p.patternName.equals(rule)).collect(Collectors.toList());
94 | // make pattern name empty string because it looks weird to see the same value for rule name and pattern name
95 | rulePatterns = rulePatterns.stream().map(p -> new PatternMatch(p.offset, "", p.patternContent)).collect(Collectors.toList());
96 | // create a PEiD rule match out of these patterns
97 | result.add(new PEidRuleMatch(rule, scanMode, rulePatterns, scannerName));
98 | }
99 | return result;
100 | }
101 |
102 | @Override
103 | protected void done() {
104 | if(isCancelled()){return;}
105 | try {
106 | List matches = get();
107 | signaturesPanel.buildPEiDTables(get());
108 | } catch (InterruptedException | ExecutionException e) {
109 | e.printStackTrace();
110 | LOGGER.error(e);
111 | }
112 |
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/PatternMatch.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails.signatures;
19 |
20 | public class PatternMatch {
21 | final long offset;
22 | final String patternContent;
23 | String patternName;
24 | String location = "NaN";
25 |
26 | public PatternMatch(long offset, String patternName, String patternContent) {
27 | this.offset = offset;
28 | this.patternName = patternName;
29 | this.patternContent = patternContent;
30 | }
31 |
32 | // {"Rule name" , "Pattern name", "Pattern content", "Offset", "Location"};
33 | // TODO location cannot be implemented here because it needs to parse the whole PE again and this is only a getter
34 | public Object[] toPatternRow(String rulename) {
35 | Object[] row = {rulename, patternName, patternContent, offset, location};
36 | return row;
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/RuleMatch.java:
--------------------------------------------------------------------------------
1 | package com.github.struppigel.gui.pedetails.signatures;
2 |
3 | import java.util.List;
4 |
5 | public interface RuleMatch {
6 | public Object[] toSummaryRow();
7 | public List toPatternRows();
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/SignaturesPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails.signatures;
19 |
20 | import com.github.struppigel.gui.FullPEData;
21 | import com.github.struppigel.gui.PEFieldsTable;
22 | import com.github.struppigel.gui.pedetails.FileContentPreviewPanel;
23 | import com.github.struppigel.gui.utils.PortexSwingUtils;
24 | import com.github.struppigel.gui.utils.WorkerKiller;
25 | import com.github.struppigel.gui.utils.WriteSettingsWorker;
26 | import com.github.struppigel.settings.PortexSettings;
27 | import com.github.struppigel.settings.PortexSettingsKey;
28 | import org.apache.logging.log4j.LogManager;
29 | import org.apache.logging.log4j.Logger;
30 |
31 | import javax.swing.*;
32 | import javax.swing.table.DefaultTableModel;
33 | import javax.swing.table.TableRowSorter;
34 | import java.awt.*;
35 | import java.io.File;
36 | import java.util.ArrayList;
37 | import java.util.List;
38 | import java.util.Vector;
39 |
40 | public class SignaturesPanel extends JPanel {
41 | private static final Logger LOGGER = LogManager.getLogger();
42 | private final JProgressBar scanRunningBar = new JProgressBar();
43 | private final PortexSettings settings;
44 | private final FileContentPreviewPanel previewPanel;
45 | private FullPEData pedata;
46 |
47 | private String rulePath; // TODO set a default path
48 | private String yaraPath;
49 |
50 | // e.g. Yara|PEiD, XORedPE, Full file|EP
51 | private final String[] summaryHeaders = {"Source", "Match name", "Scan mode"};
52 |
53 | //e.g. XORedPE, "This program", "0xcafebabe", "Resource"
54 | private final String[] patternHeaders = {"Match name", "Pattern name", "Pattern content", "Offset"}; //, "Location"};
55 | private boolean hexEnabled = true;
56 | private boolean ignoreYaraScan = true;
57 | private JTextField yaraPathTextField = new JTextField(30);
58 | private JTextField rulePathTextField = new JTextField(30);
59 |
60 | private List yaraResults = null;
61 | // TODO put this here in result table!
62 | private List peidResults = null;
63 | public SignaturesPanel(PortexSettings settings, FileContentPreviewPanel previewPanel) {
64 | this.settings = settings;
65 | this.previewPanel = previewPanel;
66 | applyLoadedSettings();
67 | }
68 |
69 | private void applyLoadedSettings() {
70 | if(settings.containsKey(PortexSettingsKey.YARA_PATH)) {
71 | yaraPath = settings.get(PortexSettingsKey.YARA_PATH);
72 | yaraPathTextField.setText(yaraPath);
73 | }
74 | if(settings.containsKey(PortexSettingsKey.YARA_SIGNATURE_PATH)) {
75 | rulePath = settings.get(PortexSettingsKey.YARA_SIGNATURE_PATH);
76 | rulePathTextField.setText(rulePath);
77 | }
78 | }
79 |
80 | private void initProgressBar() {
81 | this.removeAll();
82 | this.add(scanRunningBar);
83 | this.setLayout(new FlowLayout());
84 | scanRunningBar.setIndeterminate(true);
85 | scanRunningBar.setVisible(true);
86 | this.repaint();
87 | }
88 |
89 | private void buildTables() {
90 | this.removeAll(); //remove progress bar
91 |
92 | PEFieldsTable summaryTable = new PEFieldsTable(hexEnabled);
93 | PEFieldsTable patternTable = new PEFieldsTable(hexEnabled);
94 | patternTable.setPreviewOffsetColumn(3, previewPanel);
95 |
96 | DefaultTableModel sumModel = new PEFieldsTable.PETableModel();
97 | sumModel.setColumnIdentifiers(summaryHeaders);
98 |
99 | DefaultTableModel patModel = new PEFieldsTable.PETableModel();
100 | patModel.setColumnIdentifiers(patternHeaders);
101 |
102 | summaryTable.setModel(sumModel);
103 | patternTable.setModel(patModel);
104 |
105 | initListener(summaryTable, patternTable);
106 |
107 | fillTableModelsWithData(sumModel, patModel);
108 |
109 | JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(summaryTable),
110 | new JScrollPane(patternTable));
111 | splitPane.setDividerLocation(200);
112 |
113 | this.setLayout(new BorderLayout());
114 | this.add(splitPane, BorderLayout.CENTER);
115 |
116 | // set up buttons
117 | JPanel buttonPanel = new JPanel();
118 | JButton rescan = new JButton("Rescan");
119 | JButton pathSettings = new JButton("Settings");
120 | buttonPanel.add(rescan);
121 | buttonPanel.add(pathSettings);
122 | pathSettings.addActionListener(e -> requestPaths());
123 | rescan.addActionListener(e -> scan(false));
124 |
125 | this.add(buttonPanel, BorderLayout.SOUTH);
126 |
127 | revalidate();
128 | repaint();
129 | }
130 |
131 | private void fillTableModelsWithData(DefaultTableModel sumModel, DefaultTableModel patModel) {
132 | List allResults = new ArrayList<>();
133 | if(yaraResults != null) allResults.addAll(yaraResults);
134 | if(peidResults != null) allResults.addAll(peidResults);
135 | if(allResults.isEmpty()) {
136 | allResults.add(new EmptyRuleMatch());
137 | }
138 | for (RuleMatch match : allResults) {
139 | sumModel.addRow(match.toSummaryRow());
140 | for (Object[] row : match.toPatternRows()) {
141 | patModel.addRow(row);
142 | }
143 | }
144 | }
145 |
146 | void requestPaths() {
147 | this.removeAll();
148 |
149 | JPanel tablePanel = new JPanel();
150 | tablePanel.setLayout(new GridLayout(0, 1));
151 |
152 | JButton yaraPathButton = new JButton("...");
153 | JButton rulePathButton = new JButton("...");
154 |
155 | JPanel demand = new JPanel();
156 | demand.add(new JLabel("Add your yara and signature paths for signature scanning"));
157 | tablePanel.add(demand);
158 | JPanel firstRow = new JPanel();
159 | firstRow.setLayout(new FlowLayout());
160 | firstRow.add(new JLabel("Yara path:"));
161 | firstRow.add(yaraPathTextField);
162 | firstRow.add(yaraPathButton);
163 | tablePanel.add(firstRow);
164 | JPanel secondRow = new JPanel();
165 | secondRow.setLayout(new FlowLayout());
166 | secondRow.add(new JLabel("Signature path:"));
167 | secondRow.add(rulePathTextField);
168 | secondRow.add(rulePathButton);
169 | tablePanel.add(secondRow);
170 | JButton scanButton = new JButton("Scan");
171 | JPanel thirdRow = new JPanel();
172 | thirdRow.add(scanButton);
173 | tablePanel.add(thirdRow);
174 | setButtonListenersForRequestPath(scanButton, yaraPathButton, rulePathButton);
175 |
176 | this.setLayout(new BorderLayout());
177 | this.add(tablePanel, BorderLayout.NORTH);
178 | this.add(Box.createVerticalGlue(), BorderLayout.CENTER);
179 | revalidate();
180 | repaint();
181 | }
182 |
183 | private void setButtonListenersForRequestPath(JButton scanButton, JButton yaraPathButton, JButton rulePathButton) {
184 |
185 | scanButton.addActionListener(e -> {
186 | // this button is used in settings dialog and acts also as save button
187 | yaraPath = yaraPathTextField.getText();
188 | rulePath = rulePathTextField.getText();
189 | writeSettings();
190 | scan(true);
191 | });
192 |
193 | yaraPathButton.addActionListener(e -> {
194 | String result = PortexSwingUtils.getOpenFileNameFromUser(this);
195 | if(result != null) {
196 | yaraPath = result;
197 | yaraPathTextField.setText(yaraPath);
198 | }
199 |
200 |
201 | });
202 | rulePathButton.addActionListener(e -> {
203 | String result = PortexSwingUtils.getOpenFileNameFromUser(this);
204 | if(result != null) {
205 | rulePath = result;
206 | rulePathTextField.setText(rulePath);
207 | }
208 | });
209 | }
210 |
211 | private void writeSettings() {
212 | if (yaraPath != null && rulePath != null && new File(yaraPath).exists() && new File(rulePath).exists()) {
213 | settings.put(PortexSettingsKey.YARA_PATH, yaraPath);
214 | settings.put(PortexSettingsKey.YARA_SIGNATURE_PATH, rulePath);
215 | new WriteSettingsWorker(settings).execute();
216 | }
217 | }
218 |
219 | private void initListener(JTable summaryTable, JTable patternTable) {
220 | ListSelectionModel model = summaryTable.getSelectionModel();
221 | model.addListSelectionListener(e -> {
222 | String rule = getRuleNameForSelectedRow(summaryTable);
223 | LOGGER.info(rule + " selected");
224 | // filters exact matches with rule name at column 0
225 | RowFilter rf =
226 | new RowFilter() {
227 | @Override
228 | public boolean include(Entry entry) {
229 | if (rule == null || entry == null) return true;
230 | return rule.equals(entry.getStringValue(0));
231 | }
232 | };
233 | ((TableRowSorter) patternTable.getRowSorter()).setRowFilter(rf);
234 | });
235 | }
236 |
237 | private String getRuleNameForSelectedRow(JTable table) {
238 | final int RULE_NAME_COL = 1;
239 | DefaultTableModel model = (DefaultTableModel) table.getModel();
240 | int row = getSelectedRow(table);
241 | if (row == -1) return null;
242 | Vector vRow = (Vector) model.getDataVector().elementAt(row);
243 | Object rule = vRow.elementAt(RULE_NAME_COL);
244 | return rule.toString();
245 | }
246 |
247 | /**
248 | * Return a vector of the data in the currently selected row. Returns null if no row selected
249 | *
250 | * @param table
251 | * @return vector of the data in the currently selected row or null if nothing selected
252 | */
253 | private int getSelectedRow(JTable table) {
254 |
255 | int rowIndex = table.getSelectedRow();
256 | if (rowIndex >= 0) {
257 | return table.convertRowIndexToModel(rowIndex);
258 |
259 | }
260 | return -1;
261 | }
262 |
263 | private void scan(boolean warnUser) {
264 | if (yaraPath != null && rulePath != null && new File(yaraPath).exists() && new File(rulePath).exists()) {
265 | initProgressBar();
266 | this.ignoreYaraScan = false;
267 | YaraScanner yaraScanner = new YaraScanner(this, pedata, yaraPath, rulePath, settings);
268 | PEidScanner peidScanner = new PEidScanner(this, pedata);
269 | yaraScanner.execute();
270 | peidScanner.execute();
271 | WorkerKiller.getInstance().addWorker(yaraScanner);
272 | WorkerKiller.getInstance().addWorker(peidScanner);
273 | } else {
274 | if(warnUser) {
275 | String message = "Cannot scan";
276 | if(yaraPath == null) { message += ", because no yara path set"; }
277 | else if(rulePath == null) { message += ", because no rule path set"; }
278 | else if(!new File(yaraPath).exists()) { message += ", because yara path is not an existing file: " + yaraPath; }
279 | else if(!new File(rulePath).exists()) { message += ", because rule path is not an existing file: " + rulePath; }
280 | else { message += ". The reason is unknown :("; }
281 | JOptionPane.showMessageDialog(this,
282 | message,
283 | "Cannot scan with yara",
284 | JOptionPane.WARNING_MESSAGE);
285 | }
286 | initProgressBar();
287 | this.ignoreYaraScan = true;
288 | PEidScanner peidScanner = new PEidScanner(this, pedata);
289 | peidScanner.execute();
290 | WorkerKiller.getInstance().addWorker(peidScanner);
291 | }
292 | }
293 |
294 | public void setPeData(FullPEData data) {
295 | this.pedata = data;
296 | scan(false);
297 | }
298 |
299 | public void setHexEnabled(boolean hexEnabled) {
300 | this.hexEnabled = hexEnabled;
301 | if(pedata == null){return;}
302 | buildTables(); // new renderer settings, refresh the panel
303 | }
304 |
305 | public void buildPEiDTables(List signatureMatches) {
306 | this.peidResults = signatureMatches;
307 | if(yaraResults != null || ignoreYaraScan) {
308 | buildTables();
309 | }
310 | }
311 |
312 |
313 | public void buildYaraTables(List scanResults) {
314 | this.yaraResults = scanResults;
315 | if(peidResults != null) {
316 | buildTables();
317 | }
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/YaraRuleMatch.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails.signatures;
19 |
20 | import java.util.List;
21 | import java.util.stream.Collectors;
22 |
23 | public class YaraRuleMatch implements RuleMatch {
24 | final String ruleName;
25 | final List patterns;
26 | final List tags;
27 |
28 | public YaraRuleMatch(String ruleName, List patterns, List tags) {
29 | this.ruleName = ruleName;
30 | this.patterns = patterns;
31 | this.tags = tags;
32 | }
33 |
34 | // {"Source", "Match name", "Scan mode"};
35 | public Object[] toSummaryRow() {
36 | Object[] row = {"Yara", ruleName, "Full file"};
37 | return row;
38 | }
39 |
40 | public List toPatternRows() {
41 | return patterns.stream().map(p -> p.toPatternRow(ruleName)).collect(Collectors.toList());
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/pedetails/signatures/YaraScanner.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.pedetails.signatures;
19 |
20 | import com.github.struppigel.gui.FullPEData;
21 | import com.github.struppigel.settings.PortexSettings;
22 | import com.github.struppigel.settings.PortexSettingsKey;
23 | import org.apache.logging.log4j.LogManager;
24 | import org.apache.logging.log4j.Logger;
25 |
26 | import javax.swing.*;
27 | import java.io.BufferedReader;
28 | import java.io.IOException;
29 | import java.io.InputStreamReader;
30 | import java.util.ArrayList;
31 | import java.util.Arrays;
32 | import java.util.List;
33 | import java.util.concurrent.ExecutionException;
34 |
35 | import static com.github.struppigel.settings.PortexSettingsKey.DISABLE_YARA_WARNINGS;
36 |
37 | class YaraScanner extends SwingWorker, Void> {
38 | private static final Logger LOGGER = LogManager.getLogger();
39 | private final SignaturesPanel signaturesPanel;
40 | private final FullPEData pedata;
41 | private final String yaraPath;
42 | private final String rulePath;
43 |
44 | private PortexSettings settings;
45 |
46 | public YaraScanner(SignaturesPanel signaturesPanel, FullPEData data, String yaraPath, String rulePath, PortexSettings settings) {
47 | this.signaturesPanel = signaturesPanel;
48 | this.pedata = data;
49 | this.yaraPath = yaraPath;
50 | this.rulePath = rulePath;
51 | this.settings = settings;
52 | }
53 |
54 | @Override
55 | protected List doInBackground() throws IOException {
56 | ProcessBuilder pbuilder = new ProcessBuilder(yaraPath, "-s", rulePath, pedata.getFile().getAbsolutePath());
57 | Process process = null;
58 | List matches = new ArrayList<>();
59 | try {
60 | process = pbuilder.start();
61 | stdInHandling(process, matches);
62 | stdErrHandling(process);
63 | } finally {
64 | if (process != null) {
65 | process.destroy();
66 | }
67 | }
68 | return matches;
69 | }
70 |
71 | private void stdErrHandling(Process process) throws IOException {
72 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
73 | String line;
74 | while ((line = reader.readLine()) != null) {
75 |
76 | if (line.contains("warning:")) {
77 | LOGGER.warn("Warning while parsing yara rules: " + line);
78 | if (settings.containsKey(DISABLE_YARA_WARNINGS) && settings.get(DISABLE_YARA_WARNINGS).equals("1")) {
79 | return;
80 | }
81 | JOptionPane.showMessageDialog(signaturesPanel,
82 | line,
83 | "Rule parsing warning",
84 | JOptionPane.WARNING_MESSAGE);
85 | } else {
86 | LOGGER.error("Error while parsing yara rules: " + line);
87 | JOptionPane.showMessageDialog(signaturesPanel,
88 | line,
89 | "Rule parsing error",
90 | JOptionPane.ERROR_MESSAGE);
91 | }
92 | }
93 | }
94 | }
95 |
96 | private void stdInHandling(Process process, List matches) throws IOException {
97 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
98 | List patterns = new ArrayList<>();
99 | String line;
100 | while ((line = reader.readLine()) != null) {
101 | patterns = scanLine(matches, patterns, line);
102 | }
103 | }
104 | }
105 |
106 | private List scanLine(List matches, List patterns, String line) {
107 | if (isRuleName(line)) {
108 | String rulename = parseRulename(line);
109 | patterns = new ArrayList<>(); // fresh patterns
110 | matches.add(new YaraRuleMatch(rulename, patterns, new ArrayList<>()));
111 | } else if (isPattern(line)) {
112 | PatternMatch pattern = parsePattern(line);
113 | patterns.add(pattern);
114 | }
115 | return patterns;
116 | }
117 |
118 | private PatternMatch parsePattern(String line) {
119 | assert isPattern(line); // make sure the pattern is 0xcafebabe:...:...
120 | String[] split = line.split(":");
121 | String longStr = split[0].substring(2);// remove '0x'
122 | long offset = Long.parseLong(longStr, 16); // convert from hex
123 | String name = split[1];
124 | String content = String.join(":", Arrays.copyOfRange(split, 2, split.length));
125 | return new PatternMatch(offset, name, content);
126 | }
127 |
128 | private boolean isPattern(String line) {
129 | return line.startsWith("0x") && line.contains(":$") &&
130 | line.split(":").length >= 3 && isLongHex(line.split(":\\$")[0]);
131 | }
132 |
133 | private boolean isLongHex(String s) {
134 | if (s.startsWith("0x")) {
135 | return isLongHex(s.substring(2));
136 | }
137 | try {
138 | Long.parseLong(s, 16);
139 | return true;
140 | } catch (NumberFormatException e) {
141 | LOGGER.warn("This is not a long " + s);
142 | return false;
143 | }
144 | }
145 |
146 | private String parseRulename(String line) {
147 | assert isRuleName(line);
148 | return line.split(" ")[0];
149 | }
150 |
151 | private boolean isRuleName(String line) {
152 | return !isPattern(line) && line.split(" ").length >= 2;
153 | }
154 |
155 | @Override
156 | protected void done() {
157 | if (isCancelled()) {
158 | return;
159 | }
160 | try {
161 | signaturesPanel.buildYaraTables(get());
162 | } catch (ExecutionException e) {
163 | // most commonly when user did not choose valid yara path or signature path
164 | // so we request them again
165 | signaturesPanel.requestPaths();
166 | JOptionPane.showMessageDialog(signaturesPanel,
167 | e.getMessage(),
168 | "IO Error",
169 | JOptionPane.ERROR_MESSAGE);
170 | LOGGER.error(e);
171 | } catch (InterruptedException e) {
172 | e.printStackTrace();
173 | LOGGER.error(e);
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/utils/PELoadWorker.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.utils;
19 |
20 | import com.github.struppigel.parser.*;
21 | import com.github.struppigel.parser.sections.SectionHeader;
22 | import com.github.struppigel.parser.sections.SectionLoader;
23 | import com.github.struppigel.parser.sections.SectionTable;
24 | import com.github.struppigel.parser.sections.clr.*;
25 | import com.github.struppigel.parser.sections.debug.*;
26 | import com.github.struppigel.parser.sections.edata.ExportEntry;
27 | import com.github.struppigel.parser.sections.edata.ExportNameEntry;
28 | import com.github.struppigel.parser.sections.idata.*;
29 | import com.github.struppigel.parser.sections.rsrc.IDOrName;
30 | import com.github.struppigel.parser.sections.rsrc.Level;
31 | import com.github.struppigel.parser.sections.rsrc.Resource;
32 | import com.github.struppigel.parser.sections.rsrc.version.VersionInfo;
33 | import com.github.struppigel.tools.*;
34 | import com.github.struppigel.tools.anomalies.Anomaly;
35 | import com.github.struppigel.tools.anomalies.PEAnomalyScanner;
36 | import com.github.struppigel.tools.sigscanner.FileTypeScanner;
37 | import com.github.struppigel.tools.sigscanner.SignatureScanner;
38 | import com.github.struppigel.gui.FullPEData;
39 | import com.github.struppigel.gui.MainFrame;
40 | import com.google.common.base.Optional;
41 | import org.apache.logging.log4j.LogManager;
42 | import org.apache.logging.log4j.Logger;
43 |
44 | import javax.swing.*;
45 | import java.io.File;
46 | import java.io.IOException;
47 | import java.security.MessageDigest;
48 | import java.security.NoSuchAlgorithmException;
49 | import java.util.ArrayList;
50 | import java.util.List;
51 | import java.util.Map;
52 | import java.util.concurrent.ExecutionException;
53 | import java.util.stream.Collectors;
54 | import java.util.stream.Stream;
55 |
56 | /**
57 | * Loads and computes all the data related to PE files, so that this code does not run in the event dispatch thread.
58 | * Everything that is too complicated here must be improved in the library PortEx.
59 | */
60 | public class PELoadWorker extends SwingWorker {
61 | private static final Logger LOGGER = LogManager.getLogger();
62 | private final String NL = System.getProperty("line.separator");
63 | private final File file;
64 | private final MainFrame frame;
65 | private final JLabel progressText;
66 |
67 | public PELoadWorker(File file, MainFrame frame, JLabel progressText) {
68 | this.file = file;
69 | this.frame = frame;
70 | this.progressText = progressText;
71 | }
72 |
73 | @Override
74 | protected FullPEData doInBackground() throws Exception {
75 | setProgress(0);
76 | publish("Loading Headers...");
77 | PEData data = PELoader.loadPE(file);
78 | java.util.Optional maybeCLR = loadCLRSection(data).transform(java.util.Optional::of).or(java.util.Optional.empty());
79 | List dotnetMetaDataRootEntries = createDotNetMetadataRootEntries(maybeCLR);
80 | List dotNetStreamHeaders = createDotNetStreamHeaders(maybeCLR);
81 | List optimizedStreamEntries = createOptimizedStreamEntries(maybeCLR);
82 | Map>> clrTables = data.loadCLRTables();
83 | Map> clrTableHeaders = data.loadCLRTableHeaders();
84 | setProgress(10);
85 |
86 | publish("Calculating Hashes...");
87 | ReportCreator r = ReportCreator.newInstance(data.getFile());
88 | String hashesReport = createHashesReport(data);
89 | List hashesForSections = createHashTableEntries(data);
90 | setProgress(20);
91 |
92 | publish("Calculating Entropies...");
93 | double[] sectionEntropies = calculateSectionEntropies(data);
94 | setProgress(30);
95 |
96 | publish("Extracting Imports...");
97 | List importDLLs = data.loadImports();
98 | List delayDLLs = data.loadDelayLoadImports();
99 | List impEntries = createImportTableEntries(importDLLs);
100 | List delayLoadEntries = createImportTableEntries(delayDLLs);
101 | List boundImportEntries = createBoundImportEntries(data);
102 | setProgress(40);
103 |
104 | publish("Scanning for signatures...");
105 | String rehintsReport = r.reversingHintsReport();
106 | setProgress(50);
107 |
108 | publish("Loading Resources...");
109 | List resourceTableEntries = createResourceTableEntries(data);
110 | List manifests = data.loadManifests();
111 | List vsInfoTable = createVersionInfoEntries(data);
112 | List stringTableEntries = createStringTableEntries(data);
113 | setProgress(60);
114 |
115 | publish("Loading Exports...");
116 | List exports = data.loadExports();
117 | List exportEntries = createExportTableEntries(data);
118 | setProgress(70);
119 |
120 | publish("Loading Debug Info...");
121 | List debugTableEntries = getDebugTableEntries(data);
122 | data.loadExtendedDllCharacteristics(); // preload it so it can be accessed next time
123 | setProgress(80);
124 |
125 | publish("Scanning for Anomalies...");
126 | List anomaliesTable = createAnomalyTableEntries(data);
127 | setProgress(90);
128 |
129 | publish("Scanning Overlay...");
130 | Overlay overlay = new Overlay(data);
131 | double overlayEntropy = ShannonEntropy.entropy(data.getFile(), overlay.getOffset(), overlay.getSize());
132 | List overlaySignatures = new SignatureScanner(SignatureScanner.loadOverlaySigs()).scanAtToString(data.getFile(), overlay.getOffset());
133 | setProgress(100);
134 |
135 | publish("Done!");
136 | return new FullPEData(data, overlay, overlayEntropy, overlaySignatures, sectionEntropies, importDLLs,
137 | impEntries, delayLoadEntries, boundImportEntries, resourceTableEntries, data.loadResources(), manifests, exportEntries, exports,
138 | hashesReport, hashesForSections, anomaliesTable, debugTableEntries, vsInfoTable,
139 | rehintsReport, stringTableEntries, dotnetMetaDataRootEntries, maybeCLR, dotNetStreamHeaders,
140 | optimizedStreamEntries, clrTables, clrTableHeaders);
141 | }
142 |
143 | private Object[] createBoundImportEntry(BoundImportDescriptor bi) {
144 | List line = new ArrayList<>();
145 | line.add(bi.getName());
146 | line.add(bi.get(BoundImportDescriptorKey.OFFSET_MODULE_NAME));
147 | line.add(bi.get(BoundImportDescriptorKey.TIME_DATE_STAMP));
148 | line.add(bi.get(BoundImportDescriptorKey.NR_OF_MODULE_FORWARDER_REFS));
149 | line.add(bi.rawOffset());
150 | return line.toArray();
151 | }
152 |
153 | private List createBoundImportEntries(PEData data) throws IOException {
154 | Optional section = new SectionLoader(data).maybeLoadBoundImportSection();
155 | if(section.isPresent()) {
156 | BoundImportSection bsec = section.get();
157 | return bsec.getEntries().stream()
158 | .map(this::createBoundImportEntry)
159 | .collect(Collectors.toList());
160 | }
161 | return new ArrayList<>();
162 | }
163 |
164 | private Optional loadCLRSection(PEData data) {
165 | SectionLoader loader = new SectionLoader(data);
166 | try {
167 | Optional maybeClr = loader.maybeLoadCLRSection();
168 | return maybeClr;
169 | } catch(IOException e){
170 | LOGGER.error(e);
171 | e.printStackTrace();
172 | }
173 | return Optional.absent();
174 | }
175 |
176 | private List createOptimizedStreamEntries(java.util.Optional clr) {
177 | if(clr.isPresent() && !clr.get().isEmpty()) {
178 | MetadataRoot root = clr.get().metadataRoot();
179 | if(root.maybeGetOptimizedStream().isPresent()){
180 | OptimizedStream optStream = root.maybeGetOptimizedStream().get();
181 | return optStream.getEntriesList();
182 | }
183 | }
184 | return new ArrayList<>();
185 | }
186 |
187 | private List createDotNetStreamHeaders(java.util.Optional clr) {
188 | if(clr.isPresent() && !clr.get().isEmpty()) {
189 | MetadataRoot root = clr.get().metadataRoot();
190 | List headers = root.getStreamHeaders();
191 | return headers.stream()
192 | .map(h -> new Object[]{ h.name(), h.size(), h.offset(), root.getBSJBOffset() + h.offset() })
193 | .collect(Collectors.toList());
194 | }
195 | return new ArrayList<>();
196 | }
197 |
198 | private List createDotNetMetadataRootEntries(java.util.Optional clr) {
199 | if(clr.isPresent() && !clr.get().isEmpty()) {
200 | MetadataRoot root = clr.get().metadataRoot();
201 | Map entriesMap = root.getMetaDataEntries();
202 | return entriesMap.values().stream().collect(Collectors.toList());
203 | }
204 | return new ArrayList<>();
205 | }
206 |
207 | private List createStringTableEntries(PEData data) {
208 | Map strTable = data.loadStringTable();
209 | return strTable.entrySet().stream()
210 | .map(e -> new Object[]{e.getKey(), e.getValue()})
211 | .collect(Collectors.toList());
212 | }
213 |
214 | private List createVersionInfoEntries(PEData data) {
215 | return data.loadResources().stream()
216 | .filter(r -> r.getType().equals("RT_VERSION"))
217 | .flatMap(
218 | r -> VersionInfo.apply(r, data.getFile()).getVersionStrings().entrySet()
219 | .stream()
220 | .map(e -> new Object[]{e.getKey(), e.getValue()})
221 | )
222 | .collect(Collectors.toList());
223 | }
224 |
225 | private List createAnomalyTableEntries(PEData data) {
226 | List anomalies = PEAnomalyScanner.newInstance(data).getAnomalies();
227 | return anomalies.stream()
228 | .map(a -> new Object[]{a.description(), a.getType(), a.subtype(), a.key()})
229 | .collect(Collectors.toList());
230 | }
231 |
232 | private String createHashesReport(PEData data) {
233 | String text = "Full File Hashes" + NL + NL;
234 | try {
235 | MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
236 | MessageDigest md5 = MessageDigest.getInstance("MD5");
237 | Hasher hasher = new Hasher(data);
238 | text += "MD5: " + hash(hasher.fileHash(md5)) + NL;
239 | text += "SHA256: " + hash(hasher.fileHash(sha256)) + NL;
240 | text += "ImpHash: " + ImpHash.createString(data.getFile()) + NL;
241 | java.util.Optional maybeRich = hasher.maybeRichHash();
242 | if (maybeRich.isPresent()) {
243 | text += "Rich: " + hash(maybeRich.get()) + NL;
244 | java.util.Optional maybeRichPV = hasher.maybeRichPVHash();
245 | if (maybeRichPV.isPresent()) {
246 | text += "RichPV: " + hash(maybeRichPV.get()) + NL;
247 | }
248 | }
249 |
250 | } catch (NoSuchAlgorithmException | IOException e) {
251 | throw new RuntimeException(e);
252 | }
253 | return text;
254 | }
255 |
256 | private List createHashTableEntries(PEData data) {
257 | List entries = new ArrayList<>();
258 | SectionTable sectionTable = data.getSectionTable();
259 | try {
260 | MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
261 | MessageDigest md5 = MessageDigest.getInstance("MD5");
262 | Hasher hasher = new Hasher(data);
263 | for (SectionHeader s : sectionTable.getSectionHeaders()) {
264 | String secName = s.getNumber() + ". " + s.getName();
265 | String md5Str = hash(hasher.sectionHash(s.getNumber(), md5));
266 | String sha256Str = hash(hasher.sectionHash(s.getNumber(), sha256));
267 | md5Str = md5Str.equals("") ? "empty section" : md5Str;
268 | sha256Str = sha256Str.equals("") ? "empty section" : sha256Str;
269 | String[] md5Entry = {secName, "MD5", md5Str};
270 | String[] sha256Entry = {secName, "SHA256", sha256Str};
271 | entries.add(md5Entry);
272 | entries.add(sha256Entry);
273 | }
274 | } catch (NoSuchAlgorithmException | IOException e) {
275 | throw new RuntimeException(e);
276 | }
277 | return entries;
278 | }
279 |
280 | private String hash(byte[] array) {
281 | return ByteArrayUtil.byteToHex(array, "");
282 | }
283 |
284 | private String getCodeViewInfo(DebugDirectoryEntry d) {
285 | String report = "";
286 | CodeviewInfo c = d.getCodeView();
287 | report += "Codeview" + NL + NL;
288 | report += "Path: " + c.filePath() + NL;
289 | report += "Age: " + c.age() + NL;
290 | report += "GUID: " + CodeviewInfo.guidToString(c.guid()) + NL;
291 |
292 | return report;
293 | }
294 |
295 | private String getDebugInfo(DebugDirectoryEntry d, Boolean isRepro) {
296 | String time = isRepro ? "invalid - reproducibility build" : String.valueOf(d.getTimeDateStamp());
297 | String report = "Time date stamp: " + time + NL;
298 | report += "Type: " + d.getTypeDescription() + NL + NL;
299 | return report;
300 | }
301 |
302 | private List getDebugTableEntries(PEData pedata) {
303 | List tables = new ArrayList();
304 | try {
305 | Optional sec = new SectionLoader(pedata).maybeLoadDebugSection();
306 | if (sec.isPresent()) {
307 | DebugSection debugSec = sec.get();
308 | List debugList = debugSec.getEntries();
309 |
310 | for(DebugDirectoryEntry d : debugList) {
311 | Map dirTable = d.getDirectoryTable();
312 | List vals = dirTable.values().stream().collect(Collectors.toList());
313 | String title = d.getDebugType().toString();
314 | String debugInfo = getDebugInfo(d, pedata.isReproBuild());
315 | if(d.getDebugType() == DebugType.CODEVIEW) {
316 | try{
317 | debugInfo += getCodeViewInfo(d);
318 | } catch (IllegalStateException ise) {
319 | debugInfo += "Invalid codeview structure!";
320 | LOGGER.warn(ise.getMessage());
321 | }
322 | }
323 | if(d.getDebugType() == DebugType.REPRO) {
324 | debugInfo += d.getRepro().getInfo();
325 | }
326 | if(d.getDebugType() == DebugType.EX_DLLCHARACTERISTICS) {
327 | debugInfo += d.getExtendedDLLCharacteristics().getInfo();
328 | }
329 | tables.add(new TableContent(vals, title, debugInfo));
330 | }
331 | }
332 | } catch (IOException e) {
333 | LOGGER.error(e);
334 | e.printStackTrace();
335 | }
336 | return tables;
337 | }
338 |
339 | private List createExportTableEntries(PEData pedata) {
340 | List entries = new ArrayList<>();
341 | List exports = pedata.loadExports();
342 | for (ExportEntry e : exports) {
343 | // TODO this is a hack because of lacking support to check for export names, *facepalm*
344 | String name = e instanceof ExportNameEntry ? ((ExportNameEntry) e).name() : "";
345 | String forwarder = e.maybeGetForwarder().isPresent() ? e.maybeGetForwarder().get() : "";
346 | Object[] entry = {name, e.ordinal(), e.symbolRVA(), forwarder};
347 | entries.add(entry);
348 | }
349 | return entries;
350 | }
351 |
352 | private List createResourceTableEntries(PEData pedata) {
353 | List resources = pedata.loadResources();
354 | List entries = new ArrayList<>();
355 | for (Resource r : resources) {
356 | Map lvlIds = r.getLevelIDs();
357 | if(lvlIds == null) return entries;
358 | IDOrName nameLvl = lvlIds.get(Level.nameLevel());
359 | IDOrName langLvl = lvlIds.get(Level.languageLevel());
360 | if(nameLvl != null && langLvl != null) {
361 | String nameId = nameLvl.toString();
362 | String langId = langLvl.toString();
363 | String signatures;
364 | long offset = r.rawBytesLocation().from();
365 | long size = r.rawBytesLocation().size();
366 |
367 | Stream scanresults = FileTypeScanner.apply(pedata.getFile())
368 | .scanAtReport(offset).stream()
369 | // TODO this is a hack because lack of support from PortEx for scanAt function
370 | .map(s -> s.contains("bytes matched:") ? s.split("bytes matched:")[0] : s);
371 | signatures = scanresults.collect(Collectors.joining(", "));
372 | Object[] entry = {r.getType(), nameId, langId, offset, size, signatures};
373 | entries.add(entry);
374 | }
375 | }
376 | return entries;
377 | }
378 |
379 | private List createImportTableEntries(List imports) {
380 | List entries = new ArrayList<>();
381 | for (ImportDLL dll : imports) {
382 | for (NameImport imp : dll.getNameImports()) {
383 | Optional symbol = ImportDLL.getSymbolDescriptionForName(imp.getName());
384 | String description = "";
385 | String category = "";
386 | if (symbol.isPresent()) {
387 | description = symbol.get().getDescription().or("");
388 | // TODO replacing special chars is a hack for proper symbol descriptions, fix this in PortEx
389 | category = symbol.get().getCategory().replace("]", "").replace("[", "");
390 | String subcategory = symbol.get().getSubCategory().or("").replace(">", "").replace("<", "");
391 | if (!subcategory.equals("")) {
392 | category += " -> " + subcategory;
393 | }
394 | }
395 | Object[] entry = {dll.getName(), category, imp.getName(), description, imp.getRVA(), imp.getHint()};
396 | entries.add(entry);
397 | }
398 | for (OrdinalImport imp : dll.getOrdinalImports()) {
399 | Object[] entry = {dll.getName(), "", imp.getOrdinal() + "", "", imp.getRVA(), ""};
400 | entries.add(entry);
401 | }
402 | }
403 | return entries;
404 | }
405 |
406 | private double[] calculateSectionEntropies(PEData data) {
407 | int sectionNumber = data.getSectionTable().getNumberOfSections();
408 | double[] sectionEntropies = new double[sectionNumber];
409 | ShannonEntropy entropy = new ShannonEntropy(data);
410 | for (int i = 0; i < sectionNumber; i++) {
411 | sectionEntropies[i] = entropy.forSection(i + 1);
412 | }
413 | return sectionEntropies;
414 | }
415 |
416 | @Override
417 | protected void process(List statusText) {
418 | String lastMsg = statusText.get(statusText.size() - 1);
419 | progressText.setText(lastMsg);
420 | }
421 |
422 | @Override
423 | protected void done() {
424 | if(isCancelled()) {return;}
425 | try {
426 | FullPEData data = get();
427 | frame.setPeData(data);
428 | } catch (InterruptedException | ExecutionException e) {
429 | String message = "Could not load PE file! Reason: " + e.getMessage();
430 | if (e.getMessage().contains("given file is no PE file")) {
431 | message = "Could not load PE file! The given file is no PE file";
432 | }
433 | LOGGER.warn(message);
434 | LOGGER.warn(e);
435 | e.printStackTrace();
436 | JOptionPane.showMessageDialog(null,
437 | message,
438 | "Unable to load",
439 | JOptionPane.ERROR_MESSAGE);
440 | }
441 |
442 | }
443 |
444 | }
445 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/utils/PortexSwingUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.utils;
19 |
20 | import com.github.struppigel.gui.pedetails.PEDetailsPanel;
21 |
22 | import javax.swing.*;
23 | import java.awt.*;
24 | import java.io.File;
25 |
26 | public class PortexSwingUtils {
27 | private static final File userdir = new File(System.getProperty("user.dir"));
28 |
29 | public static String getOpenFileNameFromUser(Component parent) {
30 | JFileChooser fc = new JFileChooser(userdir);
31 | int state = fc.showOpenDialog(parent);
32 |
33 | if (state == JFileChooser.APPROVE_OPTION) {
34 | File file = fc.getSelectedFile();
35 | if (file != null) {
36 | return file.getAbsolutePath();
37 | }
38 | }
39 | return null;
40 | }
41 |
42 | /**
43 | * Determines if a file can be safely written by checking if it already exists and asking for consent if it does.
44 | * Will return whether file can be written.
45 | * @param parent GUI component where messages should be shown
46 | * @param file to check
47 | * @return true if file can be written to specified location in path
48 | */
49 | public static Boolean checkIfFileExistsAndAskIfOverwrite(Component parent, File file) {
50 | // file does not exist, so we can immediately return true
51 | if(!file.exists()) return true;
52 |
53 | // file exists, we need to ask
54 | int choice = JOptionPane.showConfirmDialog(parent,
55 | "File already exists, do you want to overwrite?",
56 | "File already exists",
57 | JOptionPane.YES_NO_OPTION);
58 | int YES_OPTION = 0;
59 | // user wants to overwrite
60 | if(choice == YES_OPTION) return true;
61 |
62 | // user does not want to overwrite, we tell them the consequences
63 | JOptionPane.showMessageDialog(parent,
64 | "Unable to save file",
65 | "File not saved",
66 | JOptionPane.WARNING_MESSAGE);
67 |
68 | return false;
69 | }
70 |
71 | public static String getSaveFileNameFromUser(Component parent, String defaultFileName) {
72 | JFileChooser fc = new JFileChooser(userdir);
73 | fc.setSelectedFile(new File(defaultFileName));
74 | int state = fc.showSaveDialog(parent);
75 | if (state == JFileChooser.APPROVE_OPTION) {
76 | File dir = fc.getCurrentDirectory();
77 | File file = fc.getSelectedFile();
78 | if (file != null && !file.isDirectory()) {
79 | return file.getAbsolutePath();
80 | }
81 | }
82 | return null;
83 | }
84 |
85 | public static String getSaveFolderNameFromUser(PEDetailsPanel parent) {
86 | JFileChooser fc = new JFileChooser(userdir);
87 | fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
88 | fc.setAcceptAllFileFilterUsed(false);
89 | int state = fc.showSaveDialog(parent);
90 | if (state == JFileChooser.APPROVE_OPTION) {
91 | File file = fc.getSelectedFile();
92 | if (file != null) {
93 | return file.getAbsolutePath();
94 | }
95 | }
96 | return null;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/utils/TableContent.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2023 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 |
19 | package com.github.struppigel.gui.utils;
20 |
21 | import com.github.struppigel.parser.StandardField;
22 |
23 | import java.util.ArrayList;
24 | import java.util.List;
25 |
26 | /**
27 | * Table entries and description. Currently used to hold the data for one tab in the TabbedPanel
28 | */
29 | public class TableContent extends ArrayList {
30 | private String title;
31 |
32 | private String description;
33 |
34 | public TableContent(List list, String title) {
35 | this(list, title, "");
36 | }
37 | public TableContent(List list, String title, String description) {
38 | super(list);
39 | this.title = title;
40 | this.description = description;
41 | }
42 |
43 | public String getTitle() {
44 | return title;
45 | }
46 |
47 | public String getDescription() {
48 | return description;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/utils/WorkerKiller.java:
--------------------------------------------------------------------------------
1 | package com.github.struppigel.gui.utils;
2 |
3 | import javax.swing.*;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.stream.Collectors;
7 |
8 | /**
9 | * Singleton that holds and cancels SwingWorkers when new PE loaded. Long running workers will otherwise set data for
10 | * previously loaded files.
11 | */
12 | public class WorkerKiller {
13 |
14 | private static WorkerKiller singleton;
15 |
16 | private List workers = new ArrayList();
17 |
18 | private WorkerKiller(){}
19 |
20 | public static synchronized WorkerKiller getInstance() {
21 | if(singleton == null) {
22 | singleton = new WorkerKiller();
23 | }
24 | return singleton;
25 | }
26 |
27 | public void addWorker(SwingWorker worker) {
28 | cleanDoneWorkers(); // use this occasion to get rid of old ones
29 | this.workers.add(worker);
30 | }
31 |
32 | private void cleanDoneWorkers() {
33 | List toRemove = workers.stream().filter(w -> w.isCancelled() || w.isDone()).collect(Collectors.toList());
34 | for(SwingWorker w : toRemove) {
35 | workers.remove(w);
36 | }
37 | }
38 |
39 | public void cancelAndDeleteWorkers() {
40 | for(SwingWorker worker : workers) {
41 | worker.cancel(true);
42 | }
43 | workers.clear();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/gui/utils/WriteSettingsWorker.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.gui.utils;
19 |
20 | import com.github.struppigel.settings.PortexSettings;
21 | import org.apache.logging.log4j.LogManager;
22 | import org.apache.logging.log4j.Logger;
23 |
24 | import javax.swing.*;
25 | import java.io.IOException;
26 |
27 | public class WriteSettingsWorker extends SwingWorker {
28 | private static final Logger LOGGER = LogManager.getLogger();
29 | private final PortexSettings settings;
30 |
31 | public WriteSettingsWorker(PortexSettings settings) {
32 | this.settings = settings;
33 | }
34 | @Override
35 | protected Boolean doInBackground() {
36 | try {
37 | settings.writeSettings();
38 | return true;
39 | } catch (IOException e) {
40 | e.printStackTrace();
41 | LOGGER.error(e);
42 | }
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/settings/LookAndFeelSetting.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2023 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.settings;
19 |
20 | /**
21 | * Available GUI themes
22 | */
23 | public enum LookAndFeelSetting {
24 | PORTEX,
25 | SYSTEM;
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/settings/PortexSettings.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | * ****************************************************************************
17 | */
18 | package com.github.struppigel.settings;
19 |
20 | import org.apache.logging.log4j.LogManager;
21 | import org.apache.logging.log4j.Logger;
22 |
23 | import javax.swing.*;
24 | import java.io.*;
25 | import java.net.URISyntaxException;
26 | import java.net.URL;
27 | import java.nio.file.Paths;
28 | import java.util.HashMap;
29 | import java.util.Map;
30 |
31 | import static com.github.struppigel.settings.PortexSettingsKey.*;
32 |
33 | public class PortexSettings extends HashMap {
34 |
35 | private static final Logger LOGGER = LogManager.getLogger();
36 | private final URL appPath = this.getClass().getProtectionDomain().getCodeSource().getLocation();
37 | private static final String SETTINGS_FILE_NAME = "settings.ini";
38 | private static final String SETTINGS_DELIMITER = ":::";
39 | private File settingsFile;
40 |
41 | public PortexSettings() {
42 | try {
43 | File app = new File(appPath.toURI());
44 | File folder = app.getAbsoluteFile();
45 | if(!folder.isDirectory()) { // different behaviour Win vs Linux here
46 | folder = folder.getParentFile();
47 | }
48 | settingsFile = Paths.get(folder.getAbsolutePath(), SETTINGS_FILE_NAME).toFile();
49 | loadSettings();
50 | } catch (URISyntaxException e) {
51 | LOGGER.fatal(e); // must never happen
52 | throw new RuntimeException(e);
53 | }
54 | }
55 |
56 | private void loadSettings() {
57 |
58 | this.clear();
59 | try {
60 | if (settingsFile.exists() && settingsFile.isFile()) {
61 | try (BufferedReader reader = new BufferedReader(new FileReader(settingsFile))) {
62 | String line;
63 | while ((line = reader.readLine()) != null) {
64 | String[] pair = line.split(SETTINGS_DELIMITER);
65 | if (pair.length >= 2) {
66 | try {
67 | PortexSettingsKey key = PortexSettingsKey.valueOf(pair[0].trim());
68 | String value = pair[1];
69 | this.put(key, value);
70 | } catch (IllegalArgumentException e) {
71 | LOGGER.warn("Non-existing settings key read! Will be ignored " + e);
72 | e.printStackTrace();
73 | }
74 | }
75 | }
76 | }
77 | LOGGER.info("Settings read successfully from " + settingsFile.getAbsolutePath());
78 | }
79 | } catch (IOException e) {
80 | e.printStackTrace();
81 | LOGGER.error(e);
82 | }
83 |
84 | applyDefaults();
85 | }
86 |
87 | private void applyDefaults() {
88 | // apply defaults
89 | applySettingIfNotSet(DISABLE_YARA_WARNINGS, "0");
90 | applySettingIfNotSet(DISABLE_UPDATE, "0");
91 | applySettingIfNotSet(LOOK_AND_FEEL, LookAndFeelSetting.PORTEX.toString());
92 | applySettingIfNotSet(VALUES_AS_HEX, "1");
93 | applySettingIfNotSet(CONTENT_PREVIEW, "1");
94 | }
95 |
96 | private void applySettingIfNotSet(PortexSettingsKey key, String value) {
97 | if(!this.containsKey(key)) {
98 | this.put(key, value);
99 | }
100 | }
101 |
102 | public boolean valueEquals(PortexSettingsKey key, String value) {
103 | return this.containsKey(key) && this.get(key).equals(value);
104 | }
105 |
106 | public void writeSettings() throws IOException {
107 | HashMap map = new HashMap<>(this); // shallow copy
108 | new SettingsWriter(settingsFile, map).execute();
109 | }
110 |
111 | private static class SettingsWriter extends SwingWorker {
112 |
113 | private final File settingsFile;
114 | private final HashMap map;
115 |
116 | public SettingsWriter(File settingsFile, HashMap map) {
117 | this.settingsFile = settingsFile;
118 | this.map = map;
119 | }
120 |
121 | @Override
122 | protected Void doInBackground() throws Exception {
123 | try {
124 | settingsFile.delete();
125 | settingsFile.createNewFile();
126 | try (PrintStream out = new PrintStream(new FileOutputStream(settingsFile))) {
127 | for (Map.Entry entry : map.entrySet()) {
128 | out.println(entry.getKey() + SETTINGS_DELIMITER + entry.getValue().trim());
129 | }
130 | LOGGER.info("Settings written to " + settingsFile.getAbsolutePath());
131 | }
132 | } catch (IOException e) {
133 | LOGGER.error("problem with writing " + settingsFile);
134 | throw e;
135 | }
136 | return null;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/main/java/com/github/struppigel/settings/PortexSettingsKey.java:
--------------------------------------------------------------------------------
1 | /**
2 | * *****************************************************************************
3 | * Copyright 2022 Karsten Hahn
4 | *