module Rex module Parser ### # # This class parses the contents of an NTFS partition file. # Author : Danil Bazin @danilbaz # ### class NTFS # # Initialize the NTFS class with an already open file handler # def initialize(file_handler) @file_handler = file_handler data = @file_handler.read(4096) # Boot sector reading @bytes_per_sector = data[11, 2].unpack("S")[0] @sector_per_cluster = data[13].unpack("C")[0] @cluster_per_mft_record = data[64].unpack("c")[0] if @cluster_per_mft_record < 0 @bytes_per_mft_record = 2**(-@cluster_per_mft_record) @cluster_per_mft_record = @bytes_per_mft_record.to_f / @bytes_per_sector / @sector_per_cluster else @bytes_per_mft_record = @bytes_per_sector * @sector_per_cluster * @cluster_per_mft_record end @bytes_per_cluster = @sector_per_cluster * @bytes_per_sector @mft_logical_cluster_number = data[48, 8].unpack("Q")[0] @mft_offset = @mft_logical_cluster_number * @sector_per_cluster * @bytes_per_sector @file_handler.seek(@mft_offset) @mft = @file_handler.read(@bytes_per_mft_record) end # # Gather the MFT entry corresponding to his number # def mft_record_from_mft_num(mft_num) cluster_from_attribute_non_resident(mft_record_attribute(@mft)[128]["data"], mft_num * @cluster_per_mft_record, @bytes_per_mft_record) end # # Get the size of the file in the $FILENAME (64) attribute # def real_size_from_filenameattribute(attribute) filename_attribute = attribute filename_attribute[48, 8].unpack("Q")[0] end # # Gather the name of the file from the $FILENAME (64) attribute # def filename_from_filenameattribute(attribute) filename_attribute = attribute length_of_name = filename_attribute[64].ord # uft16 *2 d = ::Encoding::Converter.new("UTF-16LE", "UTF-8") d.convert(filename_attribute[66, (length_of_name * 2)]) end # # Get the file from the MFT number # The size must be gived because the $FILENAME attribute # in the MFT entry does not contain it # The file is in $DATA (128) Attribute # def file_content_from_mft_num(mft_num, size) mft_record = mft_record_from_mft_num(mft_num) attribute_list = mft_record_attribute(mft_record) if attribute_list[128]["resident"] return attribute_list[128]["data"] else return cluster_from_attribute_non_resident(attribute_list[128]["data"])[0, size] end end # # parse one index record and return the name, MFT number and size of the file # def parse_index(index_entry) res = {} filename_size = index_entry[10, 2].unpack("S")[0] filename_attribute = index_entry[16, filename_size] # Should be 8 bytes but it doesn't work # mft_offset = index_entry[0.unpack("Q",:8])[0] # work with 4 bytes mft_offset = index_entry[0, 4].unpack(" mft_offset, "file_size" => real_size_from_filenameattribute(filename_attribute) } res end # # parse index_record in $INDEX_ROOT and recursively index_record in # INDEX_ALLOCATION # def parse_index_list(index_record, index_allocation_attribute) offset_index_entry_list = index_record[0, 4].unpack("> 4 run_length_size = first_runlist_byte & 15 run_length = attribute[offset + 1, run_length_size] run_length += "\x00" * (8 - run_length_size) run_length = run_length.unpack("= run_list_num && (cluster_num < run_length + run_list_num) run_list_offset = (run_offset + old_offset + [cluster_num - run_list_num, 0].max) * @bytes_per_cluster run_list_offset = run_list_offset.to_i @file_handler.seek(run_list_offset) data = "" while data.length < size_wanted data += @file_handler.read(size_wanted - data.length) end attribut += data end offset += run_offset_size + run_length_size + 1 run_list_num += run_length old_offset = run_offset end attribut = attribut[0, real_size] attribut end # # return the attribute list from the MFT record # deal with resident and non resident attributes (but not $DATA due to perforemence issue) # def mft_record_attribute(mft_record) attribute_list_offset = mft_record[20, 2].unpack("C")[0] curs = attribute_list_offset attribute_identifier = mft_record[curs, 4].unpack("I")[0] res = {} while attribute_identifier != 0xFFFFFFFF # attribute_size=mft_record[curs + 4, 4].unpack("I")[0] # should be on 4 bytes but doesnt work attribute_size = mft_record[curs + 4, 2].unpack("S")[0] #print_debug("attribute_size: #{attribute_size}, attribute_identifier: #{attribute_identifier}") # resident if mft_record[curs + 8] == "\x00" content_size = mft_record[curs + 16, 4].unpack(" res[attribute_identifier], "resident" => mft_record[curs + 8] == "\x00" } end curs += attribute_size attribute_identifier = mft_record[curs, 4].unpack("I")[0] end res end # # return the file path in the NTFS partition # def file(path) repertory = mft_record_from_mft_num(5) index_entry = {} for r in path.split("\\") attributes = mft_record_attribute(repertory) index = index_list_from_attributes(attributes) #print_debug("#{index}") unless index.key?(r) fail ArgumentError, "File path does not exist", caller end index_entry = index[r] repertory = mft_record_from_mft_num(index_entry["mft_offset"]) end file_content_from_mft_num(index_entry["mft_offset"], index_entry["file_size"]) end end end end # f = open(ARGV[0],"r") # ntfs = NTFS.new(f) # puts ntfs.file(ARGV[1]) # f.close