def parse_mft(file): # The analyzeMFT library is only somewhat written to support being modular and used in another program/script. # As such, to access some functionality it is necessary to trick the library into believing it is being # called from command line. To do so, I have implemented OptionParser to pass arguguments in a way the # library understands. The below arguments, stored in the variable options, tell analyzeMFT to use the local # timezone, perform anomaly detection while processing the $MFT and that the arguments "inmemory", # "debug" and "useGUI" are unset and therefore False. input_file = open(file, 'rb') args = ["--localtz", "True", "--anomaly", "True"] parser = OptionParser() parser.add_option("--inmemory", dest="inmemory") parser.add_option("--debug", dest="debug") parser.add_option("--localtz", dest="localtz") parser.add_option("--UseGUI", dest="UseGUI") parser.add_option("--anomaly", dest="anomaly") (options, args) = parser.parse_args(args) date_settings = date_options() # Read the first 1024 bytes of the $MFT. Each record is 1024 bytes so a single record is being read. # Also, open a handle to the CSV we will write all of the parsed $MFT data to. raw_record = input_file.read(1024) parsed_mft = csv.writer(open(os.getcwd() + "/parsed_mft.csv", 'wb'), dialect=csv.excel, quoting=1) # If the raw MFT record is not blank, pass the raw record to analyzeMFT's parser along with the # necessary options set previously. If the parsed record is an actual file it will have $FILE_NAME # attributes. Some NTFS inodes are reserved or not used and therefore will not have a $FILE_NAME # record. If this is the case, do not attempt to read its name and mark it as haviing no FN record. # When done, read the next 1024 bytes of the $MFT. The first iteration passes the argument 'True' # to the mft_to_csv function telling it to print the column headers. In doing so, we are skipping # reading in any attributes of $MFT since we are not concerned with an attacker timestomping it. # The result is a CSV containing a parse $MFT. if raw_record != "": mft_record = {} mft_record = mft.parse_record(raw_record, options) parsed_mft.writerow(mft.mft_to_csv(mft_record, True, date_settings)) raw_record = input_file.read(1024) while raw_record != "": mft_record = {} mft_record = mft.parse_record(raw_record, options) if mft_record['fncnt'] != 0: mft_record['filename'] = mft_record[('fn', 0)]["name"] else: mft_record['filename'] = "NoFNRecord" parsed_mft.writerow(mft.mft_to_csv(mft_record, False, date_settings)) raw_record = input_file.read(1024)
def process_mft_file(self): self.sizecheck() self.build_filepaths() # reset the file reading self.num_records = 0 self.file_mft.seek(0) raw_record = self.file_mft.read(1024) if self.options.output is not None: self.file_csv.writerow(mft.mft_to_csv(None, True, self.options)) while raw_record != "": record = mft.parse_record(raw_record, self.options) if self.options.debug: print(record) record['filename'] = self.mft[self.num_records]['filename'] self.do_output(record) self.num_records += 1 if record['ads'] > 0: for i in range(0, record['ads']): # print "ADS: %s" % (record['data_name', i]) record_ads = record.copy() record_ads['filename'] = record['filename'] + ':' + record[ 'data_name', i] self.do_output(record_ads) raw_record = self.file_mft.read(1024)
def process_mft_file(self): self.sizecheck() self.build_filepaths() # reset the file reading self.num_records = 0 self.file_mft.seek(0) raw_record = self.file_mft.read(1024) if self.options.output is not None: header_values = mft.mft_to_csv(None, True, self.options) self.file_csv.writerow(header_values) while raw_record: record = mft.parse_record(raw_record, self.options) record['filename'] = self.mft[self.num_records]['filename'] self.do_output(record) self.num_records += 1 if record['ads'] > 0: for i in range(0, record['ads']): record_ads = record.copy() record_ads['filename'] = record['filename'] + ':' + record[ 'data_name', i].decode("utf-8") self.do_output(record_ads) raw_record = self.file_mft.read(1024)
def build_file_paths(self): """ reading record-by-record from the MFT table, parsing it and building the file paths """ # reset the file reading self.analyzeMFT_session.file_mft.seek(0) self.analyzeMFT_session.num_records = 0 # 1024 = 1KB is the size of a record in the MFT table raw_record = self.analyzeMFT_session.file_mft.read(1024) while raw_record != "": minirec = {} # parsing the records with an analyzeMFT function record = mft.parse_record(raw_record, self.analyzeMFT_session.options) minirec['filename'] = record['filename'] minirec['fncnt'] = record['fncnt'] if record['fncnt'] == 1: minirec['par_ref'] = record['fn', 0]['par_ref'] minirec['name'] = record['fn', 0]['name'] if record['fncnt'] > 1: minirec['par_ref'] = record['fn', 0]['par_ref'] for i in (0, record['fncnt'] - 1): if record['fn', i]['nspace'] == 0x1 or record[ 'fn', i]['nspace'] == 0x3: minirec['name'] = record['fn', i]['name'] if minirec.get('name') is None: minirec['name'] = record['fn', record['fncnt'] - 1]['name'] self.analyzeMFT_session.mft[ self.analyzeMFT_session.num_records] = minirec self.analyzeMFT_session.num_records += 1 # reading the next record raw_record = self.analyzeMFT_session.file_mft.read(1024)
def get_filelist(self): num_records = 0 record = {} self.rd.seek(self.part['mftstart'], 0) raw_record = self.rd.read(1024) record = mft.parse_record(raw_record, self.options) length = record['data',0]['dataruns'][0][0] * 4096 offset = record['data',0]['dataruns'][0][1] * \ self.part['spc'] * self.part['bps'] * self.part['start'] self.rd.seek(self.part['mftstart'], 0) for i in range(0, length, 1024): raw_record = self.rd.read(1024) record = {} minirec = {} record = mft.parse_record(raw_record, self.options) if self.options.debug: print record minirec['filename'] = record['filename'] minirec['fncnt'] = record['fncnt'] if record['fncnt'] == 1: minirec['par_ref'] = record['fn',0]['par_ref'] minirec['name'] = record['fn',0]['name'] if record['fncnt'] > 1: minirec['par_ref'] = record['fn',0]['par_ref'] minirec['name'] = record['fn', record['fncnt']-1]['name'] # Corrupt records don't have data elements. Fix in analyzeMFT try: minirec['data'] = record['data', 0] #code minirec['datacnt'] = record['datacnt'] except Exception: minirec['data'] = '' minirec['datacnd'] = 0 self.filelist[num_records] = minirec num_records = num_records + 1 self.gen_filepaths()
def collect_mft(self): self.rd.seek(self.part['mftstart']) data = self.rd.read(1024) record = mft.parse_record(data, options) collect_file(record['data',0], filename)
def plaso_process_mft_file(self): # TODO - Add ADS support .... self.build_filepaths() # reset the file reading self.num_records = 0 self.file_mft.seek(0) raw_record = self.file_mft.read(1024) while raw_record: record = mft.parse_record(raw_record, self.options) record['filename'] = self.mft[self.num_records]['filename'] self.fullmft[self.num_records] = record self.num_records += 1 raw_record = self.file_mft.read(1024)
def build_filepaths(self): # reset the file reading self.file_mft.seek(0) self.num_records = 0 # 1024 is valid for current version of Windows but should really get this value from somewhere raw_record = self.file_mft.read(1024) while raw_record != "": minirec = {} record = mft.parse_record(raw_record, self.options) if self.options.debug: print(record) minirec['filename'] = record['filename'] minirec['fncnt'] = record['fncnt'] if record['fncnt'] == 1: minirec['par_ref'] = record['fn', 0]['par_ref'] minirec['name'] = record['fn', 0]['name'] if record['fncnt'] > 1: minirec['par_ref'] = record['fn', 0]['par_ref'] for i in (0, record['fncnt'] - 1): # print record['fn',i] if record['fn', i]['nspace'] == 0x1 or record[ 'fn', i]['nspace'] == 0x3: minirec['name'] = record['fn', i]['name'] if minirec.get('name') is None: minirec['name'] = record['fn', record['fncnt'] - 1]['name'] self.mft[self.num_records] = minirec if self.options.progress: if self.num_records % (self.mftsize / 5) == 0 and self.num_records > 0: print( 'Building Filepaths: {0:.0f}'.format( 100.0 * self.num_records / self.mftsize) + '%') self.num_records += 1 raw_record = self.file_mft.read(1024) self.gen_filepaths()
def update_record(self, record_no, raw_record = None): """ Update record when it changes (or to initialize) """ # if new data is provided, use the new data as the raw record, # otherwise, reparse it from the internal self.MFT_RAW file if raw_record: assert len(raw_record) == 1024 self.MFT_RAW = self.MFT_RAW[:record_no*1024] + raw_record + self.MFT_RAW[(record_no+1)*1024:] raw_record = self.MFT_RAW[record_no*1024:(record_no+1)*1024] record = mft.parse_record(raw_record, mft.set_default_options()) # Update the filepaths? if record['fncnt'] == 1: record['par_ref'] = record['fn',0]['par_ref'] record['name'] = record['fn',0]['name'] if record['fncnt'] > 1: record['par_ref'] = record['fn',0]['par_ref'] for j in (0, record['fncnt']-1): #print record['fn',i] if (record['fn', j]['nspace'] == 0x1 or record['fn', j]['nspace'] == 0x3): record['name'] = record['fn', j]['name'] if (record.get('name') == None): record['name'] = record['fn', record['fncnt']-1]['name'] # add the record to the MFT self.mft[record_no] = record # process children records, if any # children records are stored in other records or on disk (non-resident) self._process_children(record_no) # Need to call gen_filepaths() self.gen_filepaths()
def _process_children(self, record_no): """ Process all the children records for the parent MFT record at record_no Children records are all attributes stored separately, e.g. in another MFT record or (if non-resident) in the filesystem """ # check if this MFT record has an attribute list record = self.mft[record_no] if 'attribute_list' in record: # check if resident elsewhere in MFT or non-resident (in the filesystem somewhere) if record['attribute_list']['res'] != 0: # non-resident # TODO pull from filesystem - remember to use volume offsets pass # go through each attribute list entry and pluck from other MFT entries for attr_list_record in record['attribute_list']['records']: # skip if it's in the current record if attr_list_record['mft_record_no'] != record_no: # find the other record and look for the attribute we need raw_other_record = self.MFT_RAW[attr_list_record['mft_record_no']*1024:(attr_list_record['mft_record_no']+1)*1024] other_record = mft.parse_record(raw_other_record, mft.set_default_options()) self.mft[attr_list_record['mft_record_no']] = other_record for attribute in other_record['attributes']: if attribute['type'] == attr_list_record['type'] and \ attribute['name'] == attr_list_record['name']: record['attributes'].append(attribute)