如何使用ctypes和struct解析MFT文件记录字节?

问题描述 投票:0回答:1

不知道这是否是重复的,无论如何,我的谷歌功能很弱,如果我输入超过五个单词,谷歌几乎永远不会找到任何相关内容。

我正在尝试解析位于

"//?/X:/$MFT"
的主文件表,我知道尝试直接打开它会引发
PermissionDenied
。当然,我已经想出了一个办法来规避它。通过打开
"//?/X:"
这会创建一个句柄,让我可以读取引导扇区,然后我可以从那里读取 MFT...

我已经编写了代码,或者至少是绝大多数代码,我已经可以将所有MFT读入主内存,但在这个阶段内存使用率很高并且信息组织得不好。但我已经在此文档的帮助下解析了我想要的所有信息。

您可以在此处查看我的代码。

正如你从我的代码中看到的,我使用了大量的偏移量将字节分割成块并调用相应的函数来迭代地解码这些块,我将向你展示我的意思:

from typing import NamedTuple


class Record_Header_Flags(NamedTuple):
    In_Use: bool
    Directory: bool
    Extension: bool
    Special_Index: bool


class Record_Header(NamedTuple):
    LogFile_Serial: int
    Written: int
    Hardlinks: int
    Flags: Record_Header_Flags
    Record_Size: int
    Base_Record: int
    Base_Writes: int
    Record_ID: int


HEADER_FLAGS = (1, 2, 4, 8)


def parse_signed_little_endian(data: bytes) -> int:
    return (
        -1 * (1 + sum((b ^ 0xFF) * (1 << i * 8) for i, b in enumerate(data)))
        if data[-1] & 128
        else int.from_bytes(data, "little")
    )


def parse_little_endian(data: bytes) -> int:
    return int.from_bytes(data, "little")


def parse_header_flags(data: bytes) -> Record_Header_Flags:
    flag = data[0]
    return Record_Header_Flags(*(bool(flag & bit) for bit in HEADER_FLAGS))


FILE_RECORD_HEADER = (
    (8, 16, parse_little_endian),
    (16, 18, parse_little_endian),
    (18, 20, parse_little_endian),
    (22, 24, parse_header_flags),
    (24, 28, parse_little_endian),
    (32, 38, parse_little_endian),
    (38, 40, parse_little_endian),
    (44, 48, parse_little_endian),
)


def parse_record_header(data: bytes) -> Record_Header:
    return Record_Header(
        *(func(data[start:end]) for start, end, func in FILE_RECORD_HEADER)
    )


data = b"FILE0\x00\x03\x00\x9dt \x13\x0c\x00\x00\x00\x08\x00\x02\x008\x00\x01\x00\xd8\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\xff\xff\x00\x00"
print(parse_record_header(data))
Record_Header(LogFile_Serial=51860501661, Written=8, Hardlinks=2, Flags=Record_Header_Flags(In_Use=True, Directory=False, Extension=False, Special_Index=False), Record_Size=472, Base_Record=0, Base_Writes=0, Record_ID=65535)

有人告诉我这是低效且不符合 Python 风格的,正确的方法是使用

struct
ctypes
的组合。

我知道我可以使用

struct.unpack("<i", data)[0]
解析 4 字节小端序序列,使用以下格式字符串解析 4 字节无符号 LE:
"<I"
,使用
"<q"
解析 8 字节 LE,使用
"<Q"
解析 8 字节 ULE。但有些值是 6 字节的序列。

而且我不知道如何使用

ctypes
结构。

进一步的 MFT 使用非标准格式,例如 Windows 文件时间:

from datetime import datetime, timedelta
from typing import NamedTuple

EPOCH = datetime(1601, 1, 1, 0, 0, 0)

def parse_NTFS_timestamp(data: bytes) -> datetime:
    return EPOCH + timedelta(seconds=int.from_bytes(data, "little") * 1e-7)

如何使用

ctypes
struct
来解析我给出的示例,并解析包含非标准编码的字节序列,例如来自 0x10 $STANDARD_INFORMATION 的时间戳字段?

python struct ctypes ntfs-mft
1个回答
0
投票

这是

struct
ctypes
解析数据的示例。请参阅
struct
格式字符
ctypes
结构和联合

import ctypes as ct
import struct
from collections import namedtuple

class RecordHeader(ct.Structure):
    _fields_ = (('pad1', 8 * ct.c_uint8),
                ('LogFile_Serial', ct.c_uint64),
                ('Written', ct.c_uint16),
                ('HardLinks', ct.c_uint16),
                ('pad2', 2 * ct.c_uint8),
                ('Flags', ct.c_uint16),
                ('Record_Size', ct.c_uint32),
                ('pad3', 4 * ct.c_uint8),
                ('_Base_Record_Base_Writes', ct.c_uint64),
                ('pad4', 4 * ct.c_uint8),
                ('Record_ID', ct.c_uint32))

    # ctypes can't handle a 6-byte field.  Read as 8-byte field and parse with
    # properties using bit shifting and masking.
    @property
    def Base_Record(self):
        return self._Base_Record_Base_Writes & 0xFFFFFFFFFFFF

    @property
    def Base_Writes(self):
        return self._Base_Record_Base_Writes >> 48

    def __repr__(self):
        '''Display representation of the structure'''
        # Gather relevant fields and values.
        slist = [(k, getattr(self,k)) for k, _ in self._fields_ if not k.startswith('pad')]
        params = ', '.join([f'{k}={v}' for k, v in slist])
        # format the parameters for a nice display.  Add the properties as well.
        return f'RecordHeader({params}, Base_Record={self.Base_Record}, Base_Writes={self.Base_Writes})'

# OP data, but modified to test the Base_Record/Base_Writes fields with something non-zero.
data = b'FILE0\x00\x03\x00\x9dt \x13\x0c\x00\x00\x00\x08\x00\x02\x008\x00\x01\x00\xd8\x01\x00\x00\x00\x04\x00\x00\x88\x77\x66\x55\x44\x33\x22\x11\x05\x00\x00\x00\xff\xff\x00\x00'

# struct can't handle a 6-byte field either.  Read as an 8-byte field.  Process later as needed.
result = struct.unpack('<8xQHH2xHL4xQ4xL', data)
RHeader = namedtuple('RHeader', 'LogFile_Serial Written HardLinks Flags Record_Size Base_Record_Base_Writes Record_ID')
print(RHeader(*result))

# ctypes Structure example.  Processes the 6-byte/2-byte field.
rh = RecordHeader.from_buffer_copy(data)
print(rh)
print(f'{rh._Base_Record_Base_Writes=:#x} {rh.Base_Record=:#x} {rh.Base_Writes=:#x}')

输出:

RHeader(LogFile_Serial=51860501661, Written=8, HardLinks=2, Flags=1, Record_Size=472, Base_Record_Base_Writes=1234605616436508552, Record_ID=65535)
RecordHeader(LogFile_Serial=51860501661, Written=8, HardLinks=2, Flags=1, Record_Size=472, _Base_Record_Base_Writes=1234605616436508552, Record_ID=65535, Base_Record=56368583571336, Base_Writes=4386)
rh._Base_Record_Base_Writes=0x1122334455667788 rh.Base_Record=0x334455667788 rh.Base_Writes=0x1122
© www.soinside.com 2019 - 2024. All rights reserved.