Crafting the parse_setupapi() function
The parse_setupapi() function, defined on line 54, takes a string input that represents the full path to the Windows 7 setupapi.dev.log file, as detailed by the docstring on lines 55 through 59. On line 60, we open the file path provided by the main() function and read the data into a variable named in_file. This open statement didn't specify any parameters, so it uses default settings that open the file in read-only mode. This mode prevents us from accidentally writing to the file. In fact, trying to write() to a file that's been opened in read-only mode results in the following error and message:
IOError: File not open for reading
Although it does not allow writing to the file, working off a copy of the source evidence or the use of write-blocking technology should be used when handling digital evidence.
If there is any confusion regarding files and their modes, refer to Chapter 1, Now for Something Completely Different, for additional information. See the following code:
054 def parse_setupapi(setup_file):
055 """
056 Interpret the file
057 :param setup_file: path to the setupapi.dev.log
058 :return: None
059 """
060 in_file = open(setup_file)
On line 61, we read each line from the in_file variable into a new variable named data using the file object's readlines() method. This method returns a list where each element represents a single line in the file. Each element in the list is the string of text from the file delimited by the newline (\n or \r\n) character. At this newline character, the data is broken into a new element and fed as a new entry into the data list:
061 data = in_file.readlines()
With the content of the file stored in the variable data, we begin a for loop to walk through each individual line. This loop uses the enumerate() function, which wraps our iterator with a counter that keeps track of the number of iterations. This is desirable because we want to check for the pattern that identifies a USB device entry, then read the following line to get our date value. By keeping track of what element we are currently processing, we can easily pull out the next line we need to process with data [n + 1], where n is the enumerated count of the current line being processed:
063 for i, line in enumerate(data):
Once inside the loop, on line 64, we evaluate whether the current line contains the string device install (hardware initiated). To ensure that we don't miss valuable data, we will make the current line case insensitive by using the .lower() method to convert all characters in the string to lower case. If responsive, we execute lines 65 through 67. On line 65, we use the current iteration count variable, i, to access the responsive line within the data object:
064 if 'device install (hardware initiated)' in line.lower():
065 device_name = data[i].split('-')[1].strip()
After accessing the value, we call the .split() method on the string to split the values on the dash (-) character. After splitting, we access the second value in the split list and feed that string into the strip() function. The .strip() function, without any provided values, will strip whitespace characters on the left and right ends of the string. We process the responsive line so that it only contains USB identifying information.
The following is a log entry prior to processing with line 65:
>>> [Device Install (Hardware initiated) - pciven_8086&dev_100f&subsys_075015ad&rev_014&b70f118&0&0888]
The following is the log entry after processing:
pciven_8086&dev_100f&subsys_075015ad&rev_014&b70f118&0&0888]
After converting the first line from the setupapi.dev.log USB entry, we then access the data variable on line 66 to obtain the date information from the following line. Since we know the date value sits on the line after the device information data, we can add one to the iteration count variable, i, to access that next line and get the line that contains the date. Similarly to device line parsing, we call the .split() function on the start string and extract the second element from the split that represents the date. Before saving the value, we need to call .strip() to remove whitespaces on both ends of the string:
066 date = data[i+1].split('start')[1].strip()
This process removes any other characters besides the date.
The following is a log entry prior to processing with line 66:
>>> Section start 2010/11/10 10:21:14.656
The following is the log entry after processing:
2010/11/10 10:21:14.656
On line 67, we pass our extracted device_name and date values to the print_output() function. This function is called repeatedly for any responsive lines found in the loop. After the loop completes, the code on line 68 executes, which closes the setupapi.dev.log file that we initially opened, releasing the file from Python's use:
067 print_output(device_name, date)
068 in_file.close()