Restructure of code for easier reference.

* Updated doc strings
* Added examples
* Addressed linting issues
This commit is contained in:
Chapin Bryce 2020-10-16 08:40:22 -04:00
parent 77c41e1a55
commit 46cced1881
No known key found for this signature in database
GPG Key ID: AF835C2DD0747B6C
21 changed files with 379 additions and 282 deletions

View File

@ -1,4 +1,4 @@
Section 1 - Essential Scripts
Chapter 1 - Essential Scripts
==============================
.. toctree::
:maxdepth: 2
@ -6,27 +6,27 @@ Section 1 - Essential Scripts
Section 1.1 - Argparse Example
--------------------------------
.. automodule:: pyforhandbook.section_01.argparse_example
.. automodule:: pyforhandbook.ch01_essentials.argparse_example
:members:
Section 1.2 - Logging Example
--------------------------------
.. automodule:: pyforhandbook.section_01.logging_example
.. automodule:: pyforhandbook.ch01_essentials.logging_example
:members:
Section 1.3 - Open Files
------------------------
.. automodule:: pyforhandbook.section_01.open_files
.. automodule:: pyforhandbook.ch01_essentials.open_files
:members:
Section 1.4 - CSV Example
--------------------------------
.. automodule:: pyforhandbook.section_01.csv_example
.. automodule:: pyforhandbook.ch01_essentials.csv_example
:members:
Section 1.5 - Directory Recursion
---------------------------------
.. automodule:: pyforhandbook.section_01.recursion_example
.. automodule:: pyforhandbook.ch01_essentials.recursion_example
:members:
Indices and tables

View File

@ -1,4 +1,4 @@
Section 2 - Registry Parsing
Chapter 2 - Registry Parsing
==============================
.. toctree::
:maxdepth: 2
@ -6,12 +6,12 @@ Section 2 - Registry Parsing
Section 2.1 - Opening a Hive
--------------------------------
.. automodule:: pyforhandbook.section_02.yarp_base
.. automodule:: pyforhandbook.ch02_registry.yarp_base
:members:
Section 2.2 - Parsing Hive Values
----------------------------------
.. automodule:: pyforhandbook.section_02.yarp_ntuser
.. automodule:: pyforhandbook.ch02_registry.yarp_ntuser
:members:
Indices and tables

View File

@ -1,4 +1,4 @@
Section 3 - Windows Event Log Parsing
Chapter 3 - Windows Event Log Parsing
=====================================
.. toctree::
:maxdepth: 2
@ -6,7 +6,7 @@ Section 3 - Windows Event Log Parsing
Section 3.1 - Using python-evtx
----------------------------------
.. automodule:: pyforhandbook.section_03.using_python_evtx
.. automodule:: pyforhandbook.ch03_event_logs.using_python_evtx
:members:
Indices and tables

View File

@ -1,4 +1,4 @@
Section 6 - Sqlite & MacOS/Mobile/Browsers
Chapter 6 - Sqlite & MacOS/Mobile/Browsers
==========================================
.. toctree::
:maxdepth: 2
@ -6,7 +6,7 @@ Section 6 - Sqlite & MacOS/Mobile/Browsers
Section 6.1 - Opening Sqlite
--------------------------------
.. automodule:: pyforhandbook.section_06.opening_sqlite
.. automodule:: pyforhandbook.ch06_databases.opening_sqlite
:members:
Indices and tables

View File

@ -5,18 +5,18 @@
The Python Forensics Handbook
=============================
---------------------------------------------
A guide for developing Python scripts in DFIR
---------------------------------------------
--------------------------------------------------------
A reference guide for developing Python scripts in DFIR
--------------------------------------------------------
.. toctree::
:maxdepth: 1
:caption: Table of Contents:
section1
section2
section3
section6
ch01_essentials
ch02_registry
ch03_event_logs
ch06_databases
Handbook Sections
==============================

View File

