Our second iteration – setupapi_parser_v2.py
With a functioning prototype, we now have some cleanup work to do. The first iteration was a proof of concept to illustrate how a setupapi.dev.log file can be parsed for forensic artifacts. With our second revision, we will clean up and restructure the code so that it will be easier to use in the future. In addition, we will integrate a more robust command-line interface, validate any user-supplied inputs, improve processing efficiency, and display any results in a better format.
On lines 2 through 6, we import the libraries that we will need for these improvements, alongside familiar cross-version support libraries. argparse is a library that we discussed at length in Chapter 2, Python Fundamentals, and is used to implement and structure arguments from the user. Next, we import os, a library we will use in this script to check the existence of input files before continuing. This will prevent us from trying to process a file that does not exist. The os module is used to access common operating system functionality in an operating system agnostic manner. That is to say, these functions, which may be handled differently on other operating systems, are treated the same and share the same module. We can use the os module to recursively walk through a directory, create new directories, and change the permissions of an object.
Finally, we import sys, which we will use to exit the script in case an error occurs to prevent faulty or improper output. After our imports, we have kept our licensing and documentation variables from before, modifying them to provide details about the second iteration:
001 """Second iteration of the setupapi.dev.log parser."""
002 from __future__ import print_function
003 import argparse
004 from io import open
005 import os
006 import sys
...
036 __authors__ = ["Chapin Bryce", "Preston Miller"]
037 __date__ = 20181027
038 __description__ = """This scripts reads a Windows 7 Setup API
039 log and prints USB Devices to the user"""
The functions defined in our previous script are still present here. However, these functions contain new code that allows for improved handling and flows logically in a different manner. Designing our code in a modularized manner allows us to repurpose functions in new or updated scripts, limiting the need for a major overhaul. This segmentation also allows for easier debugging when reviewing an error that's raised within a function:
042 def main()
...
060 def parse_setupapi()
...
093 def print_output()
The if statement serves the same purpose as the prior iteration. The additional code shown within this conditional allows the user to provide input to modify the script's behavior. In line 106, we create an ArgumentParser object with a description, default help formatting, and epilog containing author, version, and date information. This, in conjunction with the argument options, allows us to display information about the script that might be helpful to the user when running the -h switch. See the following code:
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 )
After defining the ArgumentParser object as parser, we add the IN_FILE parameter on line 113 to allow the user to specify which file to use for input. Already, this increases the usability of our script by adding flexibility in the input file path rather than hard coding the path. At line 115, we parse any provided arguments and store them in the args variable. Finally, we call the main() function on line 118, passing a string representing the file location of setupapi.dev.log to the function, as follows:
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)
Note the difference in our flowchart. Our script is no longer very linear. The main() function calls and accepts returned data from the parse_setupapi() method (indicated by the dashed arrow). The print_output() method is called to print the parsed data to the console: