├── tests ├── usnjrnl.zip └── tests.sh ├── .travis.yml ├── LICENSE.txt ├── setup.py ├── usncarve.py └── README.rst /tests/usnjrnl.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoorBillionaire/USN-Record-Carver/HEAD/tests/usnjrnl.zip -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.2" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "3.5-dev" 9 | - "3.6" 10 | - "3.6-dev" 11 | - "3.7-dev" 12 | - "nightly" 13 | before_install: 14 | - sudo apt-get install sleuthkit 15 | - sudo pip install usnparser 16 | install: 17 | - sudo python setup.py install 18 | script: 19 | - sh tests/tests.sh 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 Adam Witt 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /tests/tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | unzip tests/usnjrnl.zip 4 | usncarve.py -h 5 | usn.py -h 6 | usncarve.py -f usnjrnl.bin -o /tmp/usn-carved.bin 7 | echo "[ + ] Carve completed" 8 | echo "[ + ] Attempting to parse carved records" 9 | xxd /tmp/usn-carved.bin 10 | usn.py -f /tmp/usn-carved.bin -o /tmp/usn-parsed.txt 11 | cat /tmp/usn-parsed.txt 12 | rm /tmp/usn-parsed.txt 13 | usn.py --csv -f /tmp/usn-carved.bin -o /tmp/usn-parsed.txt 14 | cat /tmp/usn-parsed.txt 15 | rm /tmp/usn-parsed.txt 16 | usn.py --verbose -f /tmp/usn-carved.bin -o /tmp/usn-parsed.txt 17 | cat /tmp/usn-parsed.txt 18 | rm /tmp/usn-parsed.txt 19 | usn.py --tln -f /tmp/usn-carved.bin -o /tmp/usn-parsed.txt 20 | cat /tmp/usn-parsed.txt 21 | rm /tmp/usn-parsed.txt 22 | usn.py --tln --system ThisIsASystemName -f /tmp/usn-carved.bin -o /tmp/usn-parsed.txt 23 | cat /tmp/usn-parsed.txt 24 | rm /tmp/usn-parsed.txt 25 | usn.py --body -f /tmp/usn-carved.bin -o /tmp/usn-parsed.txt 26 | cat /tmp/usn-parsed.txt 27 | mactime -b /tmp/usn-parsed.txt 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | name='usncarve', 12 | version='1.0.1', 13 | description='A Python script to carve NTFS USN journal records from binary data', 14 | long_description=long_description, 15 | url='https://github.com/PoorBillionaire/USN-Record-Carver', 16 | author='Adam Witt', 17 | author_email='accidentalassist@gmail.com', 18 | license='Apache Software License', 19 | classifiers=[ 20 | 'Development Status :: 5 - Production/Stable', 21 | 'Intended Audience :: Information Technology', 22 | 'Topic :: Security', 23 | 'License :: OSI Approved :: Apache Software License' 24 | ], 25 | 26 | keywords='DFIR NTFS USN Carve Forensics Incident Response Microsoft Windows', 27 | packages=find_packages(), 28 | scripts=['usncarve.py'] 29 | ) 30 | -------------------------------------------------------------------------------- /usncarve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import print_function 4 | 5 | import os 6 | import sys 7 | import mmap 8 | import struct 9 | import contextlib 10 | from argparse import ArgumentParser 11 | 12 | 13 | def validate_address_space(infile): 14 | if os.path.getsize(infile) > sys.maxsize: 15 | print("\n[ - ] ERROR: The running Python process does not present a " \ 16 | "large enough address space to accommodate memory mapping the " \ 17 | "intput file. Try switching to 64-bit Python.\n", 18 | file=sys.stderr) 19 | 20 | def carve_usn_records(infile, outfile): 21 | with contextlib.closing(mmap.mmap(infile.fileno(), 0, access=mmap.ACCESS_READ)) as m: 22 | offset = 0 23 | while True: 24 | offset = m.find(b'\x00\x00\x02\x00\x00\x00', offset) 25 | if offset == -1: 26 | break 27 | 28 | if m.find(b'\x00\x3c\x00', offset + 55, offset + 58) == -1: 29 | offset +=1 30 | continue 31 | 32 | offset -= 2 33 | record_length = struct.unpack(' 570: 35 | offset += 3 36 | continue 37 | 38 | outfile.write(m[offset:offset + record_length]) 39 | offset += (record_length) 40 | 41 | def main(): 42 | p = ArgumentParser() 43 | p.add_argument("-f", "--file", help="Carve USN records from the given file", required=True) 44 | p.add_argument("-o", "--outfile", help="Output to the given file", required=True) 45 | args = p.parse_args() 46 | 47 | with open(args.file, "rb") as i: 48 | with open(args.outfile, "ab") as o: 49 | try: 50 | carve_usn_records(i, o) 51 | except ValueError: 52 | validate_address_space(args.file) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | USN-Record-Carver 2 | ===================== 3 | Python script to carve NTFS USN records from arbitrary binary data 4 | 5 | Description 6 | ------------- 7 | The NTFS USN Change journal is a volume-specific log which records metadata changes to files. It is a treasure trove of information during a forensic investigation. As the change journal reaches its maximum size, clusters of the journal's disk space are marked unallocated by the operating system to be used when needed at a later time. As with many other artifacts, USN change journal records in unallocated space can be extremely valuable. Better yet, due to the compact nature of change journal records, I routinely find millions of records outside of the file system's allocated clusters. 8 | 9 | This script will carve NTFS USN journal records from arbitrary binary data, and output to a file in binary format. The investigator can then parse these records with a tool of their own choosing. At this time the script only supports raw/dd input files. 10 | 11 | Usage and Output 12 | -------------------- 13 | Simply specify the input and output files: 14 | 15 | :: 16 | 17 | dev@computer:$ python usncarve.py -f file.raw -o usn.raw 18 | 19 | Command-Line Options 20 | ----------------------- 21 | 22 | :: 23 | 24 | usage: usncarve.py [-h] -f FILE -o OUTFILE 25 | 26 | optional arguments: 27 | -h, --help show this help message and exit 28 | -f FILE, --file FILE Carve USN records from the given file 29 | -o OUTFILE, --outfile OUTFILE 30 | Output to the given file 31 | 32 | 33 | Installation 34 | -------------- 35 | Using setup.py: 36 | 37 | :: 38 | 39 | python setup.py install 40 | 41 | Using pip: 42 | 43 | :: 44 | 45 | pip install usncarve 46 | 47 | +----------------------------------------------------------------------------------------+ 48 | | Travis-CI | 49 | +========================================================================================+ 50 | | .. image:: https://travis-ci.org/PoorBillionaire/USN-Record-Carver.svg?branch=master | 51 | | :target: https://travis-ci.org/PoorBillionaire/USN-Record-Carver | 52 | +----------------------------------------------------------------------------------------+ 53 | --------------------------------------------------------------------------------