Skip to content

Commit

Permalink
Rekall integration (#6)
Browse files Browse the repository at this point in the history
* Initial commit

* Rekall integration

* Updated README.md

Co-authored-by: Tom Bonner <[email protected]>
  • Loading branch information
tombonner and Tom Bonner authored Oct 2, 2020
1 parent 547f77f commit 5679cb6
Show file tree
Hide file tree
Showing 19 changed files with 1,171 additions and 729 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.pyc
*/env*
*/__pycache*
*.egg-info*
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to this project will be documented in this file.

## [1.0.28](/../tags/1.0.28) - 2020-10-02

### Added

- [Rekall](http://www.rekall-forensic.com/) integration
- [.gitignore](.gitignore) file

### Changed

- Tidied runtime interface
- Renamed reconstructed imports section from .idata to .pe_tree

### Fixed

- Certificate parsing
- Improved PE dumping/import reconstruction
- pylint updates

## [1.0.27](/../tags/1.0.27) - 2020-05-20

### Added
Expand Down
3 changes: 2 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include *.md LICENSE
recursive-include pe_tree *.py *.ico *.png
recursive-include pe_tree *.py *.ico *.png
include requirements.txt
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PE Tree
> Python module for viewing [Portable Executable (PE) files](https://en.wikipedia.org/wiki/Portable_Executable) in a tree-view using [pefile](https://github.com/erocarrera/pefile) and [PyQt5](https://pypi.org/project/PyQt5/).
> Can also be used with [IDA Pro](https://www.hex-rays.com/products/ida/) to dump in-memory PE files and reconstruct imports.
> Can also be used with [IDA Pro](https://www.hex-rays.com/products/ida/) and [Rekall](http://www.rekall-forensic.com/) to dump in-memory PE files and reconstruct imports.
- [Features](#features)
- [Application](#application)
Expand All @@ -20,17 +20,22 @@
* [Usage](#usage)
* [Examples](#examples)
* [Dumping in-memory PE files](#dumping-in-memory-pe-files)
- [Rekall](#rekall)
* [Requirements](#requirements-2)
* [Installation](#installation-2)
* [Usage](#usage-2)
- [Configuration](#configuration)
* [Overview](#overview-1)
* [Overview](#overview)
* [Options](#options)
* [Location](#location)
* [3rd party data sharing](#3rd-party-data-sharing)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
* [Developer documentation](#developer-documentation)
- [License](#license)

## Features
- Standalone application and IDAPython plugin
- Standalone application, IDAPython plugin and Rekall plugin
- Supports Windows/Linux/Mac
- Rainbow PE ratio map:
* High-level overview of PE structures, size and file location
Expand Down Expand Up @@ -78,6 +83,11 @@
* Dump reconstructed PE files
* Automatically comment PE file structures in IDB
* Automatically label IAT offsets in IDB
- Rekall plugin:
* Operates against memory dump or live system
* View, dump and rebuild PE files from;
* Active processes and modules
* Loaded kernel-mode drivers

## Application
![PE Tree standalone application](./doc/images/pe_tree.png)
Expand Down Expand Up @@ -207,6 +217,29 @@ Finally, the BASERELOC, BOUND_IMPORT and SECURITY data directories are marked nu
Using the above approach it is possible to dump many in-memory PE files that have either been unpacked, remotely injected, reflectively loaded or hollowed etc.
## Rekall
![PE Tree Rekall plugin](./doc/images/pe_tree_rekall.png)
### Requirements
- Python 3+
### Installation
1. Install Rekall from [GitHub](https://github.com/google/rekall#quick-start).
2. Install PE Tree standalone application (see [Installation](#installation)) under the same virtual environment.
### Usage
Run Rekall and dump active processes, DLLs and drivers on a live system:
```
$ rekall --live Memory
[1] Live (Memory) 00:00:00> run -i pe_tree_rekall.py
```
Alternatively, run Rekall/PE Tree against an existing memory dump:
```
$rekall -f memory.vmem
[1] memory.vmem 00:00:00> run -i pe_tree_rekall.py
```
## Configuration
### Overview
Expand Down Expand Up @@ -244,6 +277,8 @@ recalculate_pe_checksum = False
| Application | Linux/Mac | `/tmp/pe_tree.ini` |
| IDAPython | Windows | `%APPDATA%\HexRays\IDA Pro\pe_tree.ini` |
| IDAPython | Linux/Mac | `~/.idapro/pe_tree.ini` |
| Rekall | Windows | `%TEMP%\pe_tree_rekall.ini` |
| Rekall | Linux/Mac | `/tmp/pe_tree_rekall.ini` |
### 3rd party data sharing
Expand Down
Binary file added doc/images/pe_tree_rekall.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 20 additions & 58 deletions pe_tree/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
# Standard imports
import os
import sys
import tempfile
import scandir

# Qt imports
Expand All @@ -29,9 +28,9 @@
try:
import qdarkstyle

have_darkstyle = True
HAVE_DARKSTYLE = True
except:
have_darkstyle = False
HAVE_DARKSTYLE = False

# PE Tree imports
import pe_tree.runtime
Expand All @@ -48,46 +47,6 @@ def __init__(self, widget):

self.pe_tree_form = None

def get_temp_dir(self):
"""Get temporary directory path"""
self.ret = tempfile.gettempdir()
return self.ret

def get_script_dir(self):
"""Get script directory"""
self.ret = os.path.dirname(os.path.realpath(__file__))
return self.ret

def ask_file(self, filename, caption, filter="All Files (*)", save=False):
"""Open/save file dialog"""
dialog = QtWidgets.QFileDialog()
options = QtWidgets.QFileDialog.Options()

if save == False:
# Open file dialog
filename, _ = dialog.getOpenFileName(self.widget, caption, filename, filter, options=options)
else:
# Save file dialog
if filename[0] == ".":
# Remove leading dot from section names
filename = filename[1:]

filename, _ = dialog.getSaveFileName(self.widget, caption, filename, filter, options=options)

if filename:
self.ret = filename
else:
self.ret = ""

return self.ret

def show_widget(self):
"""Display the widget"""
self.widget.show()

self.ret = True
return self.ret

def jumpto(self, item, offset):
"""Disassemble using capstone"""
if item.tree.disasm:
Expand All @@ -110,11 +69,11 @@ def log(self, output):

class ScanDir(QtCore.QRunnable):
"""Scan directory thread
Args:
pe_tree_form (pe_tree.form): PE Tree form
filename (str): Path to file/folder to scan for PE files
"""
def __init__(self, pe_tree_form, filename):
super(ScanDir, self).__init__()
Expand All @@ -140,20 +99,20 @@ def run(self):
class PETreeWindow(QtWidgets.QMainWindow):
"""Main window for the PE Tree application"""

def __init__(self, application):
def __init__(self, application, runtime, open_file=True):
super(PETreeWindow, self).__init__()

self.application = application

# Create container widget, runtime and PE Tree form
widget = QtWidgets.QWidget()
self.runtime = ApplicationRuntime(widget)
self.runtime = runtime(widget)
self.pe_tree_form = pe_tree.form.PETreeForm(widget, application, self.runtime)
self.pe_tree_form.dispatcher = application.eventDispatcher()
application.aboutToQuit.connect(self.pe_tree_form.wait_for_threads)
self.runtime.pe_tree_form = self.pe_tree_form

if have_darkstyle:
if HAVE_DARKSTYLE:
application.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())

self.setWindowTitle(pe_tree.info.__title__)
Expand All @@ -162,7 +121,7 @@ def __init__(self, application):
self.setAcceptDrops(True)

# Construct application main menu
open_menu = QtWidgets.QMenu("Open", self)
self.open_menu = QtWidgets.QMenu("Open", self)

open_file_action = QtWidgets.QAction("File", self)
open_file_action.setShortcut("Ctrl+O")
Expand All @@ -172,22 +131,22 @@ def __init__(self, application):
open_directory_action = QtWidgets.QAction("Folder", self)
open_directory_action.setShortcut("Ctrl+Shift+O")
open_directory_action.setStatusTip("Scan folder for PE files")
open_directory_action.triggered.connect(lambda x:self.open_folder())
open_directory_action.triggered.connect(self.open_folder)

open_menu.addAction(open_file_action)
open_menu.addAction(open_directory_action)
self.open_menu.addAction(open_file_action)
self.open_menu.addAction(open_directory_action)

exit_action = QtWidgets.QAction("Exit", self)
exit_action.setShortcut("Ctrl+X")
exit_action.setStatusTip("Exit")
exit_action.triggered.connect(application.quit)

about_action = QtWidgets.QAction("About", self)
about_action.triggered.connect(lambda x:self.runtime.about_box())
about_action.triggered.connect(self.runtime.about_box)

menu = self.menuBar()
file_menu = menu.addMenu("&File")
file_menu.addMenu(open_menu)
file_menu.addMenu(self.open_menu)
file_menu.addAction(exit_action)

help_menu = menu.addMenu("&Help")
Expand All @@ -198,23 +157,26 @@ def __init__(self, application):
# Map all files/folders specified on the command line
for filename in sys.argv[1:]:
self.pe_tree_form.threadpool.start(ScanDir(self.pe_tree_form, filename))
else:
elif open_file is not False:
# Ask user to select file/folder
self.open_file()

def dragEnterEvent(self, e):
"""Accept drag events containing URLs"""
if e.mimeData().hasUrls:
e.accept()
else:
e.ignore()

def dragMoveEvent(self, e):
"""Accept drag events containing URLs"""
if e.mimeData().hasUrls:
e.accept()
else:
e.ignore()

def dropEvent(self, e):
"""File drag/dropped onto the main form, attempt to map as PE"""
if e.mimeData().hasUrls:
e.accept()

Expand Down Expand Up @@ -256,11 +218,11 @@ def open_folder(self):
return self.exec_open_dialog(dialog)

def main(args=None):
# Create PE Tree Qt application
"""Create PE Tree Qt standalone application"""
application = QtWidgets.QApplication(sys.argv)
window = PETreeWindow(application)
window = PETreeWindow(application, ApplicationRuntime)
window.showMaximized()
sys.exit(application.exec_())

if __name__ == "__main__":
main()
main()
Loading

0 comments on commit 5679cb6

Please sign in to comment.