How to open $MFT in Python?

365 Views Asked by At

I need to open "//?/D:/$MFT" in binary read mode to parse its contents, I need the raw binary data from Master File Table to resolve File Record Segments from "D:/System Volume Information/Chkdsk/Chkdsk%y%m%d%H%M%S.log".

Long story short there was a power outage and this caused filesystem corruption and I ran chkdsk /f D: right away and I think some files may be corrupted because of said command. It said 107855 data files were processed, I want to know which files were affected and check if they are corrupt and if they are delete them.

I am extremely well versed in computers and programming if my reputation points don't tell you that.

Trying to open it using the usual syntax will result in... You guessed it:

In [142]: mft = open("//?/D:/$MFT", 'rb')
---------------------------------------------------------------------------
PermissionError                           Traceback (most recent call last)
Cell In[142], line 1
----> 1 mft = open("//?/D:/$MFT", 'rb')

File C:\Python310\lib\site-packages\IPython\core\interactiveshell.py:284, in _modified_open(file, *args, **kwargs)
    277 if file in {0, 1, 2}:
    278     raise ValueError(
    279         f"IPython won't let you open fd={file} by default "
    280         "as it is likely to crash IPython. If you know what you are doing, "
    281         "you can use builtins' open."
    282     )
--> 284 return io_open(file, *args, **kwargs)

PermissionError: [Errno 13] Permission denied: '//?/D:/$MFT'

Before you ask, of course I ran with Administrator privileges, in fact I have disabled LUAC via registry hack. I still get PermissionDenied. I know exactly what I am doing.

Googling python open mft gives me only a handful of relevant results, like Trying to get MFT table from Python 3 and Get hex-values / raw data from $MFT on NTFS Filesystem, none are useful.

Libraries like analyzeMFT are ancient and written for Python 2, I have looked at the source code and found it to be very poorly written, and I have already known the raw binary structure of the 1024B records, I have done extensive research enough to write a good parser, but I just can't get access to the file.

analyzeMFT when installed via PyPI (pip install analyzeMFT) will install the Python 2 version which cannot even be imported in Python 3:

In [144]: import analyzemft
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[144], line 1
----> 1 import analyzemft

File C:\Python310\lib\site-packages\analyzemft\__init__.py:2
      1 __all__ = ["mftutils", "mft", "mftsession", "bitparse"]
----> 2 import bitparse
      3 import mft
      4 import mftsession

ModuleNotFoundError: No module named 'bitparse'

I know it should be from . import bitparse, but the script files in GitHub are already patched to Python 3 and so I have copy-pasted all script files to "%pythondir%/Lib/site-packages/analyzeMFT".

And nope, it doesn't work, utilities like it only work on a dumped copy of the Master File Table and not the "hot" one:

PS C:\Users\Xeni> analyzeMFT -f '//?/D:/$MFT'
Unable to open file: //?/D:/$MFT

And they only generate human readable text serializations, I need the raw data in memory which they don't expose.

How can I open "//?/D:/$MFT" "hot"?


I have an idea, maybe I can try to open the drive root directory, like:

partition = os.open('//?/D:', os.O_BINARY)
sector = os.read(partition, 512)

I guess this will open partition boot sector, if so, then I can read 8 bytes at offset 0x30 to get $MFT cluster number, convert it to sector number and read the starting sectors to get its file size and then read all the sectors and then divide the entries into chunks of 1024 bytes to get the records.

I haven't tested this idea, will look into it.


I just did this thing I described, and it did give me the partition boot sector, and it is indeed the same structure as the Partition Boot Sector table found in Wikipedia, if so then Master File Table starts at cluster 2 and the integers seem to be little endian.

I will need further testing.


So far I have got these:

UNITS = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")
BOOT_SECTOR = [
    (0, 3, "assembly", 0),
    (3, 11, "OEM_ID", 0),
    (11, 13, "bytes/sector", 1),
    (13, 14, "sectors/cluster", 1),
    (21, 22, "media_descriptor", 0),
    (24, 26, "sectors/track", 1),
    (26, 28, "heads", 1),
    (28, 32, "hidden_sectors", 1),
    (40, 48, "total_sectors", 1),
    (48, 56, "$MFT_cluster", 1),
    (56, 64, "$MFTMirr_cluster", 1),
    (64, 65, "FRS_length", 1),
    (72, 80, "volume_serial_number", 1),
    (84, 510, "bootstrap_code", 0),
    (510, 512, "end-of-sector", 0),
]


def get_file_record_length(data: dict) -> None:
    frs_length = data["FRS_length"]
    if frs_length < 128:
        data["FRS_length"] = (
            frs_length * data["bytes/sector"] * data["sectors/cluster"]
        )
    else:
        data["FRS_length"] = 1 << (256 - frs_length)


def format_size(length: int) -> str:
    string = ""
    i = 0
    while length and i < 10:
        chunk = length & 1023
        length >>= 10
        if chunk:
            string = f"{chunk}{UNITS[i]} {string}"

        i += 1

    if length:
        string = f"{length}QiB {string}"

    return string.rstrip()


def process_boot_sector(data: dict) -> None:
    get_file_record_length(data)
    data["raw_size"] = size = data["bytes/sector"] * data["total_sectors"]
    data["readable_size"] = format_size(size)
    data["bytes/cluster"] = cluster = data["bytes/sector"] * data["sectors/cluster"]
    data["$MFT_index"] = (
        data["$MFT_cluster"] * cluster
    )

