epoch
This commit is contained in:
commit
4da4f8e149
|
@ -0,0 +1,5 @@
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.bin
|
||||||
|
*.gho
|
|
@ -0,0 +1,192 @@
|
||||||
|
import os
|
||||||
|
import enum
|
||||||
|
import zlib
|
||||||
|
import destruct
|
||||||
|
from destruct import Type, Union, Struct
|
||||||
|
|
||||||
|
|
||||||
|
def rol(v, b, n):
|
||||||
|
return ((v << n) & ((1 << b) - 1)) | (v >> (b - n))
|
||||||
|
|
||||||
|
def ror(v, b, n):
|
||||||
|
return (v >> n) | ((v << (b - n)) & ((1 << b) - 1))
|
||||||
|
|
||||||
|
class RNG:
|
||||||
|
def __init__(self):
|
||||||
|
self.state = 0
|
||||||
|
|
||||||
|
def seed(self, seed):
|
||||||
|
self.state = 0xFA07673E
|
||||||
|
for _ in range(seed):
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
res = self.state
|
||||||
|
self.state = (self.state + ror(self.state, 32, 7)) & 0xFFFFFFFF
|
||||||
|
return res
|
||||||
|
|
||||||
|
def decrypt(buf, rng):
|
||||||
|
halfway = len(buf) // 2
|
||||||
|
left = reversed(buf[0:halfway])
|
||||||
|
right = reversed(buf[halfway:])
|
||||||
|
|
||||||
|
out = bytearray()
|
||||||
|
for (l, r) in zip(left, right):
|
||||||
|
rand = rng.next() & 7
|
||||||
|
val = rol(r | (l << 8), 16, rand)
|
||||||
|
out.append(val & 0xFF)
|
||||||
|
out.append(val >> 8)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
class GhostEncrypted(Type):
|
||||||
|
def __init__(self, child, seed=None, length=None):
|
||||||
|
self.child = child
|
||||||
|
self.seed = seed
|
||||||
|
self.length = length
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
r = RNG()
|
||||||
|
r.seed(self.seed)
|
||||||
|
data = input.read(self.length)
|
||||||
|
return destruct.parse(self.child, decrypt(data, r), context)
|
||||||
|
|
||||||
|
def emit(self, value, output, context):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def sizeof(self, value, context):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{}: {!r} (seed={}, length={})>'.format(class_name(self), self.child, self.seed, self.length)
|
||||||
|
|
||||||
|
class GhostCompressionType(enum.Enum):
|
||||||
|
Uncompressed = 0
|
||||||
|
Old = 1
|
||||||
|
LZRW3 = 2
|
||||||
|
Deflate = 3
|
||||||
|
|
||||||
|
class GhostCompressed(Type):
|
||||||
|
def __init__(self, child, type=None, length=None):
|
||||||
|
self.child = child
|
||||||
|
self.type = type
|
||||||
|
self.length = length
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
data = input.read(self.length)
|
||||||
|
if self.type == GhostCompressionType.Deflate:
|
||||||
|
data = zlib.decompress(data)
|
||||||
|
elif self.type != GhostCompressionType.Uncompressed:
|
||||||
|
raise ValueError('compression type {} not implemented!'.format(self.type.name))
|
||||||
|
print('got', data)
|
||||||
|
v = destruct.parse(self.child, data, context)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def emit(self, output, value, context):
|
||||||
|
b = io.BytesIO()
|
||||||
|
val = destruct.emit(self.child, value, b, context)
|
||||||
|
data = b.getvalue()
|
||||||
|
|
||||||
|
if self.type == GhostCompressionType.Deflate:
|
||||||
|
data = zlib.compress(data)
|
||||||
|
elif self.type != GhostCompressionType.Uncompressed:
|
||||||
|
raise ValueError('compression type {} not implemented!'.format(self.type.name))
|
||||||
|
output.write(data)
|
||||||
|
|
||||||
|
def sizeof(self, value, context):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{}(type: {}, length: {})>'.format(destruct.class_name(self), self.type.name, self.length)
|
||||||
|
|
||||||
|
class GhostCompressedBuffer(Struct, generics=['D']):
|
||||||
|
length = UInt(16)
|
||||||
|
data = GhostCompressed(D, GhostCompressionType.Deflate)
|
||||||
|
|
||||||
|
def on_length(self, spec, context):
|
||||||
|
spec.data.length = self.length - 2
|
||||||
|
|
||||||
|
class GhostCompressedFile:
|
||||||
|
def __init__(self, handle, type):
|
||||||
|
self._handle = handle
|
||||||
|
self._type = type
|
||||||
|
self._pos = handle.tell()
|
||||||
|
self._buf = None
|
||||||
|
self._bufpos = 0
|
||||||
|
self._seeks = {}
|
||||||
|
|
||||||
|
def _read_buffer(self):
|
||||||
|
self._pos = self._handle.tell()
|
||||||
|
buf = destruct.parse(GhostCompressedBuffer[destruct.Data(None)], self._handle)
|
||||||
|
self._buf = buf.data
|
||||||
|
|
||||||
|
def seek(self, pos, whence=os.SEEK_SET):
|
||||||
|
if whence == os.SEEK_CUR:
|
||||||
|
pos += self._pos + self._bufpos
|
||||||
|
whence = os.SEEK_SET
|
||||||
|
|
||||||
|
if whence == os.SEEK_SET and self._buf is not None:
|
||||||
|
if self._pos <= pos <= self._pos + len(self._buf):
|
||||||
|
self._bufpos = pos - self._pos
|
||||||
|
return
|
||||||
|
|
||||||
|
if pos in self._seeks:
|
||||||
|
offset = pos - self._seeks[pos]
|
||||||
|
pos = self._seeks[pos]
|
||||||
|
else:
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
if self._buf is not None and self._bufpos < len(self._buf):
|
||||||
|
self._seeks[self._pos + self._bufpos] = self._pos
|
||||||
|
self._handle.seek(pos, whence)
|
||||||
|
self._pos = pos
|
||||||
|
self._buf = None
|
||||||
|
self._bufpos = offset
|
||||||
|
|
||||||
|
def tell(self):
|
||||||
|
if self._buf is not None:
|
||||||
|
return self._pos + self._bufpos
|
||||||
|
return self._handle.tell()
|
||||||
|
|
||||||
|
def read(self, n=-1):
|
||||||
|
data = b''
|
||||||
|
|
||||||
|
if self._buf is None:
|
||||||
|
self._read_buffer()
|
||||||
|
self._bufpos = 0
|
||||||
|
|
||||||
|
while n != 0:
|
||||||
|
remaining = len(self._buf) - self._bufpos
|
||||||
|
if n >= 0:
|
||||||
|
from_buf = min(n, remaining)
|
||||||
|
from_file = n - from_buf
|
||||||
|
else:
|
||||||
|
from_buf = remaining
|
||||||
|
from_file = -1
|
||||||
|
|
||||||
|
data += self._buf[self._bufpos:self._bufpos + from_buf]
|
||||||
|
self._bufpos += from_buf
|
||||||
|
n = from_file
|
||||||
|
|
||||||
|
if n != 0:
|
||||||
|
try:
|
||||||
|
self._read_buffer()
|
||||||
|
self._bufpos = 0
|
||||||
|
except:
|
||||||
|
if n > 0:
|
||||||
|
raise
|
||||||
|
break
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class GhostTime(Union):
|
||||||
|
timestamp = DateTime(UInt(32), timestamp=True)
|
||||||
|
seed = Data(4)
|
||||||
|
|
||||||
|
class GhostHeaderType(enum.Enum):
|
||||||
|
Drive = 1
|
||||||
|
Unk2 = 2
|
||||||
|
Unk3 = 3
|
||||||
|
Partition = 4
|
||||||
|
Unk5 = 5
|
|
@ -0,0 +1,281 @@
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import enum
|
||||||
|
import destruct
|
||||||
|
from destruct import Struct, sizeof
|
||||||
|
|
||||||
|
from .common import GhostHeaderType, GhostTime, GhostEncrypted, GhostCompressionType, GhostCompressedFile, GhostCompressed
|
||||||
|
from .properties import PropertySet, make_property_enum
|
||||||
|
from .partition import select_partition_parser, select_partition_index_parser
|
||||||
|
|
||||||
|
|
||||||
|
DRIVE_PROPERTY_TAGS = (
|
||||||
|
'SRemote',
|
||||||
|
'SDrive',
|
||||||
|
'SHeadsPerCyl',
|
||||||
|
'SectorsPerTrack',
|
||||||
|
'SCylinders',
|
||||||
|
'STotalSectors',
|
||||||
|
'SSectorsUsed',
|
||||||
|
'SEstimatedUsed',
|
||||||
|
'SPartitions',
|
||||||
|
'SVersion',
|
||||||
|
'SFlags',
|
||||||
|
'SOs2Name',
|
||||||
|
'SFingerprint',
|
||||||
|
'SMbr',
|
||||||
|
'SIsSpanned',
|
||||||
|
'SDiskFormat',
|
||||||
|
'SEndOfImagePosition',
|
||||||
|
'SPatchedFileCount',
|
||||||
|
'SOrder',
|
||||||
|
'SBootable',
|
||||||
|
'SSystemId',
|
||||||
|
'SExtended',
|
||||||
|
'SFlagInp',
|
||||||
|
'SGflags',
|
||||||
|
'SFirstSect',
|
||||||
|
'SNumSects',
|
||||||
|
'SFpos',
|
||||||
|
'SEndFpos',
|
||||||
|
'SFileOperationOffset',
|
||||||
|
'SSizeAdjustment',
|
||||||
|
'SSlot',
|
||||||
|
)
|
||||||
|
|
||||||
|
PID_PROPERTY_TAGS = (
|
||||||
|
'PID_FILE_ADDITION_DATA_STREAM_ENTRY_ARRAY',
|
||||||
|
'PID_FILE_ADDITION_DATA_STREAM_TYPE',
|
||||||
|
'PID_FILE_DELETION_ENTRY_ARRAY',
|
||||||
|
'PID_FILE_ADDITION_DATA_STREAM_LENGTH',
|
||||||
|
'PID_FILE_ADDITION_IS_HIDDEN',
|
||||||
|
'PID_FILE_ADDITION_DATA_STREAM_NAME',
|
||||||
|
'PID_FILE_DELETION_OBJECT_ID',
|
||||||
|
'PID_FILE_DELETION_ENTRY_PARENT_DIR_MFTNO',
|
||||||
|
'PID_FILE_ADDITION_PATH',
|
||||||
|
'PID_FILE_ADDITION_CREATION_DATE_TIME',
|
||||||
|
'PID_FILE_ADDITION_IS_WRITABLE',
|
||||||
|
'PID_FILE_ADDITION_IS_ARCHIVE',
|
||||||
|
'PID_FILE_ADDITION_IS_READABLE',
|
||||||
|
'PID_FILE_ADDITION_IS_DIRECTORY',
|
||||||
|
'PID_FILE_ADDITION_FILE_NAME',
|
||||||
|
'PID_FILE_ADDITION_LAST_ACCESS_DATE_TIME',
|
||||||
|
'PID_FILE_ADDITION_IS_DELETED',
|
||||||
|
'PID_FILE_ADDITION_CHILD_COUNT',
|
||||||
|
'PID_FILE_ADDITION_IS_EXECUTABLE',
|
||||||
|
'PID_FILE_DELETION_ENTRY_FILENAME',
|
||||||
|
'PID_FILE_ADDITION_DATA_STREAM_OFFSET_IN_FILE',
|
||||||
|
'PID_FILE_DELETION_MAIN_MFTNO',
|
||||||
|
'PID_FILE_ADDITION_IS_SYSTEM',
|
||||||
|
'PID_FILE_DELETION_TOTAL_FILE_ENTRY_COUNT',
|
||||||
|
'PID_FILE_ADDITION_MODIFIED_DATE_TIME',
|
||||||
|
'PID_FILE_ADDITION_FILE_SIZE',
|
||||||
|
|
||||||
|
'PID_START_BYTE',
|
||||||
|
'PID_GPT_CONTAINER_PS',
|
||||||
|
'PID_BIOS_SUPERDISK_CONTAINER_PS',
|
||||||
|
'PID_PARTIES_CONTAINER_PS',
|
||||||
|
'PID_BIOS_CONTAINER_PS',
|
||||||
|
'PID_DYNAMICDISK_CONTAINER_PS',
|
||||||
|
'PID_BYTE_COUNT',
|
||||||
|
'PID_CLEAR_SIGNATURE',
|
||||||
|
'PID_LVM_CONTAINER_PS',
|
||||||
|
'PID_DISK_NOTIFY_OS',
|
||||||
|
'PID_PARTITION_NODE_FORMAT',
|
||||||
|
'PID_CONTAINER_ID',
|
||||||
|
'PID_ADDITIONAL_BOOT_CODE_SECTOR',
|
||||||
|
'PID_PARENT_BOOT_RECORD_SECTOR_OFFSET',
|
||||||
|
'PID_PARENT_BOOT_RECORD_SLOT_NUMBER',
|
||||||
|
'PID_WIN_9X_ID_PRESERVE',
|
||||||
|
'PID_WIN_9X_ID',
|
||||||
|
'PID_WIN_NT_ID',
|
||||||
|
'PID_ADDITIONAL_BOOT_CODE',
|
||||||
|
'PID_ADDITIONAL_BOOT_CODE_DATA',
|
||||||
|
'PID_BOOT_CODE',
|
||||||
|
'PID_WIN_NT_ID_PRESERVE',
|
||||||
|
'PID_GPT_SLOT_COUNT',
|
||||||
|
'PID_GPT_UUID',
|
||||||
|
|
||||||
|
'PID_VOLUME_NAME',
|
||||||
|
'PID_VOLUME_TYPE',
|
||||||
|
'PID_FORMAT_TYPE',
|
||||||
|
'PID_CLEANLY_SHUTDOWN',
|
||||||
|
'PID_NO_HARD_ERROR',
|
||||||
|
|
||||||
|
'PID_POSITION_ID',
|
||||||
|
'PID_VOLUME_DEVICE_NAME',
|
||||||
|
'PID_FIND_FIRST',
|
||||||
|
'PID_EXTENT_CONTAINER_EXTENT_INDEX',
|
||||||
|
'PID_VOLUME_SIZE_MINIMUM_PERCENT',
|
||||||
|
'PID_VOLUME_BYTES_PER_BLOCK',
|
||||||
|
'PID_FIND_BEST_FIT',
|
||||||
|
'PID_VOLUME_SIZE_PREFERRED_PERCENT',
|
||||||
|
'PID_VOLUME_DRIVE_LETTER',
|
||||||
|
'PID_VOLUME_SIZE_PREFERRED',
|
||||||
|
'PID_VOLUME_SLOT_NUMBER',
|
||||||
|
'PID_VOLUME_START',
|
||||||
|
'PID_VOLUME_ALIGNMENT',
|
||||||
|
'PID_EXTENT_START',
|
||||||
|
'PID_IS_VOLUME_CONTAINER',
|
||||||
|
'PID_FORMAT',
|
||||||
|
'PID_ACTIVE',
|
||||||
|
'PID_VOLUME_START_PREFERRED',
|
||||||
|
'PID_EXTENT_SIZE_MINIMUM',
|
||||||
|
'PID_EXTENT',
|
||||||
|
'PID_VOLUME_SIZE_ORIGINAL',
|
||||||
|
'PID_READ_ONLY',
|
||||||
|
'PID_FIND_LAST',
|
||||||
|
'PID_EXTENT_OWNER_ID',
|
||||||
|
'PID_NAME',
|
||||||
|
'PID_ROLE_FORMAT_MATCHING_TYPE',
|
||||||
|
'PID_EXTENT_SIZE_PREFERRED',
|
||||||
|
'PID_VOLUME_SIZE_MINIMUM',
|
||||||
|
'PID_FIND_WORST_FIT',
|
||||||
|
'PID_VOLUME_PARTITION_TYPE',
|
||||||
|
'PID_EXTENT_START_PREFERRED',
|
||||||
|
'PID_VOLUME_SIZE_MAXIMUM',
|
||||||
|
'PID_VOLUME_SIZE_MAXIMUM_PERCENT',
|
||||||
|
'PID_HIDDEN',
|
||||||
|
'PID_IS_VOLUME',
|
||||||
|
'PID_ALLOCATE_TYPE',
|
||||||
|
'PID_VOLUME_NOTIFY_OS',
|
||||||
|
'PID_EXTENT_SIZE_MAXIMUM',
|
||||||
|
'PID_INCOMPATIBLE_IMAGE_VERSION',
|
||||||
|
'PID_ROLE',
|
||||||
|
'PID_SYSTEM_ID',
|
||||||
|
|
||||||
|
'PID_START_CHS',
|
||||||
|
'PID_END_CHS',
|
||||||
|
'PID_SECTOR',
|
||||||
|
'PID_HEAD',
|
||||||
|
'PID_CYLINDER',
|
||||||
|
'PID_START_CHS_MAXED_OUT',
|
||||||
|
'PID_END_CHS_MAXED_OUT',
|
||||||
|
)
|
||||||
|
|
||||||
|
DrivePropertyTag = make_property_enum('DrivePropertyTag', DRIVE_PROPERTY_TAGS + PID_PROPERTY_TAGS)
|
||||||
|
|
||||||
|
|
||||||
|
class GhostPositionMetadata(Struct):
|
||||||
|
magic = Sig(b'\x23\x00\x00\x00\xD8\x18\x2F\x01')
|
||||||
|
unk1 = Data(10)
|
||||||
|
data_offset = UInt(64)
|
||||||
|
index_offset = UInt(64)
|
||||||
|
|
||||||
|
class GhostHeader(Struct):
|
||||||
|
magic = Sig(b'\xFE\xEF')
|
||||||
|
type = Enum(GhostHeaderType, UInt(8)) # 1-5
|
||||||
|
compression = Enum(GhostCompressionType, UInt(8)) # 0-10
|
||||||
|
time = GhostTime()
|
||||||
|
bool8 = Bool()
|
||||||
|
bool9 = Bool()
|
||||||
|
binaryresearch_flag1 = Bool()
|
||||||
|
binaryresearch_flag2 = Bool()
|
||||||
|
binaryresearch_checksum = Data(15)
|
||||||
|
unk27 = Data(10)
|
||||||
|
bool37 = Bool()
|
||||||
|
unk38 = Data(4)
|
||||||
|
bool42 = Bool()
|
||||||
|
bool43 = Bool()
|
||||||
|
bool44 = Bool()
|
||||||
|
bool45 = Bool()
|
||||||
|
bool46 = Bool()
|
||||||
|
data_encrypted = Bool()
|
||||||
|
bool48 = Bool()
|
||||||
|
bool49 = Bool()
|
||||||
|
bool50 = Bool()
|
||||||
|
data_at_end = Bool()
|
||||||
|
unk52 = UInt(8) # 3 or 10
|
||||||
|
bool53 = Bool()
|
||||||
|
seed_offset = UInt(8) # must be 0
|
||||||
|
wildcard_filename = Bool()
|
||||||
|
bool56 = Bool()
|
||||||
|
unk57 = Data(4)
|
||||||
|
unk61 = UInt(8) # 0 or 1 or 2
|
||||||
|
bool62 = Bool()
|
||||||
|
bool63 = Bool()
|
||||||
|
bool64 = Bool()
|
||||||
|
bool65 = Bool()
|
||||||
|
data_length = UInt(32)
|
||||||
|
unk70 = Data(5)
|
||||||
|
pad75 = Pad(437)
|
||||||
|
|
||||||
|
def on_data_length(self, spec, context):
|
||||||
|
if self.data_length == 0:
|
||||||
|
self.data_length = 2048
|
||||||
|
|
||||||
|
class GhostIndex(Struct, generics=['E']):
|
||||||
|
magic = Sig(b'\x05')
|
||||||
|
unk1 = UInt(8) # if 3 or 4, special
|
||||||
|
_pad2 = Pad(2)
|
||||||
|
total = UInt(32)
|
||||||
|
length = UInt(16)
|
||||||
|
offsets = GhostCompressed(destruct.Arr(UInt(64)), GhostCompressionType.Deflate)
|
||||||
|
entries = Arr(Lazy(E))
|
||||||
|
|
||||||
|
def on_length(self, spec, context):
|
||||||
|
spec.offsets.length = self.length
|
||||||
|
|
||||||
|
def on_offsets(self, spec, context):
|
||||||
|
spec.entries.count = len(self.offsets)
|
||||||
|
c = spec.entries.child
|
||||||
|
spec.entries.child = lambda i: destruct.Ref(c, self.offsets[i]) if self.offsets[i] else destruct.Nothing
|
||||||
|
spec.entries = destruct.WithFile(spec.entries, lambda f: GhostCompressedFile(f, GhostCompressionType.Deflate))
|
||||||
|
|
||||||
|
class GhostIndexSet(Struct, generics=['G']):
|
||||||
|
first = GhostIndex[G]
|
||||||
|
rest = Arr(GhostIndex[G])
|
||||||
|
|
||||||
|
def on_first(self, spec, context):
|
||||||
|
spec.rest.count = math.ceil(self.first.total / len(self.first.entries)) - 1
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
value = super().parse(input, context)
|
||||||
|
for x in value.rest:
|
||||||
|
value.first.entries.extend(x.entries)
|
||||||
|
return value.first.entries
|
||||||
|
|
||||||
|
class GhostImage(Struct):
|
||||||
|
header = GhostHeader()
|
||||||
|
positions = Maybe(Ref(GhostPositionMetadata(), -destruct.sizeof(GhostPositionMetadata), os.SEEK_END))
|
||||||
|
properties = Ref(
|
||||||
|
Switch(options=dict(
|
||||||
|
encrypted=GhostEncrypted(PropertySet[DrivePropertyTag]),
|
||||||
|
plain=PropertySet[DrivePropertyTag]
|
||||||
|
)),
|
||||||
|
0, os.SEEK_CUR
|
||||||
|
)
|
||||||
|
index = Ref(GhostIndexSet[...], 0, os.SEEK_CUR)
|
||||||
|
partitions = Arr([], count=0)
|
||||||
|
|
||||||
|
def on_header(self, spec, context):
|
||||||
|
if self.header.data_encrypted:
|
||||||
|
spec.properties.child.selector = 'encrypted'
|
||||||
|
spec.properties.child.current.seed = self.header.time.seed[self.header.seed_offset]
|
||||||
|
spec.properties.child.current.length = self.header.data_length
|
||||||
|
else:
|
||||||
|
spec.properties.child.selector = 'plain'
|
||||||
|
|
||||||
|
def on_positions(self, spec, context):
|
||||||
|
spec.properties.reference = os.SEEK_END
|
||||||
|
spec.properties.point = -(self.positions.data_offset + destruct.sizeof(GhostPositionMetadata))
|
||||||
|
spec.index.reference = os.SEEK_END
|
||||||
|
spec.index.point = -(self.positions.index_offset + destruct.sizeof(GhostPositionMetadata))
|
||||||
|
|
||||||
|
def on_properties(self, spec, context):
|
||||||
|
spec.partitions.count = len(self.properties[DrivePropertyTag.SPartitions])
|
||||||
|
spec.index.child = self.get_index_parser
|
||||||
|
spec.partitions.child = self.get_partition_parser
|
||||||
|
|
||||||
|
def get_index_parser(self, i):
|
||||||
|
partition = self.properties[DrivePropertyTag.SPartitions][0] # i
|
||||||
|
p = select_partition_index_parser(partition)
|
||||||
|
return GhostIndexSet[p]()
|
||||||
|
|
||||||
|
def get_partition_parser(self, i):
|
||||||
|
partition = self.properties[DrivePropertyTag.SPartitions][0] # i
|
||||||
|
pos = partition[DrivePropertyTag.SFpos][0]
|
||||||
|
count = partition[DrivePropertyTag.SEndFpos][0] - pos
|
||||||
|
|
||||||
|
p = select_partition_parser(partition)
|
||||||
|
return destruct.Ref(destruct.Capped(p, count), pos)
|
|
@ -0,0 +1,92 @@
|
||||||
|
import enum
|
||||||
|
from destruct import Struct
|
||||||
|
|
||||||
|
from .ntfs import NTFSPartition, NTFSIndex
|
||||||
|
|
||||||
|
|
||||||
|
class GhostFlags(enum.Enum):
|
||||||
|
Linux = 2
|
||||||
|
WindowsNT = 4
|
||||||
|
|
||||||
|
class SystemID(enum.Enum):
|
||||||
|
Empty = 0x0
|
||||||
|
FAT12 = 0x1
|
||||||
|
XENIXRoot = 0x2
|
||||||
|
XENIXUsr = 0x3
|
||||||
|
FAT16 = 0x4
|
||||||
|
Extended = 0x5
|
||||||
|
FAT16B = 0x6
|
||||||
|
NTFS = 0x7
|
||||||
|
AIX = 0x8
|
||||||
|
AIXBootable = 0x9
|
||||||
|
OS2Boot = 0xA
|
||||||
|
FAT32 = 0xB
|
||||||
|
FAT32LBA = 0xC
|
||||||
|
FAT16BLBA = 0xE
|
||||||
|
ExtendedLBA = 0xF
|
||||||
|
OPUS = 16
|
||||||
|
FAT12Hidden = 0x11
|
||||||
|
Service = 0x12
|
||||||
|
FAT16Hidden = 0x14
|
||||||
|
ExtendedHidden = 0x15
|
||||||
|
FAT16BHidden = 0x16
|
||||||
|
NTFSHidden = 0x17
|
||||||
|
ASTSuspend = 0x18
|
||||||
|
COS = 0x19
|
||||||
|
FAT32Hidden = 0x1B
|
||||||
|
FAT32LBAHidden = 0x1C
|
||||||
|
FAT16LBAHidden = 0x1E
|
||||||
|
ExtendedLBAHidden = 0x1F
|
||||||
|
WinMobileUpdate = 0x20
|
||||||
|
WinMobileBoot = 0x23
|
||||||
|
NECDOS = 0x24
|
||||||
|
WinMobileImage = 0x25
|
||||||
|
WinRE = 0x27
|
||||||
|
JFS = 0x35
|
||||||
|
Plan9 = 0x39
|
||||||
|
PartitionMagic = 0x3C
|
||||||
|
Venix = 0x40
|
||||||
|
PReP = 0x41
|
||||||
|
WinDynamicExtended = 0x42
|
||||||
|
QNXPrimary = 0x4D
|
||||||
|
QNXSecondary = 0x4E
|
||||||
|
QNXTertiary = 0x4F
|
||||||
|
HURD = 0x63
|
||||||
|
LinuxSwap = 0x83
|
||||||
|
Linux = 0x83
|
||||||
|
LinuxExtended = 0x85
|
||||||
|
VolumeSetFAT16B = 0x86
|
||||||
|
VolumeSetNTFS = 0x87
|
||||||
|
VolumeSetFAT32 = 0x8B
|
||||||
|
VolumeSetFAT32LBA = 0x8C
|
||||||
|
LinuxLVM = 0x8E
|
||||||
|
LinuxHidden = 0x93
|
||||||
|
Hibernate1 = 0xA0
|
||||||
|
Hibernate2 = 0xA1
|
||||||
|
BSD = 0xA5
|
||||||
|
OpenBSD = 0xA6
|
||||||
|
NeXT = 0xA7
|
||||||
|
Darwin = 0xA8
|
||||||
|
NetBSD = 0xA9
|
||||||
|
DarwinBoot = 0xAB
|
||||||
|
DarwinRAID = 0xAC
|
||||||
|
HFS = 0xAF
|
||||||
|
SolarisBoot = 0xBE
|
||||||
|
Solaris = 0xBF
|
||||||
|
CPM = 0xDB
|
||||||
|
FAT16Utility = 0xDE
|
||||||
|
LUKS = 0xE8
|
||||||
|
BeOS = 0xEB
|
||||||
|
EFIProtective = 0xEE
|
||||||
|
EFISystem = 0xEF
|
||||||
|
VMwareVMFS = 0xFB
|
||||||
|
VMwareSwap = 0xFC
|
||||||
|
LinuxRAID = 0xFD
|
||||||
|
FAT12Recovery = 0xFE
|
||||||
|
|
||||||
|
|
||||||
|
def select_partition_parser(properties):
|
||||||
|
return NTFSPartition
|
||||||
|
|
||||||
|
def select_partition_index_parser(properties):
|
||||||
|
return NTFSIndex
|
|
@ -0,0 +1,88 @@
|
||||||
|
import enum
|
||||||
|
from destruct import Struct
|
||||||
|
|
||||||
|
from ...common import GhostHeaderType, GhostTime, GhostCompressionType
|
||||||
|
|
||||||
|
from .properties import NTFSPropertySet
|
||||||
|
from .mft import MFTRecord
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class IDPacket(Struct):
|
||||||
|
magic = Sig(b'\x0E')
|
||||||
|
unk1 = Sig(bytes([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
|
||||||
|
footer = Sig(b'\x0F')
|
||||||
|
|
||||||
|
class BufferPacket(Struct, generics=['D']):
|
||||||
|
magic = Sig(b'\x0F')
|
||||||
|
length = UInt(32)
|
||||||
|
unk5 = Sig(bytes([0, 0, 0, 0]))
|
||||||
|
footer = Sig(b'\x0E')
|
||||||
|
data = Capped(D, exact=True)
|
||||||
|
|
||||||
|
def on_length(self, spec, ctx):
|
||||||
|
spec.data.limit = self.length
|
||||||
|
|
||||||
|
class BufferChecksumPacket(Struct):
|
||||||
|
magic = Sig(b'\x0A')
|
||||||
|
checksum = UInt(32)
|
||||||
|
unk5 = Sig(bytes([0, 0, 0, 0]))
|
||||||
|
footer = Sig(b'\x0B')
|
||||||
|
|
||||||
|
class MFTPacket(Struct):
|
||||||
|
magic = Sig(b'\x0E')
|
||||||
|
type = UInt(16)
|
||||||
|
unk3 = Data(4)
|
||||||
|
id = UInt(32)
|
||||||
|
unk4 = Data(4)
|
||||||
|
footer = Sig(b'\x0F')
|
||||||
|
|
||||||
|
|
||||||
|
class NTFSIndex(Struct):
|
||||||
|
header = MFTPacket
|
||||||
|
b = Data(10)
|
||||||
|
data = MFTRecord # NTFSPropertyTag.SMftRecordSize
|
||||||
|
|
||||||
|
class NTFSHeader(Struct):
|
||||||
|
magic = Sig(b'\xFE\xEF')
|
||||||
|
type = Enum(GhostHeaderType, UInt(8)) # 1-5
|
||||||
|
compression = Enum(GhostCompressionType, UInt(8)) # 0-10
|
||||||
|
time = GhostTime()
|
||||||
|
bool8 = Bool()
|
||||||
|
bool9 = Bool()
|
||||||
|
bool10 = Bool()
|
||||||
|
bool11 = Bool()
|
||||||
|
unk12 = Data(15)
|
||||||
|
unk27 = Data(10)
|
||||||
|
bool37 = Bool()
|
||||||
|
unk38 = Data(4)
|
||||||
|
bool42 = Bool()
|
||||||
|
bool43 = Bool()
|
||||||
|
bool44 = Bool()
|
||||||
|
bool45 = Bool()
|
||||||
|
bool46 = Bool()
|
||||||
|
bool47 = Bool()
|
||||||
|
bool48 = Bool()
|
||||||
|
bool49 = Bool()
|
||||||
|
bool50 = Bool()
|
||||||
|
bool51 = Bool()
|
||||||
|
unk52 = UInt(8) # 3 or 10
|
||||||
|
bool53 = Bool()
|
||||||
|
int54 = UInt(8)
|
||||||
|
bool55 = Bool()
|
||||||
|
bool56 = Bool()
|
||||||
|
unk57 = Data(4)
|
||||||
|
unk61 = UInt(8) # 0 or 1 or 2
|
||||||
|
bool62 = Bool()
|
||||||
|
bool63 = Bool()
|
||||||
|
bool64 = Bool()
|
||||||
|
bool65 = Bool()
|
||||||
|
int66 = UInt(32)
|
||||||
|
unk70 = Data(5)
|
||||||
|
pad75 = Pad(437)
|
||||||
|
|
||||||
|
class NTFSPartition(Struct):
|
||||||
|
header = NTFSHeader()
|
||||||
|
id = IDPacket()
|
||||||
|
pbuf = BufferPacket[NTFSPropertySet]()
|
||||||
|
pbuf_checksum = BufferChecksumPacket() # contingent on SFlags & 0x10 and header->field8
|
|
@ -0,0 +1,236 @@
|
||||||
|
import os
|
||||||
|
import enum
|
||||||
|
from destruct import Struct, Switch
|
||||||
|
|
||||||
|
from .index import IndexNode
|
||||||
|
|
||||||
|
|
||||||
|
Attribute = Switch()
|
||||||
|
|
||||||
|
def attribute(id):
|
||||||
|
def inner(c):
|
||||||
|
Attribute.options[id] = c
|
||||||
|
return c
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FileFlag(enum.Flag):
|
||||||
|
ReadOnly = 1 << 0
|
||||||
|
Hidden = 1 << 1
|
||||||
|
System = 1 << 2
|
||||||
|
Archived = 1 << 5
|
||||||
|
Device = 1 << 6
|
||||||
|
Normal = 1 << 7
|
||||||
|
Temporary = 1 << 8
|
||||||
|
Sparse = 1 << 9
|
||||||
|
ReparsePoint = 1 << 10
|
||||||
|
Compressed = 1 << 11
|
||||||
|
Offline = 1 << 12
|
||||||
|
NotIndexed = 1 << 13
|
||||||
|
Encrypted = 1 << 14
|
||||||
|
Directory = 1 << 28
|
||||||
|
IndexView = 1 << 29
|
||||||
|
Unk31 = 1 << 31
|
||||||
|
|
||||||
|
@attribute(0x10)
|
||||||
|
class StandardInformationAttribute(Struct, partial=True):
|
||||||
|
creation_time = UInt(64)
|
||||||
|
modification_time = UInt(64)
|
||||||
|
meta_modification_time = UInt(64)
|
||||||
|
access_time = UInt(64)
|
||||||
|
flags = Enum(FileFlag, UInt(32))
|
||||||
|
version_max = UInt(32)
|
||||||
|
version = UInt(32)
|
||||||
|
class_id = UInt(32)
|
||||||
|
owner_id = UInt(32)
|
||||||
|
security_id = UInt(32)
|
||||||
|
quota_amount = UInt(64)
|
||||||
|
usn = UInt(64)
|
||||||
|
|
||||||
|
@attribute(0x20)
|
||||||
|
class AttributeListAttribute(Struct):
|
||||||
|
type = UInt(32)
|
||||||
|
length = UInt(16)
|
||||||
|
name_length = UInt(8)
|
||||||
|
name_offset = UInt(8)
|
||||||
|
vcn_start = UInt(64)
|
||||||
|
base_file_reference = UInt(64)
|
||||||
|
id = UInt(16)
|
||||||
|
name = Ref(Str(kind='raw', exact=True, elem_size=2, encoding='utf-16le'), reference=os.SEEK_CUR)
|
||||||
|
|
||||||
|
def on_name_length(self, spec, context):
|
||||||
|
spec.name.child.length = self.name_length
|
||||||
|
|
||||||
|
def on_name_offset(self, spec, context):
|
||||||
|
spec.name.point = self.name_offset - 0x1A
|
||||||
|
|
||||||
|
class FilenameNamespace(enum.Enum):
|
||||||
|
POSIX = 0
|
||||||
|
Win32 = 1
|
||||||
|
DOS = 2
|
||||||
|
WinDOS = 3
|
||||||
|
|
||||||
|
@attribute(0x30)
|
||||||
|
class FileNameAttribute(Struct):
|
||||||
|
parent_file = UInt(64)
|
||||||
|
creation_time = UInt(64)
|
||||||
|
modification_time = UInt(64)
|
||||||
|
meta_modification_time = UInt(64)
|
||||||
|
access_time = UInt(64)
|
||||||
|
allocated_size = UInt(64)
|
||||||
|
real_size = UInt(64)
|
||||||
|
flags = Enum(FileFlag, UInt(32))
|
||||||
|
unk3c = UInt(32)
|
||||||
|
length = UInt(8)
|
||||||
|
namespace = Enum(FilenameNamespace, UInt(8))
|
||||||
|
name = Str(kind='raw', exact=True, elem_size=2, encoding='utf-16le')
|
||||||
|
|
||||||
|
def on_length(self, spec, context):
|
||||||
|
spec.name.length = self.length
|
||||||
|
|
||||||
|
class GUID(Struct):
|
||||||
|
data = Data(16)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{{{}-{}-{}-{}-{}}}'.format(
|
||||||
|
self.data[0:4].hex(), self.data[4:6].hex(), self.data[6:8].hex(),
|
||||||
|
self.data[8:10].hex(), self.data[8:16].hex()
|
||||||
|
)
|
||||||
|
|
||||||
|
@attribute(0x40)
|
||||||
|
class ObjectIDAttribute(Struct, partial=True):
|
||||||
|
id = GUID
|
||||||
|
origin_volume_id = GUID
|
||||||
|
origin_id = GUID
|
||||||
|
origin_domain_id = GUID
|
||||||
|
|
||||||
|
class SecurityDescriptorFlag(enum.Flag):
|
||||||
|
DefaultOwner = 1 << 0
|
||||||
|
DefaultGroup = 1 << 1
|
||||||
|
HasDACL = 1 << 2
|
||||||
|
DefaultDACL = 1 << 3
|
||||||
|
HasSACL = 1 << 4
|
||||||
|
DefaultSACL = 1 << 5
|
||||||
|
NeedInheritDACL = 1 << 8
|
||||||
|
NeedInheritSACL = 1 << 9
|
||||||
|
InheritedDACL = 1 << 10
|
||||||
|
InheritedSACL = 1 << 11
|
||||||
|
ProtectedDACL = 1 << 12
|
||||||
|
ProtectedSACL = 1 << 13
|
||||||
|
ValidRMControl = 1 << 14
|
||||||
|
SelfRelative = 1 << 15
|
||||||
|
|
||||||
|
class AccessRight(enum.Flag):
|
||||||
|
StandardDelete = 1 << 16
|
||||||
|
StandardReadControl = 1 << 17
|
||||||
|
StandardWriteDAC = 1 << 18
|
||||||
|
StandardWriteOwner = 1 << 19
|
||||||
|
StandardSynchronize = 1 << 20
|
||||||
|
ACL = 1 << 23
|
||||||
|
GenericAll = 1 << 28
|
||||||
|
GenericExecute = 1 << 29
|
||||||
|
GenericWrite = 1 << 30
|
||||||
|
GenericRead = 1 << 31
|
||||||
|
|
||||||
|
class ACEType(enum.Enum):
|
||||||
|
# V1, V2
|
||||||
|
Allow = 0
|
||||||
|
Deny = 1
|
||||||
|
Audit = 2
|
||||||
|
Alarm = 3
|
||||||
|
# V3
|
||||||
|
AllowCompound = 4
|
||||||
|
# V4
|
||||||
|
AllowObject = 5
|
||||||
|
DenyObject = 6
|
||||||
|
AuditObject = 7
|
||||||
|
AlarmObject = 8
|
||||||
|
|
||||||
|
class ACEFlag(enum.Flag):
|
||||||
|
InheritObject = 1 << 0
|
||||||
|
InheritContainer = 1 << 1
|
||||||
|
InheritNoPropagate = 1 << 2
|
||||||
|
InheritOnly = 1 << 3
|
||||||
|
Inherited = 1 << 4
|
||||||
|
AuditSuccess = 1 << 6
|
||||||
|
AuditFail = 1 < 7
|
||||||
|
|
||||||
|
class ACE(Struct):
|
||||||
|
type = Enum(ACEType, UInt(8))
|
||||||
|
flags = Enum(ACEFlag, UInt(8))
|
||||||
|
size = UInt(16)
|
||||||
|
access = UInt(32)
|
||||||
|
data = Data()
|
||||||
|
|
||||||
|
def on_size(self, spec, context):
|
||||||
|
spec.data.length = self.size
|
||||||
|
|
||||||
|
class ACL(Struct):
|
||||||
|
revision = UInt(8)
|
||||||
|
_pad1 = Pad(1)
|
||||||
|
length = UInt(16)
|
||||||
|
entry_count = UInt(16)
|
||||||
|
_pad6 = Pad(2)
|
||||||
|
entries = Arr(ACE)
|
||||||
|
|
||||||
|
def on_entry_count(self, spec, context):
|
||||||
|
spec.count = self.on_entry_count
|
||||||
|
|
||||||
|
@attribute(0x50)
|
||||||
|
class SecurityDescriptorAttribute(Struct):
|
||||||
|
revision = UInt(8)
|
||||||
|
_pad1 = Pad(1)
|
||||||
|
flags = Enum(SecurityDescriptorFlag, UInt(16))
|
||||||
|
user_sid_offset = UInt(32)
|
||||||
|
group_sid_offset = UInt(32)
|
||||||
|
sacl_offset = UInt(32)
|
||||||
|
dacl_offset = UInt(32)
|
||||||
|
|
||||||
|
@attribute(0x60)
|
||||||
|
class VolumeNameAttribute(Struct):
|
||||||
|
name = Str(kind='raw', exact=True, elem_size=2, encoding='utf-16le')
|
||||||
|
|
||||||
|
class VolumeInformationFlag(enum.Flag):
|
||||||
|
Dirty = 1 << 0
|
||||||
|
ResizeLog = 1 << 1
|
||||||
|
DoUpgrade = 1 << 2
|
||||||
|
MountedByNT4 = 1 << 3
|
||||||
|
DeletingUSN = 1 << 4
|
||||||
|
RepairIDs = 1 << 5
|
||||||
|
ChkDskModified = 1 << 15
|
||||||
|
|
||||||
|
@attribute(0x70)
|
||||||
|
class VolumeInformationAttribute(Struct):
|
||||||
|
_pad0 = Pad(8)
|
||||||
|
version_major = UInt(8)
|
||||||
|
version_minor = UInt(8)
|
||||||
|
flags = Enum(VolumeInformationFlag, UInt(16))
|
||||||
|
|
||||||
|
@attribute(0x80)
|
||||||
|
class DataAttribute(Struct):
|
||||||
|
data = Data(0)
|
||||||
|
|
||||||
|
@attribute(0x90)
|
||||||
|
class IndexRootAttribute(Struct):
|
||||||
|
type = UInt(32)
|
||||||
|
collation = UInt(32)
|
||||||
|
record_size = UInt(32)
|
||||||
|
record_cluster_count = UInt(8)
|
||||||
|
_pad3 = Pad(3)
|
||||||
|
node = IndexNode[...]
|
||||||
|
|
||||||
|
def on_type(self, spec, context):
|
||||||
|
spec.node = IndexNode[Attribute.options[self.type]]
|
||||||
|
|
||||||
|
@attribute(0xA0)
|
||||||
|
class IndexAllocationAttribute(Struct, generic=['G']):
|
||||||
|
nodes = Arr(IndexNode[G])
|
||||||
|
|
||||||
|
@attribute(0xB0)
|
||||||
|
class BitmapAttribute(Struct):
|
||||||
|
data = Data(0)
|
||||||
|
|
||||||
|
@attribute(0x100)
|
||||||
|
class LoggedUtilityStreamAttribute(Struct):
|
||||||
|
data = Data(0)
|
|
@ -0,0 +1,56 @@
|
||||||
|
import enum
|
||||||
|
from destruct import Struct
|
||||||
|
|
||||||
|
|
||||||
|
def pad_to(v, n):
|
||||||
|
return (n - (v % n)) % n
|
||||||
|
|
||||||
|
def align_to(v, n):
|
||||||
|
return v + pad_to(v, n)
|
||||||
|
|
||||||
|
|
||||||
|
class IndexEntryFlag(enum.IntFlag):
|
||||||
|
HasSubNode = 1
|
||||||
|
Last = 2
|
||||||
|
|
||||||
|
class IndexEntry(Struct, generics=['G']):
|
||||||
|
file_reference = UInt(64)
|
||||||
|
length = UInt(16)
|
||||||
|
data_length = UInt(16)
|
||||||
|
flags = Enum(IndexEntryFlag, UInt(8))
|
||||||
|
_pad13 = Pad(3)
|
||||||
|
data = Switch(options={
|
||||||
|
True: Capped(G, exact=True),
|
||||||
|
False: Nothing
|
||||||
|
})
|
||||||
|
_dalign = Data()
|
||||||
|
sub_node_vcn = Switch(options={
|
||||||
|
True: UInt(64),
|
||||||
|
False: Nothing
|
||||||
|
})
|
||||||
|
|
||||||
|
def on_flags(self, spec, context):
|
||||||
|
spec.data.selector = not bool(self.flags & IndexEntryFlag.Last)
|
||||||
|
spec.sub_node_vcn.selector = bool(self.flags & IndexEntryFlag.HasSubNode)
|
||||||
|
|
||||||
|
spec._dalign.length = self.length - 16
|
||||||
|
if spec.data.selector:
|
||||||
|
spec.data.current.limit = self.data_length
|
||||||
|
spec._dalign.length -= align_to(self.data_length, 8)
|
||||||
|
if spec.sub_node_vcn.selector:
|
||||||
|
spec._dalign.length -= 8
|
||||||
|
|
||||||
|
class IndexNode(Struct, generics=['G']):
|
||||||
|
entry_offset = UInt(32)
|
||||||
|
entry_size = UInt(32)
|
||||||
|
node_size = UInt(32)
|
||||||
|
has_children = Bool()
|
||||||
|
_pad13 = Pad(3)
|
||||||
|
entries = Capped(Arr(IndexEntry[G]))
|
||||||
|
|
||||||
|
def on_entry_size(self, spec, context):
|
||||||
|
spec.entries.limit = self.entry_size - 40 # 16
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
value = super().parse(input, context)
|
||||||
|
return value.entries
|
|
@ -0,0 +1,171 @@
|
||||||
|
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
|
|
@ -0,0 +1,64 @@
|
||||||
|
import enum
|
||||||
|
from destruct import Struct
|
||||||
|
|
||||||
|
from ...properties import PropertySet, make_property_enum
|
||||||
|
|
||||||
|
|
||||||
|
NTFS_PROPERTY_TAGS = (
|
||||||
|
'SInitialised',
|
||||||
|
'SFlags',
|
||||||
|
'SFirstSector',
|
||||||
|
'SDrive',
|
||||||
|
'SOrder',
|
||||||
|
'SVersion',
|
||||||
|
'SVolSize',
|
||||||
|
'SBlockSize',
|
||||||
|
'SClusterFactor',
|
||||||
|
'SClusterSize',
|
||||||
|
'SMftRecordSize',
|
||||||
|
'SIndexRecordSize',
|
||||||
|
'SIndexClustPerRecord',
|
||||||
|
'SBootSectorCopyOffset',
|
||||||
|
'SPageFileSys',
|
||||||
|
'SBootIni',
|
||||||
|
'SVolumeLabel',
|
||||||
|
'SSectorsInUse',
|
||||||
|
'STotalNonCopiedBytes',
|
||||||
|
'SBytesToCopy',
|
||||||
|
'SImplodeBufSize',
|
||||||
|
'SBitmapClusters',
|
||||||
|
'SBitmapUsedBytes',
|
||||||
|
'SEstimatedClusters',
|
||||||
|
'SEstimatedUsedBytes',
|
||||||
|
'SBootSector',
|
||||||
|
'SClusterMap',
|
||||||
|
'SClusterSizeShift',
|
||||||
|
'SBlockSizeShift',
|
||||||
|
'SMftRecordSizeShift',
|
||||||
|
'SIndexRecordSizeShift',
|
||||||
|
'SSectorsPerLRUShift',
|
||||||
|
'SLastInt13Sector',
|
||||||
|
'SBitmapCluster',
|
||||||
|
'SSectorCount',
|
||||||
|
'STotalRootMftRecs',
|
||||||
|
'SClusterMapFloor',
|
||||||
|
'SClusterMapCeiling',
|
||||||
|
'SClusterMapNodesInUse',
|
||||||
|
'SClusterMapReadOnly',
|
||||||
|
)
|
||||||
|
|
||||||
|
NTFSPropertyTag = make_property_enum('NTFSPropertyTag', NTFS_PROPERTY_TAGS)
|
||||||
|
|
||||||
|
class PropertySetEncoding(enum.Enum):
|
||||||
|
Packed = 3
|
||||||
|
TLV = 4
|
||||||
|
|
||||||
|
class NTFSPropertySet(Struct):
|
||||||
|
type = Enum(PropertySetEncoding, UInt(16))
|
||||||
|
data = Switch(options={
|
||||||
|
PropertySetEncoding.Packed: Data(),
|
||||||
|
PropertySetEncoding.TLV: PropertySet[NTFSPropertyTag]
|
||||||
|
})
|
||||||
|
|
||||||
|
def on_type(self, spec, context):
|
||||||
|
spec.data.selector = self.type
|
|
@ -0,0 +1,119 @@
|
||||||
|
import hashlib
|
||||||
|
import enum
|
||||||
|
import destruct
|
||||||
|
from destruct import Type, Struct, Bool, Int, UInt, Data, Str
|
||||||
|
|
||||||
|
|
||||||
|
PROPERTY_OBFUSCATION_KEY = b'[one](two)*three*^four^!eleven!{ninetytwo}#3.141_592_653_589_793#|seventeen|@299792458@\x00'
|
||||||
|
|
||||||
|
def encode_property_tag(n):
|
||||||
|
d = hashlib.md5(PROPERTY_OBFUSCATION_KEY + n.encode('ascii')).digest()
|
||||||
|
return d[1] | (d[6] << 8) | (d[11] << 16) | (d[12] << 24)
|
||||||
|
|
||||||
|
def make_property_enum(name, values):
|
||||||
|
return enum.Enum(name, {n: encode_property_tag(n) for n in values})
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyType(enum.Enum):
|
||||||
|
Void = 0xC1
|
||||||
|
I8 = 0xC2
|
||||||
|
U8 = 0xC3
|
||||||
|
I16 = 0xC4
|
||||||
|
U16 = 0xC5
|
||||||
|
I32 = 0xC6
|
||||||
|
U32 = 0xC7
|
||||||
|
I64 = 0xC8
|
||||||
|
U64 = 0xC9
|
||||||
|
Data = 0xCA
|
||||||
|
Buf2 = 0xCB
|
||||||
|
Bool = 0xCC
|
||||||
|
Time = 0xCD
|
||||||
|
Str = 0xCE
|
||||||
|
PSet = 0xCF
|
||||||
|
Buf5 = 0xD1
|
||||||
|
|
||||||
|
class PropertyBuf(Struct, generics=['D']):
|
||||||
|
length = UInt(32)
|
||||||
|
data = Capped(D)
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
res = super().parse(input, context)
|
||||||
|
return res.data
|
||||||
|
|
||||||
|
def on_length(self, spec, context):
|
||||||
|
spec.data.limit = self.length
|
||||||
|
|
||||||
|
class PropertyParser(Type):
|
||||||
|
PARSERS = {
|
||||||
|
PropertyType.I8: lambda: Int(8),
|
||||||
|
PropertyType.U8: lambda: UInt(8),
|
||||||
|
PropertyType.I16: lambda: Int(16),
|
||||||
|
PropertyType.U16: lambda: UInt(16),
|
||||||
|
PropertyType.I32: lambda: Int(32),
|
||||||
|
PropertyType.U32: lambda: UInt(32),
|
||||||
|
PropertyType.I64: lambda: Int(64),
|
||||||
|
PropertyType.U64: lambda: UInt(64),
|
||||||
|
PropertyType.Data: PropertyBuf[Data(None)],
|
||||||
|
PropertyType.Buf2: PropertyBuf[Data(None)],
|
||||||
|
PropertyType.Str: PropertyBuf[Str(kind='raw')],
|
||||||
|
PropertyType.Buf5: PropertyBuf[Data(None)],
|
||||||
|
PropertyType.Bool: Bool,
|
||||||
|
PropertyType.Time: lambda: UInt(64),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, type=None, tag_type=None):
|
||||||
|
self.type = type
|
||||||
|
self.tag_type = tag_type
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
if self.type == PropertyType.PSet:
|
||||||
|
parser = PropertyBuf[PropertySet[self.tag_type]]
|
||||||
|
else:
|
||||||
|
parser = self.PARSERS[self.type]
|
||||||
|
return destruct.parse(parser(), input, context)
|
||||||
|
|
||||||
|
class Property(Struct, generics=['TT']):
|
||||||
|
tag = Enum(TT, UInt(32))
|
||||||
|
type = Enum(PropertyType, UInt(8))
|
||||||
|
amount = UInt(32)
|
||||||
|
value = Arr(PropertyParser(tag_type=TT))
|
||||||
|
|
||||||
|
def on_type(self, spec, context):
|
||||||
|
spec.value.child.type = self.type
|
||||||
|
|
||||||
|
def on_amount(self, spec, context):
|
||||||
|
spec.value.count = self.amount
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} ({}): {}'.format(self.tag, self.type.name, destruct.format_value(self.value, str))
|
||||||
|
|
||||||
|
|
||||||
|
def pad_to(v, n):
|
||||||
|
return (n - (v % n)) % n
|
||||||
|
|
||||||
|
def round_to(v, n):
|
||||||
|
return v + pad_to(v, n)
|
||||||
|
|
||||||
|
class PropertySet(Struct, generics=['TT']):
|
||||||
|
magic = Sig(b'GHPR')
|
||||||
|
version = UInt(32)
|
||||||
|
length = UInt(32)
|
||||||
|
properties = Arr(Property[TT])
|
||||||
|
_pad = Data(0)
|
||||||
|
length2 = UInt(32)
|
||||||
|
version2 = UInt(32)
|
||||||
|
magic2 = Sig(b'RPHG')
|
||||||
|
|
||||||
|
def on_length(self, spec, context):
|
||||||
|
spec.properties.max_length = self.length
|
||||||
|
spec._pad.length = pad_to(self.length, 4)
|
||||||
|
|
||||||
|
def parse(self, input, context):
|
||||||
|
value = super().parse(input, context)
|
||||||
|
res = {}
|
||||||
|
for prop in value.properties:
|
||||||
|
if prop.tag in res:
|
||||||
|
raise ValueError('Tag {} already in result!'.format(prop.tag))
|
||||||
|
res[prop.tag] = prop.value
|
||||||
|
return res
|
||||||
|
|
Loading…
Reference in New Issue