Developing our UserAssist logic processor – userassist_parser.py

The userassist_parser.py script is responsible for handling user input, creating a log file, and parsing UserAssist data from the NTUSER.DAT file. On lines 2 through 9, we import familiar and new modules to facilitate our tasks. The yarp and struct modules will grant us access to and then extract objects from the UserAssist binary data, respectively. We import our xlsx_writer and csv_writer modules, which are in the Writers directory. Other used modules have been introduced in previous chapters:

001 """UserAssist parser leveraging the YARP library."""
002 from __future__ import print_function
003 import argparse
004 import struct
005 import sys
006 import logging
007 import os
008 from Writers import xlsx_writer, csv_writer
009 from yarp import Registry

The KEYS variable defined as an empty list on line 45 will store parsed UserAssist values. The main() function, defined on line 48, will handle all coordinating logic. It calls functions to parse the UserAssist key and then to write the results. The create_dictionary() function uses the Registry module to find and store UserAssist value names and raw data in a dictionary for each GUID.

On line 134, we define the parse_values() function, which processes the binary data of each UserAssist value using struct. During this method, we determine if we are working with Windows XP- or Windows 7-based UserAssist data based on length. The get_name() function is a small function that separates the executable name from the full path:

045 KEYS = [] 
...  
048 def main(): 
... 
085 def create_dictionary(): 
... 
134 def parse_values(): 
...  
176 def get_name(): 

On lines 202 through 212, we create our argument parser object, which takes two positional arguments and one optional argument. Our REGISTRY input is the NTUSER.DAT file of interest. The OUTPUT argument is the path and filename of the desired output file. The optional -l switch is the path of the log file. If this is not supplied, the log file is created in the current working directory:

202 if __name__ == '__main__':
203 parser = argparse.ArgumentParser(description=__description__,
204 epilog='Developed by ' +
205 __author__ + ' on ' +
206 __date__)
207 parser.add_argument('REGISTRY', help='NTUSER Registry Hive.')
208 parser.add_argument('OUTPUT',
209 help='Output file (.csv or .xlsx)')
210 parser.add_argument('-l', help='File path of log file.')
211
212 args = parser.parse_args()

If the user supplies a log path, we check on line 215 if the path exists. If it does not exist, we use the os.makedirs() function to create the log directory. In either case, we instantiate the log_path variable with the supplied directory and the log file. On line 220, we create our log and write startup details in the same manner as previous chapters, before calling main() on line 227:

214     if args.l:
215 if not os.path.exists(args.l):
216 os.makedirs(args.l)
217 log_path = os.path.join(args.l, 'userassist_parser.log')
218 else:
219 log_path = 'userassist_parser.log'
220 logging.basicConfig(filename=log_path, level=logging.DEBUG,
221 format=('%(asctime)s | %(levelname)s | '
222 '%(message)s'), filemode='a')
223
224 logging.info('Starting UserAssist_Parser')
225 logging.debug('System ' + sys.platform)
226 logging.debug('Version ' + sys.version)
227 main(args.REGISTRY, args.OUTPUT)

The following flow chart depicts the interconnected functions within our UserAssist framework. Here, we can see how the main() function calls and receives data from the create_dictionary() and parse_values() functions. The parse_values() function separately calls the get_name() function: