def test_mft_4k_resident_data(): f = open(MFT_4K, 'rb') mft = MFT.MasterFileTableParser(f) cnt = 0 for fr in mft.file_records(): if mft.build_full_paths(fr) == ['/1.txt']: cnt += 1 for attr in fr.attributes(): v = attr.value_decoded() if type(v) is Attributes.Data: data = v.value md5 = hashlib.md5() md5.update(data) assert md5.hexdigest( ) == 'a75a25c964f50df4ea9398d8ccf6afbd' assert data.replace(b'ABC', b'') == b'' elif mft.build_full_paths(fr) == ['/2.txt']: cnt += 1 for attr in fr.attributes(): v = attr.value_decoded() if type(v) is Attributes.Data: md5 = hashlib.md5() md5.update(v.value) assert md5.hexdigest( ) == 'd6fbed685c98416fb7388dad7503811c' assert cnt == 2 f.close()
def test_open_file_by_child_frs(): f = open(MFT_NHC, 'rb') mft = MFT.MasterFileTableParser(f) for file_number in [8508, 8533, 8535, 8553]: file_record = mft.get_file_record_by_number(file_number) assert file_record.get_master_file_table_number() == 8508 with pytest.raises(MFT.MasterFileTableException): file_record = mft.get_file_record_by_number(8533, None, False) f.close()
def test_different_la(): f = open(MFT_DIFFERENT_LA, 'rb') c_1 = 0 c_2 = 0 mft = MFT.MasterFileTableParser(f) for file_record in mft.file_records(): paths = mft.build_full_paths(file_record) if len(paths) == 0: continue assert len(paths) == 1 path = paths[0] if path == '/ts_la/test_la.txt': for attr in file_record.attributes(): attr_value = attr.value_decoded() if type(attr_value) is not Attributes.StandardInformation: continue c_1 += 1 ts_m_1 = attr_value.get_mtime() ts_a_1 = attr_value.get_atime() ts_c_1 = attr_value.get_ctime() ts_e_1 = attr_value.get_etime() elif path == '/ts_la': for attr in file_record.attributes(): attr_value = attr.value_decoded() if type(attr_value) is not Attributes.IndexRoot: continue for index_entry in attr_value.index_entries(): attr_value = Attributes.FileName( index_entry.get_attribute()) c_2 += 1 ts_m_2 = attr_value.get_mtime() ts_a_2 = attr_value.get_atime() ts_c_2 = attr_value.get_ctime() ts_e_2 = attr_value.get_etime() assert c_1 == 1 and c_2 == 1 assert ts_m_1 == ts_m_2 and ts_c_1 == ts_c_2 and ts_e_1 == ts_e_2 and ts_a_1 != ts_a_2 assert ts_a_2 < ts_a_1 f.close()
def test_mft_mirr(): f = open(MFT_MIRR, 'rb') file_names = ['$MFT', '$MFTMirr', '$LogFile', '$Volume'] mft = MFT.MasterFileTableParser(f) for file_record in mft.file_records(): for attribute in file_record.attributes(): if type(attribute) is MFT.AttributeRecordNonresident: continue value = attribute.value_decoded() if type(value) is not Attributes.FileName: continue assert value.get_file_name() == file_names.pop(0) assert len(file_names) == 0 f.close() f = open(MFT_MIRR_4K, 'rb') file_names = ['$MFT', '$MFTMirr', '$LogFile', '$Volume'] mft = MFT.MasterFileTableParser(f) for file_record in mft.file_records(): for attribute in file_record.attributes(): if type(attribute) is MFT.AttributeRecordNonresident: continue value = attribute.value_decoded() if type(value) is not Attributes.FileName: continue assert value.get_file_name() == file_names.pop(0) assert len(file_names) == 0 f.close()
def test_mft_unicode_volume_name(): f = open(MFT_ORHPAN, 'rb') mft = MFT.MasterFileTableParser(f) volume_name = None fr_vol = mft.get_file_record_by_number(MFT.FILE_NUMBER_VOLUME) for attr in fr_vol.attributes(): v = attr.value_decoded() if type(v) is Attributes.VolumeName: volume_name = v.get_name() break assert volume_name == 'тест-test' f.close()
def test_frs(): with open(FRS, 'rb') as f: frs_raw = f.read() frs = MFT.FileRecordSegment(frs_raw) assert frs.is_in_use() assert frs.get_sequence_number() == 2 assert frs.get_reference_count() == 1 assert frs.is_base_file_record_segment() assert frs.get_logfile_sequence_number() == 31832129 assert frs.get_master_file_table_number() == 11072 attr_list = None i = 0 for attr in frs.attributes(): if i == 0: assert type(attr.value_decoded()) is Attributes.StandardInformation elif i == 1: assert type(attr.value_decoded()) is Attributes.AttributeList attr_list = attr.value_decoded() elif i == 2: assert type(attr.value_decoded()) is Attributes.FileName elif i == 3: assert type(attr.value_decoded()) is Attributes.ObjectID else: assert False i += 1 assert i == 4 i = 0 for attr_entry in attr_list.entries(): assert attr_entry.attribute_name is None if i == 0: assert attr_entry.attribute_type_code == 0x10 elif i == 1: assert attr_entry.attribute_type_code == 0x30 elif i == 2: assert attr_entry.attribute_type_code == 0x40 else: assert attr_entry.attribute_type_code == 0x80 i += 1 assert i == 6
def test_deleted(): f = open(MFT_DELETED, 'rb') found = False mft = MFT.MasterFileTableParser(f) for file_record in mft.file_records(): paths = mft.build_full_paths(file_record) if len(paths) > 0 and paths[ 0] == '/1/2/3/4/file.txt' and not file_record.is_in_use(): found = True f.close() assert found
def test_mft_unicode_file_names(): f = open(MFT_UNICODE, 'rb') mft = MFT.MasterFileTableParser(f) cnt = 0 for fr in mft.file_records(): paths = mft.build_full_paths(fr) assert len(paths) == 0 or len(paths) == 1 if len(paths) > 0: path = paths[0] if path == '/Привет' or path == '/Привет/привет.txt': cnt += 1 assert cnt == 2 f.close()
def test_compressed_sparse(): f = open(MFT_COMPRESSED_SPARSE, 'rb') tested_cnt = 0 mft = MFT.MasterFileTableParser(f) for file_record in mft.file_records(): paths = mft.build_full_paths(file_record) if len(paths) == 0: continue assert len(paths) == 1 path = paths[0] if path.endswith('/compressed.txt'): tested_cnt += 1 c = 0 for attr in file_record.attributes(): if type(attr) is MFT.AttributeRecordResident: continue c += 1 assert attr.type_code == 0x80 and attr.name is None and attr.file_size == 22308 assert c == 1 elif path.endswith('/sparse'): tested_cnt += 1 c = 0 for attr in file_record.attributes(): if type(attr) is MFT.AttributeRecordResident: continue c += 1 assert attr.type_code == 0x80 and attr.name is None and attr.file_size == 1048582 assert c == 1 assert tested_cnt == 2 f.close()
def test_ea_sizes(): f = open(FRS_EA, 'rb') frs_raw = f.read() frs = MFT.FileRecordSegment(frs_raw) for attribute in frs.attributes(): attribute_value = attribute.value_decoded() if type(attribute_value) is Attributes.EA: assert len(attribute_value.value) == 160 if type(attribute_value) is Attributes.EAInformation: assert attribute_value.get_packed_ea_size( ) == 149 and attribute_value.get_unpacked_ea_size() == 160 elif type(attribute_value) is Attributes.FileName: assert attribute_value.get_packed_ea_size( ) == 149 and attribute_value.get_unpacked_ea_size_difference( ) == 11 f.close()
def test_rp_in_frs(): with open(FRS_RP, 'rb') as f: frs_raw = f.read() frs = MFT.FileRecordSegment(frs_raw) for attribute in frs.attributes(): assert type(attribute) is MFT.AttributeRecordResident v = attribute.value_decoded() if type(v) is not Attributes.ReparsePoint: continue assert v.is_reparse_tag_microsoft() rp_buf = v.get_reparse_buffer() md5 = hashlib.md5() md5.update(rp_buf) assert md5.hexdigest() == 'a8ac63b71e1af29121c6d3c3c438926b'
def test_wsl_in_frs(): with open(FRS_EA_WSL, 'rb') as f: frs_raw = f.read() frs = MFT.FileRecordSegment(frs_raw) for attribute in frs.attributes(): assert type(attribute) is MFT.AttributeRecordResident v = attribute.value_decoded() if type(v) is not Attributes.EA: continue c = 0 for name, flags, value in v.data_parsed(): c += 1 assert flags == 0 if name == b'LXATTRB\x00': lxattrb = WSL.LXATTRB(value) mtime = lxattrb.get_mtime() assert mtime.year == 2019 and mtime.month == 1 and mtime.day == 21 elif name == b'LXXATTR\x00': lxxattr = WSL.LXXATTR(value) xattr_list = [] for xname, xvalue in lxxattr.extended_attributes(): xattr_list.append((xname, xvalue)) xattr_list.remove((b'user.1', b'11')) xattr_list.remove((b'user.2', b'22')) xattr_list.remove((b'user.3', b'33')) xattr_list.remove((b'user.4444', b'attrval')) assert len(xattr_list) == 0 else: assert False assert c == 2
def test_mft_orphan_files(): f = open(MFT_ORHPAN, 'rb') mft = MFT.MasterFileTableParser(f) cnt = 0 orphans_seen = [] for fr in mft.file_records(): paths = mft.build_full_paths(fr) if len(paths) == 0: continue assert len(paths) == 1 path = paths[0] if path.startswith('<Orphan>/'): cnt += 1 orphans_seen.append(path) assert cnt == 4 assert sorted(orphans_seen) == [ '<Orphan>/2.txt', '<Orphan>/3.txt', '<Orphan>/4.txt', '<Orphan>/5.txt' ] f.close()
def test_mft_allocated_files(): def compare_against_fls_line(fr, path, fls_line): # This function must return False if a file record and its path do not apply to an FLS line. # If they do, an assertion error (or another exception) must be raised if something is invalid. # If everything is okay, this function must return True. fls_entries = fls_line.split('\t') file_name_fls = fls_entries[1] if '/' + file_name_fls != path and not ( '/' + file_name_fls).startswith( path + ':$' ): # A file record and its path do not apply to this FLS line. return False # Run the checks. file_type_fls, inode_fls = fls_entries[0].rstrip(':').split(' ') if fr.get_flags() & MFT.FILE_FILE_NAME_INDEX_PRESENT > 0: assert file_type_fls == 'd/d' else: assert file_type_fls == 'r/r' assert inode_fls.startswith( str(fr.get_master_file_table_number()) + '-') mod_time_fls, acc_time_fls, chg_time_fls, cre_time_fls = fls_entries[ 2:6] if mod_time_fls.endswith(' (UTC)'): use_msk = False elif mod_time_fls.endswith(' (MSK)'): use_msk = True else: assert False for attr in fr.attributes(): if type(attr) is MFT.AttributeRecordNonresident: continue v = attr.value_decoded() if type(v) is Attributes.StandardInformation: if not use_msk: mod_time = v.get_mtime().strftime( '%Y-%m-%d %H:%M:%S (UTC)') else: mod_time = (v.get_mtime() + datetime.timedelta(hours=3) ).strftime('%Y-%m-%d %H:%M:%S (MSK)') assert mod_time == mod_time_fls if not use_msk: acc_time = v.get_atime().strftime( '%Y-%m-%d %H:%M:%S (UTC)') else: acc_time = (v.get_atime() + datetime.timedelta(hours=3) ).strftime('%Y-%m-%d %H:%M:%S (MSK)') assert acc_time == acc_time_fls if not use_msk: chg_time = v.get_etime().strftime( '%Y-%m-%d %H:%M:%S (UTC)') else: chg_time = (v.get_etime() + datetime.timedelta(hours=3) ).strftime('%Y-%m-%d %H:%M:%S (MSK)') assert chg_time == chg_time_fls if not use_msk: cre_time = v.get_ctime().strftime( '%Y-%m-%d %H:%M:%S (UTC)') else: cre_time = (v.get_ctime() + datetime.timedelta(hours=3) ).strftime('%Y-%m-%d %H:%M:%S (MSK)') assert cre_time == cre_time_fls size_fls, uid_fls, gid_fls = fls_entries[6:9] assert int(size_fls) >= 0 and int(uid_fls) >= 0 and int(gid_fls) >= 0 return True for mft_filename, mft_parsed_filename in MFT_ALLOCATED_TEST_LIST: f = open(mft_filename, 'rb') mft = MFT.MasterFileTableParser(f) with open(mft_parsed_filename, 'rb') as fls: fls_output = fls.read().decode('utf-8').splitlines() not_found_list = [] for fr in mft.file_records(True): paths = mft.build_full_paths(fr) if len(paths) == 0: # A file with no name, skip it. continue i_paths = [] for i_path, i_file_name in mft.build_full_paths(fr, True): i_paths.append(i_path) assert i_path.endswith('/' + i_file_name.get_file_name()) assert sorted(paths) == sorted(i_paths) for path in paths: found = False for fls_line in fls_output[:]: if compare_against_fls_line(fr, path, fls_line): found = True fls_output.remove(fls_line) if not found: not_found_list.append(path) assert len( fls_output) == 1 # A virtual directory ("$OrphanFiles") is left. not_found_list.remove('/.') # This is not present in FLS lines. for path in not_found_list: # Check that we did not find short file names only. try: file_name, file_extension = path.split('/')[-1].split('.') except ValueError: file_name = path.split('/')[-1] assert len(file_name) <= 8 and file_name.upper() == file_name else: assert len(file_name) <= 8 and file_name.upper() == file_name assert len(file_extension) <= 3 and file_extension.upper( ) == file_extension f.close()