├── .github ├── FUNDING.yml └── workflows │ ├── create-release.yml │ └── gh-pages.yml ├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── doc_src ├── .nojekyll ├── Makefile ├── _templates │ └── layout.html ├── ch01_essentials.rst ├── ch02_registry.rst ├── ch03_event_logs.rst ├── ch06_databases.rst ├── conf.py ├── index.rst └── make.bat ├── pyforhandbook ├── __init__.py ├── ch01_essentials │ ├── __init__.py │ ├── argparse_example.py │ ├── csv_example.py │ ├── logging_example.py │ ├── open_files.py │ └── recursion_example.py ├── ch02_registry │ ├── NTUSER-WIN7-trustrecords.DAT │ ├── __init__.py │ ├── yarp_base.py │ └── yarp_ntuser.py ├── ch03_event_logs │ ├── __init__.py │ └── using_python_evtx.py ├── ch06_databases │ ├── __init__.py │ └── opening_sqlite.py └── version.py ├── requirements.txt └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [chapinb] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: chapin 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.8' 18 | 19 | - name: Set env 20 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 21 | 22 | - name: Show version 23 | run: echo $RELEASE_VERSION && echo ${{ env.RELEASE_VERSION }} 24 | 25 | - name: Upgrade pip 26 | run: | 27 | # install pip=>20.1 to use "pip cache dir" 28 | python3 -m pip install --upgrade pip 29 | 30 | - name: Install dependencies 31 | run: python3 -m pip install -r ./requirements.txt 32 | 33 | - name: Build docs 34 | run: (cd doc_src && make html) 35 | 36 | - name: Compress docs 37 | run: (cd ./doc_src/_build/ && mv html pyforhandbook_html_docs_${{ env.RELEASE_VERSION }} && zip -qq -r ../../pyforhandbook_html_docs_${{ env.RELEASE_VERSION }}.zip pyforhandbook_html_docs_${{ env.RELEASE_VERSION }}) 38 | 39 | - name: Create release 40 | id: create_release 41 | uses: actions/create-release@v1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | tag_name: ${{ github.ref }} 46 | release_name: Release ${{ github.ref }} 47 | draft: false 48 | prerelease: false 49 | 50 | - name: Upload docs to release 51 | uses: actions/upload-release-asset@v1 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | upload_url: ${{ steps.create_release.outputs.upload_url }} 56 | asset_path: pyforhandbook_html_docs_${{ env.RELEASE_VERSION }}.zip 57 | asset_name: pyforhandbook_html_docs_${{ env.RELEASE_VERSION }}.zip 58 | asset_content_type: application/zip 59 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | tags: 6 | - latest 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.8' 18 | 19 | - name: Upgrade pip 20 | run: | 21 | # install pip=>20.1 to use "pip cache dir" 22 | python3 -m pip install --upgrade pip 23 | 24 | - name: Install dependencies 25 | run: python3 -m pip install -r ./requirements.txt 26 | 27 | - name: Build docs 28 | run: (cd doc_src && make html) 29 | 30 | - name: Deploy 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./doc_src/_build/html 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude locally built docs 2 | docs/ 3 | .vscode/ 4 | doc_src/_build/ 5 | .idea/ 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | venv3/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # VSCode project settings 108 | .vscode 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ 115 | 116 | # Other files 117 | *.log 118 | *.csv -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: python:3.7.3-alpine 2 | 3 | pages: 4 | script: 5 | - apk --no-cache add make gcc musl-dev git 6 | - pip install -r dev_requirements.txt 7 | - pip install -r requirements.txt 8 | - cd doc_src 9 | - make html 10 | - mv _build/html ../public 11 | artifacts: 12 | paths: 13 | - public 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Chapin Bryce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Forensics Handbook 2 | 3 | A handy reference guide for building Python scripts to help out 4 | Digital Forensic, Incident Response, and other Cyber Security 5 | tools. 6 | 7 | ## Accessing the Handbook 8 | 9 | There are several ways you can access the content in this handbook: 10 | 11 | * Online at [https://chapinb.com/python-forensics-handboook](https://chapinb.com/python-forensics-handbook) 12 | * Accessing the latest release available GitHub at 13 | [https://github.com/chapinb/python-forensics-handbook/releases](https://github.com/chapinb/python-forensics-handbook/releases) 14 | and downloading the `pyforhandbook_html_docs_{version}.zip` file. You can 15 | then extract the arvhive and open `index.html` in your web browser. 16 | * Cloning the repository and checking out the gh-pages branch. 17 | * Building it yourself! See instructions below. 18 | 19 | 20 | ## Building the handbook 21 | 22 | To build this handbook, you will need Python 3.6 or later. To start, clone the master branch (or check out 23 | a tag for the release you want to build.) Then install all requirements by running `pip install -r requirements.txt`. 24 | 25 | Once that finishes, navigate to the `doc_src/` directory and run `make html`. This will run on Windows, Linux, or macOS. 26 | After the make command completes, you can view the built documentation within the `doc_src/_build/html` folder. 27 | 28 | ## Contributing 29 | 30 | Have an idea for a section or a function that you would like to share? Please follow the above steps in 31 | "Building the handbook", add your changes, and open a pull request to get it integrated in to the repository! 32 | 33 | More details on how to contribute coming soon. In the meantime, if you have any questions about the above, feel free 34 | to open an issue on Github or reach out to me on Twitter @chapindb. 35 | -------------------------------------------------------------------------------- /doc_src/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapinb/python-forensics-handbook/6f28f39639d8add4e553f8986edc75308a914b0b/doc_src/.nojekyll -------------------------------------------------------------------------------- /doc_src/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | html: Makefile 19 | # @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" 21 | 22 | epub: Makefile 23 | @$(SPHINXBUILD) -M epub "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /doc_src/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block extrahead %} 4 | {{ super() }} 5 | 6 | 7 | 14 | {% endblock %} -------------------------------------------------------------------------------- /doc_src/ch01_essentials.rst: -------------------------------------------------------------------------------- 1 | Chapter 1 - Essential Scripts 2 | ============================== 3 | .. toctree:: 4 | :maxdepth: 2 5 | :caption: Contents: 6 | 7 | Section 1.1 - Argparse Example 8 | -------------------------------- 9 | .. automodule:: pyforhandbook.ch01_essentials.argparse_example 10 | :members: 11 | 12 | Section 1.2 - Logging Example 13 | -------------------------------- 14 | .. automodule:: pyforhandbook.ch01_essentials.logging_example 15 | :members: 16 | 17 | Section 1.3 - Open Files 18 | ------------------------ 19 | .. automodule:: pyforhandbook.ch01_essentials.open_files 20 | :members: 21 | 22 | Section 1.4 - CSV Example 23 | -------------------------------- 24 | .. automodule:: pyforhandbook.ch01_essentials.csv_example 25 | :members: 26 | 27 | Section 1.5 - Directory Recursion 28 | --------------------------------- 29 | .. automodule:: pyforhandbook.ch01_essentials.recursion_example 30 | :members: 31 | 32 | Indices and tables 33 | -------------------------------- 34 | 35 | * :ref:`genindex` 36 | * :ref:`modindex` 37 | * :ref:`search` 38 | -------------------------------------------------------------------------------- /doc_src/ch02_registry.rst: -------------------------------------------------------------------------------- 1 | Chapter 2 - Registry Parsing 2 | ============================== 3 | .. toctree:: 4 | :maxdepth: 2 5 | :caption: Contents: 6 | 7 | Section 2.1 - Opening a Hive 8 | -------------------------------- 9 | .. automodule:: pyforhandbook.ch02_registry.yarp_base 10 | :members: 11 | 12 | Section 2.2 - Parsing Hive Values 13 | ---------------------------------- 14 | .. automodule:: pyforhandbook.ch02_registry.yarp_ntuser 15 | :members: 16 | 17 | Indices and tables 18 | -------------------------------- 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /doc_src/ch03_event_logs.rst: -------------------------------------------------------------------------------- 1 | Chapter 3 - Windows Event Log Parsing 2 | ===================================== 3 | .. toctree:: 4 | :maxdepth: 2 5 | :caption: Contents: 6 | 7 | Section 3.1 - Using python-evtx 8 | ---------------------------------- 9 | .. automodule:: pyforhandbook.ch03_event_logs.using_python_evtx 10 | :members: 11 | 12 | Indices and tables 13 | -------------------------------- 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | -------------------------------------------------------------------------------- /doc_src/ch06_databases.rst: -------------------------------------------------------------------------------- 1 | Chapter 6 - Sqlite & MacOS/Mobile/Browsers 2 | ========================================== 3 | .. toctree:: 4 | :maxdepth: 2 5 | :caption: Contents: 6 | 7 | Section 6.1 - Opening Sqlite 8 | -------------------------------- 9 | .. automodule:: pyforhandbook.ch06_databases.opening_sqlite 10 | :members: 11 | 12 | Indices and tables 13 | -------------------------------- 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | -------------------------------------------------------------------------------- /doc_src/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import datetime 14 | from pyforhandbook import version as version_info 15 | 16 | # -- Project information ----------------------------------------------------- 17 | 18 | project = 'Python Forensics Handbook' 19 | copyright = version_info.__copyright__ 20 | author = version_info.__author__ 21 | 22 | # The full version, including alpha/beta/rc tags 23 | release = version_info.__version__ 24 | version = version_info.__version__ 25 | 26 | # -- General configuration --------------------------------------------------- 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | 'sphinx.ext.autodoc', 'sphinx.ext.napoleon' 33 | ] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # List of patterns, relative to source directory, that match files and 39 | # directories to ignore when looking for source files. 40 | # This pattern also affects html_static_path and html_extra_path. 41 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 42 | 43 | 44 | # -- Options for HTML output ------------------------------------------------- 45 | 46 | # The theme to use for HTML and HTML Help pages. See the documentation for 47 | # a list of builtin themes. 48 | # 49 | # html_theme = 'alabaster' 50 | 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | 59 | ## Added by CBRYCE 60 | 61 | # Hide module names in docs 62 | add_module_names = False 63 | 64 | # Allow extensions to work in epub 65 | viewcode_enable_epub = True 66 | -------------------------------------------------------------------------------- /doc_src/index.rst: -------------------------------------------------------------------------------- 1 | .. Python Forensics Handbook documentation master file, created by 2 | sphinx-quickstart on Tue May 28 07:12:54 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | The Python Forensics Handbook 7 | ============================= 8 | -------------------------------------------------------- 9 | A reference guide for developing Python scripts in DFIR 10 | -------------------------------------------------------- 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :caption: Table of Contents: 15 | 16 | ch01_essentials 17 | ch02_registry 18 | ch03_event_logs 19 | ch06_databases 20 | 21 | Handbook Sections 22 | ============================== 23 | .. automodule:: pyforhandbook 24 | :members: 25 | 26 | Indices and tables 27 | ================== 28 | 29 | * :ref:`genindex` 30 | * :ref:`modindex` 31 | * :ref:`search` 32 | -------------------------------------------------------------------------------- /doc_src/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /pyforhandbook/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. note:: 3 | IN DEVELOPMENT - More sections will release over the coming weeks/months/as 4 | time permits. Feel free to contribute as you have an idea or time to assist, 5 | otherwise stay tuned! 6 | 7 | This handbook consists of 7 sections covering common tasks for developing 8 | Python scripts for use in DFIR. Each section contains short, 9 | portable code blocks that can drop into a new script with minimal 10 | tweaking. This way, you can quickly build out your custom script 11 | without needing to re-invent the wheel each time. 12 | 13 | This handbook is not intended to be read in order - if anything 14 | this outline is the main launching point to find the correct page 15 | containing the code block you wish to reference. 16 | 17 | Please feel free to contribute your own sections with the snippets that have 18 | worked well for you, even if a similar section already exists. This handbook 19 | is hosted on GitHub at https://github.com/chapinb/python-forensics-handbook and 20 | available to read online at https://chapinb.com/python-forensics-handbook. 21 | Please consider submitting a pull request with your additions! 22 | 23 | Chapter 1 - Essential Script Elements 24 | ------------------------------------- 25 | 26 | This chapter covers code blocks that are useful across scripts 27 | and are not DFIR specific, but solid practices to integrate into 28 | projects to allow for uniformity. 29 | 30 | * Argparse 31 | - Command line parameter handling 32 | * Logging 33 | - Writing status and error messages to the console and 34 | log file 35 | * Open Files 36 | - Read text files with varying UTF encodings. 37 | * CSV Generation 38 | - For better or worse, CSV reports are very common in DFIR 39 | and this code block covers several methods for 40 | generating a CSV 41 | * Recursive File Exploration 42 | - Quick example of code to explore directories and access 43 | nested files. 44 | * Parallel Processing 45 | - Simple implementation of multithreading and multiprocessing 46 | 47 | Chapter 2 - Registry Hives 48 | ------------------------------------ 49 | 50 | In this chapter, we demonstrate how to open a registry hive, navigate through 51 | its keys, and interact with values to expose information for analysis. 52 | 53 | * Using yarp to open a single hive 54 | - Opening a hive and recovering data available in transaction logs 55 | * Parse registry hive keys and values 56 | - Building off our prior code to parse specific artifacts from an 57 | NTUSER.DAT hive, including string and binary values. Uses classes in a 58 | manner that is very flexible and permits extending functionality as 59 | needed with minimal effort. 60 | * Searching for a pattern across hive keys and values. 61 | - Looking for a provided pattern across the entire hive. 62 | 63 | Chapter 3 - Event Logs 64 | ---------------------- 65 | 66 | The functions showcased in this chapter highlight methods to access events 67 | within Windows event log files, iterating over the events, and extracting 68 | useful records for further examination. 69 | 70 | * Using python-evtx 71 | - Opening evtx files 72 | - Iterating over events 73 | * Parsing Logins 74 | - Parse out the commonly investigated 4624/4672 events 75 | 76 | Chapter 4 - Text logs 77 | --------------------- 78 | 79 | * Handling IIS Logs 80 | - Parse common fields in IIS logs into a report 81 | * Handling Syslog 82 | - Parse common syslog formats into a report 83 | * Adding in GeoIP 84 | - Function to add GeoIP recognition 85 | 86 | Chapter 5 - API calls & JSON data 87 | --------------------------------- 88 | 89 | * VirusTotal 90 | * HybridAnalysis 91 | * Manipulating JSON 92 | 93 | Chapter 6 - Databases 94 | ------------------------------------------ 95 | 96 | Databases are found within many applications and operating systems. This chapter 97 | covers methods to extract information from these common databases, along with 98 | functions that are purpose built to parse information from frequently seen 99 | database tables. 100 | 101 | * macOS Activity 102 | - KnowledgeC 103 | * Android SMS 104 | * Google Chrome History DB 105 | 106 | Chapter 7 - Opening forensic images 107 | -------------------------------------- 108 | 109 | Media acquisition and preservation formats are very common within DFIR and 110 | the ability to extract specific contents from these files leads to faster 111 | analysis and simplified usage of the tool you are building. With these functions 112 | you can read files from a forensic image and pass them straight to your other 113 | utilities for further parsing. 114 | 115 | * LibEWF 116 | - Expose an E01 as a raw image 117 | * PyTSK 118 | - Read data from a raw image (MBR) 119 | - Read data from a file (hashing) 120 | - Iterate through folders (file listing) 121 | - Perform targeted reads (file signatures) 122 | 123 | """ 124 | -------------------------------------------------------------------------------- /pyforhandbook/ch01_essentials/__init__.py: -------------------------------------------------------------------------------- 1 | """Essential Script Snippets 2 | 3 | In this section, we will cover developing essential code snippets. 4 | These code blocks are commonly used in DFIR scripting to allow 5 | for users to provide parameters to the tool, logging of progress 6 | and errors, and generation of reports. 7 | """ 8 | -------------------------------------------------------------------------------- /pyforhandbook/ch01_essentials/argparse_example.py: -------------------------------------------------------------------------------- 1 | """Example for setting up arguments for your command line utility. 2 | 3 | Example Usage: 4 | 5 | ``$ python argparse.py`` 6 | 7 | References: 8 | 9 | * https://docs.python.org/3/library/argparse.html 10 | * https://docs.python.org/3/library/os.html 11 | * https://docs.python.org/3/library/pathlib.html 12 | 13 | Argparse configuration 14 | ====================== 15 | 16 | This function shows an example of creating an argparse instance 17 | with required and optional parameters. Further, it demonstrates 18 | how to set default values and boolean arguments. the ``argparse`` 19 | module has many more features documented at 20 | https://docs.python.org/3/library/argparse.html 21 | 22 | .. literalinclude:: ../pyforhandbook/ch01_essentials/argparse_example.py 23 | :pyobject: setup_argparse 24 | 25 | """ 26 | import argparse 27 | import os 28 | from pathlib import PurePath 29 | 30 | """ 31 | Copyright 2019 Chapin Bryce 32 | 33 | Permission is hereby granted, free of charge, to any person 34 | obtaining a copy of this software and associated documentation 35 | files (the "Software"), to deal in the Software without 36 | restriction, including without limitation the rights to use, copy, 37 | modify, merge, publish, distribute, sublicense, and/or sell copies 38 | of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be 42 | included in all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 45 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 46 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 47 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 48 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 49 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 51 | DEALINGS IN THE SOFTWARE. 52 | """ 53 | 54 | __author__ = "Chapin Bryce" 55 | __date__ = 20190527 56 | __license__ = "MIT Copyright 2019 Chapin Bryce" 57 | __desc__ = """Sample script to accept command line arguments.""" 58 | __docs__ = [ 59 | "https://docs.python.org/3/library/argparse.html", 60 | "https://docs.python.org/3/library/os.html", 61 | "https://docs.python.org/3/library/pathlib.html", 62 | ] 63 | 64 | 65 | def setup_argparse(): 66 | # Setup a parser instance with common fields including a 67 | # description and epilog. The `formatter_class` instructs 68 | # argparse to show default values set for parameters. 69 | parser = argparse.ArgumentParser( 70 | description="Sample Argparse", 71 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 72 | epilog=f"Built by {__author__}, v.{__date__}", 73 | ) 74 | 75 | # The simplest form of adding an argument, the name of the 76 | # parameter and a description of its form. 77 | parser.add_argument("INPUT_FILE", help="Input file to parse") 78 | parser.add_argument("OUTPUT_FOLDER", help="Folder to store output") 79 | 80 | # An optional argument with multiple methods of specifying 81 | # the parameter. Includes a default value 82 | parser.add_argument( 83 | "-l", 84 | "--log", 85 | help="Path to log file", 86 | default=os.path.abspath( 87 | os.path.join( 88 | PurePath(__file__).parent, 89 | PurePath(__file__).name.rsplit(".", 1)[0] + ".log", 90 | ) 91 | ), 92 | ) 93 | 94 | # An optional argument which does not accept a value, instead 95 | # just modifies functionality. 96 | parser.add_argument( 97 | "-v", "--verbose", action="store_true", help="Include debug log messages" 98 | ) 99 | 100 | # Once we've specified our arguments we can parse them for 101 | # reference 102 | args = parser.parse_args() 103 | 104 | # Returning our parsed arguments for further use. 105 | return args 106 | 107 | 108 | # Only run if called directly (not imported) 109 | if __name__ == "__main__": 110 | cli_args = setup_argparse() 111 | 112 | # Show arguments 113 | print(f"Input file: {cli_args.INPUT_FILE}") 114 | print(f"Output folder: {cli_args.OUTPUT_FOLDER}") 115 | print(f"Log file: {cli_args.log}") 116 | print(f"Be verbose?: {cli_args.verbose}") 117 | -------------------------------------------------------------------------------- /pyforhandbook/ch01_essentials/csv_example.py: -------------------------------------------------------------------------------- 1 | """Example for writing datasets into CSV files. 2 | 3 | Demonstrates source datasets comprised of lists of dictionaries 4 | and lists of lists as separate functions. Example data is 5 | provided in line and will generate two identical CSVs as output. 6 | 7 | Example Usage: 8 | 9 | ``$ python csv_example.py`` 10 | 11 | References: 12 | 13 | * https://docs.python.org/3/library/csv.html 14 | * https://docs.python.org/3/library/os.html 15 | 16 | 17 | List of dictionaries to CSV 18 | =========================== 19 | 20 | Example ``data`` variable: 21 | 22 | :: 23 | 24 | [ 25 | {'name': 'apple', 'quantity': 10, 'location': 'VT'}, 26 | {'name': 'orange', 'quantity': 5, 'location': 'FL'} 27 | ] 28 | 29 | This first function shows an example of writing a list containing 30 | multiple dictionaries to a CSV file. You can optionally provide 31 | an ordered list of headers to filter what rows to show, or let the 32 | function use the keys of the first dictionary in the list to 33 | generate the header information. The latter option may produce 34 | a new order each iteration and is not preferred if you can 35 | determine the headers in advance. 36 | 37 | .. literalinclude:: ../pyforhandbook/ch01_essentials/csv_example.py 38 | :pyobject: write_csv_dicts 39 | 40 | List of ordered lists to CSV 41 | ============================ 42 | 43 | Example ``data`` variable: 44 | 45 | :: 46 | 47 | [ 48 | ['name', 'quantity', 'location'], 49 | ['apple', 10, 'VT'], 50 | ['orange', 5, 'FL'] 51 | ] 52 | 53 | This function shows an example of writing a list containing 54 | multiple lists to a CSV file. You can optionally provide 55 | an ordered list of headers, or let the function use the values 56 | of the first element in the list to generate the header 57 | information. Unlike the dictionary option, you cannot filter 58 | column data by adjusting the provided headers, you must write all 59 | columns to the CSV. 60 | 61 | .. literalinclude:: ../pyforhandbook/ch01_essentials/csv_example.py 62 | :pyobject: write_csv_lists 63 | 64 | 65 | Docstring References 66 | ==================== 67 | """ 68 | 69 | import csv 70 | 71 | """ 72 | Copyright 2019 Chapin Bryce 73 | 74 | Permission is hereby granted, free of charge, to any person 75 | obtaining a copy of this software and associated documentation 76 | files (the "Software"), to deal in the Software without 77 | restriction, including without limitation the rights to use, copy, 78 | modify, merge, publish, distribute, sublicense, and/or sell copies 79 | of the Software, and to permit persons to whom the Software is 80 | furnished to do so, subject to the following conditions: 81 | 82 | The above copyright notice and this permission notice shall be 83 | included in all copies or substantial portions of the Software. 84 | 85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 87 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 88 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 89 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 91 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 92 | DEALINGS IN THE SOFTWARE. 93 | """ 94 | 95 | __author__ = "Chapin Bryce" 96 | __date__ = 20190527 97 | __license__ = "MIT Copyright 2019 Chapin Bryce" 98 | __desc__ = """Sample script to write to CSV files.""" 99 | __docs__ = [ 100 | "https://docs.python.org/3/library/csv.html", 101 | "https://docs.python.org/3/library/os.html", 102 | ] 103 | 104 | 105 | def write_csv_dicts(outfile, data, headers=None): 106 | """Writes a list of dictionaries to a CSV file. 107 | 108 | Arguments: 109 | outfile (str): Path to output file 110 | data (list): List of dictionaries to write to file 111 | headers (list): Header row to use. If empty, will use the 112 | first dictionary in the `data` list. 113 | 114 | Example: 115 | >>> list_of_dicts = [ 116 | >>> {'name': 'apple', 'quantity': 10, 'location': 'VT'}, 117 | >>> {'name': 'orange', 'quantity': 5, 'location': 'FL'} 118 | >>> ] 119 | >>> write_csv_dicts('dict_test.csv', list_of_dicts) 120 | """ 121 | 122 | if not headers: 123 | # Use the first line of data 124 | headers = [str(x) for x in data[0].keys()] 125 | 126 | with open(outfile, "w", newline="") as open_file: 127 | # Write only provided headers, ignore others 128 | csv_file = csv.DictWriter(open_file, headers, extrasaction="ignore") 129 | csv_file.writeheader() 130 | csv_file.writerows(data) 131 | 132 | 133 | def write_csv_lists(outfile, data, headers=None): 134 | """Writes a list of lists to a CSV file. 135 | 136 | Arguments: 137 | outfile (str): Path to output file 138 | data (list): List of lists to write to file 139 | headers (list): Header row to use. If empty, will use the 140 | first list in the `data` list. 141 | 142 | Examples: 143 | >>> fields = ['name', 'quantity', 'location'] 144 | >>> list_of_lists = [ 145 | >>> ['apple', 10, 'VT'], 146 | >>> ['orange', 5, 'FL'] 147 | >>> ] 148 | >>> write_csv_lists('list_test.csv', list_of_lists, headers=fields) 149 | """ 150 | 151 | with open(outfile, "w", newline="") as open_file: 152 | # Write only provided headers, ignore others 153 | csv_file = csv.writer(open_file) 154 | for count, entry in enumerate(data): 155 | if count == 0 and headers: 156 | # If headers are defined, write them, otherwise 157 | # continue as they will be written anyways 158 | csv_file.writerow(headers) 159 | csv_file.writerow(entry) 160 | 161 | 162 | if __name__ == "__main__": 163 | sample_dict_data = [ 164 | {"id": "0", "city": "Boston", "state": "MA", "country": "USA"}, 165 | {"id": "1", "city": "New York", "state": "NY", "country": "USA"}, 166 | {"id": "2", "city": "Washington", "state": "DC", "country": "USA"}, 167 | ] 168 | 169 | write_csv_dicts("dict_test.csv", sample_dict_data) 170 | 171 | header_row = ["id", "city", "state", "country"] 172 | sample_list_data = [ 173 | ["0", "Boston", "MA", "USA"], 174 | ["1", "New York", "NY", "USA"], 175 | ["2", "Washington", "DC", "USA"], 176 | ] 177 | 178 | write_csv_lists("list_test.csv", sample_list_data, headers=header_row) 179 | -------------------------------------------------------------------------------- /pyforhandbook/ch01_essentials/logging_example.py: -------------------------------------------------------------------------------- 1 | """Example for writing logging information to the console and a 2 | log file. 3 | 4 | Example Usage: 5 | 6 | ``$ python logging_example.py`` 7 | 8 | References: 9 | 10 | * https://docs.python.org/3/library/logging.html 11 | * https://docs.python.org/3/library/os.html 12 | 13 | Logging configuration 14 | ===================== 15 | 16 | This function shows an example of creating a logging instance that 17 | writes messages to both STDERR and a file, allowing your script 18 | to write content to STDOUT uninterrupted. Additionally, you can 19 | set different logging levels for the two handlers - generally you 20 | keep debugging information in the log file while writing more 21 | critical messages to the console in STDERR. 22 | 23 | .. literalinclude:: ../pyforhandbook/ch01_essentials/logging_example.py 24 | :pyobject: setup_logging 25 | 26 | Docstring References 27 | ==================== 28 | """ 29 | import logging 30 | import sys 31 | 32 | """ 33 | Copyright 2019 Chapin Bryce 34 | 35 | Permission is hereby granted, free of charge, to any person 36 | obtaining a copy of this software and associated documentation 37 | files (the "Software"), to deal in the Software without 38 | restriction, including without limitation the rights to use, copy, 39 | modify, merge, publish, distribute, sublicense, and/or sell copies 40 | of the Software, and to permit persons to whom the Software is 41 | furnished to do so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be 44 | included in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 47 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 48 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 49 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 50 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 51 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 52 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 53 | DEALINGS IN THE SOFTWARE. 54 | """ 55 | 56 | __author__ = "Chapin Bryce" 57 | __date__ = 20190527 58 | __license__ = "MIT Copyright 2019 Chapin Bryce" 59 | __desc__ = """Sample script to display and write logging 60 | messages.""" 61 | __docs__ = [ 62 | "https://docs.python.org/3/library/logging.html", 63 | "https://docs.python.org/3/library/os.html", 64 | ] 65 | 66 | logger = logging.getLogger(name=__name__) 67 | 68 | 69 | def setup_logging(logging_obj, log_file, verbose=False): 70 | """Function to setup logging configuration and test it. 71 | 72 | Args: 73 | logging_obj: A logging instance, returned from logging.getLogger(). 74 | log_file: File path to write log messages to. 75 | verbose: Whether or not to enable the debug level in STDERR output. 76 | 77 | Examples: 78 | >>> sample_logger = logging.getLogger(name=__name__) 79 | >>> log_path = "sample.log" 80 | >>> setup_logging(sample_logger, log_path, verbose=True) 81 | >>> sample_logger.debug("This is a debug message") 82 | >>> sample_logger.info("This is an info message") 83 | >>> sample_logger.warning("This is a warning message") 84 | >>> sample_logger.error("This is a error message") 85 | >>> sample_logger.critical("This is a critical message") 86 | """ 87 | logging_obj.setLevel(logging.DEBUG) 88 | 89 | # Logging formatter. Best to keep consistent for most use cases 90 | log_format = logging.Formatter( 91 | "%(asctime)s %(filename)s %(levelname)s %(module)s " 92 | "%(funcName)s %(lineno)d %(message)s" 93 | ) 94 | 95 | # Setup STDERR logging, allowing you uninterrupted 96 | # STDOUT redirection 97 | stderr_handle = logging.StreamHandler(stream=sys.stderr) 98 | if verbose: 99 | stderr_handle.setLevel(logging.DEBUG) 100 | else: 101 | stderr_handle.setLevel(logging.INFO) 102 | stderr_handle.setFormatter(log_format) 103 | 104 | # Setup file logging 105 | file_handle = logging.FileHandler(log_file, "a") 106 | file_handle.setLevel(logging.DEBUG) 107 | file_handle.setFormatter(log_format) 108 | 109 | # Add handles 110 | logging_obj.addHandler(stderr_handle) 111 | logging_obj.addHandler(file_handle) 112 | 113 | 114 | if __name__ == "__main__": 115 | setup_logging(logger, "sample.log") 116 | logger.warning("This is a warning!") 117 | -------------------------------------------------------------------------------- /pyforhandbook/ch01_essentials/open_files.py: -------------------------------------------------------------------------------- 1 | """Example for reading data from encoded text files. 2 | 3 | Demonstrates how to handle setting the proper encoding for 4 | UTF-8, UTF-16-LE, and UTF-16-BE with the ability to easily 5 | expand to support checking other file magic values/signatures. 6 | 7 | Example Usage: 8 | 9 | ``$ python open_files.py`` 10 | 11 | References: 12 | 13 | * https://docs.python.org/3/library/io.html 14 | 15 | 16 | Open files with proper encoding 17 | =============================== 18 | 19 | This first function shows an example of opening a file after checking for a 20 | byte-order mark (BOM). While this method could be expanded to check for a file's 21 | magic value/file signature, this low-tech method will help with parsing a 22 | collection of files that may be UTF-8, UTF-16-LE, and UTF-16-BE, three very 23 | common text file encodings. Feel free to build and share on this. 24 | 25 | .. literalinclude:: ../pyforhandbook/ch01_essentials/open_files.py 26 | :pyobject: open_file 27 | 28 | Docstring References 29 | ==================== 30 | """ 31 | 32 | from io import open 33 | 34 | """ 35 | Copyright 2019 Chapin Bryce 36 | 37 | Permission is hereby granted, free of charge, to any person 38 | obtaining a copy of this software and associated documentation 39 | files (the "Software"), to deal in the Software without 40 | restriction, including without limitation the rights to use, copy, 41 | modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be 46 | included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 49 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 50 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 52 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 53 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 55 | DEALINGS IN THE SOFTWARE. 56 | """ 57 | 58 | __author__ = "Chapin Bryce" 59 | __date__ = 20191103 60 | __license__ = "MIT Copyright 2019 Chapin Bryce" 61 | __desc__ = """Sample script to read encoded text files.""" 62 | __docs__ = [ 63 | "https://docs.python.org/3/library/csv.html", 64 | "https://docs.python.org/3/library/os.html", 65 | ] 66 | 67 | 68 | def open_file(input_file): 69 | """Opens an encoded text file and prints the contents 70 | 71 | Arguments: 72 | input_file (str): Path to file to open 73 | """ 74 | 75 | test_encoding = open(input_file, "rb") 76 | bom = test_encoding.read(2) 77 | file_encoding = "utf-8" 78 | if bom == b"FEFF": 79 | file_encoding = "utf-16-le" 80 | elif bom == b"FFFE": 81 | file_encoding = "utf-16-be" 82 | 83 | with open(input_file, "r", encoding=file_encoding) as open_input_file: 84 | for raw_line in open_input_file: 85 | line = raw_line.strip() 86 | print(line) 87 | 88 | 89 | if __name__ == "__main__": 90 | import argparse 91 | 92 | parser = argparse.ArgumentParser( 93 | description=__desc__, 94 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 95 | epilog=f"Built by {__author__}, v.{__date__}", 96 | ) 97 | parser.add_argument("INPUT_FILE", help="Text file to read") 98 | args = parser.parse_args() 99 | 100 | open_file(args.INPUT_FILE) 101 | -------------------------------------------------------------------------------- /pyforhandbook/ch01_essentials/recursion_example.py: -------------------------------------------------------------------------------- 1 | """File recursion example. 2 | 3 | Demonstration of iterating through a directory to interact with 4 | files. 5 | 6 | Example Usage: 7 | 8 | ``$ python recursion_example.py`` 9 | 10 | References: 11 | 12 | * https://docs.python.org/3/library/os.html 13 | 14 | List a directory 15 | ================ 16 | 17 | This function shows an example of displaying all files and 18 | folders within a single directory. From here you can further 19 | interact with individual files and folders or iterate recursively 20 | by calling the function on identified subdirectories. 21 | 22 | .. literalinclude:: ../pyforhandbook/ch01_essentials/recursion_example.py 23 | :pyobject: list_directory 24 | 25 | List a directory recursively 26 | ============================ 27 | 28 | This function shows an example of displaying all files and 29 | folders within a all directories. You don't need to worry about 30 | additional function calls as the ``os.walk()`` method handles 31 | the recursion on subdirectories and your logic can focus on 32 | handling the processing of files. This sample shows a method of 33 | counting the number of files, subdirectories, and files ending in 34 | ".py" as an example. 35 | 36 | .. literalinclude:: ../pyforhandbook/ch01_essentials/recursion_example.py 37 | :pyobject: iterate_files 38 | 39 | """ 40 | import os 41 | 42 | """ 43 | Copyright 2019 Chapin Bryce 44 | 45 | Permission is hereby granted, free of charge, to any person 46 | obtaining a copy of this software and associated documentation 47 | files (the "Software"), to deal in the Software without 48 | restriction, including without limitation the rights to use, copy, 49 | modify, merge, publish, distribute, sublicense, and/or sell copies 50 | of the Software, and to permit persons to whom the Software is 51 | furnished to do so, subject to the following conditions: 52 | 53 | The above copyright notice and this permission notice shall be 54 | included in all copies or substantial portions of the Software. 55 | 56 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 57 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 58 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 59 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 60 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 61 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 63 | DEALINGS IN THE SOFTWARE. 64 | """ 65 | 66 | __author__ = "Chapin Bryce" 67 | __date__ = 20190527 68 | __license__ = "MIT Copyright 2019 Chapin Bryce" 69 | __desc__ = """Sample script to iterate over a folder of files.""" 70 | __docs__ = ["https://docs.python.org/3/library/os.html"] 71 | 72 | 73 | def list_directory(path): 74 | """List all file and folder entries in `path`. 75 | 76 | Args: 77 | path (str): A directory within a mounted file system. May be relative or 78 | absolute. 79 | 80 | Examples: 81 | >>> list_directory('.') 82 | 83 | """ 84 | print(f"Files and folders in '{os.path.abspath(path)}':") 85 | # Quick and easy method for listing items within a single 86 | # folder. 87 | for entry in os.listdir(path): 88 | # Print all entry names 89 | print(f"\t{entry}") 90 | 91 | 92 | def iterate_files(path): 93 | """Recursively iterate over a path, findings all files within the folder 94 | and its subdirectories. 95 | 96 | Args: 97 | path (str): A directory within a mounted file system. May be relative or 98 | absolute. 99 | 100 | Examples: 101 | >>> number_of_py_files = 0 102 | >>> for f in iterate_files('../'): 103 | ... if f.endswith('.py'): 104 | ... number_of_py_files += 1 105 | >>> print(f"\t{number_of_py_files} python files found " 106 | ... f"in {os.path.abspath('../')}") 107 | """ 108 | # Though `os.walk()` exposes a list of directories in the 109 | # current `root`, it is rarely used since we are generally 110 | # interested in the files found within the subdirectories. 111 | # For this reason, it is common to see `dirs` named `_`. 112 | # DO NOT NAME `dirs` as `dir` since `dir` is a reserved word! 113 | for root, dirs, files in os.walk(os.path.abspath(path)): 114 | # Both `dirs` and `files` are lists containing all entries 115 | # at the current `root`. 116 | for file_name in files: 117 | # To effectively reference a file, you should include 118 | # the below line which creates a full path reference 119 | # to the specific file, regardless of how nested it is 120 | # We can then hand `file_entry` off to other functions. 121 | yield os.path.join(root, file_name) 122 | 123 | 124 | if __name__ == "__main__": 125 | abspath = os.path.abspath 126 | print(f"Listing {abspath('.')}") 127 | list_directory(".") 128 | print(f"\nRecursively counting files in {abspath('../../')}") 129 | num_py_files = 0 130 | for file_entry in iterate_files("../"): 131 | if file_entry.endswith(".py"): 132 | num_py_files += 1 133 | print(f"\t{num_py_files} python files found in {abspath('../')}") 134 | -------------------------------------------------------------------------------- /pyforhandbook/ch02_registry/NTUSER-WIN7-trustrecords.DAT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapinb/python-forensics-handbook/6f28f39639d8add4e553f8986edc75308a914b0b/pyforhandbook/ch02_registry/NTUSER-WIN7-trustrecords.DAT -------------------------------------------------------------------------------- /pyforhandbook/ch02_registry/__init__.py: -------------------------------------------------------------------------------- 1 | """Registry Script Snippets 2 | 3 | Parsing registry hives is a common task for Windows host analysis. 4 | The ``yarp`` library is a robust library for parsing registry 5 | hives and this section will show examples of how to use it. 6 | """ 7 | -------------------------------------------------------------------------------- /pyforhandbook/ch02_registry/yarp_base.py: -------------------------------------------------------------------------------- 1 | """Using the `yarp` library to open Windows registry hives using a class 2 | structure that is very portable and flexible. 3 | 4 | Example Usage: 5 | 6 | ``$ python yarp_base.py {NTUSER HIVE}`` 7 | 8 | References: 9 | 10 | * https://github.com/msuhanov/yarp 11 | * https://docs.python.org/3/library/struct.html 12 | * https://docs.python.org/3/library/datetime.html 13 | 14 | 15 | Opening a Registry Hive 16 | ======================= 17 | 18 | This class demonstrates how to open a registry hive file with the `yarp` tool. 19 | This library not only allows us to open a single offline hive, but also 20 | leverage any available transaction logs to include additional information 21 | otherwise available on the Window's system. This class handles both the opening 22 | of the primary hive and attempted recovery of the transaction logs. 23 | 24 | .. literalinclude:: ../pyforhandbook/ch02_registry/yarp_base.py 25 | :pyobject: RegistryBase 26 | 27 | Docstring References 28 | ==================== 29 | """ 30 | # Installed via: 31 | # pip install https://github.com/msuhanov/yarp/archive/1.0.28.tar.gz 32 | from yarp import Registry, RegistryHelpers 33 | 34 | 35 | """ 36 | Copyright 2019 Chapin Bryce 37 | 38 | Permission is hereby granted, free of charge, to any person 39 | obtaining a copy of this software and associated documentation 40 | files (the "Software"), to deal in the Software without 41 | restriction, including without limitation the rights to use, copy, 42 | modify, merge, publish, distribute, sublicense, and/or sell copies 43 | of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be 47 | included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 50 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 51 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 52 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 53 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 54 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 56 | DEALINGS IN THE SOFTWARE. 57 | """ 58 | 59 | __author__ = "Chapin Bryce" 60 | __date__ = 20190707 61 | __license__ = "MIT Copyright 2019 Chapin Bryce" 62 | __desc__ = """Registry parsing class that opens an offline hive.""" 63 | __docs__ = [ 64 | "https://github.com/msuhanov/yarp", 65 | "https://docs.python.org/3/library/datetime.html", 66 | "https://docs.python.org/3/library/struct.html", 67 | ] 68 | 69 | 70 | class RegistryBase: 71 | """Base class containing common registry parsing code. Will open a hive 72 | and attempt recovery using available transaction logs""" 73 | 74 | def __init__(self, reg_file): 75 | """Base __init__ method, responsible for opening a hive.""" 76 | self.reg_file = reg_file 77 | self.tx_log_files = [] 78 | self.hive = None 79 | self._open_hive() 80 | 81 | def _open_hive(self): 82 | """Open a registry hive with yarp. Must be an open file object with read 83 | permissions. Will attempt to recover the hive with transaction logs if 84 | present. 85 | """ 86 | self.hive = Registry.RegistryHive(self.reg_file) 87 | self._recover_hive() 88 | 89 | def _recover_hive(self): 90 | """Search for transaction logs and attempt recovery of the hive.""" 91 | hive_path = self.hive.registry_file.file_object.name 92 | tx_logs = RegistryHelpers.DiscoverLogFiles(hive_path) 93 | self.tx_log_files = [] 94 | for tx_path in ["log_path", "log1_path", "log2_path"]: 95 | log_obj = None 96 | if getattr(tx_logs, tx_path, None): 97 | log_obj = open(getattr(tx_logs, tx_path), "rb") 98 | self.tx_log_files.append(log_obj) 99 | self.hive.recover_auto(*self.tx_log_files) 100 | 101 | def close(self): 102 | """Properly close a hive.""" 103 | self.hive = None 104 | self.reg_file.close() 105 | for log in self.tx_log_files: 106 | if log: 107 | log.close() 108 | 109 | 110 | def main(reg_file): 111 | reg = RegistryBase(reg_file) 112 | print("Hive: " + reg.hive.registry_file.file_object.name) 113 | print("Last written time: " + reg.hive.last_written_timestamp().isoformat()) 114 | print("Root key: " + reg.hive.root_key().name()) 115 | 116 | 117 | if __name__ == "__main__": 118 | import argparse 119 | 120 | parser = argparse.ArgumentParser( 121 | description="Registry Parsing", 122 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 123 | epilog=f"Built by {__author__}, v.{__date__}", 124 | ) 125 | parser.add_argument( 126 | "REG_FILE", help="Path to registry file", type=argparse.FileType("rb") 127 | ) 128 | args = parser.parse_args() 129 | main(args.REG_FILE) 130 | -------------------------------------------------------------------------------- /pyforhandbook/ch02_registry/yarp_ntuser.py: -------------------------------------------------------------------------------- 1 | """Using the `yarp` library to parse NTUSER.DAT Windows registry hives 2 | using a class structure that is very portable and flexible. Parses the 3 | MountPoints2 and TrustRecords keys for with string and binary values. 4 | 5 | Example Usage: 6 | 7 | ``$ python yarp_ntuser.py {NTUSER HIVE}`` 8 | 9 | References: 10 | 11 | * https://github.com/msuhanov/yarp 12 | * https://docs.python.org/3/library/struct.html 13 | * https://docs.python.org/3/library/datetime.html 14 | 15 | Creating a Hive Specific Parser 16 | =============================== 17 | 18 | Since we have a strong base class providing functionality to open hives, we can 19 | build hive specific parsing classes that are tailored to handle artifacts 20 | distinct to a single hive type. In this case we set up a class to handle 21 | NTUSER.DAT files, though could get more specific on Windows versions, etc. In 22 | this class we store a few useful details including fixed values used by other 23 | methods and metadata about the class. 24 | 25 | .. literalinclude:: ../pyforhandbook/ch02_registry/yarp_ntuser.py 26 | :pyobject: NTUSER.__init__ 27 | 28 | Reading Hive String Values 29 | ========================== 30 | 31 | With an open hive, we can begin to parse values from a known key location 32 | within the hive. This method allows us to specify a key path and inspect each 33 | of the sub-keys. For each of the sub-keys, we can then get the names and data 34 | associated with each value in the key. Additionally we could - if needed - 35 | continue to recurse on sub-keys here. Instead we return this cursory information 36 | for the caller to display as they wish. Since the values within MountPoints2 37 | store string data, we don't need to perform further parsing of the record. 38 | 39 | .. literalinclude:: ../pyforhandbook/ch02_registry/yarp_ntuser.py 40 | :pyobject: NTUSER.parse_mount_points2 41 | 42 | Reading Hive Binary Values 43 | ========================== 44 | 45 | Similarly to our prior example, we can get a key by path. In this case we don't 46 | have a sense of what Office versions are available in the key and have elected 47 | to iterate through each of those using the `parse_office_versions()` method. 48 | Using each of the versions, we then access the respective `TrustRecords` key. 49 | If found, we then parse the binary data (retrieved with the same `.data()` 50 | method) using Struct to extract a timestamp and integer marking whether a 51 | trusted macro was used. These parsed attributes are then returned to the caller 52 | to be displayed. 53 | 54 | .. literalinclude:: ../pyforhandbook/ch02_registry/yarp_ntuser.py 55 | :pyobject: NTUSER.parse_trust_records 56 | 57 | Docstring References 58 | ==================== 59 | """ 60 | 61 | from datetime import datetime, timedelta 62 | import struct 63 | 64 | from pyforhandbook.ch02_registry.yarp_base import RegistryBase 65 | 66 | 67 | """ 68 | Copyright 2019 Chapin Bryce 69 | 70 | Permission is hereby granted, free of charge, to any person 71 | obtaining a copy of this software and associated documentation 72 | files (the "Software"), to deal in the Software without 73 | restriction, including without limitation the rights to use, copy, 74 | modify, merge, publish, distribute, sublicense, and/or sell copies 75 | of the Software, and to permit persons to whom the Software is 76 | furnished to do so, subject to the following conditions: 77 | 78 | The above copyright notice and this permission notice shall be 79 | included in all copies or substantial portions of the Software. 80 | 81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 82 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 83 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 84 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 85 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 86 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 87 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 88 | DEALINGS IN THE SOFTWARE. 89 | """ 90 | 91 | __author__ = "Chapin Bryce" 92 | __date__ = 20190707 93 | __license__ = "MIT Copyright 2019 Chapin Bryce" 94 | __desc__ = """Registry parsing class that parses the NTUSER.DAT hive.""" 95 | __docs__ = [ 96 | "https://github.com/msuhanov/yarp", 97 | "https://docs.python.org/3/library/datetime.html", 98 | "https://docs.python.org/3/library/struct.html", 99 | ] 100 | 101 | 102 | class NTUSER(RegistryBase): 103 | """Class to handle the parsing of the NTUSER.DAT hive.""" 104 | 105 | def __init__(self, reg_path): 106 | super().__init__(reg_path) 107 | self.hive_type = "NTUSER.DAT" 108 | self.macro_enabled_val = 2147483647 109 | 110 | def parse_mount_points2(self): 111 | """Demonstration of parsing values from a key by path.""" 112 | key_path = ( 113 | "Software\\Microsoft\\Windows\\CurrentVersion" 114 | "\\Explorer\\MountPoints2" 115 | ) 116 | for mp in self.hive.find_key(key_path).subkeys(): 117 | yield { 118 | "name": mp.name().replace("#", "\\"), 119 | "values": {x.name(): x.data() for x in mp.values()}, 120 | "last_written": mp.last_written_timestamp(), 121 | } 122 | 123 | def parse_office_versions(self): 124 | """Get Office versions within an open Registry hive. 125 | 126 | Yields: 127 | (str): Office version number (ie. '15.0') 128 | """ 129 | office_versions = self.hive.find_key("Software\\Microsoft\\Office") 130 | for sub_key in office_versions.subkeys(): 131 | key_name = sub_key.name() 132 | try: 133 | _ = float(key_name) 134 | is_ver_num = True 135 | except ValueError: 136 | is_ver_num = False 137 | 138 | if is_ver_num: 139 | yield key_name 140 | 141 | def parse_trust_records(self): 142 | """Demonstration of parsing binary values within a key.""" 143 | trust_record_path = ( 144 | "Software\\Microsoft\\Office\\{OFFICE_VERSION}" 145 | "\\Word\\Security\\Trusted Documents\\TrustRecords" 146 | ) 147 | for office_version in self.parse_office_versions(): 148 | trust_rec_key = self.hive.find_key( 149 | trust_record_path.format(OFFICE_VERSION=office_version) 150 | ) 151 | if not trust_rec_key: 152 | continue 153 | 154 | for rec in trust_rec_key.values(): 155 | date_val, macro_enabled = struct.unpack("q12xI", rec.data()) 156 | ms = date_val / 10.0 157 | dt_date = datetime(1601, 1, 1) + timedelta(microseconds=ms) 158 | yield { 159 | "doc": rec.name(), 160 | "dt": dt_date.isoformat(), 161 | "macro": macro_enabled == self.macro_enabled_val, 162 | } 163 | 164 | 165 | def main(reg_file): 166 | reg = NTUSER(reg_file) 167 | # Call an example parsing method and display the values from NTUSER keys 168 | print("{:=^30}".format(" MountPoints2 ")) 169 | for mount_point in reg.parse_mount_points2(): 170 | print(f"Found MountPoints2 path '{mount_point['name']}' with values:") 171 | value_str = "\tlast written time: {}\n".format( 172 | mount_point["last_written"].isoformat() 173 | ) 174 | value_str += "\n".join( 175 | [f"\t{x}: {y}" for x, y in mount_point["values"].items()] 176 | ) 177 | print(value_str) 178 | 179 | print("{:=^30}".format(" TrustRecords ")) 180 | for tr in reg.parse_trust_records(): 181 | print(f"Document: {tr['doc']}") 182 | print(f"\tCreated Date: {tr['dt']}") 183 | print(f"\tMacro Enabled: {tr['macro']}") 184 | 185 | 186 | if __name__ == "__main__": 187 | import argparse 188 | 189 | parser = argparse.ArgumentParser( 190 | description="Registry Parsing", 191 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 192 | epilog=f"Built by {__author__}, v.{__date__}", 193 | ) 194 | parser.add_argument( 195 | "REG_FILE", help="Path to registry file", type=argparse.FileType("rb") 196 | ) 197 | args = parser.parse_args() 198 | main(args.REG_FILE) 199 | -------------------------------------------------------------------------------- /pyforhandbook/ch03_event_logs/__init__.py: -------------------------------------------------------------------------------- 1 | """Windows Event Log Snippets 2 | 3 | Parsing event logs is a common task for Windows host analysis. 4 | The ``python-evtx`` library is a robust library for parsing event logs 5 | and this section will show examples of how to leverage this library to 6 | answer common questions in the event log. 7 | """ 8 | -------------------------------------------------------------------------------- /pyforhandbook/ch03_event_logs/using_python_evtx.py: -------------------------------------------------------------------------------- 1 | """Example for opening EVTX files, iterating over events, and filtering events. 2 | 3 | Demonstrates how to open an EVTX file and get basic details about the event log. 4 | This section makes use of python-evtx, a python library for reading event log 5 | files. To install, run ``pip install python-evtx``. 6 | 7 | Other libraries for parsing these event logs exist and we welcome others to 8 | add snippets that showcase how to make use of them in reading EVTX files. 9 | 10 | Example Usage: 11 | 12 | ``$ python using_python_evtx.py System.evtx`` 13 | 14 | References: 15 | 16 | * https://github.com/williballenthin/python-evtx 17 | 18 | 19 | Open Windows Event Logs (EVTX) 20 | ============================== 21 | 22 | This function shows an example of opening an EVTX file and parsing out several 23 | header metadata parameters about the file. 24 | 25 | .. literalinclude:: ../pyforhandbook/ch03_event_logs/using_python_evtx.py 26 | :pyobject: open_evtx 27 | 28 | Iterate over record XML data (EVTX) 29 | =================================== 30 | 31 | In this function, we iterate over the records within an EVTX file and expose 32 | the raw XML. This leverages a yield generator for 33 | low impact on resources. 34 | 35 | Additionally, if you would like to parse the XML, or interact with the child 36 | elements, you can enable it by assigning the `parse_xml` parameter as True, 37 | which will then call the ``.lxml()`` method on the individual event record. 38 | This requires the installation of the lxml Library, as it returns a lxml.etree 39 | object that you can interact with. 40 | 41 | .. literalinclude:: ../pyforhandbook/ch03_event_logs/using_python_evtx.py 42 | :pyobject: get_events 43 | 44 | Filtering records within events logs 45 | ==================================== 46 | 47 | Now that we have :func:`get_events()`, we can begin to perform operations on 48 | the newly accessible data. In this function, we extract information from the 49 | LXML object, and use that to filter results based on Event ID and other fields 50 | within the results. You can easily extend this to support other fields, 51 | filters, and return values. Some examples include: 52 | 53 | - extracting all login and logoff events, with their session identifiers, 54 | then calculating the session durations 55 | - Identify PowerShell events and expose arguments for further processing 56 | (ie. Base64 decoding, shellcode analysis) 57 | 58 | .. literalinclude:: ../pyforhandbook/ch03_event_logs/using_python_evtx.py 59 | :pyobject: filter_events_json 60 | 61 | Docstring References 62 | ==================== 63 | """ 64 | import json 65 | from collections import OrderedDict 66 | import Evtx.Evtx as evtx 67 | 68 | 69 | """ 70 | Copyright 2019 Chapin Bryce 71 | 72 | Permission is hereby granted, free of charge, to any person 73 | obtaining a copy of this software and associated documentation 74 | files (the "Software"), to deal in the Software without 75 | restriction, including without limitation the rights to use, copy, 76 | modify, merge, publish, distribute, sublicense, and/or sell copies 77 | of the Software, and to permit persons to whom the Software is 78 | furnished to do so, subject to the following conditions: 79 | 80 | The above copyright notice and this permission notice shall be 81 | included in all copies or substantial portions of the Software. 82 | 83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 84 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 85 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 86 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 87 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 88 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 89 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 90 | DEALINGS IN THE SOFTWARE. 91 | """ 92 | 93 | __author__ = "Chapin Bryce" 94 | __date__ = 20191103 95 | __license__ = "MIT Copyright 2019 Chapin Bryce" 96 | __desc__ = """Sample script to read EVTX files.""" 97 | __docs__ = ["https://github.com/williballenthin/python-evtx"] 98 | 99 | 100 | def open_evtx(input_file): 101 | """Opens a Windows Event Log and displays common log parameters. 102 | 103 | Arguments: 104 | input_file (str): Path to evtx file to open 105 | 106 | Examples: 107 | >>> open_evtx("System.evtx") 108 | File version (major): 3 109 | File version (minor): 1 110 | File is ditry: True 111 | File is full: False 112 | Next record number: 10549 113 | 114 | """ 115 | 116 | with evtx.Evtx(input_file) as open_log: 117 | header = open_log.get_file_header() 118 | properties = OrderedDict( 119 | [ 120 | ("major_version", "File version (major)"), 121 | ("minor_version", "File version (minor)"), 122 | ("is_dirty", "File is dirty"), 123 | ("is_full", "File is full"), 124 | ("next_record_number", "Next record number"), 125 | ] 126 | ) 127 | 128 | for key, value in properties.items(): 129 | print(f"{value}: {getattr(header, key)()}") 130 | 131 | 132 | def get_events(input_file, parse_xml=False): 133 | """Opens a Windows Event Log and returns XML information from 134 | the event record. 135 | 136 | Arguments: 137 | input_file (str): Path to evtx file to open 138 | parse_xml (bool): If True, return an lxml object, otherwise a string 139 | 140 | Yields: 141 | (generator): XML information in object or string format 142 | 143 | Examples: 144 | >>> for event_xml in enumerate(get_events("System.evtx")): 145 | >>> print(event_xml) 146 | 147 | """ 148 | with evtx.Evtx(input_file) as event_log: 149 | for record in event_log.records(): 150 | if parse_xml: 151 | yield record.lxml() 152 | else: 153 | yield record.xml() 154 | 155 | 156 | def filter_events_json(event_data, event_ids, fields=None): 157 | """Provide events where the event id is found within the provided list 158 | of event ids. If found, it will return a JSON formatted object per event. 159 | 160 | If a list of fields are provided, it will filter the resulting JSON event 161 | object to contain only those fields. 162 | 163 | Arguments: 164 | event_data (genertor): Iterable containing event data as XML. Preferably 165 | the result of the :func:`get_events()` method. 166 | event_ids (list): A list of event identifiers. Each element should be a 167 | string value, even though the identifier is an integer. 168 | fields (list): Collection of fields from the XML data to include in the 169 | JSON output. Only supports top-level fields. 170 | 171 | Yields: 172 | (dict): A dictionary containing the filtered record information 173 | 174 | Example: 175 | 176 | >>> filtered_logins = filter_events_json( 177 | >>> get_events("System.evtx", parse_xml=True), 178 | >>> event_ids=['4624', '4625'], 179 | >>> fields=["SubjectUserName", "SubjectUserSid", 180 | >>> "SubjectDomainName", "TargetUserName", "TargetUserSid", 181 | >>> "TargetDomainName", "WorkstationName", "IpAddress", 182 | >>> "IpPort", "ProcessName"] 183 | >>> ) 184 | >>> for filtered_login in filtered_logins: 185 | >>> print(json.dumps(filtered_login, indent=2)) 186 | 187 | """ 188 | for evt in event_data: 189 | system_tag = evt.find("System", evt.nsmap) 190 | event_id = system_tag.find("EventID", evt.nsmap) 191 | if event_id.text in event_ids: 192 | event_data = evt.find("EventData", evt.nsmap) 193 | json_data = {} 194 | for data in event_data.getchildren(): 195 | if not fields or data.attrib["Name"] in fields: 196 | # If we don't have a specified field filter list, print all 197 | # Otherwise filter for only those fields within the list 198 | json_data[data.attrib["Name"]] = data.text 199 | 200 | yield json_data 201 | 202 | 203 | if __name__ == "__main__": 204 | import argparse 205 | 206 | parser = argparse.ArgumentParser( 207 | description=__desc__, 208 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 209 | epilog=f"Built by {__author__}, v.{__date__}", 210 | ) 211 | parser.add_argument("EVTX_FILE", help="EVTX file to read") 212 | args = parser.parse_args() 213 | 214 | print("EVTX File Header Information") 215 | open_evtx(args.EVTX_FILE) 216 | print("EVTX File records") 217 | for count, event in enumerate(get_events(args.EVTX_FILE)): 218 | if count >= 3: 219 | break 220 | print(event) 221 | 222 | print("Filter for Login events") 223 | logins = filter_events_json( 224 | get_events(args.EVTX_FILE, parse_xml=True), 225 | event_ids=["4624"], 226 | fields=[ 227 | "SubjectUserName", 228 | "SubjectUserSid", 229 | "SubjectDomainName", 230 | "TargetUserName", 231 | "TargetUserSid", 232 | "TargetDomainName", 233 | "WorkstationName", 234 | "IpAddress", 235 | "IpPort", 236 | "ProcessName", 237 | ], 238 | ) 239 | for login in logins: 240 | print(json.dumps(login, indent=2)) 241 | -------------------------------------------------------------------------------- /pyforhandbook/ch06_databases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapinb/python-forensics-handbook/6f28f39639d8add4e553f8986edc75308a914b0b/pyforhandbook/ch06_databases/__init__.py -------------------------------------------------------------------------------- /pyforhandbook/ch06_databases/opening_sqlite.py: -------------------------------------------------------------------------------- 1 | """Example for opening and exploring Sqlite databases. 2 | 3 | Example Usage: 4 | 5 | ``$ python opening_sqlite.py history_db`` 6 | 7 | References: 8 | 9 | * https://docs.python.org/3/library/argparse.html 10 | * https://docs.python.org/3/library/os.html 11 | * https://docs.python.org/3/library/sqlite3.html 12 | 13 | Opening Sqlite configuration 14 | ============================ 15 | 16 | This function shows an example of opening a Sqlite database with Python. 17 | Additional information regarding Sqlite modules can be 18 | seen at https://docs.python.org/3/library/sqlite3.html. 19 | 20 | .. literalinclude:: ../pyforhandbook/ch06_databases/opening_sqlite.py 21 | :pyobject: open_sqlite 22 | 23 | Listing Tables configuration 24 | ============================ 25 | 26 | This function shows an example of listing available tables in an opened Sqlite 27 | database. 28 | 29 | .. literalinclude:: ../pyforhandbook/ch06_databases/opening_sqlite.py 30 | :pyobject: list_tables 31 | """ 32 | import argparse 33 | import sqlite3 34 | 35 | """ 36 | Copyright 2019 Brittney Argirakis 37 | 38 | Permission is hereby granted, free of charge, to any person 39 | obtaining a copy of this software and associated documentation 40 | files (the "Software"), to deal in the Software without 41 | restriction, including without limitation the rights to use, copy, 42 | modify, merge, publish, distribute, sublicense, and/or sell copies 43 | of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be 47 | included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 50 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 51 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 52 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 53 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 54 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 56 | DEALINGS IN THE SOFTWARE. 57 | """ 58 | 59 | __author__ = "Brittney Argirakis" 60 | __date__ = 20191126 61 | __license__ = "MIT Copyright 2019 Brittney Argirakis" 62 | __desc__ = """Sample script to open a SqLite DB.""" 63 | __docs__ = [ 64 | "https://docs.python.org/3/library/argparse.html", 65 | "https://docs.python.org/3/library/os.html", 66 | "https://docs.python.org/3/library/sqlite3.html", 67 | ] 68 | 69 | 70 | def open_sqlite(input_db): 71 | """Open a SQLite database 72 | 73 | Args: 74 | input_db: Path to a SQLite database to open 75 | 76 | Returns: 77 | A connection to a SQLite database 78 | """ 79 | print("Provided Database: {}".format(input_db)) 80 | return sqlite3.connect(input_db) 81 | 82 | 83 | def list_tables(conn): 84 | """List all tables in a SQLite database 85 | 86 | Args: 87 | conn: An open connection from a SQLite database 88 | 89 | Returns: 90 | list: List of table names found in the database 91 | """ 92 | cur = conn.cursor() 93 | cur.execute("SELECT name FROM sqlite_master") 94 | return [i[0] for i in cur.fetchall()] 95 | 96 | 97 | if __name__ == "__main__": 98 | parser = argparse.ArgumentParser( 99 | description=__desc__, 100 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 101 | epilog=f"Built by {__author__}, v.{__date__}", 102 | ) 103 | parser.add_argument("db", help="path to the database to read") 104 | args = parser.parse_args() 105 | connection = open_sqlite(args.db) 106 | listed_tables = list_tables(connection) 107 | 108 | print(listed_tables) 109 | -------------------------------------------------------------------------------- /pyforhandbook/version.py: -------------------------------------------------------------------------------- 1 | __author__ = "Chapin Bryce" 2 | __authors__ = ["Chapin Bryce", "Brittney Argirakis"] 3 | __license__ = "MIT" 4 | __version__ = "0.1.2" 5 | __copyright__ = "2020, Chapin Bryce" 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | alabaster==0.7.12 3 | Sphinx==3.2.1 4 | sphinx-rtd-theme==0.5.0 5 | black==20.8b1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | from pyforhandbook import version 4 | 5 | setuptools.setup( 6 | name="pyforhandbook", 7 | version=version.__version__, 8 | author=version.__author__, 9 | author_email="python@chapinb.com", 10 | description="Handbook of Python functions for rapid development " 11 | "of forensic tools.", 12 | url="https://chapinb.com/python-forensic-handbook", 13 | packages=setuptools.find_packages(), 14 | install_requires=[ 15 | 'yarp @ git+https://github.com/msuhanov/yarp@1.0.28#egg=yarp', 16 | 'python-evtx==0.6.1', 17 | 'lxml==4.5.2', 18 | ], 19 | classifiers=[ 20 | "Programming Language :: Python :: 3.5", 21 | "Programming Language :: Python :: 3.6", 22 | "Programming Language :: Python :: 3.7", 23 | "Programming Language :: Python :: 3.8", 24 | "Programming Language :: Python :: 3 :: Only", 25 | "License :: OSI Approved :: MIT License", 26 | "Operating System :: OS Independent", 27 | "Development Status :: 5 - Production/Stable", 28 | "Intended Audience :: Telecommunications Industry", 29 | "Intended Audience :: System Administrators", 30 | "Intended Audience :: Other Audience", 31 | "Intended Audience :: Information Technology", 32 | "Intended Audience :: Developers", 33 | "Intended Audience :: Education", 34 | "Natural Language :: English", 35 | "Topic :: Scientific/Engineering :: Information Analysis", 36 | "Topic :: Security", 37 | "Topic :: Utilities", 38 | "Topic :: Documentation", 39 | "Topic :: Software Development :: Documentation" 40 | ] 41 | ) 42 | --------------------------------------------------------------------------------