├── Chapter01 ├── comments.py ├── hello.py └── hellov2.py ├── Chapter02 ├── argument_parser.py ├── unix_converter.py ├── usb_lookup.py └── user_input.py ├── Chapter03 ├── setupapi_parser.py ├── setupapi_parser_v1.py ├── setupapi_parser_v2.py └── usb_lookup.py ├── Chapter04 ├── bitcoin_address_lookup.py ├── bitcoin_address_lookup.v1.py ├── bitcoin_address_lookup.v2.py ├── book.json ├── book.xml └── unix_converter.py ├── Chapter05 ├── file_lister.py └── file_lister_peewee.py ├── Chapter06 ├── .DS_Store ├── Neguhe Qrag.bin ├── Writers │ ├── __init__.py │ ├── csv_writer.py │ └── xlsx_writer.py ├── rot13.py ├── simplexlsx.v1.py ├── simplexlsx.v2.py ├── simplexlsx.v3.py └── userassist_parser.py ├── Chapter07 ├── TEST_DATA_README.md ├── fuzzy_hasher.py ├── hashing_example.py ├── ssdeep_python.py └── test_data │ ├── file_1 │ ├── file_1a │ ├── file_2 │ ├── file_2a │ ├── file_3 │ └── file_3a ├── Chapter08 ├── .DS_Store ├── img_42.jpg ├── metadata_parser.py ├── plugins │ ├── __init__.py │ ├── exif_parser.py │ ├── id3_parser.py │ └── office_parser.py ├── processors │ ├── __init__.py │ └── utility.py └── writers │ ├── __init__.py │ ├── csv_writer.py │ └── kml_writer.py ├── Chapter09 └── date_decoder.py ├── Chapter10 └── pysysinfo.py ├── Chapter11 ├── Dockerfile ├── docker_libs │ ├── LIBPFF-LICENSE.txt │ └── usr │ │ ├── lib │ │ └── python2.7 │ │ │ └── dist-packages │ │ │ ├── pypff.a │ │ │ ├── pypff.la │ │ │ └── pypff.so │ │ └── local │ │ └── lib │ │ ├── libpff.a │ │ ├── libpff.la │ │ └── libpff.so.1.0.0 ├── pst_indexer.py └── stats_template.html ├── Chapter12 ├── places.sqlite-wal └── wal_crawler.py ├── Chapter13 ├── .DS_Store ├── __MACOSX │ └── chapter_13 │ │ ├── ._.DS_Store │ │ ├── ._requirements.txt │ │ └── plugins │ │ └── ._.DS_Store ├── chapter_13 │ ├── .DS_Store │ ├── framework.py │ ├── plugins │ │ ├── .DS_Store │ │ ├── __init__.py │ │ ├── exif.py │ │ ├── helper │ │ │ ├── __init__.py │ │ │ ├── usb_lookup.py │ │ │ └── utility.py │ │ ├── id3.py │ │ ├── office.py │ │ ├── pst_indexer.py │ │ ├── setupapi.py │ │ ├── userassist.py │ │ └── wal_crawler.py │ ├── requirements.txt │ └── writers │ │ ├── __init__.py │ │ ├── csv_writer.py │ │ ├── kml_writer.py │ │ └── xlsx_writer.py ├── framework.py ├── plugins │ ├── .DS_Store │ ├── __init__.py │ ├── exif.py │ ├── helper │ │ ├── __init__.py │ │ ├── usb_lookup.py │ │ └── utility.py │ ├── id3.py │ ├── office.py │ ├── pst_indexer.py │ ├── setupapi.py │ ├── userassist.py │ └── wal_crawler.py ├── requirements.txt └── writers │ ├── __init__.py │ ├── csv_writer.py │ ├── kml_writer.py │ └── xlsx_writer.py ├── LICENSE └── README.md /Chapter01/comments.py: -------------------------------------------------------------------------------- 1 | # This is a comment 2 | print(5 + 5) # This is an inline comment. Everything to the right of the # symbol does not get executed 3 | """We can use three quotes to create 4 | multi-line comments.""" 5 | -------------------------------------------------------------------------------- /Chapter01/hello.py: -------------------------------------------------------------------------------- 1 | print("Hello World!") -------------------------------------------------------------------------------- /Chapter01/hellov2.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | print("Hello World!") -------------------------------------------------------------------------------- /Chapter02/argument_parser.py: -------------------------------------------------------------------------------- 1 | """Sample argparse example.""" 2 | from __future__ import print_function 3 | import argparse 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | __authors__ = ["Chapin Bryce", "Preston Miller"] 34 | __date__ = 20181027 35 | __description__ = "Argparse command-line parser sample" 36 | 37 | 38 | def main(args): 39 | print(args) 40 | 41 | 42 | if __name__ == '__main__': 43 | parser = argparse.ArgumentParser( 44 | description=__description__, 45 | epilog='Built by {}. Version {}'.format( 46 | ", ".join(__authors__), __date__), 47 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 48 | ) 49 | 50 | # Add positional required arguments 51 | parser.add_argument('timezone', help='timezone to apply') 52 | 53 | # Add non-positional required argument 54 | parser.add_argument('--source', 55 | help='source information', required=True) 56 | 57 | # Add optional arguments, allowing shorthand argument 58 | parser.add_argument('-l', '--log', help='Path to log file') 59 | 60 | # Using actions 61 | parser.add_argument('--no-email', 62 | help='disable emails', action="store_false") 63 | parser.add_argument('--send-email', 64 | help='enable emails', action="store_true") 65 | # Append values for each argument instance. 66 | parser.add_argument('--emails', 67 | help='email addresses to notify', action="append") 68 | # Count the number of instances. i.e. -vvv 69 | parser.add_argument('-v', help='add verbosity', action='count') 70 | 71 | # Defaults 72 | parser.add_argument('--length', default=55, type=int) 73 | parser.add_argument('--name', default='Alfred', type=str) 74 | 75 | # Handling Files 76 | parser.add_argument('input_file', type=argparse.FileType('r')) 77 | parser.add_argument('output_file', type=argparse.FileType('w')) 78 | 79 | # Allow only specified choices 80 | parser.add_argument('--file-type', 81 | choices=['E01', 'RAW', 'Ex01']) 82 | 83 | # Parsing defined arguments 84 | arguments = parser.parse_args() 85 | main(arguments) 86 | -------------------------------------------------------------------------------- /Chapter02/unix_converter.py: -------------------------------------------------------------------------------- 1 | """Script to convert UNIX timestamps.""" 2 | from __future__ import print_function 3 | import datetime 4 | import sys 5 | 6 | if sys.version_info[0] == 3: 7 | get_input = input 8 | elif sys.version_info[0] == 2: 9 | get_input = raw_input 10 | else: 11 | raise NotImplementedError( 12 | "Unsupported version of Python used.") 13 | 14 | """ 15 | MIT License 16 | Copyright (c) 2018 Chapin Bryce, Preston Miller 17 | Please share comments and questions at: 18 | https://github.com/PythonForensics/Learning-Python-for-Forensics 19 | or email pyforcookbook@gmail.com 20 | 21 | Permission is hereby granted, free of charge, to any person 22 | obtaining a copy of this software and associated documentation 23 | files (the "Software"), to deal in the Software without 24 | restriction, including without limitation the rights to use, 25 | copy, modify, merge, publish, distribute, sublicense, and/or 26 | sell copies of the Software, and to permit persons to whom the 27 | Software is furnished to do so, subject to the following 28 | conditions: 29 | 30 | The above copyright notice and this permission notice shall be 31 | included in all copies or substantial portions of the Software. 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 34 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 36 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 37 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 38 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 39 | OTHER DEALINGS IN THE SOFTWARE. 40 | """ 41 | 42 | __authors__ = ["Chapin Bryce", "Preston Miller"] 43 | __date__ = 20181027 44 | __description__ = """Convert unix formatted timestamps (seconds 45 | since Epoch [1970-01-01 00:00:00]) to human readable.""" 46 | 47 | 48 | def main(): 49 | unix_ts = int(get_input('Unix timestamp to convert:\n>> ')) 50 | print(unix_converter(unix_ts)) 51 | 52 | 53 | def unix_converter(timestamp): 54 | date_ts = datetime.datetime.utcfromtimestamp(timestamp) 55 | return date_ts.strftime('%m/%d/%Y %I:%M:%S %p') 56 | 57 | if __name__ == '__main__': 58 | main() 59 | -------------------------------------------------------------------------------- /Chapter02/usb_lookup.py: -------------------------------------------------------------------------------- 1 | """Script to lookup USB vendor and product values.""" 2 | from __future__ import print_function 3 | try: 4 | from urllib2 import urlopen 5 | except ImportError: 6 | from urllib.request import urlopen 7 | import argparse 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ 36 | 37 | __authors__ = ["Chapin Bryce", "Preston Miller"] 38 | __date__ = 20181027 39 | __description__ = "USB vid/pid lookup utility" 40 | 41 | 42 | def main(vid, pid): 43 | url = 'http://www.linux-usb.org/usb.ids' 44 | usbs = {} 45 | usb_file = urlopen(url) 46 | curr_id = '' 47 | 48 | for line in usb_file: 49 | if isinstance(line, bytes): 50 | line = line.decode('latin-1') 51 | if line.startswith('#') or line in ('\n', '\t'): 52 | continue 53 | else: 54 | if not(line.startswith('\t')) and line[0].isalnum(): 55 | uid, name = line.strip().split(' ', 1) 56 | curr_id = uid 57 | usbs[uid] = [name.strip(), {}] 58 | elif line.startswith('\t') and line.count('\t') == 1: 59 | uid, name = line.strip().split(' ', 1) 60 | usbs[curr_id][1][uid] = name.strip() 61 | 62 | search_key(vid, pid, usbs) 63 | 64 | 65 | def search_key(vendor_key, product_key, usb_dict): 66 | vendor = usb_dict.get(vendor_key, None) 67 | if vendor is None: 68 | print('Vendor ID not found') 69 | exit() 70 | 71 | product = vendor[1].get(product_key, None) 72 | if product is None: 73 | print('Vendor: {}\nProduct Id not found.'.format( 74 | vendor[0])) 75 | exit(0) 76 | 77 | print('Vendor: {}\nProduct: {}'.format(vendor[0], product)) 78 | 79 | 80 | if __name__ == '__main__': 81 | parser = argparse.ArgumentParser( 82 | description=__description__, 83 | epilog='Built by {}. Version {}'.format( 84 | ", ".join(__authors__), __date__), 85 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 86 | ) 87 | parser.add_argument('vid', help="VID value") 88 | parser.add_argument('pid', help="pID value") 89 | args = parser.parse_args() 90 | main(args.vid, args.pid) 91 | -------------------------------------------------------------------------------- /Chapter02/user_input.py: -------------------------------------------------------------------------------- 1 | """Replicate user input in the console.""" 2 | from __future__ import print_function 3 | import sys 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | __authors__ = ["Chapin Bryce", "Preston Miller"] 34 | __date__ = 20181027 35 | __description__ = "Replicate user input in the console" 36 | 37 | 38 | def main(): 39 | args = sys.argv 40 | print('Script:', args[0]) 41 | args.pop(0) 42 | for i, argument in enumerate(sys.argv): 43 | print('Argument {}: {}'.format(i, argument)) 44 | print('Type: {}'.format(type(argument))) 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /Chapter03/setupapi_parser.py: -------------------------------------------------------------------------------- 1 | """Third iteration of the setupapi.dev.log parser.""" 2 | from __future__ import print_function 3 | import argparse 4 | from io import open 5 | import os 6 | import sys 7 | import usb_lookup 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ 36 | 37 | __authors__ = ["Chapin Bryce", "Preston Miller"] 38 | __date__ = 20181027 39 | __description__ = """This scripts reads a Windows 7 Setup API 40 | log and prints USB Devices to the user""" 41 | 42 | def main(in_file, local_usb_ids=None): 43 | """ 44 | Main function to handle operation 45 | :param in_file: Str - Path to setupapi log to analyze 46 | :return: None 47 | """ 48 | 49 | if os.path.isfile(in_file): 50 | device_information = parse_setupapi(in_file) 51 | usb_ids = prep_usb_lookup(local_usb_ids) 52 | for device in device_information: 53 | parsed_info = parse_device_info(device) 54 | if isinstance(parsed_info, dict): 55 | parsed_info = get_device_names(usb_ids, 56 | parsed_info) 57 | if parsed_info is not None: 58 | print_output(parsed_info) 59 | print('\n\n{} parsed and printed successfully.'.format( 60 | in_file)) 61 | 62 | else: 63 | print("Input: {} was not found. Please check your path " 64 | "and permissions.".format(in_file)) 65 | sys.exit(1) 66 | 67 | 68 | def parse_setupapi(setup_log): 69 | """ 70 | Read data from provided file for Device Install Events for 71 | USB Devices 72 | :param setup_log: str - Path to valid setup api log 73 | :return: tuple of str - Device name and date 74 | """ 75 | device_list = list() 76 | unique_list = set() 77 | with open(setup_log) as in_file: 78 | for line in in_file: 79 | lower_line = line.lower() 80 | if 'device install (hardware initiated)' in \ 81 | lower_line and ('vid' in lower_line or 82 | 'ven' in lower_line): 83 | device_name = line.split('-')[1].strip() 84 | date = next(in_file).split('start')[1].strip() 85 | if device_name not in unique_list: 86 | device_list.append((device_name, date)) 87 | unique_list.add(device_name) 88 | 89 | return device_list 90 | 91 | 92 | def parse_device_info(device_info): 93 | """ 94 | Parses Vendor, Product, Revision and UID from a Setup API 95 | entry 96 | :param device_info: string of device information to parse 97 | :return: dictionary of parsed information or original string 98 | if error 99 | """ 100 | # Initialize variables 101 | vid = '' 102 | pid = '' 103 | rev = '' 104 | uid = '' 105 | 106 | # Split string into segments on \\ 107 | segments = device_info[0].split('\\') 108 | 109 | if 'usb' not in segments[0].lower(): 110 | return None 111 | # Eliminate non-USB devices from output 112 | # May hide other storage devices 113 | 114 | for item in segments[1].split('&'): 115 | lower_item = item.lower() 116 | if 'ven' in lower_item or 'vid' in lower_item: 117 | vid = item.split('_', 1)[-1] 118 | elif 'dev' in lower_item or 'pid' in lower_item or \ 119 | 'prod' in lower_item: 120 | pid = item.split('_', 1)[-1] 121 | elif 'rev' in lower_item or 'mi' in lower_item: 122 | rev = item.split('_', 1)[-1] 123 | 124 | if len(segments) >= 3: 125 | uid = segments[2].strip(']') 126 | 127 | if vid != '' or pid != '': 128 | return {'Vendor ID': vid.lower(), 129 | 'Product ID': pid.lower(), 130 | 'Revision': rev, 131 | 'UID': uid, 132 | 'First Installation Date': device_info[1]} 133 | # Unable to parse data, returning whole string 134 | return device_info 135 | 136 | 137 | def prep_usb_lookup(local_usb_ids=None): 138 | """ 139 | Prepare the lookup of USB devices through accessing the most 140 | recent copy of the database at http://linux-usb.org/usb.ids 141 | or using the provided file and parsing it into a queriable 142 | dictionary format. 143 | """ 144 | if local_usb_ids: 145 | usb_file = open(local_usb_ids, encoding='latin1') 146 | else: 147 | usb_file = usb_lookup.get_usb_file() 148 | return usb_lookup.parse_file(usb_file) 149 | 150 | 151 | def get_device_names(usb_dict, device_info): 152 | """ 153 | Query `usb_lookup.py` for device information based on VID/PID. 154 | :param usb_dict: Dictionary from usb_lookup.py of known 155 | devices. 156 | :param device_info: Dictionary containing 'Vendor ID' and 157 | 'Product ID' keys and values. 158 | :return: original dictionary with 'Vendor Name' and 159 | 'Product Name' keys and values 160 | """ 161 | device_name = usb_lookup.search_key( 162 | usb_dict, [device_info['Vendor ID'], 163 | device_info['Product ID']]) 164 | 165 | device_info['Vendor Name'] = device_name[0] 166 | device_info['Product Name'] = device_name[1] 167 | 168 | return device_info 169 | 170 | 171 | def print_output(usb_information): 172 | """ 173 | Print formatted information about USB Device 174 | :param usb_information: dictionary containing key/value 175 | data about each device or tuple of device information 176 | :return: None 177 | """ 178 | print('{:-^15}'.format('')) 179 | 180 | if isinstance(usb_information, dict): 181 | for key_name, value_name in usb_information.items(): 182 | print('{}: {}'.format(key_name, value_name)) 183 | elif isinstance(usb_information, tuple): 184 | print('Device: {}'.format(usb_information[0])) 185 | print('Date: {}'.format(usb_information[1])) 186 | 187 | if __name__ == '__main__': 188 | # Run this code if the script is run from the command line. 189 | parser = argparse.ArgumentParser( 190 | description=__description__, 191 | epilog='Built by {}. Version {}'.format( 192 | ", ".join(__authors__), __date__), 193 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 194 | ) 195 | 196 | parser.add_argument('IN_FILE', 197 | help='Windows 7 SetupAPI file') 198 | parser.add_argument('--local', 199 | help='Path to local usb.ids file') 200 | 201 | args = parser.parse_args() 202 | 203 | # Run main program 204 | main(args.IN_FILE, args.local) 205 | -------------------------------------------------------------------------------- /Chapter03/setupapi_parser_v1.py: -------------------------------------------------------------------------------- 1 | """First iteration of the setupapi.dev.log parser.""" 2 | from __future__ import print_function 3 | from io import open 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | __authors__ = ["Chapin Bryce", "Preston Miller"] 34 | __date__ = 20181027 35 | __description__ = """This scripts reads a Windows 7 Setup API 36 | log and prints USB Devices to the user""" 37 | 38 | 39 | def main(): 40 | """ 41 | Primary controller for script. 42 | :return: None 43 | """ 44 | # Insert your own path to your sample setupapi.dev.log here. 45 | file_path = 'setupapi.dev.log' 46 | 47 | # Print version information when the script is run 48 | print('='*22) 49 | print('SetupAPI Parser, v', __date__) 50 | print('='*22) 51 | parse_setupapi(file_path) 52 | 53 | 54 | def parse_setupapi(setup_file): 55 | """ 56 | Interpret the file 57 | :param setup_file: path to the setupapi.dev.log 58 | :return: None 59 | """ 60 | in_file = open(setup_file) 61 | data = in_file.readlines() 62 | 63 | for i, line in enumerate(data): 64 | if 'device install (hardware initiated)' in line.lower(): 65 | device_name = data[i].split('-')[1].strip() 66 | date = data[i+1].split('start')[1].strip() 67 | print_output(device_name, date) 68 | in_file.close() 69 | 70 | 71 | def print_output(usb_name, usb_date): 72 | """ 73 | Print the information discovered 74 | :param usb_name: String USB Name to print 75 | :param usb_date: String USB Date to print 76 | :return: None 77 | """ 78 | print('Device: {}'.format(usb_name)) 79 | print('First Install: {}'.format(usb_date)) 80 | 81 | 82 | if __name__ == '__main__': 83 | # Run the program 84 | main() 85 | -------------------------------------------------------------------------------- /Chapter03/setupapi_parser_v2.py: -------------------------------------------------------------------------------- 1 | """Second iteration of the setupapi.dev.log parser.""" 2 | from __future__ import print_function 3 | import argparse 4 | from io import open 5 | import os 6 | import sys 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ 35 | 36 | __authors__ = ["Chapin Bryce", "Preston Miller"] 37 | __date__ = 20181027 38 | __description__ = """This scripts reads a Windows 7 Setup API 39 | log and prints USB Devices to the user""" 40 | 41 | 42 | def main(in_file): 43 | """ 44 | Main function to handle operation 45 | :param in_file: string path to Windows 7 setupapi.dev.log 46 | :return: None 47 | """ 48 | if os.path.isfile(in_file): 49 | print('{:=^22}'.format('')) 50 | print('{} {}'.format('SetupAPI Parser, v', __date__)) 51 | print('{:=^22} \n'.format('')) 52 | device_information = parse_setupapi(in_file) 53 | for device in device_information: 54 | print_output(device[0], device[1]) 55 | else: 56 | print('Input is not a file.') 57 | sys.exit(1) 58 | 59 | 60 | def parse_setupapi(setup_log): 61 | """ 62 | Read data from provided file for Device Install Events for 63 | USB Devices 64 | :param setup_log: str - Path to valid setup api log 65 | :return: list of tuples - Tuples contain device name and date 66 | in that order 67 | """ 68 | device_list = list() 69 | with open(setup_log) as in_file: 70 | for line in in_file: 71 | lower_line = line.lower() 72 | # if 'Device Install (Hardware initiated)' in line: 73 | if 'device install (hardware initiated)' in \ 74 | lower_line and ('ven' in lower_line or 75 | 'vid' in lower_line): 76 | device_name = line.split('-')[1].strip() 77 | 78 | if 'usb' not in device_name.split( 79 | '\\')[0].lower(): 80 | continue 81 | # Remove most non-USB devices 82 | # This can remove records that may be 83 | # relevant so please always validate that 84 | # the data reduction does not remove results 85 | # of interest to you. 86 | 87 | date = next(in_file).split('start')[1].strip() 88 | device_list.append((device_name, date)) 89 | 90 | return device_list 91 | 92 | 93 | def print_output(usb_name, usb_date): 94 | """ 95 | Print the information discovered 96 | :param usb_name: String USB Name to print 97 | :param usb_date: String USB Date to print 98 | :return: None 99 | """ 100 | print('Device: {}'.format(usb_name)) 101 | print('First Install: {}\n'.format(usb_date)) 102 | 103 | 104 | if __name__ == '__main__': 105 | # Run this code if the script is run from the command line. 106 | parser = argparse.ArgumentParser( 107 | description=__description__, 108 | epilog='Built by {}. Version {}'.format( 109 | ", ".join(__authors__), __date__), 110 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 111 | ) 112 | 113 | parser.add_argument('IN_FILE', 114 | help='Windows 7 SetupAPI file') 115 | args = parser.parse_args() 116 | 117 | # Run main program 118 | main(args.IN_FILE) 119 | -------------------------------------------------------------------------------- /Chapter03/usb_lookup.py: -------------------------------------------------------------------------------- 1 | """Updated USB Lookup script based on the version found in Chapter 2.""" 2 | from __future__ import print_function 3 | import argparse 4 | try: 5 | from urllib2 import urlopen 6 | except ImportError: 7 | from urllib.request import urlopen 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | """ 32 | 33 | __authors__ = ["Chapin Bryce", "Preston Miller"] 34 | __date__ = 20181027 35 | __description__ = """Reads Linux-usb.org's USB.ids file and parses into 36 | usable data for parsing VID/PIDs""" 37 | 38 | 39 | def main(vid, pid, ids_file=None): 40 | """ 41 | Main function to control operation. Requires arguments passed as VID PID 42 | on the command line. If discovered in data set, the common names will be 43 | printed to stdout 44 | :return: None 45 | """ 46 | if ids_file: 47 | usb_file = open(ids_file, encoding='latin1') 48 | else: 49 | usb_file = get_usb_file() 50 | usbs = parse_file(usb_file) 51 | results = search_key(usbs, (vid, pid)) 52 | print("Vendor: {}\nProduct: {}".format(results[0], results[1])) 53 | 54 | def get_usb_file(): 55 | """ 56 | Retrieves USB.ids database from the web. 57 | """ 58 | url = 'http://www.linux-usb.org/usb.ids' 59 | return urlopen(url) 60 | 61 | 62 | def parse_file(usb_file): 63 | """ 64 | Parses the USB.ids file. If this is run offline, please download the 65 | USB.ids and pass the open file to this function. 66 | ie: parse_file(open('path/to/USB.ids', 'r')) 67 | :return: dictionary of entires for querying 68 | """ 69 | usbs = {} 70 | curr_id = '' 71 | for line in usb_file: 72 | if isinstance(line, bytes): 73 | line = line.decode('latin-1') 74 | if line.startswith('#') or line in ('\n', '\t'): 75 | continue 76 | else: 77 | if not line.startswith('\t') and (line[0].isdigit() or 78 | line[0].islower()): 79 | uid, name = get_record(line.strip()) 80 | curr_id = uid 81 | usbs[uid] = [name.strip(), {}] 82 | elif line.startswith('\t') and line.count('\t') == 1: 83 | uid, name = get_record(line.strip()) 84 | usbs[curr_id][1][uid] = name.strip() 85 | return usbs 86 | 87 | 88 | def get_record(record_line): 89 | """ 90 | Split records out by dynamic position. By finding the space, we can 91 | determine the location to split the record for extraction. To learn more 92 | about this, uncomment the print statements and see what the code is 93 | doing behind the scenes! 94 | """ 95 | # print("Line: {}".format(record_line)) 96 | split = record_line.find(' ') 97 | # print("Split: {}".format(split)) 98 | record_id = record_line[:split] 99 | # print("Record ID: ".format(record_id)) 100 | record_name = record_line[split + 1:] 101 | # print("Record Name: ".format(record_name)) 102 | return record_id, record_name 103 | 104 | 105 | def search_key(usb_dict, ids): 106 | """ 107 | Compare provided IDs to the built USB dictionary. If found, it will 108 | return the common name, otherwise returns the string "unknown". 109 | """ 110 | vendor_key = ids[0] 111 | product_key = ids[1] 112 | 113 | vendor, vendor_data = usb_dict.get(vendor_key, ['unknown', {}]) 114 | product = 'unknown' 115 | if vendor != 'unknown': 116 | product = vendor_data.get(product_key, 'unknown') 117 | 118 | return vendor, product 119 | 120 | 121 | if __name__ == '__main__': 122 | parser = argparse.ArgumentParser( 123 | description=__description__, 124 | epilog="version {} built by {}. MIT License 2018.".format( 125 | __date__, " & ".join(__authors__)), 126 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 127 | ) 128 | parser.add_argument('vid', help='Number representing the VID. ie. 0123') 129 | parser.add_argument('pid', help='Number representing the PID. ie. 0123') 130 | parser.add_argument('--ids', '-i', help='Local path to the usb.ids file') 131 | args = parser.parse_args() 132 | main(args.vid, args.pid, args.ids) 133 | -------------------------------------------------------------------------------- /Chapter04/bitcoin_address_lookup.v1.py: -------------------------------------------------------------------------------- 1 | """First iteration of the Bitcoin JSON transaction parser.""" 2 | import argparse 3 | import json 4 | import urllib.request 5 | import unix_converter as unix 6 | import sys 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ 35 | 36 | __author__ = 'Preston Miller & Chapin Bryce' 37 | __date__ = '20180729' 38 | __description__ = """This scripts downloads address transactions 39 | using blockchain.info public APIs""" 40 | 41 | 42 | def main(address): 43 | """ 44 | The main function handles coordinating logic 45 | :param address: The Bitcoin Address to lookup 46 | :return: Nothing 47 | """ 48 | raw_account = get_address(address) 49 | account = json.loads(raw_account.read()) 50 | print_transactions(account) 51 | 52 | 53 | def get_address(address): 54 | """ 55 | The get_address function uses the blockchain.info Data API 56 | to pull pull down account information and transactions for 57 | address of interest 58 | :param address: The Bitcoin Address to lookup 59 | :return: The response of the url request 60 | """ 61 | url = 'https://blockchain.info/address/{}?format=json' 62 | formatted_url = url.format(address) 63 | try: 64 | return urllib.request.urlopen(formatted_url) 65 | except urllib.error.URLError: 66 | print('Received URL Error for {}'.format(formatted_url)) 67 | sys.exit(1) 68 | 69 | 70 | def print_transactions(account): 71 | """ 72 | The print_transaction function is responsible for presenting 73 | transaction details to end user. 74 | :param account: The JSON decoded account and transaction data 75 | :return: 76 | """ 77 | print_header(account) 78 | print('Transactions') 79 | import pdb; pdb.set_trace() 80 | for i, tx in enumerate(account['txs']): 81 | print('Transaction #{}'.format(i)) 82 | print('Transaction Hash:', tx['hash']) 83 | print('Transaction Date: {}'.format( 84 | unix.unix_converter(tx['time']))) 85 | for output in tx['out']: 86 | inputs = get_inputs(tx) 87 | if len(inputs) > 1: 88 | print('{} --> {} ({:.8f} BTC)'.format( 89 | ' & '.join(inputs), output['addr'], 90 | output['value'] * 10**-8)) 91 | else: 92 | print('{} --> {} ({:.8f} BTC)'.format( 93 | ''.join(inputs), output['addr'], 94 | output['value'] * 10**-8)) 95 | 96 | print('{:=^22}\n'.format('')) 97 | 98 | 99 | def print_header(account): 100 | """ 101 | The print_header function prints overall header information 102 | containing basic address information. 103 | :param account: The JSON decoded account and transaction data 104 | :return: Nothing 105 | """ 106 | print('Address:', account['address']) 107 | print('Current Balance: {:.8f} BTC'.format( 108 | account['final_balance'] * 10**-8)) 109 | print('Total Sent: {:.8f} BTC'.format( 110 | account['total_sent'] * 10**-8)) 111 | print('Total Received: {:.8f} BTC'.format( 112 | account['total_received'] * 10**-8)) 113 | print('Number of Transactions:', account['n_tx']) 114 | print('{:=^22}\n'.format('')) 115 | 116 | 117 | def get_inputs(tx): 118 | """ 119 | The get_inputs function is a small helper function that returns 120 | input addresses for a given transaction 121 | :param tx: A single instance of a Bitcoin transaction 122 | :return: inputs, a list of inputs 123 | """ 124 | inputs = [] 125 | for input_addr in tx['inputs']: 126 | inputs.append(input_addr['prev_out']['addr']) 127 | return inputs 128 | 129 | if __name__ == '__main__': 130 | # Run this code if the script is run from the command line. 131 | parser = argparse.ArgumentParser( 132 | description='BTC Address Lookup', 133 | epilog='Developed by ' + __author__ + ' on ' + __date__) 134 | 135 | parser.add_argument('ADDR', help='Bitcoin Address') 136 | 137 | args = parser.parse_args() 138 | 139 | # Print Script Information 140 | print('{:=^22}'.format('')) 141 | print('{}'.format('Bitcoin Address Lookup')) 142 | print('{:=^22} \n'.format('')) 143 | 144 | # Run main program 145 | main(args.ADDR) 146 | -------------------------------------------------------------------------------- /Chapter04/bitcoin_address_lookup.v2.py: -------------------------------------------------------------------------------- 1 | """Second iteration of the Bitcoin JSON transaction parser.""" 2 | import argparse 3 | import json 4 | import logging 5 | import sys 6 | import os 7 | import urllib.request 8 | import unix_converter as unix 9 | 10 | """ 11 | MIT License 12 | Copyright (c) 2018 Chapin Bryce, Preston Miller 13 | Please share comments and questions at: 14 | https://github.com/PythonForensics/Learning-Python-for-Forensics 15 | or email pyforcookbook@gmail.com 16 | 17 | Permission is hereby granted, free of charge, to any person 18 | obtaining a copy of this software and associated documentation 19 | files (the "Software"), to deal in the Software without 20 | restriction, including without limitation the rights to use, 21 | copy, modify, merge, publish, distribute, sublicense, and/or 22 | sell copies of the Software, and to permit persons to whom the 23 | Software is furnished to do so, subject to the following 24 | conditions: 25 | 26 | The above copyright notice and this permission notice shall be 27 | included in all copies or substantial portions of the Software. 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 30 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 31 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 32 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 33 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 34 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 35 | OTHER DEALINGS IN THE SOFTWARE. 36 | """ 37 | 38 | __author__ = 'Preston Miller & Chapin Bryce' 39 | __date__ = '20180729' 40 | __description__ = """This scripts downloads address transactions 41 | using blockchain.info public APIs""" 42 | 43 | 44 | def main(address): 45 | """ 46 | The main function handles coordinating logic 47 | :param address: The Bitcoin Address to lookup 48 | :return: Nothing 49 | """ 50 | logging.info('Initiated program for {} address'.format( 51 | address)) 52 | logging.info( 53 | 'Obtaining JSON structured data from blockchain.info') 54 | raw_account = get_address(address) 55 | account = json.loads(raw_account.read()) 56 | print_transactions(account) 57 | 58 | 59 | def get_address(address): 60 | """ 61 | The get_address function uses the blockchain.info Data API 62 | to pull pull down account information and transactions for 63 | address of interest 64 | :param address: The Bitcoin Address to lookup 65 | :return: The response of the url request 66 | """ 67 | url = 'https://blockchain.info/address/{}?format=json' 68 | formatted_url = url.format(address) 69 | try: 70 | return urllib.request.urlopen(formatted_url) 71 | except urllib.error.URLError as e: 72 | logging.error('URL Error for {}'.format(formatted_url)) 73 | if hasattr(e, 'code') and hasattr(e, 'headers'): 74 | logging.debug('{}: {}'.format(e.code, e.reason)) 75 | logging.debug('{}'.format(e.headers)) 76 | print('Received URL Error for {}'.format(formatted_url)) 77 | logging.info('Program exiting...') 78 | sys.exit(1) 79 | 80 | 81 | def print_transactions(account): 82 | """ 83 | The print_transaction function is responsible for presenting 84 | transaction details to end user. 85 | :param account: The JSON decoded account and transaction data 86 | :return: Nothing 87 | """ 88 | logging.info( 89 | 'Printing account and transaction data to console.') 90 | print_header(account) 91 | print('Transactions') 92 | for i, tx in enumerate(account['txs']): 93 | print('Transaction #{}'.format(i)) 94 | print('Transaction Hash:', tx['hash']) 95 | print('Transaction Date: {}'.format( 96 | unix.unix_converter(tx['time']))) 97 | for output in tx['out']: 98 | inputs = get_inputs(tx) 99 | if len(inputs) > 1: 100 | print('{} --> {} ({:.8f} BTC)'.format( 101 | ' & '.join(inputs), output['addr'], 102 | output['value'] * 10**-8)) 103 | elif len(inputs) == 1: 104 | print('{} --> {} ({:.8f} BTC)'.format( 105 | ''.join(inputs), output['addr'], 106 | output['value'] * 10**-8)) 107 | else: 108 | logging.warn( 109 | 'Detected 0 inputs for transaction {}').format( 110 | tx['hash']) 111 | print('Detected 0 inputs for transaction.') 112 | 113 | print('{:=^22}\n'.format('')) 114 | 115 | 116 | def print_header(account): 117 | """ 118 | The print_header function prints overall header information 119 | containing basic address information. 120 | :param account: The JSON decoded account and transaction data 121 | :return: Nothing 122 | """ 123 | print('Address:', account['address']) 124 | print('Current Balance: {:.8f} BTC'.format( 125 | account['final_balance'] * 10**-8)) 126 | print('Total Sent: {:.8f} BTC'.format( 127 | account['total_sent'] * 10**-8)) 128 | print('Total Received: {:.8f} BTC'.format( 129 | account['total_received'] * 10**-8)) 130 | print('Number of Transactions:', account['n_tx']) 131 | print('{:=^22}\n'.format('')) 132 | 133 | 134 | def get_inputs(tx): 135 | """ 136 | The get_inputs function is a small helper function that returns 137 | input addresses for a given transaction 138 | :param tx: A single instance of a Bitcoin transaction 139 | :return: inputs, a list of inputs 140 | """ 141 | inputs = [] 142 | for input_addr in tx['inputs']: 143 | inputs.append(input_addr['prev_out']['addr']) 144 | return inputs 145 | 146 | if __name__ == '__main__': 147 | # Run this code if the script is run from the command line. 148 | parser = argparse.ArgumentParser( 149 | description='BTC Address Lookup', 150 | epilog='Developed by ' + __author__ + ' on ' + __date__) 151 | 152 | parser.add_argument('ADDR', help='Bitcoin Address') 153 | parser.add_argument('-l', help="""Specify log directory. 154 | Defaults to current working directory.""") 155 | 156 | args = parser.parse_args() 157 | 158 | # Set up Log 159 | if args.l: 160 | if not os.path.exists(args.l): 161 | os.makedirs(args.l) 162 | log_path = os.path.join(args.l, 'btc_addr_lookup.log') 163 | else: 164 | log_path = 'btc_addr_lookup.log' 165 | logging.basicConfig( 166 | filename=log_path, level=logging.DEBUG, 167 | format='%(asctime)s | %(levelname)s | %(message)s', 168 | filemode='w') 169 | 170 | logging.info('Starting Bitcoin Address Lookup') 171 | logging.debug('System ' + sys.platform) 172 | logging.debug('Version ' + sys.version) 173 | 174 | # Print Script Information 175 | print('{:=^22}'.format('')) 176 | print('{}'.format('Bitcoin Address Lookup')) 177 | print('{:=^22} \n'.format('')) 178 | 179 | # Run main program 180 | main(args.ADDR) 181 | -------------------------------------------------------------------------------- /Chapter04/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Learning Python Forensics", 3 | "authors": "Preston Miller & Chapin Bryce", 4 | "publisher": "Packt Publishing", 5 | "pageCount": 500, 6 | "numberOfChapters": 13, 7 | "chapters": 8 | [ 9 | { 10 | "chapterNumber": 1, 11 | "chapterTitle": "Now for Something Completely Different", 12 | "pageCount": 30 13 | }, 14 | { 15 | "chapterNumber": 2, 16 | "chapterTitle": "Python Fundamentals", 17 | "pageCount": 25 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Chapter04/book.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Preston Miller & Chapin Bryce 4 | 5 | 6 | 1 7 | Now for Something Completely Different 8 | 30 9 | 10 | 11 | 2 12 | Python Fundamentals 13 | 25 14 | 15 | 16 | 13 17 | 500 18 | Packt Publishing 19 | Learning Python for Forensics 20 | -------------------------------------------------------------------------------- /Chapter04/unix_converter.py: -------------------------------------------------------------------------------- 1 | """UNIX timestamp converter script.""" 2 | from __future__ import print_function 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | import datetime 33 | 34 | __author__ = 'Preston Miller & Chapin Bryce' 35 | __date__ = '20180729' 36 | __description__ = """Convert unix formatted timestamps (seconds 37 | since Epoch [1970-01-01 00:00:00]) to human readable""" 38 | 39 | def main(): 40 | unix_ts = int(raw_input('Unix timestamp to convert:\n>> ')) 41 | print(unix_converter(unix_ts)) 42 | 43 | def unix_converter(timestamp): 44 | date_ts = datetime.datetime.utcfromtimestamp(timestamp) 45 | return date_ts.strftime('%m/%d/%Y %I:%M:%S %p') 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /Chapter06/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter06/.DS_Store -------------------------------------------------------------------------------- /Chapter06/Neguhe Qrag.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter06/Neguhe Qrag.bin -------------------------------------------------------------------------------- /Chapter06/Writers/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import csv_writer 6 | import xlsx_writer 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ -------------------------------------------------------------------------------- /Chapter06/Writers/csv_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | if sys.version_info[0] == 2: 4 | import unicodecsv as csv 5 | elif sys.version_info[0] == 3: 6 | import csv 7 | from datetime import datetime, timedelta 8 | import logging 9 | 10 | """ 11 | MIT License 12 | Copyright (c) 2018 Chapin Bryce, Preston Miller 13 | Please share comments and questions at: 14 | https://github.com/PythonForensics/Learning-Python-for-Forensics 15 | or email pyforcookbook@gmail.com 16 | 17 | Permission is hereby granted, free of charge, to any person 18 | obtaining a copy of this software and associated documentation 19 | files (the "Software"), to deal in the Software without 20 | restriction, including without limitation the rights to use, 21 | copy, modify, merge, publish, distribute, sublicense, and/or 22 | sell copies of the Software, and to permit persons to whom the 23 | Software is furnished to do so, subject to the following 24 | conditions: 25 | 26 | The above copyright notice and this permission notice shall be 27 | included in all copies or substantial portions of the Software. 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 30 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 31 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 32 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 33 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 34 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 35 | OTHER DEALINGS IN THE SOFTWARE. 36 | """ 37 | 38 | def csv_writer(data, out_file): 39 | """ 40 | The csv_writer function writes the parsed UA data to a csv 41 | file 42 | :param data: the list of lists containing parsed UA data 43 | :param out_file: the desired output directory and filename 44 | for the csv file 45 | :return: Nothing 46 | """ 47 | print('[+] Writing CSV output.') 48 | logging.info('Writing CSV to ' + out_file + '.') 49 | headers = ['ID', 'Name', 'Path', 'Session ID', 'Count', 50 | 'Last Used Date (UTC)', 'Focus Time (ms)', 'Focus Count'] 51 | 52 | if sys.version_info[0] == 2: 53 | csvfile = open(out_file, "wb") 54 | elif sys.version_info[0] == 3: 55 | csvfile = open(out_file, "w", newline='', 56 | encoding='utf-8') 57 | 58 | with csvfile: 59 | writer = csv.DictWriter(csvfile, fieldnames=headers, 60 | extrasaction='ignore') 61 | # Writes the header from list supplied to fieldnames 62 | # keyword argument 63 | writer.writeheader() 64 | 65 | for i, dictionary in enumerate(data): 66 | # Insert the 'ID' value to each dictionary in the 67 | # list. Add 1 to start ID at 1 instead of 0. 68 | dictionary['ID'] = i + 1 69 | # Convert the FILETIME object in the fourth index to 70 | # human readable value 71 | dictionary['Last Used Date (UTC)'] = file_time( 72 | dictionary['Last Used Date (UTC)']) 73 | writer.writerow(dictionary) 74 | 75 | csvfile.flush() 76 | csvfile.close() 77 | msg = 'Completed writing CSV file. Program exiting successfully.' 78 | print('[*]', msg) 79 | logging.info(msg) 80 | 81 | 82 | def file_time(ft): 83 | """ 84 | The fileTime function converts Windows FILETIME objects into 85 | human readable value 86 | :param ft: the FILETIME to convert 87 | :return: date_str, the human readable datetime value 88 | """ 89 | if ft is not None and ft != 0: 90 | return datetime(1601, 1, 1) + timedelta(microseconds=ft / 10) 91 | else: 92 | return 0 93 | -------------------------------------------------------------------------------- /Chapter06/rot13.py: -------------------------------------------------------------------------------- 1 | """Prototype ROT-13 encoder and decoder.""" 2 | from __future__ import print_function 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | def rot_code(data): 33 | """ 34 | The rot_code function encodes/decodes data using string 35 | indexing 36 | :param data: A string 37 | :return: The rot-13 encoded/decoded string 38 | """ 39 | rot_chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 40 | 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 41 | 'u', 'v', 'w', 'x', 'y', 'z'] 42 | 43 | substitutions = [] 44 | 45 | # Walk through each individual character 46 | for c in data: 47 | 48 | # Walk through each individual character 49 | if c.isupper(): 50 | 51 | try: 52 | # Find the position of the character in 53 | # rot_chars list 54 | index = rot_chars.index(c.lower()) 55 | except ValueError: 56 | substitutions.append(c) 57 | continue 58 | 59 | # Calculate the relative index that is 13 60 | # characters away from the index 61 | substitutions.append( 62 | (rot_chars[(index-13)]).upper()) 63 | 64 | else: 65 | 66 | try: 67 | # Find the position of the character in 68 | # rot_chars list 69 | index = rot_chars.index(c) 70 | except ValueError: 71 | substitutions.append(c) 72 | continue 73 | 74 | substitutions.append(rot_chars[((index-13))]) 75 | 76 | return ''.join(substitutions) 77 | 78 | if __name__ == '__main__': 79 | print(rot_code('Jul, EBG-13?')) 80 | -------------------------------------------------------------------------------- /Chapter06/simplexlsx.v1.py: -------------------------------------------------------------------------------- 1 | """First iteration of a simple XLSX writer.""" 2 | import xlsxwriter 3 | from datetime import datetime 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | school_data = [['Department', 'Students', 'Cumulative GPA', 34 | 'Final Date'], 35 | ['Computer Science', 235, 3.44, 36 | datetime(2015, 7, 23, 18, 0, 0)], 37 | ['Chemistry', 201, 3.26, 38 | datetime(2015, 7, 25, 9, 30, 0)], 39 | ['Forensics', 99, 3.8, 40 | datetime(2015, 7, 23, 9, 30, 0)], 41 | ['Astronomy', 115, 3.21, 42 | datetime(2015, 7, 19, 15, 30, 0)]] 43 | 44 | 45 | def write_xlsx(data): 46 | """ 47 | The write_xlsx function creates an XLSX spreadsheet from a 48 | list of lists 49 | :param data: A list of lists to be written in the spreadsheet 50 | :return: Nothing 51 | """ 52 | workbook = xlsxwriter.Workbook('MyWorkbook.xlsx') 53 | main_sheet = workbook.add_worksheet('MySheet') 54 | 55 | date_format = workbook.add_format( 56 | {'num_format': 'mm/dd/yy hh:mm:ss AM/PM'}) 57 | 58 | for i, entry in enumerate(data): 59 | if i == 0: 60 | main_sheet.write(i, 0, entry[0]) 61 | main_sheet.write(i, 1, entry[1]) 62 | main_sheet.write(i, 2, entry[2]) 63 | main_sheet.write(i, 3, entry[3]) 64 | else: 65 | main_sheet.write(i, 0, entry[0]) 66 | main_sheet.write_number(i, 1, entry[1]) 67 | main_sheet.write_number(i, 2, entry[2]) 68 | main_sheet.write_datetime(i, 3, entry[3], date_format) 69 | 70 | workbook.close() 71 | 72 | 73 | write_xlsx(school_data) 74 | -------------------------------------------------------------------------------- /Chapter06/simplexlsx.v2.py: -------------------------------------------------------------------------------- 1 | """Second iteration of a simple XLSX writer.""" 2 | 3 | import xlsxwriter 4 | from datetime import datetime 5 | 6 | """ 7 | MIT License 8 | Copyright (c) 2018 Chapin Bryce, Preston Miller 9 | Please share comments and questions at: 10 | https://github.com/PythonForensics/Learning-Python-for-Forensics 11 | or email pyforcookbook@gmail.com 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or 18 | sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 26 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 28 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 29 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | """ 33 | 34 | school_data = [['Computer Science', 235, 3.44, 35 | datetime(2015, 7, 23, 18, 0, 0)], 36 | ['Chemistry', 201, 3.26, 37 | datetime(2015, 7, 25, 9, 30, 0)], 38 | ['Forensics', 99, 3.8, 39 | datetime(2015, 7, 23, 9, 30, 0)], 40 | ['Astronomy', 115, 3.21, 41 | datetime(2015, 7, 19, 15, 30, 0)]] 42 | 43 | 44 | def write_xlsx(data): 45 | """ 46 | The write_xlsx function creates an XLSX spreadsheet from a 47 | list of lists 48 | :param data: A list of lists to be written in the spreadsheet 49 | :return: Nothing 50 | """ 51 | workbook = xlsxwriter.Workbook('MyWorkbook.xlsx') 52 | main_sheet = workbook.add_worksheet('MySheet') 53 | 54 | date_format = workbook.add_format( 55 | {'num_format': 'mm/dd/yy hh:mm:ss AM/PM'}) 56 | length = str(len(data) + 1) 57 | 58 | main_sheet.add_table(('A1:D' + length), 59 | {'data': data, 60 | 'columns': [{'header': 'Department'}, {'header': 'Students'}, 61 | {'header': 'Cumulative GPA'}, 62 | {'header': 'Final Date', 'format': date_format}]}) 63 | 64 | workbook.close() 65 | 66 | 67 | write_xlsx(school_data) 68 | -------------------------------------------------------------------------------- /Chapter06/simplexlsx.v3.py: -------------------------------------------------------------------------------- 1 | """Third iteration of a simple XLSX writer.""" 2 | 3 | import xlsxwriter 4 | from datetime import datetime 5 | 6 | """ 7 | MIT License 8 | Copyright (c) 2018 Chapin Bryce, Preston Miller 9 | Please share comments and questions at: 10 | https://github.com/PythonForensics/Learning-Python-for-Forensics 11 | or email pyforcookbook@gmail.com 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or 18 | sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 26 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 28 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 29 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | """ 33 | 34 | school_data = [['Computer Science', 235, 3.44, 35 | datetime(2015, 7, 23, 18, 0, 0)], 36 | ['Chemistry', 201, 3.26, 37 | datetime(2015, 7, 25, 9, 30, 0)], 38 | ['Forensics', 99, 3.8, 39 | datetime(2015, 7, 23, 9, 30, 0)], 40 | ['Astronomy', 115, 3.21, 41 | datetime(2015, 7, 19, 15, 30, 0)]] 42 | 43 | 44 | def write_xlsx(data): 45 | """ 46 | The write_xlsx function creates an XLSX spreadsheet from a 47 | list of lists 48 | :param data: A list of lists to be written in the spreadsheet 49 | :return: Nothing 50 | """ 51 | workbook = xlsxwriter.Workbook('MyWorkbook.xlsx') 52 | main_sheet = workbook.add_worksheet('MySheet') 53 | 54 | date_format = workbook.add_format( 55 | {'num_format': 'mm/dd/yy hh:mm:ss AM/PM'}) 56 | length = str(len(data) + 1) 57 | 58 | main_sheet.add_table(('A1:D' + length), 59 | {'data': data, 60 | 'columns': [{'header': 'Department'}, {'header': 'Students'}, 61 | {'header': 'Cumulative GPA'}, 62 | {'header': 'Final Date', 63 | 'format': date_format}]}) 64 | 65 | department_grades = workbook.add_chart({'type':'column'}) 66 | department_grades.set_title( 67 | {'name':'Department and Grade distribution'}) 68 | department_grades.add_series( 69 | {'categories':'=MySheet!$A$2:$A$5', 70 | 'values':'=MySheet!$C$2:$C$5'}) 71 | main_sheet.insert_chart('A8', department_grades) 72 | workbook.close() 73 | 74 | 75 | write_xlsx(school_data) 76 | -------------------------------------------------------------------------------- /Chapter07/TEST_DATA_README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This file describes the contents of the other files in this directory. Each of the 4 | files named `file_#` were created using the below bash for loop on a macOS 5 | system: 6 | 7 | ```bash 8 | for i in 1 2 3; do dd if=/dev/urandom of="file_$i" bs=1K count=200; done 9 | ``` 10 | 11 | These files are treated as "originals" and variations of these files were 12 | generated to demonstrate how ssdeep can compare files through different types 13 | of modifications. 14 | 15 | The below descriptions identify what changes were made to each of the 16 | files. For simplicity, we append the letter "a" after each modified version 17 | so it is clear which original they relate to and that they are a modified 18 | version of the original. 19 | 20 | All offsets are decimal, not hexadecimal. 21 | 22 | ## `file_1a` 23 | 24 | from decimal offset 27000 of `file_1`, 24000 bytes were selected and moved 25 | to offset 74000 (50000) 26 | 27 | ## `file_2a` 28 | 29 | from decimal offset 50000 of `file_2`, the same 24000 bytes from `file_1` were 30 | inserted 31 | 32 | ## `file_3a` 33 | 34 | from decimal offset 0 of `file_3`, 3500 bytes were removed. 35 | 36 | -------------------------------------------------------------------------------- /Chapter07/hashing_example.py: -------------------------------------------------------------------------------- 1 | """Sample script to hash large files effiently.""" 2 | import argparse 3 | import hashlib 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | HASH_LIBS = ['md5', 'sha1', 'sha256', 'sha512'] 34 | BUFFER_SIZE = 1024**3 35 | 36 | parser = argparse.ArgumentParser() 37 | parser.add_argument("FILE", help="File to hash") 38 | parser.add_argument("-a", "--algorithm", 39 | help="Hash algorithm to use", choices=HASH_LIBS, 40 | default="sha512") 41 | args = parser.parse_args() 42 | 43 | alg = getattr(hashlib, args.algorithm)() 44 | 45 | with open(args.FILE, 'rb') as input_file: 46 | 47 | buffer_data = input_file.read(BUFFER_SIZE) 48 | while buffer_data: 49 | alg.update(buffer_data) 50 | buffer_data = input_file.read(BUFFER_SIZE) 51 | 52 | print(alg.hexdigest()) 53 | -------------------------------------------------------------------------------- /Chapter07/ssdeep_python.py: -------------------------------------------------------------------------------- 1 | """Example script that uses the ssdeep python bindings.""" 2 | import argparse 3 | import logging 4 | import os 5 | import sys 6 | 7 | import ssdeep 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ 36 | 37 | __authors__ = ["Chapin Bryce", "Preston Miller"] 38 | __date__ = 20181027 39 | __description__ = '''Compare known file to another file or files 40 | in a directory using ssdeep.''' 41 | 42 | # Argument handling constants 43 | OUTPUT_OPTS = ['txt', 'json', 'csv'] 44 | logger = logging.getLogger(__file__) 45 | 46 | 47 | def main(known_file, comparison, output_type): 48 | """ 49 | The main function handles the main operations of the script 50 | :param known_file: path to known file 51 | :param comparison: path to look for similar files 52 | :param output_type: type of output to provide 53 | :return: None 54 | """ 55 | 56 | # Check output formats 57 | if output_type not in OUTPUT_OPTS: 58 | logger.error( 59 | "Unsupported output format '{}' selected. Please " 60 | "use one of {}".format( 61 | output_type, ", ".join(OUTPUT_OPTS))) 62 | sys.exit(2) 63 | elif output_type == 'csv': 64 | # Special handling for CSV headers 65 | print('"similarity","known_file","known_hash",' 66 | '"comp_file","comp_hash"') 67 | 68 | # Check provided file paths 69 | known_file = os.path.abspath(known_file) 70 | comparison = os.path.abspath(comparison) 71 | 72 | # Generate ssdeep signature for known file 73 | if not os.path.exists(known_file): 74 | logger.error("Error - path {} not found".format( 75 | comparison)) 76 | sys.exit(1) 77 | 78 | known_hash = ssdeep.hash_from_file(known_file) 79 | 80 | # Generate and test ssdeep signature for comparison file(s) 81 | if os.path.isdir(comparison): 82 | # Process files in folders 83 | for root, _, files in os.walk(comparison): 84 | for f in files: 85 | file_entry = os.path.join(root, f) 86 | comp_hash = ssdeep.hash_from_file(file_entry) 87 | comp_val = ssdeep.compare(known_hash, comp_hash) 88 | output(known_file, known_hash, 89 | file_entry, comp_hash, 90 | comp_val, output_type) 91 | 92 | elif os.path.isfile(comparison): 93 | # Process a single file 94 | comp_hash = ssdeep.hash_from_file(comparison) 95 | comp_val = ssdeep.compare(known_hash, comp_hash) 96 | output(known_file, known_hash, file_entry, comp_hash, 97 | comp_val, output_type) 98 | else: 99 | logger.error("Error - path {} not found".format( 100 | comparison)) 101 | sys.exit(1) 102 | 103 | 104 | def output(known_file, known_hash, comp_file, comp_hash, comp_val, 105 | output_type='txt'): 106 | """Write the output of the script in the specified format 107 | :param sigval (str): Calculated hash 108 | :param filename (str): name of the file processed 109 | :param output_type (str): Formatter to use for output 110 | """ 111 | comp_val = str(comp_val) 112 | if output_type == 'txt': 113 | msg = "{similarity} - {known_file} {known_hash} | " 114 | msg += "{comp_file} {comp_hash}" 115 | elif output_type == 'json': 116 | msg = '{{"similarity": {similarity}, "known_file": ' 117 | msg += '"{known_file}", "known_hash": "{known_hash}", ' 118 | msg += '"comparison_file": "{comp_file}", ' 119 | msg += '"comparison_hash": "{comp_hash}"}}' 120 | elif output_type == 'csv': 121 | msg = '"{similarity}","{known_file}","{known_hash}"' 122 | msg += '"{comp_file}","{comp_hash}"' 123 | else: 124 | raise NotImplementedError( 125 | "Unsupported output type: {}".format(output_type)) 126 | 127 | print(msg.format( 128 | similarity=comp_val, 129 | known_file=known_file, 130 | known_hash=known_hash, 131 | comp_file=comp_file, 132 | comp_hash=comp_hash)) 133 | 134 | if __name__ == '__main__': 135 | parser = argparse.ArgumentParser( 136 | description=__description__, 137 | epilog='Built by {}. Version {}'.format( 138 | ", ".join(__authors__), __date__), 139 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 140 | ) 141 | parser.add_argument('KNOWN', 142 | help='Path to known file to use to compare') 143 | parser.add_argument('COMPARISON', 144 | help='Path to file or directory to compare to known. ' 145 | 'Will recurse through all sub directories') 146 | parser.add_argument('-o', '--output-type', 147 | help='Format of output.', choices=OUTPUT_OPTS, 148 | default="txt") 149 | parser.add_argument('-l', help='specify log file path', 150 | default="./") 151 | 152 | args = parser.parse_args() 153 | 154 | if args.l: 155 | if not os.path.exists(args.l): 156 | os.makedirs(args.l) 157 | log_path = os.path.join(args.l, 'ssdeep_python.log') 158 | else: 159 | log_path = 'ssdeep_python.log' 160 | 161 | 162 | logger.setLevel(logging.DEBUG) 163 | msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-20s" 164 | "%(levelname)-8s %(message)s") 165 | strhndl = logging.StreamHandler(sys.stderr) # Set to stderr 166 | strhndl.setFormatter(fmt=msg_fmt) 167 | fhndl = logging.FileHandler(log_path, mode='a') 168 | fhndl.setFormatter(fmt=msg_fmt) 169 | logger.addHandler(strhndl) 170 | logger.addHandler(fhndl) 171 | 172 | logger.info('Starting SSDeep Python v. {}'.format(__date__)) 173 | logger.debug('System ' + sys.platform) 174 | logger.debug('Version ' + sys.version.replace("\n", " ")) 175 | 176 | logger.info('Script Starting') 177 | main(args.KNOWN, args.COMPARISON, args.output_type) 178 | logger.info('Script Completed') 179 | -------------------------------------------------------------------------------- /Chapter07/test_data/file_1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter07/test_data/file_1 -------------------------------------------------------------------------------- /Chapter07/test_data/file_1a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter07/test_data/file_1a -------------------------------------------------------------------------------- /Chapter07/test_data/file_2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter07/test_data/file_2 -------------------------------------------------------------------------------- /Chapter07/test_data/file_2a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter07/test_data/file_2a -------------------------------------------------------------------------------- /Chapter07/test_data/file_3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter07/test_data/file_3 -------------------------------------------------------------------------------- /Chapter07/test_data/file_3a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter07/test_data/file_3a -------------------------------------------------------------------------------- /Chapter08/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter08/.DS_Store -------------------------------------------------------------------------------- /Chapter08/img_42.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter08/img_42.jpg -------------------------------------------------------------------------------- /Chapter08/metadata_parser.py: -------------------------------------------------------------------------------- 1 | """EXIF, ID3, and Office Metadata parser.""" 2 | from __future__ import print_function 3 | import argparse 4 | import os 5 | import sys 6 | import logging 7 | 8 | import plugins 9 | import writers 10 | 11 | """ 12 | MIT License 13 | Copyright (c) 2018 Chapin Bryce, Preston Miller 14 | Please share comments and questions at: 15 | https://github.com/PythonForensics/Learning-Python-for-Forensics 16 | or email pyforcookbook@gmail.com 17 | 18 | Permission is hereby granted, free of charge, to any person 19 | obtaining a copy of this software and associated documentation 20 | files (the "Software"), to deal in the Software without 21 | restriction, including without limitation the rights to use, 22 | copy, modify, merge, publish, distribute, sublicense, and/or 23 | sell copies of the Software, and to permit persons to whom the 24 | Software is furnished to do so, subject to the following 25 | conditions: 26 | 27 | The above copyright notice and this permission notice shall be 28 | included in all copies or substantial portions of the Software. 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 31 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 32 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 33 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 34 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 35 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | OTHER DEALINGS IN THE SOFTWARE. 37 | """ 38 | 39 | __author__ = 'Preston Miller & Chapin Bryce' 40 | __date__ = '20181125' 41 | __description__ = ('This scripts handles processing and output ' 42 | 'of various embedded metadata files') 43 | 44 | 45 | def main(input_dir, output_dir): 46 | """ 47 | The main function generates a file listing, sends files to be 48 | processed, and output written. 49 | :param input_dir: The input directory to scan for suported 50 | embedded metadata containing files 51 | :param output_dir: The output directory to write metadata 52 | reports to 53 | :return: Nothing. 54 | """ 55 | # Create lists to store each supported embedded metadata 56 | # before writing to output 57 | exif_metadata = [] 58 | office_metadata = [] 59 | id3_metadata = [] 60 | 61 | # Walk through list of files 62 | msg = 'Generating file listing and running plugins.' 63 | print('[+]', msg) 64 | logging.info(msg) 65 | for root, subdir, files in os.walk(input_dir, topdown=True): 66 | for file_name in files: 67 | current_file = os.path.join(root, file_name) 68 | ext = os.path.splitext(current_file)[1].lower() 69 | 70 | # PLUGINS 71 | if ext == '.jpeg' or ext == '.jpg': 72 | try: 73 | ex_metadata, exif_headers = plugins.exif_parser.exif_parser( 74 | current_file) 75 | exif_metadata.append(ex_metadata) 76 | except TypeError: 77 | print(('[-] File signature mismatch. ' 78 | 'Continuing to next file.')) 79 | logging.error((('JPG & TIFF File Signature ' 80 | 'check failed for ' + current_file))) 81 | continue 82 | 83 | elif ext == '.docx' or ext == '.pptx' or ext == '.xlsx': 84 | try: 85 | of_metadata, office_headers = plugins.office_parser.office_parser( 86 | current_file) 87 | office_metadata.append(of_metadata) 88 | except TypeError: 89 | print(('[-] File signature mismatch. ' 90 | 'Continuing to next file.')) 91 | logging.error((('DOCX, XLSX, & PPTX File ' 92 | 'Signature check failed for ' + current_file)) 93 | ) 94 | continue 95 | 96 | elif ext == '.mp3': 97 | try: 98 | id_metadata, id3_headers = plugins.id3_parser.id3_parser( 99 | current_file) 100 | id3_metadata.append(id_metadata) 101 | except TypeError: 102 | print(('[-] File signature mismatch. ' 103 | 'Continuing to next file.')) 104 | logging.error((('MP3 File Signature check ' 105 | 'failed for ' + current_file))) 106 | continue 107 | 108 | # WRITERS 109 | msg = 'Writing output to ' + output_dir 110 | print('[+]', msg) 111 | logging.info(msg) 112 | 113 | if len(exif_metadata) > 0: 114 | writers.kml_writer.kml_writer(exif_metadata, 115 | output_dir, 'exif_metadata.kml') 116 | writers.csv_writer.csv_writer(exif_metadata, exif_headers, 117 | output_dir, 'exif_metadata.csv') 118 | 119 | if len(office_metadata) > 0: 120 | writers.csv_writer.csv_writer(office_metadata, 121 | office_headers, output_dir, 'office_metadata.csv') 122 | 123 | if len(id3_metadata) > 0: 124 | writers.csv_writer.csv_writer(id3_metadata, id3_headers, 125 | output_dir, 'id3_metadata.csv') 126 | 127 | msg = 'Program completed successfully -- exiting..' 128 | print('[*]', msg) 129 | logging.info(msg) 130 | 131 | if __name__ == '__main__': 132 | 133 | parser = argparse.ArgumentParser(description=__description__, 134 | epilog='Developed by ' + 135 | __author__ + ' on ' + 136 | __date__) 137 | parser.add_argument('INPUT_DIR', help='Input Directory') 138 | parser.add_argument('OUTPUT_DIR', help='Output Directory') 139 | parser.add_argument('-l', help='File path of log file.') 140 | args = parser.parse_args() 141 | 142 | if args.l: 143 | if not os.path.exists(args.l): 144 | os.makedirs(args.l) 145 | log_path = os.path.join(args.l, 'metadata_parser.log') 146 | else: 147 | log_path = 'metadata_parser.log' 148 | logging.basicConfig(filename=log_path, level=logging.DEBUG, 149 | format=('%(asctime)s | %(levelname)s | ' 150 | '%(message)s'), filemode='a') 151 | 152 | logging.info('Starting Metadata_Parser') 153 | logging.debug('System ' + sys.platform) 154 | logging.debug('Version ' + sys.version) 155 | 156 | if not os.path.exists(args.OUTPUT_DIR): 157 | os.makedirs(args.OUTPUT_DIR) 158 | 159 | if(os.path.exists(args.INPUT_DIR) and 160 | os.path.isdir(args.INPUT_DIR)): 161 | main(args.INPUT_DIR, args.OUTPUT_DIR) 162 | else: 163 | msg =('Supplied input directory does not exist or is' 164 | 'not a directory') 165 | print('[-]', msg) 166 | logging.error(msg) 167 | sys.exit(1) -------------------------------------------------------------------------------- /Chapter08/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import exif_parser 6 | import id3_parser 7 | import office_parser 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ -------------------------------------------------------------------------------- /Chapter08/plugins/id3_parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import gmtime, strftime 3 | 4 | from mutagen import mp3, id3 5 | 6 | import processors 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ 35 | 36 | 37 | def id3_parser(filename): 38 | """ 39 | The id3_parser function confirms the file type and sends it to 40 | be processed. 41 | :param filename: name of the file potentially containing exif 42 | metadata. 43 | :return: A dictionary from get_tags, containing the embedded 44 | EXIF metadata. 45 | """ 46 | 47 | # MP3 signatures 48 | signatures = ['494433'] 49 | if processors.utility.check_header( 50 | filename, signatures, 3) == True: 51 | return get_tags(filename) 52 | else: 53 | print(('File signature does not match known ' 54 | 'MP3 signatures.')) 55 | raise TypeError(('File signature does not match ' 56 | 'MP3 object.')) 57 | 58 | 59 | def get_tags(filename): 60 | """ 61 | The get_tags function extracts the ID3 metadata from the data 62 | object. 63 | :param filename: the path and name to the data object. 64 | :return: tags and headers, tags is a dictionary containing ID3 65 | metadata and headers are the order of keys for the CSV output. 66 | """ 67 | 68 | # Set up CSV headers 69 | header = ['Path', 'Name', 'Size', 'Filesystem CTime', 70 | 'Filesystem MTime', 'Title', 'Subtitle', 'Artist', 'Album', 71 | 'Album/Artist', 'Length (Sec)', 'Year', 'Category', 72 | 'Track Number', 'Comments', 'Publisher', 'Bitrate', 73 | 'Sample Rate', 'Encoding', 'Channels', 'Audio Layer'] 74 | tags = {} 75 | tags['Path'] = filename 76 | tags['Name'] = os.path.basename(filename) 77 | tags['Size'] = processors.utility.convert_size( 78 | os.path.getsize(filename)) 79 | tags['Filesystem CTime'] = strftime('%m/%d/%Y %H:%M:%S', 80 | gmtime(os.path.getctime(filename))) 81 | tags['Filesystem MTime'] = strftime('%m/%d/%Y %H:%M:%S', 82 | gmtime(os.path.getmtime(filename))) 83 | 84 | # MP3 Specific metadata 85 | audio = mp3.MP3(filename) 86 | if 'TENC' in audio.keys(): 87 | tags['Encoding'] = audio['TENC'][0] 88 | tags['Bitrate'] = audio.info.bitrate 89 | tags['Channels'] = audio.info.channels 90 | tags['Audio Layer'] = audio.info.layer 91 | tags['Length (Sec)'] = audio.info.length 92 | tags['Sample Rate'] = audio.info.sample_rate 93 | 94 | # ID3 embedded metadata tags 95 | id = id3.ID3(filename) 96 | if 'TPE1' in id.keys(): 97 | tags['Artist'] = id['TPE1'][0] 98 | if 'TRCK' in id.keys(): 99 | tags['Track Number'] = id['TRCK'][0] 100 | if 'TIT3' in id.keys(): 101 | tags['Subtitle'] = id['TIT3'][0] 102 | if 'COMM::eng' in id.keys(): 103 | tags['Comments'] = id['COMM::eng'][0] 104 | if 'TDRC' in id.keys(): 105 | tags['Year'] = id['TDRC'][0] 106 | if 'TALB' in id.keys(): 107 | tags['Album'] = id['TALB'][0] 108 | if 'TIT2' in id.keys(): 109 | tags['Title'] = id['TIT2'][0] 110 | if 'TCON' in id.keys(): 111 | tags['Category'] = id['TCON'][0] 112 | if 'TPE2' in id.keys(): 113 | tags['Album/Artist'] = id['TPE2'][0] 114 | if 'TPUB' in id.keys(): 115 | tags['Publisher'] = id['TPUB'][0] 116 | 117 | return tags, header 118 | -------------------------------------------------------------------------------- /Chapter08/plugins/office_parser.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | import os 3 | from time import gmtime, strftime 4 | 5 | from lxml import etree 6 | import processors 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ 35 | 36 | 37 | def office_parser(filename): 38 | """ 39 | The office_parser function confirms the file type and sends it 40 | to be processed. 41 | :param filename: name of the file potentially containing 42 | embedded metadata. 43 | :return: A dictionary from get_tags, containing the embedded 44 | metadata. 45 | """ 46 | 47 | # DOCX, XLSX, and PPTX signatures 48 | signatures = ['504b030414000600'] 49 | if processors.utility.check_header( 50 | filename, signatures, 8) == True: 51 | return get_tags(filename) 52 | else: 53 | print(('File signature does not match known ' 54 | 'signatures.')) 55 | raise TypeError(('File signature does not match ' 56 | 'Office objects.')) 57 | 58 | 59 | def get_tags(filename): 60 | """ 61 | The get_tags function extracts the office metadata from the 62 | data object. 63 | :param filename: the path and name to the data object. 64 | :return: tags and headers, tags is a dictionary containing 65 | office metadata and headers are the order of keys for the CSV 66 | output. 67 | """ 68 | 69 | # Set up CSV headers 70 | headers = ['Path', 'Name', 'Size', 'Filesystem CTime', 71 | 'Filesystem MTime', 'Title', 'Author(s)','Create Date', 72 | 'Modify Date', 'Last Modified By Date', 'Subject', 'Keywords', 73 | 'Description', 'Category', 'Status', 'Revision', 74 | 'Edit Time (Min)', 'Page Count', 'Word Count', 75 | 'Character Count', 'Line Count', 76 | 'Paragraph Count', 'Slide Count', 'Note Count', 77 | 'Hidden Slide Count', 'Company', 'Hyperlink Base'] 78 | 79 | # Create a ZipFile class from the input object 80 | # This allows us to read or write to the 'Zip archive' 81 | zf = zipfile.ZipFile(filename) 82 | 83 | # These two XML files contain the embedded metadata of 84 | # interest 85 | try: 86 | core = etree.fromstring(zf.read('docProps/core.xml')) 87 | app = etree.fromstring(zf.read('docProps/app.xml')) 88 | except KeyError as e: 89 | assert Warning(e) 90 | return {}, headers 91 | 92 | tags = {} 93 | tags['Path'] = filename 94 | tags['Name'] = os.path.basename(filename) 95 | tags['Size'] = processors.utility.convert_size( 96 | os.path.getsize(filename)) 97 | tags['Filesystem CTime'] = strftime('%m/%d/%Y %H:%M:%S', 98 | gmtime(os.path.getctime(filename))) 99 | tags['Filesystem MTime'] = strftime('%m/%d/%Y %H:%M:%S', 100 | gmtime(os.path.getmtime(filename))) 101 | 102 | # Core Tags 103 | 104 | for child in core.iterchildren(): 105 | 106 | if 'title' in child.tag: 107 | tags['Title'] = child.text 108 | if 'subject' in child.tag: 109 | tags['Subject'] = child.text 110 | if 'creator' in child.tag: 111 | tags['Author(s)'] = child.text 112 | if 'keywords' in child.tag: 113 | tags['Keywords'] = child.text 114 | if 'description' in child.tag: 115 | tags['Description'] = child.text 116 | if 'lastModifiedBy' in child.tag: 117 | tags['Last Modified By Date'] = child.text 118 | if 'created' in child.tag: 119 | tags['Create Date'] = child.text 120 | if 'modified' in child.tag: 121 | tags['Modify Date'] = child.text 122 | if 'category' in child.tag: 123 | tags['Category'] = child.text 124 | if 'contentStatus' in child.tag: 125 | tags['Status'] = child.text 126 | 127 | if (filename.endswith('.docx') or 128 | filename.endswith('.pptx')): 129 | if 'revision' in child.tag: 130 | tags['Revision'] = child.text 131 | 132 | # App Tags 133 | for child in app.iterchildren(): 134 | 135 | if filename.endswith('.docx'): 136 | if 'TotalTime' in child.tag: 137 | tags['Edit Time (Min)'] = child.text 138 | if 'Pages' in child.tag: 139 | tags['Page Count'] = child.text 140 | if 'Words' in child.tag: 141 | tags['Word Count'] = child.text 142 | if 'Characters' in child.tag: 143 | tags['Character Count'] = child.text 144 | if 'Lines' in child.tag: 145 | tags['Line Count'] = child.text 146 | if 'Paragraphs' in child.tag: 147 | tags['Paragraph Count'] = child.text 148 | if 'Company' in child.tag: 149 | tags['Company'] = child.text 150 | if 'HyperlinkBase' in child.tag: 151 | tags['Hyperlink Base'] = child.text 152 | 153 | elif filename.endswith('.pptx'): 154 | if 'TotalTime' in child.tag: 155 | tags['Edit Time (Min)'] = child.text 156 | if 'Words' in child.tag: 157 | tags['Word Count'] = child.text 158 | if 'Paragraphs' in child.tag: 159 | tags['Paragraph Count'] = child.text 160 | if 'Slides' in child.tag: 161 | tags['Slide Count'] = child.text 162 | if 'Notes' in child.tag: 163 | tags['Note Count'] = child.text 164 | if 'HiddenSlides' in child.tag: 165 | tags['Hidden Slide Count'] = child.text 166 | if 'Company' in child.tag: 167 | tags['Company'] = child.text 168 | if 'HyperlinkBase' in child.tag: 169 | tags['Hyperlink Base'] = child.text 170 | else: 171 | if 'Company' in child.tag: 172 | tags['Company'] = child.text 173 | if 'HyperlinkBase' in child.tag: 174 | tags['Hyperlink Base'] = child.text 175 | 176 | return tags, headers 177 | -------------------------------------------------------------------------------- /Chapter08/processors/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import utility 6 | 7 | """ 8 | MIT License 9 | Copyright (c) 2018 Chapin Bryce, Preston Miller 10 | Please share comments and questions at: 11 | https://github.com/PythonForensics/Learning-Python-for-Forensics 12 | or email pyforcookbook@gmail.com 13 | 14 | Permission is hereby granted, free of charge, to any person 15 | obtaining a copy of this software and associated documentation 16 | files (the "Software"), to deal in the Software without 17 | restriction, including without limitation the rights to use, 18 | copy, modify, merge, publish, distribute, sublicense, and/or 19 | sell copies of the Software, and to permit persons to whom the 20 | Software is furnished to do so, subject to the following 21 | conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE. 33 | """ -------------------------------------------------------------------------------- /Chapter08/processors/utility.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import logging 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | 33 | def check_header(filename, headers, size): 34 | """ 35 | The check_header function reads a supplied size of the file 36 | and checks against known signatures to determine the file 37 | type. 38 | :param filename: The name of the file. 39 | :param headers: A list of known file signatures for the 40 | file type(s). 41 | :param size: The amount of data to read from the file for 42 | signature verification. 43 | :return: Boolean, True if the signatures match; 44 | otherwise, False. 45 | """ 46 | with open(filename, 'rb') as infile: 47 | header = infile.read(size) 48 | hex_header = binascii.hexlify(header).decode('utf-8') 49 | for signature in headers: 50 | if hex_header == signature: 51 | return True 52 | else: 53 | pass 54 | logging.warn(('The signature for {} ({}) does not match ' 55 | 'known signatures: {}').format( 56 | filename, hex_header, headers)) 57 | return False 58 | 59 | def convert_size(size): 60 | """ 61 | The convert_size function converts an integer representing 62 | bytes into a human-readable format. 63 | :param size: The size in bytes of a file 64 | :return: The human-readable size. 65 | """ 66 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] 67 | index = 0 68 | while size > 1024: 69 | size /= 1024. 70 | index += 1 71 | return '{:.2f} {}'.format(size, sizes[index]) 72 | 73 | -------------------------------------------------------------------------------- /Chapter08/writers/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import kml_writer 6 | import csv_writer 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ -------------------------------------------------------------------------------- /Chapter08/writers/csv_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import os 4 | if sys.version_info[0] == 2: 5 | import unicodecsv as csv 6 | elif sys.version_info[0] == 3: 7 | import csv 8 | import logging 9 | 10 | 11 | """ 12 | MIT License 13 | Copyright (c) 2018 Chapin Bryce, Preston Miller 14 | Please share comments and questions at: 15 | https://github.com/PythonForensics/Learning-Python-for-Forensics 16 | or email pyforcookbook@gmail.com 17 | 18 | Permission is hereby granted, free of charge, to any person 19 | obtaining a copy of this software and associated documentation 20 | files (the "Software"), to deal in the Software without 21 | restriction, including without limitation the rights to use, 22 | copy, modify, merge, publish, distribute, sublicense, and/or 23 | sell copies of the Software, and to permit persons to whom the 24 | Software is furnished to do so, subject to the following 25 | conditions: 26 | 27 | The above copyright notice and this permission notice shall be 28 | included in all copies or substantial portions of the Software. 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 31 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 32 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 33 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 34 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 35 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | OTHER DEALINGS IN THE SOFTWARE. 37 | """ 38 | 39 | 40 | def csv_writer(output_data, headers, output_dir, output_name): 41 | """ 42 | The csv_writer function uses the csv DictWriter module to 43 | write the list of dictionaries. The DictWriter can take 44 | a fieldnames argument, as a list, which represents the 45 | desired order of columns. 46 | :param output_data: The list of dictionaries containing 47 | embedded metadata. 48 | :param headers: A list of keys in the dictionary that 49 | represent the desired order of columns in the output. 50 | :param output_dir: The folder to write the output CSV to. 51 | :param output_name: The name of the output CSV. 52 | :return: 53 | """ 54 | msg = 'Writing ' + output_name + ' CSV output.' 55 | print('[+]', msg) 56 | logging.info(msg) 57 | 58 | out_file = os.path.join(output_dir, output_name) 59 | 60 | if sys.version_info[0] == 2: 61 | csvfile = open(out_file, "wb") 62 | elif sys.version_info[0] == 3: 63 | csvfile = open(out_file, "w", newline='', 64 | encoding='utf-8') 65 | 66 | with csvfile: 67 | # We use DictWriter instead of Writer to write 68 | # dictionaries to CSV. 69 | writer = csv.DictWriter(csvfile, fieldnames=headers) 70 | 71 | # Writerheader writes the header based on the supplied 72 | # headers object 73 | writer.writeheader() 74 | for dictionary in output_data: 75 | if dictionary: 76 | writer.writerow(dictionary) 77 | -------------------------------------------------------------------------------- /Chapter08/writers/kml_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import logging 4 | 5 | import simplekml 6 | 7 | """ 8 | MIT License 9 | Copyright (c) 2018 Chapin Bryce, Preston Miller 10 | Please share comments and questions at: 11 | https://github.com/PythonForensics/Learning-Python-for-Forensics 12 | or email pyforcookbook@gmail.com 13 | 14 | Permission is hereby granted, free of charge, to any person 15 | obtaining a copy of this software and associated documentation 16 | files (the "Software"), to deal in the Software without 17 | restriction, including without limitation the rights to use, 18 | copy, modify, merge, publish, distribute, sublicense, and/or 19 | sell copies of the Software, and to permit persons to whom the 20 | Software is furnished to do so, subject to the following 21 | conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE. 33 | """ 34 | 35 | 36 | def kml_writer(output_data, output_dir, output_name): 37 | """ 38 | The kml_writer function writes JPEG and TIFF EXIF GPS data to 39 | a Google Earth KML file. This file can be opened 40 | in Google Earth and will use the GPS coordinates to create 41 | 'pins' on the map of the taken photo's location. 42 | :param output_data: The embedded EXIF metadata to be written 43 | :param output_dir: The output directory to write the KML file. 44 | :param output_name: The name of the output KML file. 45 | :return: 46 | """ 47 | msg = 'Writing ' + output_name + ' KML output.' 48 | print('[+]', msg) 49 | logging.info(msg) 50 | # Instantiate a Kml object and pass along the output filename 51 | kml = simplekml.Kml(name=output_name) 52 | for exif in output_data: 53 | if ('Latitude' in exif.keys() and 54 | 'Latitude Reference' in exif.keys() and 55 | 'Longitude Reference' in exif.keys() and 56 | 'Longitude' in exif.keys()): 57 | 58 | if 'Original Date' in exif.keys(): 59 | dt = exif['Original Date'] 60 | else: 61 | dt = 'N/A' 62 | 63 | if exif['Latitude Reference'] == 'S': 64 | latitude = '-' + exif['Latitude'] 65 | else: 66 | latitude = exif['Latitude'] 67 | 68 | if exif['Longitude Reference'] == 'W': 69 | longitude = '-' + exif['Longitude'] 70 | else: 71 | longitude = exif['Longitude'] 72 | 73 | kml.newpoint(name=exif['Name'], 74 | description='Originally Created: ' + dt, 75 | coords=[(longitude, latitude)]) 76 | else: 77 | pass 78 | kml.save(os.path.join(output_dir, output_name)) -------------------------------------------------------------------------------- /Chapter11/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile was created for use with Learning Python for 2 | # Forensics, Second Edition. It is intended to configure the 3 | # environment to run the PST Indexing script. 4 | 5 | # Install tested base image 6 | FROM ubuntu:18.04 7 | 8 | # Create and navigate to working directory 9 | RUN ["mkdir", "/opt/book"] 10 | WORKDIR /opt/book 11 | 12 | # Install Dependencies 13 | RUN ["apt-get", "update"] 14 | RUN ["apt-get", "install", "-y", "python-dev", "python-pip", "lighttpd"] 15 | EXPOSE 80/tcp 16 | 17 | # Copy across the libpff and pypff built library, v. 20180812, fecb691 18 | COPY --chown=root:root docker_libs/ / 19 | RUN ["ldconfig"] 20 | 21 | # Copy across scripts 22 | COPY "pst_indexer.py" "/opt/book" 23 | COPY "stats_template.html" "/opt/book" 24 | 25 | # Pip install tested dependency versions 26 | RUN ["pip", "install", "unicodecsv==0.14.1", "jinja2==2.10"] -------------------------------------------------------------------------------- /Chapter11/docker_libs/usr/lib/python2.7/dist-packages/pypff.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter11/docker_libs/usr/lib/python2.7/dist-packages/pypff.a -------------------------------------------------------------------------------- /Chapter11/docker_libs/usr/lib/python2.7/dist-packages/pypff.la: -------------------------------------------------------------------------------- 1 | # pypff.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='pypff.so' 9 | 10 | # Names of this library. 11 | library_names='pypff.so pypff.so pypff.so' 12 | 13 | # The name of the static archive. 14 | old_library='pypff.a' 15 | 16 | # Linker flags that cannot go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs=' -L/usr/lib/python2.7/config-x86_64-linux-gnu -L/usr/lib -lpython2.7 -lutil -lm /usr/local/lib/libpff.la -lpthread -ldl' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for pypff. 26 | current=0 27 | age=0 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=yes 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/usr/local/lib/python2.7/dist-packages' 42 | -------------------------------------------------------------------------------- /Chapter11/docker_libs/usr/lib/python2.7/dist-packages/pypff.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter11/docker_libs/usr/lib/python2.7/dist-packages/pypff.so -------------------------------------------------------------------------------- /Chapter11/docker_libs/usr/local/lib/libpff.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter11/docker_libs/usr/local/lib/libpff.a -------------------------------------------------------------------------------- /Chapter11/docker_libs/usr/local/lib/libpff.la: -------------------------------------------------------------------------------- 1 | # libpff.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='libpff.so.1' 9 | 10 | # Names of this library. 11 | library_names='libpff.so.1.0.0 libpff.so.1 libpff.so' 12 | 13 | # The name of the static archive. 14 | old_library='libpff.a' 15 | 16 | # Linker flags that cannot go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs=' -lpthread -ldl' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libpff. 26 | current=1 27 | age=0 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/usr/local/lib' 42 | -------------------------------------------------------------------------------- /Chapter11/docker_libs/usr/local/lib/libpff.so.1.0.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter11/docker_libs/usr/local/lib/libpff.so.1.0.0 -------------------------------------------------------------------------------- /Chapter12/places.sqlite-wal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter12/places.sqlite-wal -------------------------------------------------------------------------------- /Chapter13/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter13/.DS_Store -------------------------------------------------------------------------------- /Chapter13/__MACOSX/chapter_13/._.DS_Store: -------------------------------------------------------------------------------- 1 | Mac OS X  2Fx @ATTRxx -------------------------------------------------------------------------------- /Chapter13/__MACOSX/chapter_13/._requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter13/__MACOSX/chapter_13/._requirements.txt -------------------------------------------------------------------------------- /Chapter13/__MACOSX/chapter_13/plugins/._.DS_Store: -------------------------------------------------------------------------------- 1 | Mac OS X  2Fx @ATTRxx -------------------------------------------------------------------------------- /Chapter13/chapter_13/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter13/chapter_13/.DS_Store -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter13/chapter_13/plugins/.DS_Store -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import wal_crawler 6 | import setupapi 7 | import userassist 8 | import exif 9 | import id3 10 | import office 11 | import pst_indexer 12 | 13 | """ 14 | MIT License 15 | Copyright (c) 2018 Chapin Bryce, Preston Miller 16 | Please share comments and questions at: 17 | https://github.com/PythonForensics/Learning-Python-for-Forensics 18 | or email pyforcookbook@gmail.com 19 | 20 | Permission is hereby granted, free of charge, to any person 21 | obtaining a copy of this software and associated documentation 22 | files (the "Software"), to deal in the Software without 23 | restriction, including without limitation the rights to use, 24 | copy, modify, merge, publish, distribute, sublicense, and/or 25 | sell copies of the Software, and to permit persons to whom the 26 | Software is furnished to do so, subject to the following 27 | conditions: 28 | 29 | The above copyright notice and this permission notice shall be 30 | included in all copies or substantial portions of the Software. 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 32 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 33 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 34 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 35 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 36 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 37 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 38 | OTHER DEALINGS IN THE SOFTWARE. 39 | """ -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/helper/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import utility 6 | import usb_lookup 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/helper/usb_lookup.py: -------------------------------------------------------------------------------- 1 | """Updated USB Lookup script based on the version found in Chapter 2.""" 2 | from __future__ import print_function 3 | import argparse 4 | import sys 5 | try: 6 | from urllib2 import urlopen 7 | except ImportError: 8 | from urllib.request import urlopen 9 | 10 | """ 11 | MIT License 12 | Copyright (c) 2018 Chapin Bryce, Preston Miller 13 | Please share comments and questions at: 14 | https://github.com/PythonForensics/Learning-Python-for-Forensics 15 | or email pyforcookbook@gmail.com 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | The above copyright notice and this permission notice shall be included in 24 | all copies or substantial portions of the Software. 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | """ 33 | 34 | def main(vid, pid, ids_file=None): 35 | """ 36 | Main function to control operation. Requires arguments passed as VID PID 37 | on the command line. If discovered in data set, the common names will be 38 | printed to stdout 39 | :return: None 40 | """ 41 | if ids_file: 42 | usb_file = open(ids_file, encoding='latin1') 43 | else: 44 | usb_file = get_usb_file() 45 | usbs = parse_file(usb_file) 46 | results = search_key(usbs, (vid, pid)) 47 | print("Vendor: {}\nProduct: {}".format(results[0], results[1])) 48 | 49 | 50 | def get_usb_file(): 51 | """ 52 | Retrieves USB.ids database from the web. 53 | """ 54 | url = 'http://www.linux-usb.org/usb.ids' 55 | return urlopen(url) 56 | 57 | 58 | def parse_file(usb_file): 59 | """ 60 | Parses the USB.ids file. If this is run offline, please 61 | download the USB.ids and pass the open file to this function. 62 | ie: parse_file(open('path/to/USB.ids', 'r')) 63 | :return: dictionary of entires for querying 64 | """ 65 | usbs = {} 66 | curr_id = '' 67 | for line in usb_file: 68 | if isinstance(line, bytes): 69 | line = line.decode('latin-1') 70 | if line.startswith('#') or line in ('\n', '\t'): 71 | continue 72 | else: 73 | if not line.startswith('\t') and (line[0].isdigit() or 74 | line[0].islower()): 75 | uid, name = get_record(line.strip()) 76 | curr_id = uid 77 | usbs[uid] = [name.strip(), {}] 78 | elif line.startswith('\t') and line.count('\t') == 1: 79 | uid, name = get_record(line.strip()) 80 | usbs[curr_id][1][uid] = name.strip() 81 | return usbs 82 | 83 | 84 | def get_record(record_line): 85 | """ 86 | Split records out by dynamic position. By finding the space, 87 | we can determine the location to split the record for 88 | extraction. To learn more about this, uncomment the print 89 | statements and see what the code is doing behind the scenes! 90 | """ 91 | # print("Line: {}".format(record_line)) 92 | split = record_line.find(' ') 93 | # print("Split: {}".format(split)) 94 | record_id = record_line[:split] 95 | # print("Record ID: ".format(record_id)) 96 | record_name = record_line[split + 1:] 97 | # print("Record Name: ".format(record_name)) 98 | return record_id, record_name 99 | 100 | 101 | def search_key(usb_dict, ids): 102 | """ 103 | Compare provided IDs to the built USB dictionary. If found, 104 | it will return the common name, otherwise returns the string 105 | "unknown". 106 | """ 107 | vendor_key = ids[0] 108 | product_key = ids[1] 109 | 110 | vendor, vendor_data = usb_dict.get(vendor_key, ['unknown', {}]) 111 | product = 'unknown' 112 | if vendor != 'unknown': 113 | product = vendor_data.get(product_key, 'unknown') 114 | 115 | return vendor, product 116 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/helper/utility.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from datetime import datetime, timedelta 3 | import logging 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | 34 | def check_header(filename, headers, size): 35 | """ 36 | The check_header function reads a supplied size of the file 37 | and checks against known signatures to determine the file 38 | type. 39 | :param filename: The name of the file. 40 | :param headers: A list of known file signatures for the 41 | file type(s). 42 | :param size: The amount of data to read from the file for 43 | signature verification. 44 | :return: Boolean, True if the signatures match; 45 | otherwise, False. 46 | """ 47 | with open(filename, 'rb') as infile: 48 | header = infile.read(size) 49 | hex_header = binascii.hexlify(header).decode('utf-8') 50 | for signature in headers: 51 | if hex_header == signature: 52 | return True 53 | else: 54 | pass 55 | logging.warn(('The signature for {} ({}) does not match ' 56 | 'known signatures: {}').format( 57 | filename, hex_header, headers)) 58 | return False 59 | 60 | 61 | def convert_size(size): 62 | """ 63 | The convert_size function converts an integer representing 64 | bytes into a human-readable format. 65 | :param size: The size in bytes of a file 66 | :return: The human-readable size. 67 | """ 68 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] 69 | index = 0 70 | while size > 1024: 71 | size /= 1024. 72 | index += 1 73 | return '{:.2f} {}'.format(size, sizes[index]) 74 | 75 | 76 | def file_time(ft): 77 | """ 78 | The fileTime function converts Windows FILETIME objects into 79 | human readable value 80 | :param ft: the FILETIME to convert 81 | :return: date_str, the human readable datetime value 82 | """ 83 | if ft is not None and ft != 0: 84 | return datetime(1601, 1, 1) + timedelta(microseconds=ft / 10) 85 | else: 86 | return 0 -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/id3.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import gmtime, strftime 3 | from helper import utility 4 | from mutagen import mp3, id3 5 | 6 | """ 7 | MIT License 8 | Copyright (c) 2018 Chapin Bryce, Preston Miller 9 | Please share comments and questions at: 10 | https://github.com/PythonForensics/Learning-Python-for-Forensics 11 | or email pyforcookbook@gmail.com 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or 18 | sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 26 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 28 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 29 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | """ 33 | 34 | 35 | def main(filename): 36 | """ 37 | The main function confirms the file type and sends it to 38 | be processed. 39 | :param filename: name of the file potentially containing exif 40 | metadata. 41 | :return: A dictionary from get_tags, containing the embedded 42 | EXIF metadata. 43 | """ 44 | 45 | # MP3 signatures 46 | signatures = ['494433'] 47 | if utility.check_header(filename, signatures, 3) is True: 48 | return get_tags(filename) 49 | else: 50 | raise TypeError 51 | 52 | 53 | def get_tags(filename): 54 | """ 55 | The get_tags function extracts the ID3 metadata from the data 56 | object. 57 | :param filename: the path and name to the data object. 58 | :return: tags and headers, tags is a dictionary containing ID3 59 | metadata and headers are the order of keys for the CSV output. 60 | """ 61 | 62 | # Set up CSV headers 63 | header = ['Path', 'Name', 'Size', 'Filesystem CTime', 64 | 'Filesystem MTime', 'Title', 'Subtitle', 'Artist', 'Album', 65 | 'Album/Artist', 'Length (Sec)', 'Year', 'Category', 66 | 'Track Number', 'Comments', 'Publisher', 'Bitrate', 67 | 'Sample Rate', 'Encoding', 'Channels', 'Audio Layer'] 68 | tags = {} 69 | tags['Path'] = filename 70 | tags['Name'] = os.path.basename(filename) 71 | tags['Size'] = utility.convert_size( 72 | os.path.getsize(filename)) 73 | tags['Filesystem CTime'] = strftime('%m/%d/%Y %H:%M:%S', 74 | gmtime(os.path.getctime(filename))) 75 | tags['Filesystem MTime'] = strftime('%m/%d/%Y %H:%M:%S', 76 | gmtime(os.path.getmtime(filename))) 77 | 78 | # MP3 Specific metadata 79 | audio = mp3.MP3(filename) 80 | if 'TENC' in audio.keys(): 81 | tags['Encoding'] = audio['TENC'][0] 82 | tags['Bitrate'] = audio.info.bitrate 83 | tags['Channels'] = audio.info.channels 84 | tags['Audio Layer'] = audio.info.layer 85 | tags['Length (Sec)'] = audio.info.length 86 | tags['Sample Rate'] = audio.info.sample_rate 87 | 88 | # ID3 embedded metadata tags 89 | id = id3.ID3(filename) 90 | if 'TPE1' in id.keys(): 91 | tags['Artist'] = id['TPE1'][0] 92 | if 'TRCK' in id.keys(): 93 | tags['Track Number'] = id['TRCK'][0] 94 | if 'TIT3' in id.keys(): 95 | tags['Subtitle'] = id['TIT3'][0] 96 | if 'COMM::eng' in id.keys(): 97 | tags['Comments'] = id['COMM::eng'][0] 98 | if 'TDRC' in id.keys(): 99 | tags['Year'] = id['TDRC'][0] 100 | if 'TALB' in id.keys(): 101 | tags['Album'] = id['TALB'][0] 102 | if 'TIT2' in id.keys(): 103 | tags['Title'] = id['TIT2'][0] 104 | if 'TCON' in id.keys(): 105 | tags['Category'] = id['TCON'][0] 106 | if 'TPE2' in id.keys(): 107 | tags['Album/Artist'] = id['TPE2'][0] 108 | if 'TPUB' in id.keys(): 109 | tags['Publisher'] = id['TPUB'][0] 110 | 111 | return tags, header 112 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/office.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | import os 3 | from time import gmtime, strftime 4 | from helper import utility 5 | 6 | from lxml import etree 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ 35 | 36 | 37 | def main(filename): 38 | 39 | """ 40 | The main function confirms the file type and sends it 41 | to be processed. 42 | :param filename: name of the file potentially containing 43 | embedded metadata. 44 | :return: A dictionary from getTags, containing the embedded 45 | metadata. 46 | """ 47 | 48 | # DOCX, XLSX, and PPTX signatures 49 | signatures = ['504b030414000600'] 50 | if utility.check_header(filename, signatures, 8) is True: 51 | return get_tags(filename) 52 | else: 53 | raise TypeError 54 | 55 | 56 | def get_tags(filename): 57 | """ 58 | The get_tags function extracts the office metadata from the 59 | data object. 60 | :param filename: the path and name to the data object. 61 | :return: tags and headers, tags is a dictionary containing 62 | office metadata and headers are the order of keys for the CSV 63 | output. 64 | """ 65 | 66 | # Set up CSV headers 67 | headers = ['Path', 'Name', 'Size', 'Filesystem CTime', 68 | 'Filesystem MTime', 'Title', 'Author(s)','Create Date', 69 | 'Modify Date', 'Last Modified By Date', 'Subject', 'Keywords', 70 | 'Description', 'Category', 'Status', 'Revision', 71 | 'Edit Time (Min)', 'Page Count', 'Word Count', 72 | 'Character Count', 'Line Count', 73 | 'Paragraph Count', 'Slide Count', 'Note Count', 74 | 'Hidden Slide Count', 'Company', 'Hyperlink Base'] 75 | 76 | # Create a ZipFile class from the input object 77 | # This allows us to read or write to the 'Zip archive' 78 | try: 79 | zf = zipfile.ZipFile(filename) 80 | except zipfile.BadZipfile: 81 | return {}, headers 82 | 83 | # These two XML files contain the embedded metadata of 84 | # interest 85 | try: 86 | core = etree.fromstring(zf.read('docProps/core.xml')) 87 | app = etree.fromstring(zf.read('docProps/app.xml')) 88 | except KeyError as e: 89 | assert Warning(e) 90 | return {}, headers 91 | 92 | tags = {} 93 | tags['Path'] = filename 94 | tags['Name'] = os.path.basename(filename) 95 | tags['Size'] = utility.convert_size( 96 | os.path.getsize(filename)) 97 | tags['Filesystem CTime'] = strftime('%m/%d/%Y %H:%M:%S', 98 | gmtime(os.path.getctime(filename))) 99 | tags['Filesystem MTime'] = strftime('%m/%d/%Y %H:%M:%S', 100 | gmtime(os.path.getmtime(filename))) 101 | 102 | # Core Tags 103 | 104 | for child in core.iterchildren(): 105 | 106 | if 'title' in child.tag: 107 | tags['Title'] = child.text 108 | if 'subject' in child.tag: 109 | tags['Subject'] = child.text 110 | if 'creator' in child.tag: 111 | tags['Author(s)'] = child.text 112 | if 'keywords' in child.tag: 113 | tags['Keywords'] = child.text 114 | if 'description' in child.tag: 115 | tags['Description'] = child.text 116 | if 'lastModifiedBy' in child.tag: 117 | tags['Last Modified By Date'] = child.text 118 | if 'created' in child.tag: 119 | tags['Create Date'] = child.text 120 | if 'modified' in child.tag: 121 | tags['Modify Date'] = child.text 122 | if 'category' in child.tag: 123 | tags['Category'] = child.text 124 | if 'contentStatus' in child.tag: 125 | tags['Status'] = child.text 126 | 127 | if (filename.endswith('.docx') or 128 | filename.endswith('.pptx')): 129 | if 'revision' in child.tag: 130 | tags['Revision'] = child.text 131 | 132 | # App Tags 133 | for child in app.iterchildren(): 134 | 135 | if filename.endswith('.docx'): 136 | if 'TotalTime' in child.tag: 137 | tags['Edit Time (Min)'] = child.text 138 | if 'Pages' in child.tag: 139 | tags['Page Count'] = child.text 140 | if 'Words' in child.tag: 141 | tags['Word Count'] = child.text 142 | if 'Characters' in child.tag: 143 | tags['Character Count'] = child.text 144 | if 'Lines' in child.tag: 145 | tags['Line Count'] = child.text 146 | if 'Paragraphs' in child.tag: 147 | tags['Paragraph Count'] = child.text 148 | if 'Company' in child.tag: 149 | tags['Company'] = child.text 150 | if 'HyperlinkBase' in child.tag: 151 | tags['Hyperlink Base'] = child.text 152 | 153 | elif filename.endswith('.pptx'): 154 | if 'TotalTime' in child.tag: 155 | tags['Edit Time (Min)'] = child.text 156 | if 'Words' in child.tag: 157 | tags['Word Count'] = child.text 158 | if 'Paragraphs' in child.tag: 159 | tags['Paragraph Count'] = child.text 160 | if 'Slides' in child.tag: 161 | tags['Slide Count'] = child.text 162 | if 'Notes' in child.tag: 163 | tags['Note Count'] = child.text 164 | if 'HiddenSlides' in child.tag: 165 | tags['Hidden Slide Count'] = child.text 166 | if 'Company' in child.tag: 167 | tags['Company'] = child.text 168 | if 'HyperlinkBase' in child.tag: 169 | tags['Hyperlink Base'] = child.text 170 | else: 171 | if 'Company' in child.tag: 172 | tags['Company'] = child.text 173 | if 'HyperlinkBase' in child.tag: 174 | tags['Hyperlink Base'] = child.text 175 | 176 | return tags, headers 177 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/pst_indexer.py: -------------------------------------------------------------------------------- 1 | import pypff 2 | 3 | """ 4 | MIT License 5 | Copyright (c) 2018 Chapin Bryce, Preston Miller 6 | Please share comments and questions at: 7 | https://github.com/PythonForensics/Learning-Python-for-Forensics 8 | or email pyforcookbook@gmail.com 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without 13 | restriction, including without limitation the rights to use, 14 | copy, modify, merge, publish, distribute, sublicense, and/or 15 | sell copies of the Software, and to permit persons to whom the 16 | Software is furnished to do so, subject to the following 17 | conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | """ 30 | 31 | 32 | def main(pst_file): 33 | """ 34 | The main function opens a PST and calls functions to parse 35 | and report data from the PST 36 | :param pst_file: A string representing the path to the PST 37 | file to analyze 38 | :return: None 39 | """ 40 | opst = pypff.open(pst_file) 41 | root = opst.get_root_folder() 42 | 43 | message_data = folder_traverse(root, [], 44 | **{'pst_name': pst_file, 'folder_name': 'root'}) 45 | 46 | header = ['pst_name', 'folder_name', 'creation_time', 47 | 'submit_time', 'delivery_time', 'sender', 'subject', 'attachment_count'] 48 | 49 | return message_data, header 50 | 51 | 52 | def folder_traverse(base, message_data, pst_name, folder_name): 53 | """ 54 | The folder_traverse function walks through the base of the 55 | folder and scans for sub-folders and messages 56 | :param base: Base folder to scan for new items within the 57 | folder 58 | :param message_data: A list of data for output 59 | :param pst_name: A string representing the name of the PST 60 | file 61 | :param folder_name: A string representing the name of the 62 | folder 63 | :return: None 64 | """ 65 | for folder in base.sub_folders: 66 | if folder.number_of_sub_folders: 67 | message_data = folder_traverse(folder, message_data, 68 | pst_name, folder.name) 69 | message_data = check_for_messages(folder, message_data, 70 | pst_name, folder.name) 71 | return message_data 72 | 73 | 74 | def check_for_messages(folder, message_data, pst_name, folder_name): 75 | """ 76 | The check_for_messages function reads folder messages if 77 | present and passes them to the report function 78 | :param folder: pypff.Folder object 79 | :param message_data: list to pass and extend with message info 80 | :param pst_name: A string representing the name of the PST 81 | file 82 | :param folder_name: A string representing the name of the 83 | folder 84 | :return: Dictionary of results by folder 85 | """ 86 | for message in folder.sub_messages: 87 | message_dict = process_message(message) 88 | message_dict['pst_name'] = pst_name 89 | message_dict['folder_name'] = folder_name 90 | message_data.append(message_dict) 91 | return message_data 92 | 93 | 94 | def process_message(message): 95 | """ 96 | The process_message function processes multi-field messages to 97 | simplify collection of information 98 | :param message: The pypff.Message object 99 | :return: A dictionary with message fields (values) and their 100 | data (keys) 101 | """ 102 | return { 103 | "subject": message.subject, 104 | "sender": message.sender_name, 105 | "header": message.transport_headers, 106 | "body": message.plain_text_body, 107 | "creation_time": message.creation_time, 108 | "submit_time": message.client_submit_time, 109 | "delivery_time": message.delivery_time, 110 | "attachment_count": message.number_of_attachments, 111 | } 112 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/setupapi.py: -------------------------------------------------------------------------------- 1 | from helper import usb_lookup 2 | 3 | """ 4 | MIT License 5 | Copyright (c) 2018 Chapin Bryce, Preston Miller 6 | Please share comments and questions at: 7 | https://github.com/PythonForensics/Learning-Python-for-Forensics 8 | or email pyforcookbook@gmail.com 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without 13 | restriction, including without limitation the rights to use, 14 | copy, modify, merge, publish, distribute, sublicense, and/or 15 | sell copies of the Software, and to permit persons to whom the 16 | Software is furnished to do so, subject to the following 17 | conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | """ 30 | 31 | 32 | def main(in_file): 33 | """ 34 | Main function to handle operation 35 | :param in_file: Str - Path to setupapi log to analyze 36 | :return: list of USB data and list of headers for output 37 | """ 38 | headers = ['Vendor ID', 'Vendor Name', 'Product ID', 39 | 'Product Name', 'Revision', 'UID', 40 | 'First Installation Date'] 41 | data = [] 42 | 43 | device_information = parse_setupapi(in_file) 44 | usb_ids = prep_usb_lookup() 45 | for device in device_information: 46 | parsed_info = parse_device_info(device) 47 | if isinstance(parsed_info, dict): 48 | parsed_info = get_device_names(usb_ids, parsed_info) 49 | data.append(parsed_info) 50 | else: 51 | pass 52 | return data, headers 53 | 54 | 55 | def parse_setupapi(setup_log): 56 | """ 57 | Read data from provided file for Device Install Events for 58 | USB Devices 59 | :param setup_log: str - Path to valid setup api log 60 | :return: tuple of str - Device name and date 61 | """ 62 | device_list = list() 63 | unique_list = set() 64 | with open(setup_log) as in_file: 65 | for line in in_file: 66 | lower_line = line.lower() 67 | if 'device install (hardware initiated)' in \ 68 | lower_line and ('vid' in lower_line or 69 | 'ven' in lower_line): 70 | device_name = line.split('-')[1].strip() 71 | date = next(in_file).split('start')[1].strip() 72 | if device_name not in unique_list: 73 | device_list.append((device_name, date)) 74 | unique_list.add(device_name) 75 | 76 | return device_list 77 | 78 | 79 | def parse_device_info(device_info): 80 | """ 81 | Parses Vendor, Product, Revision and UID from a Setup API 82 | entry 83 | :param device_info: string of device information to parse 84 | :return: dictionary of parsed information or original string 85 | if error 86 | """ 87 | # Initialize variables 88 | vid = '' 89 | pid = '' 90 | rev = '' 91 | uid = '' 92 | 93 | # Split string into segments on \\ 94 | segments = device_info[0].split('\\') 95 | 96 | if 'usb' not in segments[0].lower(): 97 | return None 98 | # Eliminate non-USB devices from output 99 | # May hide other storage devices 100 | 101 | for item in segments[1].split('&'): 102 | lower_item = item.lower() 103 | if 'ven' in lower_item or 'vid' in lower_item: 104 | vid = item.split('_', 1)[-1] 105 | elif 'dev' in lower_item or 'pid' in lower_item or \ 106 | 'prod' in lower_item: 107 | pid = item.split('_', 1)[-1] 108 | elif 'rev' in lower_item or 'mi' in lower_item: 109 | rev = item.split('_', 1)[-1] 110 | 111 | if len(segments) >= 3: 112 | uid = segments[2].strip(']') 113 | 114 | if vid != '' or pid != '': 115 | return {'Vendor ID': vid.lower(), 116 | 'Product ID': pid.lower(), 117 | 'Revision': rev, 118 | 'UID': uid, 119 | 'First Installation Date': device_info[1]} 120 | # Unable to parse data, returning whole string 121 | return device_info 122 | 123 | 124 | def prep_usb_lookup(local_usb_ids=None): 125 | """ 126 | Prepare the lookup of USB devices through accessing the most 127 | recent copy of the database at http://linux-usb.org/usb.ids 128 | or using the provided file and parsing it into a queriable 129 | dictionary format. 130 | """ 131 | if local_usb_ids: 132 | usb_file = open(local_usb_ids, encoding='latin1') 133 | else: 134 | usb_file = usb_lookup.get_usb_file() 135 | return usb_lookup.parse_file(usb_file) 136 | 137 | 138 | def get_device_names(usb_dict, device_info): 139 | """ 140 | Query `usb_lookup.py` for device information based on VID/PID. 141 | :param usb_dict: Dictionary from usb_lookup.py of known 142 | devices. 143 | :param device_info: Dictionary containing 'Vendor ID' and 144 | 'Product ID' keys and values. 145 | :return: original dictionary with 'Vendor Name' and 146 | 'Product Name' keys and values 147 | """ 148 | device_name = usb_lookup.search_key( 149 | usb_dict, [device_info['Vendor ID'], 150 | device_info['Product ID']]) 151 | 152 | device_info['Vendor Name'] = device_name[0] 153 | device_info['Product Name'] = device_name[1] 154 | 155 | return device_info 156 | 157 | 158 | def print_output(usb_information): 159 | """ 160 | Print formatted information about USB Device 161 | :param usb_information: dictionary containing key/value 162 | data about each device or tuple of device information 163 | :return: None 164 | """ 165 | print('{:-^15}'.format('')) 166 | 167 | if isinstance(usb_information, dict): 168 | for key_name, value_name in usb_information.items(): 169 | print('{}: {}'.format(key_name, value_name)) 170 | elif isinstance(usb_information, tuple): 171 | print('Device: {}'.format(usb_information[0])) 172 | print('Date: {}'.format(usb_information[1])) 173 | 174 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/plugins/userassist.py: -------------------------------------------------------------------------------- 1 | """UserAssist parser leveraging the YARP library.""" 2 | import struct 3 | import sys 4 | import logging 5 | 6 | from helper import utility 7 | 8 | from yarp import Registry 9 | 10 | """ 11 | MIT License 12 | Copyright (c) 2018 Chapin Bryce, Preston Miller 13 | Please share comments and questions at: 14 | https://github.com/PythonForensics/Learning-Python-for-Forensics 15 | or email pyforcookbook@gmail.com 16 | 17 | Permission is hereby granted, free of charge, to any person 18 | obtaining a copy of this software and associated documentation 19 | files (the "Software"), to deal in the Software without 20 | restriction, including without limitation the rights to use, 21 | copy, modify, merge, publish, distribute, sublicense, and/or 22 | sell copies of the Software, and to permit persons to whom the 23 | Software is furnished to do so, subject to the following 24 | conditions: 25 | 26 | The above copyright notice and this permission notice shall be 27 | included in all copies or substantial portions of the Software. 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 30 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 31 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 32 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 33 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 34 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 35 | OTHER DEALINGS IN THE SOFTWARE. 36 | """ 37 | 38 | # KEYS will contain sub-lists of each parsed UserAssist (UA) key 39 | KEYS = [] 40 | 41 | 42 | def main(registry, **kwargs): 43 | """ 44 | The main function handles main logic of script. 45 | :param registry: Registry hive to process 46 | :return: Nothing. 47 | """ 48 | if utility.check_header(registry, ['72656766'], 4) is not True: 49 | logging.error('Incorrect file detected based on name') 50 | raise TypeError 51 | # Create dictionary of ROT-13 decoded UA key and its value 52 | apps = create_dictionary(registry) 53 | ua_type = parse_values(apps) 54 | 55 | if ua_type == 0: 56 | logging.info('Detected XP based Userassist values.') 57 | 58 | else: 59 | logging.info('Detected Win7 based Userassist values.') 60 | 61 | headers = ['Name', 'Path', 'Session ID', 'Count', 62 | 'Last Used Date (UTC)', 'Focus Time (ms)', 'Focus Count'] 63 | return KEYS, headers 64 | 65 | 66 | def create_dictionary(registry): 67 | """ 68 | The create_dictionary function creates a list of dictionaries 69 | where keys are the ROT-13 decoded app names and values are 70 | the raw hex data of said app. 71 | :param registry: Registry Hive to process 72 | :return: apps_list, A list containing dictionaries for 73 | each app 74 | """ 75 | try: 76 | # Open the registry file to be parsed 77 | registry_file = open(registry, "rb") 78 | reg = Registry.RegistryHive(registry_file) 79 | except (IOError, UnicodeDecodeError) as e: 80 | msg = 'Invalid NTUSER.DAT path or Registry ID.' 81 | logging.error(msg) 82 | raise TypeError 83 | 84 | # Navigate to the UserAssist key 85 | ua_key = reg.find_key( 86 | ('SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer' 87 | '\\UserAssist')) 88 | if ua_key is None: 89 | msg = 'UserAssist Key not found in Registry file.' 90 | logging.error(msg) 91 | raise TypeError 92 | apps_list = [] 93 | # Loop through each subkey in the UserAssist key 94 | for ua_subkey in ua_key.subkeys(): 95 | # For each subkey in the UserAssist key, detect a subkey 96 | # called Count that has more than 0 values to parse. 97 | if(ua_subkey.subkey('Count') and 98 | ua_subkey.subkey('Count').values_count() > 0): 99 | apps = {} 100 | for v in ua_subkey.subkey('Count').values(): 101 | if sys.version_info[0] == 2: 102 | apps[v.name().encode('utf-8').decode( 103 | 'rot-13')] = v.data_raw() 104 | elif sys.version_info[0] == 3: 105 | import codecs 106 | enc = codecs.getencoder('rot-13') 107 | apps[enc(str(v.name()))[0]] = v.data_raw() 108 | 109 | apps_list.append(apps) 110 | return apps_list 111 | 112 | 113 | def parse_values(data): 114 | """ 115 | The parse_values function uses struct to unpack the raw value 116 | data from the UA key 117 | :param data: A list containing dictionaries of UA 118 | application data 119 | :return: ua_type, based on the size of the raw data from 120 | the dictionary values. 121 | """ 122 | ua_type = -1 123 | msg = 'Parsing UserAssist values.' 124 | logging.info(msg) 125 | 126 | for dictionary in data: 127 | for v in dictionary.keys(): 128 | # WinXP based UA keys are 16 bytes 129 | if len(dictionary[v]) == 16: 130 | raw = struct.unpack('<2iq', dictionary[v]) 131 | ua_type = 0 132 | KEYS.append({'Name': get_name(v), 'Path': v, 133 | 'Session ID': raw[0], 'Count': raw[1], 134 | 'Last Used Date (UTC)': utility.file_time(raw[2]), 135 | 'Focus Time (ms)': '', 'Focus Count': ''}) 136 | # Win7 based UA keys are 72 bytes 137 | elif len(dictionary[v]) == 72: 138 | raw = struct.unpack('<4i44xq4x', dictionary[v]) 139 | ua_type = 1 140 | KEYS.append({'Name': get_name(v), 'Path': v, 141 | 'Session ID': raw[0], 'Count': raw[1], 142 | 'Last Used Date (UTC)': utility.file_time(raw[4]), 143 | 'Focus Time (ms)': raw[3],'Focus Count': raw[2]}) 144 | else: 145 | # If the key is not WinXP or Win7 based -- ignore. 146 | msg = 'Ignoring {} value that is {} bytes'.format( 147 | str(v), str(len(dictionary[v]))) 148 | logging.info(msg) 149 | continue 150 | return ua_type 151 | 152 | 153 | def get_name(full_name): 154 | """ 155 | the get_name function splits the name of the application 156 | returning the executable name and ignoring the 157 | path details. 158 | :param full_name: the path and executable name 159 | :return: the executable name 160 | """ 161 | # Determine if '\\' and ':' are within the full_name 162 | if ':' in full_name and '\\' in full_name: 163 | # Find if ':' comes before '\\' 164 | if full_name.rindex(':') > full_name.rindex('\\'): 165 | # Split on ':' and return the last element 166 | # (the executable) 167 | return full_name.split(':')[-1] 168 | else: 169 | # Otherwise split on '\\' 170 | return full_name.split('\\')[-1] 171 | # When just ':' or '\\' is in the full_name, split on 172 | # that item and return the last element (the executable) 173 | elif ':' in full_name: 174 | return full_name.split(':')[-1] 175 | else: 176 | return full_name.split('\\')[-1] 177 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/requirements.txt: -------------------------------------------------------------------------------- 1 | xlsxwriter==1.1.2 2 | colorama==0.4.1 3 | pyfiglet==0.8.post0 4 | mutagen==1.42.0 5 | pillow==5.4.0 6 | simplekml==1.3.1 7 | tqdm==4.28.1 8 | unicodecsv==0.14.1 9 | jinja2==2.10 10 | lxml==4.3.0 11 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/writers/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import csv_writer 6 | import kml_writer 7 | import xlsx_writer 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ -------------------------------------------------------------------------------- /Chapter13/chapter_13/writers/csv_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import os 4 | if sys.version_info[0] == 2: 5 | import unicodecsv as csv 6 | elif sys.version_info[0] == 3: 7 | import csv 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ 36 | 37 | 38 | def writer(output, headers, output_data, **kwargs): 39 | """ 40 | The writer function uses the csv.DictWriter module to write 41 | list(s) of dictionaries. The DictWriter can take a fieldnames 42 | argument, as a list, which represents the desired order of 43 | columns. 44 | :param output: The name of the output CSV. 45 | :param headers: A list of keys in the dictionary that 46 | represent the desired order of columns in the output. 47 | :param output_data: The list of dictionaries containing 48 | embedded metadata. 49 | :return: None 50 | """ 51 | 52 | if sys.version_info[0] == 2: 53 | csvfile = open(output, "wb") 54 | elif sys.version_info[0] == 3: 55 | csvfile = open(output, "w", newline='', 56 | encoding='utf-8') 57 | 58 | with csvfile: 59 | # We use DictWriter instead of writer to write 60 | # dictionaries to CSV. 61 | w = csv.DictWriter(csvfile, fieldnames=headers, 62 | extrasaction='ignore') 63 | 64 | # Writerheader writes the header based on the supplied 65 | # headers object 66 | try: 67 | w.writeheader() 68 | except TypeError: 69 | print(('[-] Received empty headers...\n' 70 | '[-] Skipping writing output.')) 71 | return 72 | 73 | if 'recursion' in kwargs.keys(): 74 | for l in output_data: 75 | for data in l: 76 | if data: 77 | w.writerow(data) 78 | else: 79 | for data in output_data: 80 | if data: 81 | w.writerow(data) -------------------------------------------------------------------------------- /Chapter13/chapter_13/writers/kml_writer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import simplekml 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | def writer(output, output_name, output_data): 33 | """ 34 | The writer function writes JPEG and TIFF EXIF GPS data to a 35 | Google Earth KML file. This file can be opened in Google 36 | Earth and will use the GPS coordinates to create 'pins' on 37 | the map of the taken photo's location. 38 | :param output: The output directory to write the KML file. 39 | :param output_name: The name of the output KML file. 40 | :param output_data: The embedded EXIF metadata to be written 41 | :return: 42 | """ 43 | 44 | kml = simplekml.Kml(name=output_name) 45 | for exif in output_data: 46 | if('Latitude' in exif.keys() and 47 | 'Latitude Reference' in exif.keys() and 48 | 'Longitude Reference' in exif.keys() and 49 | 'Longitude' in exif.keys()): 50 | 51 | if 'Original Date' in exif.keys(): 52 | dt = exif['Original Date'] 53 | else: 54 | dt = 'N/A' 55 | 56 | if exif['Latitude Reference'] == 'S': 57 | latitude = '-' + exif['Latitude'] 58 | else: 59 | latitude = exif['Latitude'] 60 | 61 | if exif['Longitude Reference'] == 'W': 62 | longitude = '-' + exif['Longitude'] 63 | else: 64 | longitude = exif['Longitude'] 65 | 66 | kml.newpoint(name=exif['Name'], 67 | description='Originally Created: ' + dt, 68 | coords=[(longitude, latitude)]) 69 | else: 70 | pass 71 | kml.save(os.path.join(output, output_name)) 72 | -------------------------------------------------------------------------------- /Chapter13/chapter_13/writers/xlsx_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import xlsxwriter 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | ALPHABET = [chr(i) for i in range(ord('A'), ord('Z') + 1)] 33 | 34 | 35 | def writer(output, headers, output_data, **kwargs): 36 | """ 37 | The writer function writes excel output for the framework 38 | :param output: the output filename for the excel spreadsheet 39 | :param headers: the name of the spreadsheet columns 40 | :param output_data: the data to be written to the excel 41 | spreadsheet 42 | :return: Nothing 43 | """ 44 | wb = xlsxwriter.Workbook(output) 45 | 46 | if headers is None: 47 | print('[-] Received empty headers... \n' 48 | '[-] Skipping writing output.') 49 | return 50 | 51 | if len(headers) <= 26: 52 | title_length = ALPHABET[len(headers) - 1] 53 | else: 54 | title_length = 'Z' 55 | 56 | ws = add_worksheet(wb, title_length) 57 | 58 | if 'recursion' in kwargs.keys(): 59 | for i, data in enumerate(output_data): 60 | if i > 0: 61 | ws = add_worksheet(wb, title_length) 62 | cell_length = len(data) 63 | tmp = [] 64 | for dictionary in data: 65 | tmp.append( 66 | [str(dictionary[x]) if x in dictionary.keys() else '' for x in headers] 67 | ) 68 | 69 | ws.add_table( 70 | 'A3:' + title_length + str(3 + cell_length), 71 | {'data': tmp, 72 | 'columns': [{'header': x} for x in headers]}) 73 | 74 | else: 75 | cell_length = len(output_data) 76 | tmp = [] 77 | for data in output_data: 78 | tmp.append([str(data[x]) if x in data.keys() else '' for x in headers]) 79 | ws.add_table( 80 | 'A3:' + title_length + str(3 + cell_length), 81 | {'data': tmp, 82 | 'columns': [{'header': x} for x in headers]}) 83 | 84 | wb.close() 85 | 86 | 87 | def add_worksheet(wb, length, name=None): 88 | """ 89 | The add_worksheet function creates a new formatted worksheet 90 | in the workbook 91 | :param wb: The workbook object 92 | :param length: The range of rows to merge 93 | :param name: The name of the worksheet 94 | :return: ws, the worksheet 95 | """ 96 | title_format = wb.add_format({'bold': True, 97 | 'font_color': 'black', 'bg_color': 'white', 'font_size': 30, 98 | 'font_name': 'Arial', 'align': 'center'}) 99 | ws = wb.add_worksheet(name) 100 | 101 | ws.merge_range('A1:' + length + '1', 'XYZ Corp', 102 | title_format) 103 | ws.merge_range('A2:' + length + '2', 'Case ####', 104 | title_format) 105 | return ws -------------------------------------------------------------------------------- /Chapter13/plugins/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Python-for-Forensics-Second-Edition/97612919073599c8cb107ea3c6a9f27a3a938fad/Chapter13/plugins/.DS_Store -------------------------------------------------------------------------------- /Chapter13/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import wal_crawler 6 | import setupapi 7 | import userassist 8 | import exif 9 | import id3 10 | import office 11 | import pst_indexer 12 | 13 | """ 14 | MIT License 15 | Copyright (c) 2018 Chapin Bryce, Preston Miller 16 | Please share comments and questions at: 17 | https://github.com/PythonForensics/Learning-Python-for-Forensics 18 | or email pyforcookbook@gmail.com 19 | 20 | Permission is hereby granted, free of charge, to any person 21 | obtaining a copy of this software and associated documentation 22 | files (the "Software"), to deal in the Software without 23 | restriction, including without limitation the rights to use, 24 | copy, modify, merge, publish, distribute, sublicense, and/or 25 | sell copies of the Software, and to permit persons to whom the 26 | Software is furnished to do so, subject to the following 27 | conditions: 28 | 29 | The above copyright notice and this permission notice shall be 30 | included in all copies or substantial portions of the Software. 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 32 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 33 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 34 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 35 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 36 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 37 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 38 | OTHER DEALINGS IN THE SOFTWARE. 39 | """ -------------------------------------------------------------------------------- /Chapter13/plugins/helper/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import utility 6 | import usb_lookup 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ -------------------------------------------------------------------------------- /Chapter13/plugins/helper/usb_lookup.py: -------------------------------------------------------------------------------- 1 | """Updated USB Lookup script based on the version found in Chapter 2.""" 2 | from __future__ import print_function 3 | import argparse 4 | import sys 5 | try: 6 | from urllib2 import urlopen 7 | except ImportError: 8 | from urllib.request import urlopen 9 | 10 | """ 11 | MIT License 12 | Copyright (c) 2018 Chapin Bryce, Preston Miller 13 | Please share comments and questions at: 14 | https://github.com/PythonForensics/Learning-Python-for-Forensics 15 | or email pyforcookbook@gmail.com 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | The above copyright notice and this permission notice shall be included in 24 | all copies or substantial portions of the Software. 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | """ 33 | 34 | def main(vid, pid, ids_file=None): 35 | """ 36 | Main function to control operation. Requires arguments passed as VID PID 37 | on the command line. If discovered in data set, the common names will be 38 | printed to stdout 39 | :return: None 40 | """ 41 | if ids_file: 42 | usb_file = open(ids_file, encoding='latin1') 43 | else: 44 | usb_file = get_usb_file() 45 | usbs = parse_file(usb_file) 46 | results = search_key(usbs, (vid, pid)) 47 | print("Vendor: {}\nProduct: {}".format(results[0], results[1])) 48 | 49 | 50 | def get_usb_file(): 51 | """ 52 | Retrieves USB.ids database from the web. 53 | """ 54 | url = 'http://www.linux-usb.org/usb.ids' 55 | return urlopen(url) 56 | 57 | 58 | def parse_file(usb_file): 59 | """ 60 | Parses the USB.ids file. If this is run offline, please 61 | download the USB.ids and pass the open file to this function. 62 | ie: parse_file(open('path/to/USB.ids', 'r')) 63 | :return: dictionary of entires for querying 64 | """ 65 | usbs = {} 66 | curr_id = '' 67 | for line in usb_file: 68 | if isinstance(line, bytes): 69 | line = line.decode('latin-1') 70 | if line.startswith('#') or line in ('\n', '\t'): 71 | continue 72 | else: 73 | if not line.startswith('\t') and (line[0].isdigit() or 74 | line[0].islower()): 75 | uid, name = get_record(line.strip()) 76 | curr_id = uid 77 | usbs[uid] = [name.strip(), {}] 78 | elif line.startswith('\t') and line.count('\t') == 1: 79 | uid, name = get_record(line.strip()) 80 | usbs[curr_id][1][uid] = name.strip() 81 | return usbs 82 | 83 | 84 | def get_record(record_line): 85 | """ 86 | Split records out by dynamic position. By finding the space, 87 | we can determine the location to split the record for 88 | extraction. To learn more about this, uncomment the print 89 | statements and see what the code is doing behind the scenes! 90 | """ 91 | # print("Line: {}".format(record_line)) 92 | split = record_line.find(' ') 93 | # print("Split: {}".format(split)) 94 | record_id = record_line[:split] 95 | # print("Record ID: ".format(record_id)) 96 | record_name = record_line[split + 1:] 97 | # print("Record Name: ".format(record_name)) 98 | return record_id, record_name 99 | 100 | 101 | def search_key(usb_dict, ids): 102 | """ 103 | Compare provided IDs to the built USB dictionary. If found, 104 | it will return the common name, otherwise returns the string 105 | "unknown". 106 | """ 107 | vendor_key = ids[0] 108 | product_key = ids[1] 109 | 110 | vendor, vendor_data = usb_dict.get(vendor_key, ['unknown', {}]) 111 | product = 'unknown' 112 | if vendor != 'unknown': 113 | product = vendor_data.get(product_key, 'unknown') 114 | 115 | return vendor, product 116 | -------------------------------------------------------------------------------- /Chapter13/plugins/helper/utility.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from datetime import datetime, timedelta 3 | import logging 4 | 5 | """ 6 | MIT License 7 | Copyright (c) 2018 Chapin Bryce, Preston Miller 8 | Please share comments and questions at: 9 | https://github.com/PythonForensics/Learning-Python-for-Forensics 10 | or email pyforcookbook@gmail.com 11 | 12 | Permission is hereby granted, free of charge, to any person 13 | obtaining a copy of this software and associated documentation 14 | files (the "Software"), to deal in the Software without 15 | restriction, including without limitation the rights to use, 16 | copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following 19 | conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | """ 32 | 33 | 34 | def check_header(filename, headers, size): 35 | """ 36 | The check_header function reads a supplied size of the file 37 | and checks against known signatures to determine the file 38 | type. 39 | :param filename: The name of the file. 40 | :param headers: A list of known file signatures for the 41 | file type(s). 42 | :param size: The amount of data to read from the file for 43 | signature verification. 44 | :return: Boolean, True if the signatures match; 45 | otherwise, False. 46 | """ 47 | with open(filename, 'rb') as infile: 48 | header = infile.read(size) 49 | hex_header = binascii.hexlify(header).decode('utf-8') 50 | for signature in headers: 51 | if hex_header == signature: 52 | return True 53 | else: 54 | pass 55 | logging.warn(('The signature for {} ({}) does not match ' 56 | 'known signatures: {}').format( 57 | filename, hex_header, headers)) 58 | return False 59 | 60 | 61 | def convert_size(size): 62 | """ 63 | The convert_size function converts an integer representing 64 | bytes into a human-readable format. 65 | :param size: The size in bytes of a file 66 | :return: The human-readable size. 67 | """ 68 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] 69 | index = 0 70 | while size > 1024: 71 | size /= 1024. 72 | index += 1 73 | return '{:.2f} {}'.format(size, sizes[index]) 74 | 75 | 76 | def file_time(ft): 77 | """ 78 | The fileTime function converts Windows FILETIME objects into 79 | human readable value 80 | :param ft: the FILETIME to convert 81 | :return: date_str, the human readable datetime value 82 | """ 83 | if ft is not None and ft != 0: 84 | return datetime(1601, 1, 1) + timedelta(microseconds=ft / 10) 85 | else: 86 | return 0 -------------------------------------------------------------------------------- /Chapter13/plugins/id3.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import gmtime, strftime 3 | from helper import utility 4 | from mutagen import mp3, id3 5 | 6 | """ 7 | MIT License 8 | Copyright (c) 2018 Chapin Bryce, Preston Miller 9 | Please share comments and questions at: 10 | https://github.com/PythonForensics/Learning-Python-for-Forensics 11 | or email pyforcookbook@gmail.com 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or 18 | sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 26 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 28 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 29 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 31 | OTHER DEALINGS IN THE SOFTWARE. 32 | """ 33 | 34 | 35 | def main(filename): 36 | """ 37 | The main function confirms the file type and sends it to 38 | be processed. 39 | :param filename: name of the file potentially containing exif 40 | metadata. 41 | :return: A dictionary from get_tags, containing the embedded 42 | EXIF metadata. 43 | """ 44 | 45 | # MP3 signatures 46 | signatures = ['494433'] 47 | if utility.check_header(filename, signatures, 3) is True: 48 | return get_tags(filename) 49 | else: 50 | raise TypeError 51 | 52 | 53 | def get_tags(filename): 54 | """ 55 | The get_tags function extracts the ID3 metadata from the data 56 | object. 57 | :param filename: the path and name to the data object. 58 | :return: tags and headers, tags is a dictionary containing ID3 59 | metadata and headers are the order of keys for the CSV output. 60 | """ 61 | 62 | # Set up CSV headers 63 | header = ['Path', 'Name', 'Size', 'Filesystem CTime', 64 | 'Filesystem MTime', 'Title', 'Subtitle', 'Artist', 'Album', 65 | 'Album/Artist', 'Length (Sec)', 'Year', 'Category', 66 | 'Track Number', 'Comments', 'Publisher', 'Bitrate', 67 | 'Sample Rate', 'Encoding', 'Channels', 'Audio Layer'] 68 | tags = {} 69 | tags['Path'] = filename 70 | tags['Name'] = os.path.basename(filename) 71 | tags['Size'] = utility.convert_size( 72 | os.path.getsize(filename)) 73 | tags['Filesystem CTime'] = strftime('%m/%d/%Y %H:%M:%S', 74 | gmtime(os.path.getctime(filename))) 75 | tags['Filesystem MTime'] = strftime('%m/%d/%Y %H:%M:%S', 76 | gmtime(os.path.getmtime(filename))) 77 | 78 | # MP3 Specific metadata 79 | audio = mp3.MP3(filename) 80 | if 'TENC' in audio.keys(): 81 | tags['Encoding'] = audio['TENC'][0] 82 | tags['Bitrate'] = audio.info.bitrate 83 | tags['Channels'] = audio.info.channels 84 | tags['Audio Layer'] = audio.info.layer 85 | tags['Length (Sec)'] = audio.info.length 86 | tags['Sample Rate'] = audio.info.sample_rate 87 | 88 | # ID3 embedded metadata tags 89 | id = id3.ID3(filename) 90 | if 'TPE1' in id.keys(): 91 | tags['Artist'] = id['TPE1'][0] 92 | if 'TRCK' in id.keys(): 93 | tags['Track Number'] = id['TRCK'][0] 94 | if 'TIT3' in id.keys(): 95 | tags['Subtitle'] = id['TIT3'][0] 96 | if 'COMM::eng' in id.keys(): 97 | tags['Comments'] = id['COMM::eng'][0] 98 | if 'TDRC' in id.keys(): 99 | tags['Year'] = id['TDRC'][0] 100 | if 'TALB' in id.keys(): 101 | tags['Album'] = id['TALB'][0] 102 | if 'TIT2' in id.keys(): 103 | tags['Title'] = id['TIT2'][0] 104 | if 'TCON' in id.keys(): 105 | tags['Category'] = id['TCON'][0] 106 | if 'TPE2' in id.keys(): 107 | tags['Album/Artist'] = id['TPE2'][0] 108 | if 'TPUB' in id.keys(): 109 | tags['Publisher'] = id['TPUB'][0] 110 | 111 | return tags, header 112 | -------------------------------------------------------------------------------- /Chapter13/plugins/office.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | import os 3 | from time import gmtime, strftime 4 | from helper import utility 5 | 6 | from lxml import etree 7 | 8 | """ 9 | MIT License 10 | Copyright (c) 2018 Chapin Bryce, Preston Miller 11 | Please share comments and questions at: 12 | https://github.com/PythonForensics/Learning-Python-for-Forensics 13 | or email pyforcookbook@gmail.com 14 | 15 | Permission is hereby granted, free of charge, to any person 16 | obtaining a copy of this software and associated documentation 17 | files (the "Software"), to deal in the Software without 18 | restriction, including without limitation the rights to use, 19 | copy, modify, merge, publish, distribute, sublicense, and/or 20 | sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following 22 | conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE. 34 | """ 35 | 36 | 37 | def main(filename): 38 | 39 | """ 40 | The main function confirms the file type and sends it 41 | to be processed. 42 | :param filename: name of the file potentially containing 43 | embedded metadata. 44 | :return: A dictionary from getTags, containing the embedded 45 | metadata. 46 | """ 47 | 48 | # DOCX, XLSX, and PPTX signatures 49 | signatures = ['504b030414000600'] 50 | if utility.check_header(filename, signatures, 8) is True: 51 | return get_tags(filename) 52 | else: 53 | raise TypeError 54 | 55 | 56 | def get_tags(filename): 57 | """ 58 | The get_tags function extracts the office metadata from the 59 | data object. 60 | :param filename: the path and name to the data object. 61 | :return: tags and headers, tags is a dictionary containing 62 | office metadata and headers are the order of keys for the CSV 63 | output. 64 | """ 65 | 66 | # Set up CSV headers 67 | headers = ['Path', 'Name', 'Size', 'Filesystem CTime', 68 | 'Filesystem MTime', 'Title', 'Author(s)','Create Date', 69 | 'Modify Date', 'Last Modified By Date', 'Subject', 'Keywords', 70 | 'Description', 'Category', 'Status', 'Revision', 71 | 'Edit Time (Min)', 'Page Count', 'Word Count', 72 | 'Character Count', 'Line Count', 73 | 'Paragraph Count', 'Slide Count', 'Note Count', 74 | 'Hidden Slide Count', 'Company', 'Hyperlink Base'] 75 | 76 | # Create a ZipFile class from the input object 77 | # This allows us to read or write to the 'Zip archive' 78 | try: 79 | zf = zipfile.ZipFile(filename) 80 | except zipfile.BadZipfile: 81 | return {}, headers 82 | 83 | # These two XML files contain the embedded metadata of 84 | # interest 85 | try: 86 | core = etree.fromstring(zf.read('docProps/core.xml')) 87 | app = etree.fromstring(zf.read('docProps/app.xml')) 88 | except KeyError as e: 89 | assert Warning(e) 90 | return {}, headers 91 | 92 | tags = {} 93 | tags['Path'] = filename 94 | tags['Name'] = os.path.basename(filename) 95 | tags['Size'] = utility.convert_size( 96 | os.path.getsize(filename)) 97 | tags['Filesystem CTime'] = strftime('%m/%d/%Y %H:%M:%S', 98 | gmtime(os.path.getctime(filename))) 99 | tags['Filesystem MTime'] = strftime('%m/%d/%Y %H:%M:%S', 100 | gmtime(os.path.getmtime(filename))) 101 | 102 | # Core Tags 103 | 104 | for child in core.iterchildren(): 105 | 106 | if 'title' in child.tag: 107 | tags['Title'] = child.text 108 | if 'subject' in child.tag: 109 | tags['Subject'] = child.text 110 | if 'creator' in child.tag: 111 | tags['Author(s)'] = child.text 112 | if 'keywords' in child.tag: 113 | tags['Keywords'] = child.text 114 | if 'description' in child.tag: 115 | tags['Description'] = child.text 116 | if 'lastModifiedBy' in child.tag: 117 | tags['Last Modified By Date'] = child.text 118 | if 'created' in child.tag: 119 | tags['Create Date'] = child.text 120 | if 'modified' in child.tag: 121 | tags['Modify Date'] = child.text 122 | if 'category' in child.tag: 123 | tags['Category'] = child.text 124 | if 'contentStatus' in child.tag: 125 | tags['Status'] = child.text 126 | 127 | if (filename.endswith('.docx') or 128 | filename.endswith('.pptx')): 129 | if 'revision' in child.tag: 130 | tags['Revision'] = child.text 131 | 132 | # App Tags 133 | for child in app.iterchildren(): 134 | 135 | if filename.endswith('.docx'): 136 | if 'TotalTime' in child.tag: 137 | tags['Edit Time (Min)'] = child.text 138 | if 'Pages' in child.tag: 139 | tags['Page Count'] = child.text 140 | if 'Words' in child.tag: 141 | tags['Word Count'] = child.text 142 | if 'Characters' in child.tag: 143 | tags['Character Count'] = child.text 144 | if 'Lines' in child.tag: 145 | tags['Line Count'] = child.text 146 | if 'Paragraphs' in child.tag: 147 | tags['Paragraph Count'] = child.text 148 | if 'Company' in child.tag: 149 | tags['Company'] = child.text 150 | if 'HyperlinkBase' in child.tag: 151 | tags['Hyperlink Base'] = child.text 152 | 153 | elif filename.endswith('.pptx'): 154 | if 'TotalTime' in child.tag: 155 | tags['Edit Time (Min)'] = child.text 156 | if 'Words' in child.tag: 157 | tags['Word Count'] = child.text 158 | if 'Paragraphs' in child.tag: 159 | tags['Paragraph Count'] = child.text 160 | if 'Slides' in child.tag: 161 | tags['Slide Count'] = child.text 162 | if 'Notes' in child.tag: 163 | tags['Note Count'] = child.text 164 | if 'HiddenSlides' in child.tag: 165 | tags['Hidden Slide Count'] = child.text 166 | if 'Company' in child.tag: 167 | tags['Company'] = child.text 168 | if 'HyperlinkBase' in child.tag: 169 | tags['Hyperlink Base'] = child.text 170 | else: 171 | if 'Company' in child.tag: 172 | tags['Company'] = child.text 173 | if 'HyperlinkBase' in child.tag: 174 | tags['Hyperlink Base'] = child.text 175 | 176 | return tags, headers 177 | -------------------------------------------------------------------------------- /Chapter13/plugins/pst_indexer.py: -------------------------------------------------------------------------------- 1 | import pypff 2 | 3 | """ 4 | MIT License 5 | Copyright (c) 2018 Chapin Bryce, Preston Miller 6 | Please share comments and questions at: 7 | https://github.com/PythonForensics/Learning-Python-for-Forensics 8 | or email pyforcookbook@gmail.com 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without 13 | restriction, including without limitation the rights to use, 14 | copy, modify, merge, publish, distribute, sublicense, and/or 15 | sell copies of the Software, and to permit persons to whom the 16 | Software is furnished to do so, subject to the following 17 | conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | """ 30 | 31 | 32 | def main(pst_file): 33 | """ 34 | The main function opens a PST and calls functions to parse 35 | and report data from the PST 36 | :param pst_file: A string representing the path to the PST 37 | file to analyze 38 | :return: None 39 | """ 40 | opst = pypff.open(pst_file) 41 | root = opst.get_root_folder() 42 | 43 | message_data = folder_traverse(root, [], 44 | **{'pst_name': pst_file, 'folder_name': 'root'}) 45 | 46 | header = ['pst_name', 'folder_name', 'creation_time', 47 | 'submit_time', 'delivery_time', 'sender', 'subject', 'attachment_count'] 48 | 49 | return message_data, header 50 | 51 | 52 | def folder_traverse(base, message_data, pst_name, folder_name): 53 | """ 54 | The folder_traverse function walks through the base of the 55 | folder and scans for sub-folders and messages 56 | :param base: Base folder to scan for new items within the 57 | folder 58 | :param message_data: A list of data for output 59 | :param pst_name: A string representing the name of the PST 60 | file 61 | :param folder_name: A string representing the name of the 62 | folder 63 | :return: None 64 | """ 65 | for folder in base.sub_folders: 66 | if folder.number_of_sub_folders: 67 | message_data = folder_traverse(folder, message_data, 68 | pst_name, folder.name) 69 | message_data = check_for_messages(folder, message_data, 70 | pst_name, folder.name) 71 | return message_data 72 | 73 | 74 | def check_for_messages(folder, message_data, pst_name, folder_name): 75 | """ 76 | The check_for_messages function reads folder messages if 77 | present and passes them to the report function 78 | :param folder: pypff.Folder object 79 | :param message_data: list to pass and extend with message info 80 | :param pst_name: A string representing the name of the PST 81 | file 82 | :param folder_name: A string representing the name of the 83 | folder 84 | :return: Dictionary of results by folder 85 | """ 86 | for message in folder.sub_messages: 87 | message_dict = process_message(message) 88 | message_dict['pst_name'] = pst_name 89 | message_dict['folder_name'] = folder_name 90 | message_data.append(message_dict) 91 | return message_data 92 | 93 | 94 | def process_message(message): 95 | """ 96 | The process_message function processes multi-field messages to 97 | simplify collection of information 98 | :param message: The pypff.Message object 99 | :return: A dictionary with message fields (values) and their 100 | data (keys) 101 | """ 102 | return { 103 | "subject": message.subject, 104 | "sender": message.sender_name, 105 | "header": message.transport_headers, 106 | "body": message.plain_text_body, 107 | "creation_time": message.creation_time, 108 | "submit_time": message.client_submit_time, 109 | "delivery_time": message.delivery_time, 110 | "attachment_count": message.number_of_attachments, 111 | } 112 | -------------------------------------------------------------------------------- /Chapter13/plugins/setupapi.py: -------------------------------------------------------------------------------- 1 | from helper import usb_lookup 2 | 3 | """ 4 | MIT License 5 | Copyright (c) 2018 Chapin Bryce, Preston Miller 6 | Please share comments and questions at: 7 | https://github.com/PythonForensics/Learning-Python-for-Forensics 8 | or email pyforcookbook@gmail.com 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without 13 | restriction, including without limitation the rights to use, 14 | copy, modify, merge, publish, distribute, sublicense, and/or 15 | sell copies of the Software, and to permit persons to whom the 16 | Software is furnished to do so, subject to the following 17 | conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | """ 30 | 31 | 32 | def main(in_file): 33 | """ 34 | Main function to handle operation 35 | :param in_file: Str - Path to setupapi log to analyze 36 | :return: list of USB data and list of headers for output 37 | """ 38 | headers = ['Vendor ID', 'Vendor Name', 'Product ID', 39 | 'Product Name', 'Revision', 'UID', 40 | 'First Installation Date'] 41 | data = [] 42 | 43 | device_information = parse_setupapi(in_file) 44 | usb_ids = prep_usb_lookup() 45 | for device in device_information: 46 | parsed_info = parse_device_info(device) 47 | if isinstance(parsed_info, dict): 48 | parsed_info = get_device_names(usb_ids, parsed_info) 49 | data.append(parsed_info) 50 | else: 51 | pass 52 | return data, headers 53 | 54 | 55 | def parse_setupapi(setup_log): 56 | """ 57 | Read data from provided file for Device Install Events for 58 | USB Devices 59 | :param setup_log: str - Path to valid setup api log 60 | :return: tuple of str - Device name and date 61 | """ 62 | device_list = list() 63 | unique_list = set() 64 | with open(setup_log) as in_file: 65 | for line in in_file: 66 | lower_line = line.lower() 67 | if 'device install (hardware initiated)' in \ 68 | lower_line and ('vid' in lower_line or 69 | 'ven' in lower_line): 70 | device_name = line.split('-')[1].strip() 71 | date = next(in_file).split('start')[1].strip() 72 | if device_name not in unique_list: 73 | device_list.append((device_name, date)) 74 | unique_list.add(device_name) 75 | 76 | return device_list 77 | 78 | 79 | def parse_device_info(device_info): 80 | """ 81 | Parses Vendor, Product, Revision and UID from a Setup API 82 | entry 83 | :param device_info: string of device information to parse 84 | :return: dictionary of parsed information or original string 85 | if error 86 | """ 87 | # Initialize variables 88 | vid = '' 89 | pid = '' 90 | rev = '' 91 | uid = '' 92 | 93 | # Split string into segments on \\ 94 | segments = device_info[0].split('\\') 95 | 96 | if 'usb' not in segments[0].lower(): 97 | return None 98 | # Eliminate non-USB devices from output 99 | # May hide other storage devices 100 | 101 | for item in segments[1].split('&'): 102 | lower_item = item.lower() 103 | if 'ven' in lower_item or 'vid' in lower_item: 104 | vid = item.split('_', 1)[-1] 105 | elif 'dev' in lower_item or 'pid' in lower_item or \ 106 | 'prod' in lower_item: 107 | pid = item.split('_', 1)[-1] 108 | elif 'rev' in lower_item or 'mi' in lower_item: 109 | rev = item.split('_', 1)[-1] 110 | 111 | if len(segments) >= 3: 112 | uid = segments[2].strip(']') 113 | 114 | if vid != '' or pid != '': 115 | return {'Vendor ID': vid.lower(), 116 | 'Product ID': pid.lower(), 117 | 'Revision': rev, 118 | 'UID': uid, 119 | 'First Installation Date': device_info[1]} 120 | # Unable to parse data, returning whole string 121 | return device_info 122 | 123 | 124 | def prep_usb_lookup(local_usb_ids=None): 125 | """ 126 | Prepare the lookup of USB devices through accessing the most 127 | recent copy of the database at http://linux-usb.org/usb.ids 128 | or using the provided file and parsing it into a queriable 129 | dictionary format. 130 | """ 131 | if local_usb_ids: 132 | usb_file = open(local_usb_ids, encoding='latin1') 133 | else: 134 | usb_file = usb_lookup.get_usb_file() 135 | return usb_lookup.parse_file(usb_file) 136 | 137 | 138 | def get_device_names(usb_dict, device_info): 139 | """ 140 | Query `usb_lookup.py` for device information based on VID/PID. 141 | :param usb_dict: Dictionary from usb_lookup.py of known 142 | devices. 143 | :param device_info: Dictionary containing 'Vendor ID' and 144 | 'Product ID' keys and values. 145 | :return: original dictionary with 'Vendor Name' and 146 | 'Product Name' keys and values 147 | """ 148 | device_name = usb_lookup.search_key( 149 | usb_dict, [device_info['Vendor ID'], 150 | device_info['Product ID']]) 151 | 152 | device_info['Vendor Name'] = device_name[0] 153 | device_info['Product Name'] = device_name[1] 154 | 155 | return device_info 156 | 157 | 158 | def print_output(usb_information): 159 | """ 160 | Print formatted information about USB Device 161 | :param usb_information: dictionary containing key/value 162 | data about each device or tuple of device information 163 | :return: None 164 | """ 165 | print('{:-^15}'.format('')) 166 | 167 | if isinstance(usb_information, dict): 168 | for key_name, value_name in usb_information.items(): 169 | print('{}: {}'.format(key_name, value_name)) 170 | elif isinstance(usb_information, tuple): 171 | print('Device: {}'.format(usb_information[0])) 172 | print('Date: {}'.format(usb_information[1])) 173 | 174 | -------------------------------------------------------------------------------- /Chapter13/plugins/userassist.py: -------------------------------------------------------------------------------- 1 | """UserAssist parser leveraging the YARP library.""" 2 | import struct 3 | import sys 4 | import logging 5 | 6 | from helper import utility 7 | 8 | from yarp import Registry 9 | 10 | """ 11 | MIT License 12 | Copyright (c) 2018 Chapin Bryce, Preston Miller 13 | Please share comments and questions at: 14 | https://github.com/PythonForensics/Learning-Python-for-Forensics 15 | or email pyforcookbook@gmail.com 16 | 17 | Permission is hereby granted, free of charge, to any person 18 | obtaining a copy of this software and associated documentation 19 | files (the "Software"), to deal in the Software without 20 | restriction, including without limitation the rights to use, 21 | copy, modify, merge, publish, distribute, sublicense, and/or 22 | sell copies of the Software, and to permit persons to whom the 23 | Software is furnished to do so, subject to the following 24 | conditions: 25 | 26 | The above copyright notice and this permission notice shall be 27 | included in all copies or substantial portions of the Software. 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 30 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 31 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 32 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 33 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 34 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 35 | OTHER DEALINGS IN THE SOFTWARE. 36 | """ 37 | 38 | # KEYS will contain sub-lists of each parsed UserAssist (UA) key 39 | KEYS = [] 40 | 41 | 42 | def main(registry, **kwargs): 43 | """ 44 | The main function handles main logic of script. 45 | :param registry: Registry hive to process 46 | :return: Nothing. 47 | """ 48 | if utility.check_header(registry, ['72656766'], 4) is not True: 49 | logging.error('Incorrect file detected based on name') 50 | raise TypeError 51 | # Create dictionary of ROT-13 decoded UA key and its value 52 | apps = create_dictionary(registry) 53 | ua_type = parse_values(apps) 54 | 55 | if ua_type == 0: 56 | logging.info('Detected XP based Userassist values.') 57 | 58 | else: 59 | logging.info('Detected Win7 based Userassist values.') 60 | 61 | headers = ['Name', 'Path', 'Session ID', 'Count', 62 | 'Last Used Date (UTC)', 'Focus Time (ms)', 'Focus Count'] 63 | return KEYS, headers 64 | 65 | 66 | def create_dictionary(registry): 67 | """ 68 | The create_dictionary function creates a list of dictionaries 69 | where keys are the ROT-13 decoded app names and values are 70 | the raw hex data of said app. 71 | :param registry: Registry Hive to process 72 | :return: apps_list, A list containing dictionaries for 73 | each app 74 | """ 75 | try: 76 | # Open the registry file to be parsed 77 | registry_file = open(registry, "rb") 78 | reg = Registry.RegistryHive(registry_file) 79 | except (IOError, UnicodeDecodeError) as e: 80 | msg = 'Invalid NTUSER.DAT path or Registry ID.' 81 | logging.error(msg) 82 | raise TypeError 83 | 84 | # Navigate to the UserAssist key 85 | ua_key = reg.find_key( 86 | ('SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer' 87 | '\\UserAssist')) 88 | if ua_key is None: 89 | msg = 'UserAssist Key not found in Registry file.' 90 | logging.error(msg) 91 | raise TypeError 92 | apps_list = [] 93 | # Loop through each subkey in the UserAssist key 94 | for ua_subkey in ua_key.subkeys(): 95 | # For each subkey in the UserAssist key, detect a subkey 96 | # called Count that has more than 0 values to parse. 97 | if(ua_subkey.subkey('Count') and 98 | ua_subkey.subkey('Count').values_count() > 0): 99 | apps = {} 100 | for v in ua_subkey.subkey('Count').values(): 101 | if sys.version_info[0] == 2: 102 | apps[v.name().encode('utf-8').decode( 103 | 'rot-13')] = v.data_raw() 104 | elif sys.version_info[0] == 3: 105 | import codecs 106 | enc = codecs.getencoder('rot-13') 107 | apps[enc(str(v.name()))[0]] = v.data_raw() 108 | 109 | apps_list.append(apps) 110 | return apps_list 111 | 112 | 113 | def parse_values(data): 114 | """ 115 | The parse_values function uses struct to unpack the raw value 116 | data from the UA key 117 | :param data: A list containing dictionaries of UA 118 | application data 119 | :return: ua_type, based on the size of the raw data from 120 | the dictionary values. 121 | """ 122 | ua_type = -1 123 | msg = 'Parsing UserAssist values.' 124 | logging.info(msg) 125 | 126 | for dictionary in data: 127 | for v in dictionary.keys(): 128 | # WinXP based UA keys are 16 bytes 129 | if len(dictionary[v]) == 16: 130 | raw = struct.unpack('<2iq', dictionary[v]) 131 | ua_type = 0 132 | KEYS.append({'Name': get_name(v), 'Path': v, 133 | 'Session ID': raw[0], 'Count': raw[1], 134 | 'Last Used Date (UTC)': utility.file_time(raw[2]), 135 | 'Focus Time (ms)': '', 'Focus Count': ''}) 136 | # Win7 based UA keys are 72 bytes 137 | elif len(dictionary[v]) == 72: 138 | raw = struct.unpack('<4i44xq4x', dictionary[v]) 139 | ua_type = 1 140 | KEYS.append({'Name': get_name(v), 'Path': v, 141 | 'Session ID': raw[0], 'Count': raw[1], 142 | 'Last Used Date (UTC)': utility.file_time(raw[4]), 143 | 'Focus Time (ms)': raw[3],'Focus Count': raw[2]}) 144 | else: 145 | # If the key is not WinXP or Win7 based -- ignore. 146 | msg = 'Ignoring {} value that is {} bytes'.format( 147 | str(v), str(len(dictionary[v]))) 148 | logging.info(msg) 149 | continue 150 | return ua_type 151 | 152 | 153 | def get_name(full_name): 154 | """ 155 | the get_name function splits the name of the application 156 | returning the executable name and ignoring the 157 | path details. 158 | :param full_name: the path and executable name 159 | :return: the executable name 160 | """ 161 | # Determine if '\\' and ':' are within the full_name 162 | if ':' in full_name and '\\' in full_name: 163 | # Find if ':' comes before '\\' 164 | if full_name.rindex(':') > full_name.rindex('\\'): 165 | # Split on ':' and return the last element 166 | # (the executable) 167 | return full_name.split(':')[-1] 168 | else: 169 | # Otherwise split on '\\' 170 | return full_name.split('\\')[-1] 171 | # When just ':' or '\\' is in the full_name, split on 172 | # that item and return the last element (the executable) 173 | elif ':' in full_name: 174 | return full_name.split(':')[-1] 175 | else: 176 | return full_name.split('\\')[-1] 177 | -------------------------------------------------------------------------------- /Chapter13/requirements.txt: -------------------------------------------------------------------------------- 1 | xlsxwriter==1.1.2 2 | colorama==0.4.1 3 | pyfiglet==0.8.post0 4 | mutagen==1.42.0 5 | pillow==5.4.0 6 | simplekml==1.3.1 7 | tqdm==4.28.1 8 | unicodecsv==0.14.1 9 | jinja2==2.10 10 | lxml==4.3.0 11 | -------------------------------------------------------------------------------- /Chapter13/writers/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(os.path.join(os.path.dirname(__file__))) 4 | 5 | import csv_writer 6 | import kml_writer 7 | import xlsx_writer 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ -------------------------------------------------------------------------------- /Chapter13/writers/csv_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import os 4 | if sys.version_info[0] == 2: 5 | import unicodecsv as csv 6 | elif sys.version_info[0] == 3: 7 | import csv 8 | 9 | """ 10 | MIT License 11 | Copyright (c) 2018 Chapin Bryce, Preston Miller 12 | Please share comments and questions at: 13 | https://github.com/PythonForensics/Learning-Python-for-Forensics 14 | or email pyforcookbook@gmail.com 15 | 16 | Permission is hereby granted, free of charge, to any person 17 | obtaining a copy of this software and associated documentation 18 | files (the "Software"), to deal in the Software without 19 | restriction, including without limitation the rights to use, 20 | copy, modify, merge, publish, distribute, sublicense, and/or 21 | sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following 23 | conditions: 24 | 25 | The above copyright notice and this permission notice shall be 26 | included in all copies or substantial portions of the Software. 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | """ 36 | 37 | 38 | def writer(output, headers, output_data, **kwargs): 39 | """ 40 | The writer function uses the csv.DictWriter module to write 41 | list(s) of dictionaries. The DictWriter can take a fieldnames 42 | argument, as a list, which represents the desired order of 43 | columns. 44 | :param output: The name of the output CSV. 45 | :param headers: A list of keys in the dictionary that 46 | represent the desired order of columns in the output. 47 | :param output_data: The list of dictionaries containing 48 | embedded metadata. 49 | :return: None 50 | """ 51 | 52 | if sys.version_info[0] == 2: 53 | csvfile = open(output, "wb") 54 | elif sys.version_info[0] == 3: 55 | csvfile = open(output, "w", newline='', 56 | encoding='utf-8') 57 | 58 | with csvfile: 59 | # We use DictWriter instead of writer to write 60 | # dictionaries to CSV. 61 | w = csv.DictWriter(csvfile, fieldnames=headers, 62 | extrasaction='ignore') 63 | 64 | # Writerheader writes the header based on the supplied 65 | # headers object 66 | try: 67 | w.writeheader() 68 | except TypeError: 69 | print(('[-] Received empty headers...\n' 70 | '[-] Skipping writing output.')) 71 | return 72 | 73 | if 'recursion' in kwargs.keys(): 74 | for l in output_data: 75 | for data in l: 76 | if data: 77 | w.writerow(data) 78 | else: 79 | for data in output_data: 80 | if data: 81 | w.writerow(data) -------------------------------------------------------------------------------- /Chapter13/writers/kml_writer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import simplekml 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | def writer(output, output_name, output_data): 33 | """ 34 | The writer function writes JPEG and TIFF EXIF GPS data to a 35 | Google Earth KML file. This file can be opened in Google 36 | Earth and will use the GPS coordinates to create 'pins' on 37 | the map of the taken photo's location. 38 | :param output: The output directory to write the KML file. 39 | :param output_name: The name of the output KML file. 40 | :param output_data: The embedded EXIF metadata to be written 41 | :return: 42 | """ 43 | 44 | kml = simplekml.Kml(name=output_name) 45 | for exif in output_data: 46 | if('Latitude' in exif.keys() and 47 | 'Latitude Reference' in exif.keys() and 48 | 'Longitude Reference' in exif.keys() and 49 | 'Longitude' in exif.keys()): 50 | 51 | if 'Original Date' in exif.keys(): 52 | dt = exif['Original Date'] 53 | else: 54 | dt = 'N/A' 55 | 56 | if exif['Latitude Reference'] == 'S': 57 | latitude = '-' + exif['Latitude'] 58 | else: 59 | latitude = exif['Latitude'] 60 | 61 | if exif['Longitude Reference'] == 'W': 62 | longitude = '-' + exif['Longitude'] 63 | else: 64 | longitude = exif['Longitude'] 65 | 66 | kml.newpoint(name=exif['Name'], 67 | description='Originally Created: ' + dt, 68 | coords=[(longitude, latitude)]) 69 | else: 70 | pass 71 | kml.save(os.path.join(output, output_name)) 72 | -------------------------------------------------------------------------------- /Chapter13/writers/xlsx_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import xlsxwriter 3 | 4 | """ 5 | MIT License 6 | Copyright (c) 2018 Chapin Bryce, Preston Miller 7 | Please share comments and questions at: 8 | https://github.com/PythonForensics/Learning-Python-for-Forensics 9 | or email pyforcookbook@gmail.com 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without 14 | restriction, including without limitation the rights to use, 15 | copy, modify, merge, publish, distribute, sublicense, and/or 16 | sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following 18 | conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | OTHER DEALINGS IN THE SOFTWARE. 30 | """ 31 | 32 | ALPHABET = [chr(i) for i in range(ord('A'), ord('Z') + 1)] 33 | 34 | 35 | def writer(output, headers, output_data, **kwargs): 36 | """ 37 | The writer function writes excel output for the framework 38 | :param output: the output filename for the excel spreadsheet 39 | :param headers: the name of the spreadsheet columns 40 | :param output_data: the data to be written to the excel 41 | spreadsheet 42 | :return: Nothing 43 | """ 44 | wb = xlsxwriter.Workbook(output) 45 | 46 | if headers is None: 47 | print('[-] Received empty headers... \n' 48 | '[-] Skipping writing output.') 49 | return 50 | 51 | if len(headers) <= 26: 52 | title_length = ALPHABET[len(headers) - 1] 53 | else: 54 | title_length = 'Z' 55 | 56 | ws = add_worksheet(wb, title_length) 57 | 58 | if 'recursion' in kwargs.keys(): 59 | for i, data in enumerate(output_data): 60 | if i > 0: 61 | ws = add_worksheet(wb, title_length) 62 | cell_length = len(data) 63 | tmp = [] 64 | for dictionary in data: 65 | tmp.append( 66 | [str(dictionary[x]) if x in dictionary.keys() else '' for x in headers] 67 | ) 68 | 69 | ws.add_table( 70 | 'A3:' + title_length + str(3 + cell_length), 71 | {'data': tmp, 72 | 'columns': [{'header': x} for x in headers]}) 73 | 74 | else: 75 | cell_length = len(output_data) 76 | tmp = [] 77 | for data in output_data: 78 | tmp.append([str(data[x]) if x in data.keys() else '' for x in headers]) 79 | ws.add_table( 80 | 'A3:' + title_length + str(3 + cell_length), 81 | {'data': tmp, 82 | 'columns': [{'header': x} for x in headers]}) 83 | 84 | wb.close() 85 | 86 | 87 | def add_worksheet(wb, length, name=None): 88 | """ 89 | The add_worksheet function creates a new formatted worksheet 90 | in the workbook 91 | :param wb: The workbook object 92 | :param length: The range of rows to merge 93 | :param name: The name of the worksheet 94 | :return: ws, the worksheet 95 | """ 96 | title_format = wb.add_format({'bold': True, 97 | 'font_color': 'black', 'bg_color': 'white', 'font_size': 30, 98 | 'font_name': 'Arial', 'align': 'center'}) 99 | ws = wb.add_worksheet(name) 100 | 101 | ws.merge_range('A1:' + length + '1', 'XYZ Corp', 102 | title_format) 103 | ws.merge_range('A2:' + length + '2', 'Case ####', 104 | title_format) 105 | return ws -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 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 | # Learning Python for Forensics - Second Edition 2 | 3 | Learning Python for Forensics - Second Edition 4 | 5 | This is the code repository for [Learning Python for Forensics - Second Edition](https://www.packtpub.com/networking-and-servers/learning-python-forensics-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789341690), published by Packt. 6 | 7 | **Leverage the power of Python in forensic investigations** 8 | 9 | ## What is this book about? 10 | Digital forensics plays an integral role in solving complex cybercrimes and helping organizations make sense of cybersecurity incidents. This second edition of Learning Python for Forensics illustrates how Python can be used to support these digital investigations and permits the examiner to automate the parsing of forensic artifacts to spend more time examining actionable data. 11 | 12 | This book covers the following exciting features: 13 | * Learn how to develop Python scripts to solve complex forensic problems 14 | * Build scripts using an iterative design 15 | * Design code to accommodate present and future hurdles 16 | * Leverage built-in and community-sourced libraries 17 | * Understand the best practices in forensic programming 18 | 19 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1789341698) today! 20 | 21 | https://www.packtpub.com/ 23 | 24 | 25 | ## Instructions and Navigations 26 | All of the code is organized into folders. For example, Chapter02. 27 | 28 | The code will look like the following: 29 | ``` 30 | >>> epoch_timestamp = 874281600 31 | >>> datetime_timestamp = datetime.datetime.utcfromtimestamp(epoch_timestamp) 32 | ``` 33 | 34 | **Following is what you need for this book:** 35 | If you are a forensics student, hobbyist, or professional seeking to increase your understanding in forensics through the use of a programming language, then Learning Python for Forensics is for you. You are not required to have previous experience in programming to learn and master the content within this book. This material, created by forensic professionals, was written with a unique perspective and understanding for examiners who wish to learn programming. 36 | 37 | With the following software and hardware list you can run all code files present in the book. 38 | 39 | ### Software and Hardware List 40 | 41 | | Chapter | Software required | OS required | 42 | | -------- | ------------------------------------| -------------------| 43 | | 1 - 13 | Python 2.7 or 3.7, IDE for Python, | Windows, Linux | 44 | | | and SQLite | | 45 | 46 | 47 | 48 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://www.packtpub.com/sites/default/files/downloads/9781789341690_ColorImages.pdf). 49 | 50 | 51 | ### Related products 52 | * Mastering Reverse Engineering [[Packt]](https://www.packtpub.com/networking-and-servers/mastering-reverse-engineering?utm_source=github&utm_medium=repository&utm_campaign=9781788838849) [[Amazon]](https://www.amazon.com/dp/B07BXTBP8W) 53 | 54 | * Learning Android Forensics - Second Edition [[Packt]](https://www.packtpub.com/networking-and-servers/learning-android-forensics-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789131017) [[Amazon]](https://www.amazon.com/dp/1789131014) 55 | 56 | ## Get to Know the Author(s) 57 | **Preston Miller** 58 | is a consultant at an internationally recognized risk management firm. Preston holds an undergraduate degree from Vassar College and a master's degree in digital forensics from Marshall University. While at Marshall, Preston unanimously received the prestigious J. Edgar Hoover Foundation's scientific scholarship. Preston is a published author, recently of Python Digital Forensics Cookbook, which won the Forensic 4:cast Digital Forensics Book of the Year award in 2018. Preston is a member of the GIAC advisory board and holds multiple industry-recognized certifications in his field. 59 | 60 | **Chapin Bryce** 61 | is a consultant at a global firm that is a leader in digital forensics and incident response investigations. After graduating from Champlain College with a bachelor's degree in computer and digital forensics, Chapin dove into the field of digital forensics and incident response joining the GIAC advisory board and earning four GIAC certifications: GCIH, GCFE, GCFA, and GNFA. As a member of multiple ongoing research and development projects, he has authored several books and articles in professional and academic publications, including Python Digital Forensics Cookbook (Forensic 4:Cast Digital Forensics Book of the Year, 2018), Learning Python for Forensics, First Edition, and Digital Forensic Magazine. 62 | 63 | 64 | ## Other books by the authors 65 | * [Python Digital Forensics Cookbook](https://www.packtpub.com/networking-and-servers/python-digital-forensics-cookbook?utm_source=github&utm_medium=repository&utm_campaign=9781783987467) 66 | * [Learning Python for Forensics](https://www.packtpub.com/networking-and-servers/learning-python-forensics?utm_source=github&utm_medium=repository&utm_campaign=9781783285235) 67 | 68 | ### Suggestions and Feedback 69 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 70 | --------------------------------------------------------------------------------