@ -1,9 +1,10 @@
"""
IN DEVELOPMENT - More sections will release over the coming weeks/months/as
time permits. Feel free to contribute as you have an idea or time to assist,
otherwise stay tuned!
.. note::
IN DEVELOPMENT - More sections will release over the coming weeks/months/as
time permits. Feel free to contribute as you have an idea or time to assist,
otherwise stay tuned!
This handbook has 7 sections covering common tasks for developing
This handbook consists of 7 sections covering common tasks for developing
Python scripts for use in DFIR. Each section contains short,
portable code blocks that can drop into a new script with minimal
tweaking. This way, you can quickly build out your custom script
@ -19,7 +20,7 @@ is hosted on GitHub at https://github.com/chapinb/python-forensics-handbook and
available to read online at https://chapinb.com/python-forensics-handbook.
Please consider submitting a pull request with your additions!
Section 1 - Essential Script Elements
Chapter 1 - Essential Script Elements
-------------------------------------
This chapter covers code blocks that are useful across scripts
@ -43,9 +44,12 @@ projects to allow for uniformity.
* Parallel Processing
- Simple implementation of multithreading and multiprocessing
Section 2 - Registry Hives with YARP
Chapter 2 - Registry Hives
------------------------------------
In this chapter, we demonstrate how to open a registry hive, navigate through
its keys, and interact with values to expose information for analysis.
* Using yarp to open a single hive
- Opening a hive and recovering data available in transaction logs
* Parse registry hive keys and values
@ -56,16 +60,20 @@ Section 2 - Registry Hives with YARP
* Searching for a pattern across hive keys and values.
- Looking for a provided pattern across the entire hive.
Section 3 - Event Logs
Chapter 3 - Event Logs
----------------------
The functions showcased in this chapter highlight methods to access events
within Windows event log files, iterating over the events, and extracting
useful records for further examination.
* Using python-evtx
- Opening evtx files
- Iterating over events
* Parsing Logins (with types, levels, privs)
* Parsing Logins
- Parse out the commonly investigated 4624/4672 events
Section 4 - Text logs
Chapter 4 - Text logs
---------------------
* Handling IIS Logs
@ -75,30 +83,41 @@ Section 4 - Text logs
* Adding in GeoIP
- Function to add GeoIP recognition
Section 5 - API calls & JSON data
Chapter 5 - API calls & JSON data
---------------------------------
* VirusTotal
* HybridAnalysis
* Manipulating JSON
Section 6 - SQLite & macOS/mobile/browsers
Chapter 6 - Databases
------------------------------------------
Databases are found within many applications and operating systems. This chapter
covers methods to extract information from these common databases, along with
functions that are purpose built to parse information from frequently seen
database tables.
* macOS Activity
- KnowledgeC
* Andriod SMS
* Google Chome History DB
* Android SMS
* Google Chrome History DB
Section 7 - Opening forensic images
Chapter 7 - Opening forensic images
--------------------------------------
Media acquisition and preservation formats are very common within DFIR and
the ability to extract specific contents from these files leads to faster
analysis and simplified usage of the tool you are building. With these functions
you can read files from a forensic image and pass them straight to your other
utilities for further parsing.
* LibEWF
- Expose an E01 as a raw image
* PyTSK
- Read data from a raw image (MBR)
- Read data from a file (hashing)
- Iterate through folders (file listing)
- Perform targetted reads (file sigs)
- Perform targeted reads (file signatures)
"""

View File

