btparser/cparser/MFTRecord.bt

310 lines
11 KiB
Plaintext

//--------------------------------------
//--- 010 Editor v6.0.3 Binary Template
//
// File: MFTRecord.bt
// Author: Andrea Barberio <insomniac@slackware.it>
// Revision: 1
// Purpose: Parsing of MFT records in NTFS file systems
//--------------------------------------
string getMFTNameFromRecurdNumber(UINT32 record_number) {
// Return the name of well-known record numbers
switch (record_number) {
case 0:
return "$MFT";
case 1:
return "$MFTMirr";
case 2:
return "$LogFile";
case 3:
return "$Volume";
case 4:
return "$AttrDef";
case 5:
return "$. (root dir)";
case 6:
return "$Bitmap";
case 7:
return "$Boot";
case 8:
return "$BadClus";
case 9:
return "$Secure";
case 10:
return "$UpCase";
case 11:
return "$Extend";
default:
return "";
}
}
string GetMFTRecordComment(struct MFTRecord &record) {
string ret;
SPrintf(ret, "Record # %u %s (%s)",
record.mftheader.mft_rec_number,
getMFTNameFromRecurdNumber(record.mftheader.mft_rec_number),
HeaderFlagsToString(record.mftheader.flags)
);
return ret;
}
wstring GetMFTNameFromAttribute(struct Attributes &attrs) {
// TODO handle differences between NT and 2k fields
switch (attrs.attribute.fixed_header.type) {
case STANDARD_INFORMATION:
return "$STANDARD_INFORMATION";
case ATTRIBUTE_LIST:
return "$ATTRIBUTE_LIST";
case FILE_NAME:
string ret;
SPrintf(ret, "$FILE_NAME (%s)",
attrs.attribute.resident_attribute_content.file_name);
return ret;
case OBJECT_ID:
return "$OBJECT_ID";
case SECURITY_DESCRIPTOR:
return "$SECURITY_DESCRIPTOR";
case VOLUME_NAME:
return "$VOLUME_NAME";
case VOLUME_INFORMATION:
return "$VOLUME_INFORMATION";
case DATA:
string ret;
if (attrs.attribute.fixed_header.non_res_flag)
return "$DATA (Non resident)";
else
return "$DATA (Resident)";
case INDEX_ROOT:
return "$INDEX_ROOT";
case INDEX_ALLOCATION:
return "$INDEX_ALLOCATION";
case BITMAP:
string ret;
SPrintf(ret,
"$BITMAP (map of bits telling for each record if they are in use or not)");
return ret;
case REPARSE_POINT:
return "$REPARSE_POINT";
case EA_INFORMATION:
return "$EA_INFORMATION";
case EA:
return "$EA";
case LOGGED_UTILITY_STREAM:
return "$LOGGED_UTILITY_STREAM";
default:
return "";
}
}
string HeaderFlagsToString(UINT16 flags) {
string ret;
if (flags & 0x02)
Strcat(ret, "Directory, ");
else
Strcat(ret, "File, ");
if (flags & 0x01)
Strcat(ret, "In use");
else
Strcat(ret, "Not in use");
return ret;
}
string AttributeFlagsToString(UINT16 flags) {
string ret;
if (flags & 0x01)
Strcat(ret, "Compressed, ");
else
Strcat(ret, "Not Compressed, ");
if (flags & 0x4000)
Strcat(ret, "Encrypted, ");
else
Strcat(ret, "Not Encrypted, ");
if (flags & 0x8000)
Strcat(ret, "Sparse");
else
Strcat(ret, "Not Sparse");
return ret;
}
string GetParentDirComment(uint64 ref_to_parent_dir) {
string ret,
details;
// FIXME for some reason the input number is truncated, hence the seq_num is
// wrong
UINT16 seq_num = ref_to_parent_dir >> 24;
UQUAD mft_entry = ref_to_parent_dir & 0x0000ffffffffffff;
if (mft_entry == 0x05) {
// record 0x05 is the root directory ($.)
Strcat(ret, "Root dir, ");
}
SPrintf(details, "Seq num = %04xh [!!] , MFT entry = %012xh", seq_num, mft_entry);
Strcat(ret, details);
return ret;
}
string GetAllocSize(UQUAD alloc_size) {
// assuming 4096-byte cluster size
string ret;
UQUAD num_clusters = alloc_size / 4096 + (alloc_size % 4096 ? 1 : 0);
SPrintf(ret, "%lu clusters (each cluster 4096 bytes)", num_clusters);
return ret;
}
string GetDataRunStart(UINT16 data_run_info) {
string ret;
SPrintf(ret, "Starting at 0x%x (assuming cluster size = 4096)", data_run_info * 4096);
return ret;
}
typedef struct MFTAttribute {
local int64 mftattribute_start = FTell();
struct FixedHeader {
enum <UINT32> {
STANDARD_INFORMATION = 0x10,
ATTRIBUTE_LIST = 0x20,
FILE_NAME = 0x30,
OBJECT_ID = 0x40, // on NT it's VOLUME_VERSION, on 2k it's OBJECT_ID
SECURITY_DESCRIPTOR = 0x50,
VOLUME_NAME = 0x60,
VOLUME_INFORMATION = 0x70,
DATA = 0x80,
INDEX_ROOT = 0x90,
INDEX_ALLOCATION = 0xa0,
BITMAP = 0xb0,
REPARSE_POINT = 0xc0, // on NT it's SYMBOLIC_LINK, on 2k it's REPARSE_POINT
EA_INFORMATION = 0xd0,
EA = 0xe0,
LOGGED_UTILITY_STREAM = 0x100 // on NT it's PROPERTY_SET, on 2k it's LOGGED_UTILITY_STREAM
} type; // Attribute type identifier
UINT32 attr_length; // length of attribute
enum <UBYTE> {
RESIDENT = 0,
NON_RESIDENT = 1
} non_res_flag <comment="Resident: stored in the record. Non-resident: stored in the data runs">; // resident flag
UBYTE name_length; // length of name
UINT16 name_offset; // offset to name
UINT16 flags <comment=AttributeFlagsToString>; // flags
UINT16 attribute_id; // attribute identifier
} fixed_header;
if (fixed_header.non_res_flag) {
struct NonResidentAttributeHeader {
UQUAD start_vcn; // starting Virtual Cluster Number of the runlist
UQUAD end_vcn; // ending Virtual Cluster Number of the runlist
UINT16 datarun_offset; // offset to the runlist
UINT16 compression_size <comment="If 0, not compressed">; // compression unit size
UINT32 padding; // unused
UQUAD alloc_size <comment=GetAllocSize>; // allocated size of attribute content
UQUAD real_size <comment="File size in bytes">; // actual size of attribute content
UQUAD stream_size <comment="Equal to real_size unless resized">; // initialized size of attribute content
} non_resident_attribute_header;
if (fixed_header.type == DATA) {
struct AttributeContentNonResidentData {
UBYTE unknown;
UBYTE num_of_clusters;
UINT16 data_run_start <format=hex, comment=GetDataRunStart>;
char padding[3];
} attribute_content_non_resident_data;
} else if (fixed_header.type == BITMAP) {
struct AttributeContentNonResidentBitmap {
char bitmap[fixed_header.attr_length - non_resident_attribute_header.datarun_offset];
} attribute_content_non_resident_bitmap;
} else {
// TODO define other data types
}
} else {
struct ResidentAttributeHeader {
UINT32 content_size;
UINT16 content_offset;
UINT16 indexed_tag;
} resident_attribute_header;
struct ResidentAttributeContent {
if (fixed_header.type == STANDARD_INFORMATION) {
struct AttributeContentStandardInformation {
FILETIME creation_time;
FILETIME alteration_time;
FILETIME mft_changed_time;
FILETIME read_time;
UINT32 dos_permissions;
UINT32 max_num_of_versions;
UINT32 version_number;
UINT32 class_id;
} attribute_content_standard_information;
if (resident_attribute_header.content_size != 48) {
UINT32 owner_id;
UINT32 security_id;
UQUAD quota_charged;
UQUAD update_sequence_number;
}
} else if (fixed_header.type == FILE_NAME) {
struct AttributeContentFileName {
UQUAD file_ref_to_parent_dir <format=hex, comment=GetParentDirComment>;
FILETIME file_creation_time;
FILETIME file_modification_time;
FILETIME mft_modification_time;
FILETIME file_access_time;
UQUAD allocated_size;
UQUAD real_size;
UINT32 flags;
UINT32 used_by_eas_and_reparse;
UBYTE filename_length_unicode;
UBYTE filename_namespace;
} attribute_content_file_name;
// variable, file name in Unicode
wchar_t file_name[attribute_content_file_name.filename_length_unicode];
} else {
UCHAR data[resident_attribute_header.content_size];
// TODO define other data types
}
} resident_attribute_content;
}
UCHAR padding[fixed_header.attr_length - (FTell() - mftattribute_start)];
} mftattribute;
struct MFTRecord {
struct MFTHeader {
char file_signature[4] <comment="Must be FILE">; // magic number, "FILE"
UINT16 fixup_offset; // offset to the update sequence
UINT16 fixup_size; // number of entries in the fixup array
UQUAD log_seq_number; // $LogFile Sequence Number (LSN)
UINT16 sequence; // Sequence Number
UINT16 hard_links <comment="Number of hard links">; // Hard link count
UINT16 attrib_offset; // Offset to first attribute
UINT16 flags <comment=HeaderFlagsToString>; // Flags: 0x01: record in use, 0x02: directory
UINT32 rec_length <comment="Length of the bytes used for this record. Must be <= 1024">; // Used size of MFT entry
UINT32 all_length <comment="Length of the whole record. Must be 1024">; // Allocated size of MFT entry
UQUAD base_mft_rec; // File reference to the base FILE record
UINT16 next_attr_id; // Next attribute ID
UINT16 fixup_pattern; // Align to 4 bytes boundary
UINT32 mft_rec_number <comment=getMFTNameFromRecurdNumber>; // Number of this MFT record
} mftheader;
UQUAD fixup; // is this correct?
local int i;
local int64 pos;
local UINT32 terminator;
for (i = 0; i == i ; i++) {
pos = FTell();
terminator = ReadUInt();
if (terminator == 0xffffffff) {
struct Terminator {
UINT32 terminator <format=hex, comment="Must be FF FF FF FF">;
} terminator <comment="Attribute list terminator">;
break;
} else {
struct Attributes {
mftattribute attribute;
} attributes <comment=GetMFTNameFromAttribute>;
}
}
} mftrecord <comment=GetMFTRecordComment>;