exorcise/exorcise/partition/ntfs/mft.py

172 lines
4.6 KiB
Python

import os
import enum
from destruct import Struct
from .attributes import Attribute
class MFTFileType(enum.IntEnum):
MFT = 0
MFTMirror = 1
LogFile = 2
Volume = 3
AttributeDefinition = 4
Root = 5
Bitmap = 6
BootSector = 7
BadClusters = 8
Secure = 9
UpcaseTable = 10
Extension = 11
Reserved12 = 12
Reserved13 = 13
Reserved14 = 14
Reserved15 = 15
Normal = -1
@classmethod
def _missing_(cls, v):
return cls.Normal
class MFTAttributeFlag(enum.IntFlag):
Compressed = 1
Encrypted = 0x4000
Sparse = 0x8000
class MFTAttributeResidentData(Struct):
length = UInt(32)
offset = UInt(16)
indexed = UInt(8)
_pad7 = Pad(1)
value = Ref(Capped(Attribute), reference=os.SEEK_CUR)
def on_length(self, spec, context):
spec.value.child.limit = self.length
spec.value.child.child.selector = self._type
def on_offset(self, spec, context):
spec.value.point = self.offset - 0x18
def parse(self, input, context):
data = super().parse(input, context)
return data.value
class DataRun(Struct):
size = UInt(8)
length = UInt(0)
offset = Int(0)
def on_size(self, spec, context):
spec.length.n = 8 * (self.size & 0xF)
spec.offset.n = 8 * (self.size >> 4)
def parse(self, input, context):
value = super().parse(input, context)
if not value.size:
return None
if not value.offset:
value.offset = None
return value
class MFTAttributeNonResidentData(Struct):
vcn_first = UInt(64)
vcn_last = UInt(64)
run_offset = UInt(16)
unit_size = UInt(16)
_pad14 = Pad(4)
alloc_size = UInt(64)
real_size = UInt(64)
data_size = UInt(64)
runs = Ref(Arr(DataRun, stop_value=None), reference=os.SEEK_CUR)
def on_run_offset(self, spec, context):
spec.runs.point = self.run_offset - 0x40
def parse(self, input, context):
value = super().parse(input, context)
offset = 0
runs = []
for run in value.runs:
if run.offset is not None:
offset += run.offset
runs.append((run.length, offset))
else:
runs.append((run.length, None))
value.runs = runs
return value
class MFTAttributeData(Struct):
length = UInt(32)
nonresident = Bool()
name_length = UInt(8)
name_offset = UInt(16)
flags = Enum(MFTAttributeFlag, UInt(16))
id = UInt(16)
name = Ref(Str(kind='raw', exact=True, elem_size=2, encoding='utf-16le'))
data = Capped(Switch(options={
True: MFTAttributeResidentData(),
False: MFTAttributeNonResidentData(),
}), exact=True)
def on_nonresident(self, spec, context):
spec.data.child.selector = not self.nonresident
spec.data.child.current._type = self._type
def on_name_length(self, spec, context):
spec.name.child.length = self.name_length
spec.data.limit = max(0, self.length - 0x10)
def on_name_offset(self, spec, context):
spec.name.reference = os.SEEK_CUR
spec.name.point = self.name_offset - 0x10
def parse(self, input, context):
value = super().parse(input, context)
return (value.name, value.data)
class MFTAttribute(Struct):
type = UInt(32)
data = Switch(options={
True: MFTAttributeData(),
False: Nothing,
})
def on_type(self, spec, context):
spec.data.selector = self.type < 0x1000 # != 0xFFFFFFFF
spec.data.current._type = self.type
def parse(self, input, context):
value = super().parse(input, context)
return value.data
class MFTFlag(enum.IntFlag):
InUse = 1
Directory = 2
class MFTRecord(Struct):
magic = Sig(b'FILE')
update_offset = UInt(16)
fixup_count = UInt(16)
logfile_seq = UInt(64)
seq = UInt(16)
link_count = UInt(16)
attr_offset = UInt(16)
flags = Enum(MFTFlag, UInt(16))
size_used = UInt(32)
size_alloc = UInt(32)
record_ref = UInt(64)
next_attr_id = UInt(16)
pad2a = Pad(2)
number = Enum(MFTFileType, UInt(32))
attributes = Ref(Arr(MFTAttribute, stop_value=None))
def on_attr_offset(self, spec, context):
spec.attributes.reference = os.SEEK_CUR
spec.attributes.point = self.attr_offset - 0x30
def on_attributes(self, spec, context):
attrs = {}
for name, attr in self.attributes:
attrs.setdefault(name, [])
attrs[name].append(attr)
self.attributes = attrs