@ -19,7 +19,7 @@ how to set default values and boolean arguments. the ``argparse``
module has many more features documented at
https://docs.python.org/3/library/argparse.html
.. literalinclude:: ../pyforhandbook/section_01/argparse_example.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/argparse_example.py
:pyobject: setup_argparse
"""
@ -51,44 +51,51 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20190527
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Sample script to accept command line arguments.'''
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Sample script to accept command line arguments."""
__docs__ = [
'https://docs.python.org/3/library/argparse.html',
'https://docs.python.org/3/library/os.html',
'https://docs.python.org/3/library/pathlib.html'
"https://docs.python.org/3/library/argparse.html",
"https://docs.python.org/3/library/os.html",
"https://docs.python.org/3/library/pathlib.html",
]
def setup_argparse():
# Setup a parser instance with common fields including a
# description and epilog. The `formatter_class` instructs
# argparse to show default values set for parameters.
parser = argparse.ArgumentParser(
description='Sample Argparse',
description="Sample Argparse",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog=f"Built by {__author__}, v.{__date__}"
epilog=f"Built by {__author__}, v.{__date__}",
)
# The simplest form of adding an argument, the name of the
# parameter and a description of its form.
parser.add_argument('INPUT_FILE', help="Input file to parse")
parser.add_argument('OUTPUT_FOLDER',
help="Folder to store output")
parser.add_argument("INPUT_FILE", help="Input file to parse")
parser.add_argument("OUTPUT_FOLDER", help="Folder to store output")
# An optional argument with multiple methods of specifying
# the parameter. Includes a default value
parser.add_argument('-l', '--log', help="Path to log file",
default=os.path.abspath(os.path.join(
PurePath(__file__).parent,
PurePath(__file__).name.rsplit('.', 1)[0] + '.log'))
parser.add_argument(
"-l",
"--log",
help="Path to log file",
default=os.path.abspath(
os.path.join(
PurePath(__file__).parent,
PurePath(__file__).name.rsplit(".", 1)[0] + ".log",
)
),
)
# An optional argument which does not accept a value, instead
# just modifies functionality.
parser.add_argument('-v', '--verbose', action='store_true',
help='Include debug log messages')
parser.add_argument(
"-v", "--verbose", action="store_true", help="Include debug log messages"
)
# Once we've specified our arguments we can parse them for
# reference
@ -97,12 +104,13 @@ def setup_argparse():
# Returning our parsed arguments for further use.
return args
# Only run if called directly (not imported)
if __name__ == '__main__':
args = setup_argparse()
if __name__ == "__main__":
cli_args = setup_argparse()
# Show arguments
print(f'Input file: {args.INPUT_FILE}')
print(f'Output folder: {args.OUTPUT_FOLDER}')
print(f'Log file: {args.log}')
print(f'Be verbose?: {args.verbose}')
print(f"Input file: {cli_args.INPUT_FILE}")
print(f"Output folder: {cli_args.OUTPUT_FOLDER}")
print(f"Log file: {cli_args.log}")
print(f"Be verbose?: {cli_args.verbose}")

View File

@ -31,10 +31,10 @@ multiple dictionaries to a CSV file. You can optionally provide
an ordered list of headers to filter what rows to show, or let the
function use the keys of the first dictionary in the list to
generate the header information. The latter option may produce
a new order each iteration and is not prefered if you can
a new order each iteration and is not preferred if you can
determine the headers in advance.
.. literalinclude:: ../pyforhandbook/section_01/csv_example.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/csv_example.py
:pyobject: write_csv_dicts
List of ordered lists to CSV
@ -58,7 +58,7 @@ information. Unlike the dictionary option, you cannot filter
column data by adjusting the provided headers, you must write all
columns to the CSV.
.. literalinclude:: ../pyforhandbook/section_01/csv_example.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/csv_example.py
:pyobject: write_csv_lists
@ -92,13 +92,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20190527
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Sample script to write to CSV files.'''
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Sample script to write to CSV files."""
__docs__ = [
'https://docs.python.org/3/library/csv.html',
'https://docs.python.org/3/library/os.html'
"https://docs.python.org/3/library/csv.html",
"https://docs.python.org/3/library/os.html",
]
@ -110,18 +110,24 @@ def write_csv_dicts(outfile, data, headers=None):
data (list): List of dictionaries to write to file
headers (list): Header row to use. If empty, will use the
first dictionary in the `data` list.
Example:
>>> list_of_dicts = [
>>> {'name': 'apple', 'quantity': 10, 'location': 'VT'},
>>> {'name': 'orange', 'quantity': 5, 'location': 'FL'}
>>> ]
>>> write_csv_dicts('dict_test.csv', list_of_dicts)
"""
if not headers:
# Use the first line of data
headers = [str(x) for x in data[0].keys()]
with open(outfile, 'w', newline="") as open_file:
with open(outfile, "w", newline="") as open_file:
# Write only provided headers, ignore others
csvfile = csv.DictWriter(open_file, headers,
extrasaction='ignore')
csvfile.writeheader()
csvfile.writerows(data)
csv_file = csv.DictWriter(open_file, headers, extrasaction="ignore")
csv_file.writeheader()
csv_file.writerows(data)
def write_csv_lists(outfile, data, headers=None):
@ -132,35 +138,41 @@ def write_csv_lists(outfile, data, headers=None):
data (list): List of lists to write to file
headers (list): Header row to use. If empty, will use the
first list in the `data` list.
Examples:
>>> fields = ['name', 'quantity', 'location']
>>> list_of_lists = [
>>> ['apple', 10, 'VT'],
>>> ['orange', 5, 'FL']
>>> ]
>>> write_csv_lists('list_test.csv', list_of_lists, headers=fields)
"""
with open(outfile, 'w', newline="") as open_file:
with open(outfile, "w", newline="") as open_file:
# Write only provided headers, ignore others
csvfile = csv.writer(open_file)
csv_file = csv.writer(open_file)
for count, entry in enumerate(data):
if count == 0 and headers:
# If headers are defined, write them, otherwise
# continue as they will be written anyways
csvfile.writerow(headers)
csvfile.writerow(entry)
csv_file.writerow(headers)
csv_file.writerow(entry)
sample_dict_data = [
{'id': '0', 'city': 'Boston', 'state': 'MA',
'country': 'USA'},
{'id': '1', 'city': 'New York', 'state': 'NY',
'country': 'USA'},
{'id': '2', 'city': 'Washington', 'state': 'DC',
'country': 'USA'},
]
write_csv_dicts('dict_test.csv', sample_dict_data)
if __name__ == "__main__":
sample_dict_data = [
{"id": "0", "city": "Boston", "state": "MA", "country": "USA"},
{"id": "1", "city": "New York", "state": "NY", "country": "USA"},
{"id": "2", "city": "Washington", "state": "DC", "country": "USA"},
]
header_row = ['id', 'city', 'state', 'country']
sample_list_data = [
['0', 'Boston', 'MA', 'USA'],
['1', 'New York', 'NY', 'USA'],
['2', 'Washington', 'DC', 'USA']
]
write_csv_dicts("dict_test.csv", sample_dict_data)
write_csv_lists('list_test.csv', sample_list_data,
headers=header_row)
header_row = ["id", "city", "state", "country"]
sample_list_data = [
["0", "Boston", "MA", "USA"],
["1", "New York", "NY", "USA"],
["2", "Washington", "DC", "USA"],
]
write_csv_lists("list_test.csv", sample_list_data, headers=header_row)

View File

@ -20,7 +20,7 @@ set different logging levels for the two handlers - generally you
keep debugging information in the log file while writing more
critical messages to the console in STDERR.
.. literalinclude:: ../pyforhandbook/section_01/logging_example.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/logging_example.py
:pyobject: setup_logging
Docstring References
@ -53,61 +53,64 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20190527
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Sample script to display and write logging
messages.'''
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Sample script to display and write logging
messages."""
__docs__ = [
'https://docs.python.org/3/library/logging.html',
'https://docs.python.org/3/library/os.html'
"https://docs.python.org/3/library/logging.html",
"https://docs.python.org/3/library/os.html",
]
logger = None
logger = logging.getLogger(name=__name__)
def setup_logging():
"""Function to setup logging configuration and test it."""
# Allow us to modify the `logger` variable within a function
global logger
# Set logger object, uses module's name
logger = logging.getLogger(name=__name__)
def setup_logging(logging_obj, verbose=False):
"""Function to setup logging configuration and test it.
# Set default logger level to DEBUG. You can change this later
logger.setLevel(logging.DEBUG)
Args:
logging_obj: A logging instance, returned from logging.getLogger().
verbose: Whether or not to enable the debug level in STDERR output.
# Logging formatter. Best to keep consistent for most usecases
Examples:
>>> sample_logger = logging.getLogger(name=__name__)
>>> sample_logger = setup_logging(sample_logger)
>>> sample_logger.debug("This is a debug message")
>>> sample_logger.info("This is an info message")
>>> sample_logger.warning("This is a warning message")
>>> sample_logger.error("This is a error message")
>>> sample_logger.critical("This is a critical message")
"""
logging_obj.setLevel(logging.DEBUG)
# Logging formatter. Best to keep consistent for most use cases
log_format = logging.Formatter(
'%(asctime)s %(filename)s %(levelname)s %(module)s '
'%(funcName)s %(lineno)d %(message)s')
"%(asctime)s %(filename)s %(levelname)s %(module)s "
"%(funcName)s %(lineno)d %(message)s"
)
# Setup STDERR logging, allowing you uninterrupted
# STDOUT redirection
stderr_handle = logging.StreamHandler(stream=sys.stderr)
stderr_handle.setLevel(logging.INFO)
if verbose:
stderr_handle.setLevel(logging.DEBUG)
else:
stderr_handle.setLevel(logging.INFO)
stderr_handle.setFormatter(log_format)
# Setup file logging
file_handle = logging.FileHandler('sample.log', 'a')
file_handle = logging.FileHandler("sample.log", "a")
file_handle.setLevel(logging.DEBUG)
file_handle.setFormatter(log_format)
# Add handles
logger.addHandler(stderr_handle)
logger.addHandler(file_handle)
logging_obj.addHandler(stderr_handle)
logging_obj.addHandler(file_handle)
# Sample log messages
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is a error message")
logger.critical("This is a critical message")
return logging_obj
def sample_function():
"""Sample function to demonstrate logging formatting."""
logger.info("Called from a function")
sample_function()
if __name__ == "__main__":
setup_logging()
setup_logging(logger)
logger.warning("This is a warning!")

View File

@ -22,7 +22,7 @@ magic value/file signature, this low-tech method will help with parsing a
collection of files that may be UTF-8, UTF-16-LE, and UTF-16-BE, three very
common text file encodings. Feel free to build and share on this.
.. literalinclude:: ../pyforhandbook/section_01/open_files.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/open_files.py
:pyobject: open_file
Docstring References
@ -30,7 +30,6 @@ Docstring References
"""
from io import open
import os
"""
Copyright 2019 Chapin Bryce
@ -56,13 +55,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20191103
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Sample script to read encoded text files.'''
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Sample script to read encoded text files."""
__docs__ = [
'https://docs.python.org/3/library/csv.html',
'https://docs.python.org/3/library/os.html'
"https://docs.python.org/3/library/csv.html",
"https://docs.python.org/3/library/os.html",
]
@ -73,28 +72,29 @@ def open_file(input_file):
input_file (str): Path to file to open
"""
test_encoding = open(input_file, 'rb')
test_encoding = open(input_file, "rb")
bom = test_encoding.read(2)
file_encoding = 'utf-8'
if bom == b'FEFF':
file_encoding = 'utf-16-le'
elif bom == b'FFFE':
file_encoding = 'utf-16-be'
file_encoding = "utf-8"
if bom == b"FEFF":
file_encoding = "utf-16-le"
elif bom == b"FFFE":
file_encoding = "utf-16-be"
with open(input_file, 'r', encoding=file_encoding) as fopen:
for raw_line in fopen:
with open(input_file, "r", encoding=file_encoding) as open_input_file:
for raw_line in open_input_file:
line = raw_line.strip()
print(line)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description=__desc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog=f"Built by {__author__}, v.{__date__}"
epilog=f"Built by {__author__}, v.{__date__}",
)
parser.add_argument('INPUT_FILE', help="Text file to read")
parser.add_argument("INPUT_FILE", help="Text file to read")
args = parser.parse_args()
open_file(args.INPUT_FILE)

View File

@ -19,7 +19,7 @@ folders within a single directory. From here you can further
interact with individual files and folders or iterate recursively
by calling the function on identified subdirectories.
.. literalinclude:: ../pyforhandbook/section_01/recursion_example.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/recursion_example.py
:pyobject: list_directory
List a directory recursively
@ -33,7 +33,7 @@ handling the processing of files. This sample shows a method of
counting the number of files, subdirectories, and files ending in
".py" as an example.
.. literalinclude:: ../pyforhandbook/section_01/recursion_example.py
.. literalinclude:: ../pyforhandbook/ch01_essentials/recursion_example.py
:pyobject: iterate_files
"""
@ -63,16 +63,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20190527
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Sample script to iterate over a folder of files.'''
__docs__ = [
'https://docs.python.org/3/library/os.html'
]
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Sample script to iterate over a folder of files."""
__docs__ = ["https://docs.python.org/3/library/os.html"]
def list_directory(path):
"""List all file and folder entries in `path`."""
"""List all file and folder entries in `path`.
Args:
path (str): A directory within a mounted file system. May be relative or
absolute.
Examples:
>>> list_directory('.')
"""
print(f"Files and folders in '{os.path.abspath(path)}':")
# Quick and easy method for listing items within a single
# folder.
@ -80,7 +88,23 @@ def list_directory(path):
# Print all entry names
print(f"\t{entry}")
def iterate_files(path):
"""Recursively iterate over a path, findings all files within the folder
and its subdirectories.
Args:
path (str): A directory within a mounted file system. May be relative or
absolute.
Examples:
>>> number_of_py_files = 0
>>> for f in iterate_files('../'):
... if f.endswith('.py'):
... number_of_py_files += 1
>>> print(f"\t{number_of_py_files} python files found "
... f"in {os.path.abspath('../')}")
"""
# Though `os.walk()` exposes a list of directories in the
# current `root`, it is rarely used since we are generally
# interested in the files found within the subdirectories.
@ -89,23 +113,21 @@ def iterate_files(path):
for root, dirs, files in os.walk(os.path.abspath(path)):
# Both `dirs` and `files` are lists containing all entries
# at the current `root`.
for fentry in files:
for file_name in files:
# To effectively reference a file, you should include
# the below line which creates a full path reference
# to the specific file, regardless of how nested it is
file_entry = os.path.join(root, fentry)
# We can then hand `file_entry` off to other functions.
yield file_entry
yield os.path.join(root, file_name)
if __name__ == "__main__":
abspath = os.path.abspath
print(f"Listing {abspath('.')}")
list_directory('.')
print(f"\nRecurively counting files in {abspath('../../')}")
list_directory(".")
print(f"\nRecursively counting files in {abspath('../../')}")
num_py_files = 0
for file_entry in iterate_files('../../'):
if file_entry.endswith('.py'):
for file_entry in iterate_files("../"):
if file_entry.endswith(".py"):
num_py_files += 1
print(f"\t{num_py_files} python files found "
f"in {abspath('../../')}")
print(f"\t{num_py_files} python files found in {abspath('../')}")

View File

@ -21,14 +21,12 @@ leverage any available transaction logs to include additional information
otherwise available on the Window's system. This class handles both the opening
of the primary hive and attempted recovery of the transaction logs.
.. literalinclude:: ../pyforhandbook/section_02/yarp_base.py
.. literalinclude:: ../pyforhandbook/ch02_registry/yarp_base.py
:pyobject: RegistryBase
Docstring References
====================
"""
from datetime import datetime, timedelta
import struct
# Installed via:
# pip install https://github.com/msuhanov/yarp/archive/1.0.28.tar.gz
from yarp import Registry, RegistryHelpers
@ -58,20 +56,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20190707
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Registry parsing class that opens an offline hive.'''
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Registry parsing class that opens an offline hive."""
__docs__ = [
'https://github.com/msuhanov/yarp',
'https://docs.python.org/3/library/datetime.html',
'https://docs.python.org/3/library/struct.html'
"https://github.com/msuhanov/yarp",
"https://docs.python.org/3/library/datetime.html",
"https://docs.python.org/3/library/struct.html",
]
class RegistryBase():
class RegistryBase:
"""Base class containing common registry parsing code. Will open a hive
and attempt recovery using available transaction logs"""
def __init__(self, reg_file):
"""Base __init__ method, responsible for opening a hive."""
self.reg_file = reg_file
@ -92,10 +91,10 @@ class RegistryBase():
hive_path = self.hive.registry_file.file_object.name
tx_logs = RegistryHelpers.DiscoverLogFiles(hive_path)
self.tx_log_files = []
for tx_path in ['log_path', 'log1_path', 'log2_path']:
for tx_path in ["log_path", "log1_path", "log2_path"]:
log_obj = None
if getattr(tx_logs, tx_path, None):
log_obj = open(getattr(tx_logs, tx_path), 'rb')
log_obj = open(getattr(tx_logs, tx_path), "rb")
self.tx_log_files.append(log_obj)
self.hive.recover_auto(*self.tx_log_files)
@ -117,12 +116,14 @@ def main(reg_file):
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description='Registry Parsing',
description="Registry Parsing",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog=f"Built by {__author__}, v.{__date__}"
epilog=f"Built by {__author__}, v.{__date__}",
)
parser.add_argument(
"REG_FILE", help="Path to registry file", type=argparse.FileType("rb")
)
parser.add_argument('REG_FILE', help='Path to registry file',
type=argparse.FileType('rb'))
args = parser.parse_args()
main(args.REG_FILE)

View File

@ -22,7 +22,7 @@ NTUSER.DAT files, though could get more specific on Windows versions, etc. In
this class we store a few useful details including fixed values used by other
methods and metadata about the class.
.. literalinclude:: ../pyforhandbook/section_02/yarp_ntuser.py
.. literalinclude:: ../pyforhandbook/ch02_registry/yarp_ntuser.py
:pyobject: NTUSER.__init__
Reading Hive String Values
@ -30,14 +30,14 @@ Reading Hive String Values
With an open hive, we can begin to parse values from a known key location
within the hive. This method allows us to specify a key path and inspect each
of the subkeys. For each of the subkeys, we can then get the names and data
of the sub-keys. For each of the sub-keys, we can then get the names and data
associated with each value in the key. Additionally we could - if needed -
continue to recurse on subkeys here. Instead we return this cursory information
continue to recurse on sub-keys here. Instead we return this cursory information
for the caller to display as they wish. Since the values within MountPoints2
store string data, we don't need to perform further parsing of the record.
.. literalinclude:: ../pyforhandbook/section_02/yarp_ntuser.py
:pyobject: NTUSER.parse_mountpoints2
.. literalinclude:: ../pyforhandbook/ch02_registry/yarp_ntuser.py
:pyobject: NTUSER.parse_mount_points2
Reading Hive Binary Values
==========================
@ -51,8 +51,8 @@ method) using Struct to extract a timestamp and integer marking whether a
trusted macro was used. These parsed attributes are then returned to the caller
to be displayed.
.. literalinclude:: ../pyforhandbook/section_02/yarp_ntuser.py
:pyobject: NTUSER.parse_trustrecords
.. literalinclude:: ../pyforhandbook/ch02_registry/yarp_ntuser.py
:pyobject: NTUSER.parse_trust_records
Docstring References
====================
@ -61,11 +61,8 @@ Docstring References
from datetime import datetime, timedelta
import struct
import yarp
try:
from pyforhandbook.section_02.yarp_base import RegistryBase
except ImportError:
from yarp_base import RegistryBase
from pyforhandbook.ch02_registry.yarp_base import RegistryBase
"""
Copyright 2019 Chapin Bryce
@ -91,34 +88,37 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20190707
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Registry parsing class that parses the NTUSER.DAT hive.'''
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Registry parsing class that parses the NTUSER.DAT hive."""
__docs__ = [
'https://github.com/msuhanov/yarp',
'https://docs.python.org/3/library/datetime.html',
'https://docs.python.org/3/library/struct.html'
"https://github.com/msuhanov/yarp",
"https://docs.python.org/3/library/datetime.html",
"https://docs.python.org/3/library/struct.html",
]
class NTUSER(RegistryBase):
"""Class to handle the parsing of the NTUSER.DAT hive."""
def __init__(self, reg_path):
super().__init__(reg_path)
self.hive_type = 'NTUSER.DAT'
self.hive_type = "NTUSER.DAT"
self.macro_enabled_val = 2147483647
def parse_mountpoints2(self):
def parse_mount_points2(self):
"""Demonstration of parsing values from a key by path."""
key_path = ('Software\\Microsoft\\Windows\\CurrentVersion'
'\\Explorer\\MountPoints2')
key_path = (
"Software\\Microsoft\\Windows\\CurrentVersion"
"\\Explorer\\MountPoints2"
)
for mp in self.hive.find_key(key_path).subkeys():
mp_data = {}
mp_data['name'] = mp.name().replace('#', '\\')
mp_data['values'] = {x.name(): x.data() for x in mp.values()}
mp_data['last_written'] = mp.last_written_timestamp()
yield mp_data
yield {
"name": mp.name().replace("#", "\\"),
"values": {x.name(): x.data() for x in mp.values()},
"last_written": mp.last_written_timestamp(),
}
def parse_office_versions(self):
"""Get Office versions within an open Registry hive.
@ -126,10 +126,9 @@ class NTUSER(RegistryBase):
Yields:
(str): Office version number (ie. '15.0')
"""
office_versions = self.hive.find_key('Software\\Microsoft\\Office')
for subkey in office_versions.subkeys():
key_name = subkey.name()
is_ver_num = False
office_versions = self.hive.find_key("Software\\Microsoft\\Office")
for sub_key in office_versions.subkeys():
key_name = sub_key.name()
try:
_ = float(key_name)
is_ver_num = True
@ -139,52 +138,61 @@ class NTUSER(RegistryBase):
if is_ver_num:
yield key_name
def parse_trustrecords(self):
def parse_trust_records(self):
"""Demonstration of parsing binary values within a key."""
trust_record_path = 'Software\\Microsoft\\Office\\{OFFICE_VERSION}' \
'\\Word\\Security\\Trusted Documents\\TrustRecords'
trust_record_path = (
"Software\\Microsoft\\Office\\{OFFICE_VERSION}"
"\\Word\\Security\\Trusted Documents\\TrustRecords"
)
for office_version in self.parse_office_versions():
trust_rec_key = self.hive.find_key(
trust_record_path.format(OFFICE_VERSION=office_version))
trust_record_path.format(OFFICE_VERSION=office_version)
)
if not trust_rec_key:
continue
for rec in trust_rec_key.values():
date_val, macro_enabled = struct.unpack('q12xI', rec.data())
ms = date_val/10.0
date_val, macro_enabled = struct.unpack("q12xI", rec.data())
ms = date_val / 10.0
dt_date = datetime(1601, 1, 1) + timedelta(microseconds=ms)
yield {
'doc': rec.name(),
'dt': dt_date.isoformat(),
'macro': macro_enabled == self.macro_enabled_val
"doc": rec.name(),
"dt": dt_date.isoformat(),
"macro": macro_enabled == self.macro_enabled_val,
}
def main(reg_file):
reg = NTUSER(reg_file)
# Call an example parsing method and display the values from NTUSER keys
print("{:=^30}".format(' MountPoints2 '))
for mount_point in reg.parse_mountpoints2():
print("{:=^30}".format(" MountPoints2 "))
for mount_point in reg.parse_mount_points2():
print(f"Found MountPoints2 path '{mount_point['name']}' with values:")
value_str = '\tlast written time: {}\n'.format(
mount_point["last_written"].isoformat())
value_str = "\tlast written time: {}\n".format(
mount_point["last_written"].isoformat()
)
value_str += "\n".join(
[f"\t{x}: {y}" for x, y in mount_point['values'].items()])
[f"\t{x}: {y}" for x, y in mount_point["values"].items()]
)
print(value_str)
print("{:=^30}".format(' TrustRecords '))
for tr in reg.parse_trustrecords():
print("{:=^30}".format(" TrustRecords "))
for tr in reg.parse_trust_records():
print(f"Document: {tr['doc']}")
print(f"\tCreated Date: {tr['dt']}")
print(f"\tMacro Enabled: {tr['macro']}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description='Registry Parsing',
description="Registry Parsing",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog=f"Built by {__author__}, v.{__date__}"
epilog=f"Built by {__author__}, v.{__date__}",
)
parser.add_argument(
"REG_FILE", help="Path to registry file", type=argparse.FileType("rb")
)
parser.add_argument('REG_FILE', help='Path to registry file',
type=argparse.FileType('rb'))
args = parser.parse_args()
main(args.REG_FILE)
main(args.REG_FILE)

View File

@ -22,7 +22,7 @@ Open Windows Event Logs (EVTX)
This function shows an example of opening an EVTX file and parsing out several
header metadata parameters about the file.
.. literalinclude:: ../pyforhandbook/section_03/using_python_evtx.py
.. literalinclude:: ../pyforhandbook/ch03_event_logs/using_python_evtx.py
:pyobject: open_evtx
Iterate over record XML data (EVTX)
@ -38,7 +38,7 @@ which will then call the ``.lxml()`` method on the individual event record.
This requires the installation of the lxml Library, as it returns a lxml.etree
object that you can interact with.
.. literalinclude:: ../pyforhandbook/section_03/using_python_evtx.py
.. literalinclude:: ../pyforhandbook/ch03_event_logs/using_python_evtx.py
:pyobject: get_events
Filtering records within events logs
@ -55,7 +55,7 @@ filters, and return values. Some examples include:
- Identify PowerShell events and expose arguments for further processing
(ie. Base64 decoding, shellcode analysis)
.. literalinclude:: ../pyforhandbook/section_03/using_python_evtx.py
.. literalinclude:: ../pyforhandbook/ch03_event_logs/using_python_evtx.py
:pyobject: filter_events_json
Docstring References
@ -90,13 +90,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Chapin Bryce'
__author__ = "Chapin Bryce"
__date__ = 20191103
__license__ = 'MIT Copyright 2019 Chapin Bryce'
__desc__ = '''Sample script to read EVTX files.'''
__docs__ = [
'https://github.com/williballenthin/python-evtx'
]
__license__ = "MIT Copyright 2019 Chapin Bryce"
__desc__ = """Sample script to read EVTX files."""
__docs__ = ["https://github.com/williballenthin/python-evtx"]
def open_evtx(input_file):
@ -117,13 +115,15 @@ def open_evtx(input_file):
with evtx.Evtx(input_file) as open_log:
header = open_log.get_file_header()
properties = OrderedDict([
('major_version', 'File version (major)'),
('minor_version', 'File version (minor)'),
('is_dirty', 'File is ditry'),
('is_full', 'File is full'),
('next_record_number', 'Next record number')
])
properties = OrderedDict(
[
("major_version", "File version (major)"),
("minor_version", "File version (minor)"),
("is_dirty", "File is dirty"),
("is_full", "File is full"),
("next_record_number", "Next record number"),
]
)
for key, value in properties.items():
print(f"{value}: {getattr(header, key)()}")
@ -192,22 +192,23 @@ def filter_events_json(event_data, event_ids, fields=None):
event_data = evt.find("EventData", evt.nsmap)
json_data = {}
for data in event_data.getchildren():
if not fields or data.attrib['Name'] in fields:
if not fields or data.attrib["Name"] in fields:
# If we don't have a specified field filter list, print all
# Otherwise filter for only those fields within the list
json_data[data.attrib['Name']] = data.text
json_data[data.attrib["Name"]] = data.text
yield json_data
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description=__desc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog=f"Built by {__author__}, v.{__date__}"
epilog=f"Built by {__author__}, v.{__date__}",
)
parser.add_argument('EVTX_FILE', help="EVTX file to read")
parser.add_argument("EVTX_FILE", help="EVTX file to read")
args = parser.parse_args()
print("EVTX File Header Information")
@ -221,10 +222,19 @@ if __name__ == "__main__":
print("Filter for Login events")
logins = filter_events_json(
get_events(args.EVTX_FILE, parse_xml=True),
event_ids=['4624'],
fields=["SubjectUserName", "SubjectUserSid", "SubjectDomainName",
"TargetUserName", "TargetUserSid", "TargetDomainName",
"WorkstationName", "IpAddress", "IpPort", "ProcessName"]
event_ids=["4624"],
fields=[
"SubjectUserName",
"SubjectUserSid",
"SubjectDomainName",
"TargetUserName",
"TargetUserSid",
"TargetDomainName",
"WorkstationName",
"IpAddress",
"IpPort",
"ProcessName",
],
)
for login in logins:
print(json.dumps(login, indent=2))

View File

@ -17,19 +17,19 @@ This function shows an example of opening a Sqlite database with Python.
Additional information regarding Sqlite modules can be
seen at https://docs.python.org/3/library/sqlite3.html.
.. literalinclude:: ../pyforhandbook/section_06/opening_sqlite.py
.. literalinclude:: ../pyforhandbook/ch06_databases/opening_sqlite.py
:pyobject: open_sqlite
Listing Tables configuration
============================
This function shows an example of listing available tables in an opened Sqlite database.
This function shows an example of listing available tables in an opened Sqlite
database.
.. literalinclude:: ../pyforhandbook/section_06/opening_sqlite.py
.. literalinclude:: ../pyforhandbook/ch06_databases/opening_sqlite.py
:pyobject: list_tables
"""
import argparse
import os
import sqlite3
"""
@ -56,39 +56,53 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
__author__ = 'Brittney Argirakis'
__author__ = "Brittney Argirakis"
__date__ = 20191126
__license__ = 'MIT Copyright 2019 Brittney Argirakis'
__desc__ = '''Sample script to open a SqLite DB.'''
__license__ = "MIT Copyright 2019 Brittney Argirakis"
__desc__ = """Sample script to open a SqLite DB."""
__docs__ = [
'https://docs.python.org/3/library/argparse.html',
'https://docs.python.org/3/library/os.html',
'https://docs.python.org/3/library/sqlite3.html'
"https://docs.python.org/3/library/argparse.html",
"https://docs.python.org/3/library/os.html",
"https://docs.python.org/3/library/sqlite3.html",
]
def open_sqlite(inputdb):
print("Provided Database: {}".format(inputdb))
return sqlite3.connect(inputdb)
def open_sqlite(input_db):
"""Open a SQLite database
Args:
input_db: Path to a SQLite database to open
Returns:
A connection to a SQLite database
"""
print("Provided Database: {}".format(input_db))
return sqlite3.connect(input_db)
def list_tables(conn):
"""List all tables in a SQLite database
Args:
conn: An open connection from a SQLite database
Returns:
list: List of table names found in the database
"""
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master")
table_list = []
for i in cur.fetchall():
table_list.append(i[0])
return table_list
return [i[0] for i in cur.fetchall()]
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description=__desc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog=f"Built by {__author__}, v.{__date__}"
epilog=f"Built by {__author__}, v.{__date__}",
)
parser.add_argument("db", help="path to the database to read")
args = parser.parse_args()
conn = open_sqlite(args.db)
listed_tables = list_tables(conn)
connection = open_sqlite(args.db)
listed_tables = list_tables(connection)
print(listed_tables)

View File

@ -1,5 +1,5 @@
__author__ = 'Chapin Bryce'
__authors__ = ['Chapin Bryce', 'Brittney Argirakis']
__license__ = ''
__version__ = '0.1.0'
__copyright__ = '2020, Chapin Bryce'
__author__ = "Chapin Bryce"
__authors__ = ["Chapin Bryce", "Brittney Argirakis"]
__license__ = ""
__version__ = "0.1.0"
__copyright__ = "2020, Chapin Bryce"