def open_partition(drive: str) -> dict:
    partition = open(f"//?/{drive}:", "rb")
    sector = partition.read(512)
    decoded = {}
    for start, end, name, little in BOOT_SECTOR:
        data = sector[start:end]
        if little:
            data = int.from_bytes(data, "little")

        decoded[name] = data

    process_boot_sector(decoded)
    partition.seek(0)
    return {
        "handle": partition,
        "info": decoded,
    }


partition = open_partition("D")
handle = partition["handle"]
info = partition["info"]
frs_length = info["FRS_length"]
handle.seek(info["$MFT_index"])
MFT = handle.read(frs_length)
In [2]: info
Out[2]:
{'assembly': b'\xebR\x90',
 'OEM_ID': b'NTFS    ',
 'bytes/sector': 512,
 'sectors/cluster': 8,
 'media_descriptor': b'\xf8',
 'sectors/track': 63,
 'heads': 255,
 'hidden_sectors': 40,
 'total_sectors': 7810824157,
 '$MFT_cluster': 2,
 '$MFTMirr_cluster': 171046397,
 'FRS_length': 1024,
 'volume_serial_number': 18170874752001262933,
 'bootstrap_code': b'\xfa3\xc0\x8e\xd0\xbc\x00|\xfbh\xc0\x07\x1f\x1ehf\x00\xcb\x88\x16\x0e\x00f\x81>\x03\x00NTFSu\x15\xb4A\xbb\xaaU\xcd\x13r\x0c\x81\xfbU\xaau\x06\xf7\xc1\x01\x00u\x03\xe9\xdd\x00\x1e\x83\xec\x18h\x1a\x00\xb4H\x8a\x16\x0e\x00\x8b\xf4\x16\x1f\xcd\x13\x9f\x83\xc4\x18\x9eX\x1fr\xe1;\x06\x0b\x00u\xdb\xa3\x0f\x00\xc1.\x0f\x00\x04\x1eZ3\xdb\xb9\x00 +\xc8f\xff\x06\x11\x00\x03\x16\x0f\x00\x8e\xc2\xff\x06\x16\x00\xe8K\x00+\xc8w\xef\xb8\x00\xbb\xcd\x1af#\xc0u-f\x81\xfbTCPAu$\x81\xf9\x02\x01r\x1e\x16h\x07\xbb\x16hp\x0e\x16h\t\x00fSfSfU\x16\x16\x16h\xb8\x01fa\x0e\x07\xcd\x1a3\xc0\xbf(\x10\xb9\xd8\x0f\xfc\xf3\xaa\xe9_\x01\x90\x90f`\x1e\x06f\xa1\x11\x00f\x03\x06\x1c\x00\x1efh\x00\x00\x00\x00fP\x06Sh\x01\x00h\x10\x00\xb4B\x8a\x16\x0e\x00\x16\x1f\x8b\xf4\xcd\x13fY[ZfYfY\x1f\x0f\x82\x16\x00f\xff\x06\x11\x00\x03\x16\x0f\x00\x8e\xc2\xff\x0e\x16\x00u\xbc\x07\x1ffa\xc3\xa0\xf8\x01\xe8\t\x00\xa0\xfb\x01\xe8\x03\x00\xf4\xeb\xfd\xb4\x01\x8b\xf0\xac<\x00t\t\xb4\x0e\xbb\x07\x00\xcd\x10\xeb\xf2\xc3\r\nA disk read error occurred\x00\r\nBOOTMGR is missing\x00\r\nBOOTMGR is compressed\x00\r\nPress Ctrl+Alt+Del to restart\r\n\x00\x8c\xa9\xbe\xd6\x00\x00',
 'end-of-sector': b'U\xaa',
 'raw_size': 3999141968384,
 'readable_size': '3TiB 652GiB 502MiB 1006KiB 512B',
 'bytes/cluster': 4096,
 '$MFT_index': 8192}

In [3]: MFT
Out[3]: b'FILE0\x00\x03\x00+M\x00\xd5\x04\x00\x00\x00\x01\x00\x01\x008\x00\x01\x00\x98\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x81\x04\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00`\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00H\x00\x00\x00\x18\x00\x00\x00\xb07\x86i6\x1e\xd7\x01\xb07\x86i6\x1e\xd7\x01\xb07\x86i6\x1e\xd7\x01\xb07\x86i6\x1e\xd7\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00h\x00\x00\x00\x00\x00\x18\x00\x00\x00\x01\x00J\x00\x00\x00\x18\x00\x01\x00\x05\x00\x00\x00\x00\x00\x05\x00\xb07\x86i6\x1e\xd7\x01\xb07\x86i6\x1e\xd7\x01\xb07\x86i6\x1e\xd7\x01\xb07\x86i6\x1e\xd7\x01\x00\x00,\x89\x00\x00\x00\x00\x00\x00,\x89\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x04\x03$\x00M\x00F\x00T\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00H\x00\x00\x00\x01\x00@\x00\x00\x001\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbf\x92\x08\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00,\x89\x00\x00\x00\x00\x00\x00,\x89\x00\x00\x00\x00\x00\x00,\x89\x00\x00\x00\x00\x13\xc0\x92\x08\x02\x00\x00\x00\xb0\x00\x00\x00H\x00\x00\x00\x01\x00@\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00P\x04\x00\x00\x00\x00\x00`I\x04\x00\x00\x00\x00\x00`I\x04\x00\x00\x00\x00\x001E\xc2\x92\x08\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x04'

I need more research to parse all of these things.

0

There are 0 best solutions below