From ad571ff710ddd251a28ffde9556eef609cba01ff Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:01:07 -0400 Subject: [PATCH 01/10] Added /DFIR/ with patterns Added /DFIR/ sub-directory. Contains modified versions of built-in patterns for semi-automated Disk/Volume/Filesystem parsing geared towards Digital Forensics. Originals in /fs/ should remain in tact for spot placement. --- patterns/DFIR/DFIR_README.md | 85 ++ patterns/DFIR/DISK_PARSER.hexpat | 673 +++++++++++++ patterns/DFIR/FAT32.hexpat | 788 +++++++++++++++ patterns/DFIR/NTFS.hexpat | 1571 ++++++++++++++++++++++++++++++ patterns/DFIR/exFAT.hexpat | 621 ++++++++++++ 5 files changed, 3738 insertions(+) create mode 100644 patterns/DFIR/DFIR_README.md create mode 100644 patterns/DFIR/DISK_PARSER.hexpat create mode 100644 patterns/DFIR/FAT32.hexpat create mode 100644 patterns/DFIR/NTFS.hexpat create mode 100644 patterns/DFIR/exFAT.hexpat diff --git a/patterns/DFIR/DFIR_README.md b/patterns/DFIR/DFIR_README.md new file mode 100644 index 00000000..9bd88513 --- /dev/null +++ b/patterns/DFIR/DFIR_README.md @@ -0,0 +1,85 @@ +ImHex Pattern Files - Digital Forensics + - [ImHex-DFIR-Patterns](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns) + +Enhanced features of the stock Disk/Filesystem pattern files for forensic review of disk content. + - [ImHex](https://github.com/WerWolv/ImHex) + - [ImHex Patterns](https://github.com/WerWolv/ImHex-Patterns) + +Install: + - Create a new folder called "DFIR" + - Add these updated pattern files to "DFIR" +[Folder_Structure (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/1-Folder_Structure.png) + +Use: + - Open a physical disk via Raw Provider (read-only) + - EXAMPLE: /dev/disk6 + - Import Pattern File + - EXAMPLE: DISK_PARSER.hexpat +[Pattern_Selection (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/2-DISK_PARSER-Pattern.png) + + - DISK_PARSER.hexpat + - Recognize MBR/GPT Disks and parse MPT/GPT + - Including Logical Volumes in an Extended Partition (container) + - Auto load file system patterns for FAT32, exFAT, NTFS formatted volumes + - Optional Disk Report + +[DISK > MBR/GPT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3-DISK-HYBRID.png) +[DISK > MBR > MPT > 3 Primaries | 2 Logicals in an Extended (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3a-DISK-MBR.png) + + - FAT32.hexpat + - Auto loaded by DISK_PARSER.hexpat + - Parse VBR, FAT1, FAT2, Root Dir, and 1 level of SubDirs + - FAT1/FAT2 Cluster chaining with SFN resolution + - LFN/SFN Alias grouping in Root Dir + - Recognize deleted entries (xE5) + - File Content pointer + - D/T Conversions + - Optional FAT32 Volume Report + +[VOLUME > FAT32 > FAT1 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/4-FAT32-1_SMALL_TXT.png) +[VOLUME > FAT32 > Root Dir (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/5-FAT32_ROOT_DIR.png) +[VOLUME > FAT32 > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/6-FAT32_SFN_POINTER.png) + + - exFAT.hexpat + - Auto loaded by DISK_PARSER.hexpat + - Parse VBR/Boot Sector/Extended Sectors, FAT1, Root Dir + - Recognize active directory entries (x85, xC0, xC1) + - Recognize inactive directory entries (x05, x40, x41) + - xC0/x40 File Content pointer + - D/T Conversions + - Optional exFAT Volume Report + +[VOLUME > exFAT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/7-exFAT-1.png) +[VOLUME > exFAT > Root Dir > xC0 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/8-exFAT_xC0.png) +[VOLUME > exFAT > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/9-exFAT-Data_Pointer.png) + + - NTFS.hexpat + - Auto loaded by DISK_PARSER.hexpat + - Parse VBR (Boot Sector), $MFT, Root Dir, and Indexes + - Recursively parse the $Metadata files, $Attributes, and user files/dirs + - Added file record | parent [MFT#] [SEQ#] indicators + - Parse x80/xB0 Data Runs + - File Content pointer + - D/T Conversions + - Optional NTFS Volume Report + +[VOLUME > NTFS > $MFT > D/T Conversion (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/10-NTFS-DT.png) +[VOLUME > NTFS > $MFT > x80 Run List (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/11-NTFS-DATA_RUN.png) +[VOLUME > NTFS > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/12-NTFS-DATA_POINTER.png) + + - Optional Reports + - Simply copy the console output to a file... + + - To enable/disable the reports: + - Open each DFIR related .hexpat + - Find the report constant (near the top) + - "true" = enabled + - "false" = disabled + + Example Report: GPT > FAT32|exFAT +[exFAT_Report](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/exFAT_Report.txt) + + Example Report: MBR > 5 Logical Volumes (2 in an Extended) > All FAT32 Volumes +[MBR_5_VOLs](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/MBR_5_VOLs.txt) + + diff --git a/patterns/DFIR/DISK_PARSER.hexpat b/patterns/DFIR/DISK_PARSER.hexpat new file mode 100644 index 00000000..d01fcb2a --- /dev/null +++ b/patterns/DFIR/DISK_PARSER.hexpat @@ -0,0 +1,673 @@ +#pragma author Formula Zero One Technologies +#pragma description Disk / Volume / Filesystem Structures (MBR, GPT, FAT32, exFAT, NTFS) +#pragma MIME application/x-ima +#pragma endian little +#pragma loop_limit 16000 + + +// ----------------------------------------------------------------------------- +// CREDIT +// ----------------------------------------------------------------------------- +// Based on /fs/pattern.hexpat by WerWolv +// ----------------------------------------------------------------------------- +// TODO +// ----------------------------------------------------------------------------- +// Auto apply pattern?... +// EXTENDED PARTITION and logical volumes in the container +// Other File Systems for GPT Entries +// Refine File System Detection/Match +// ----------------------------------------------------------------------------- +// IMPORTS +// ----------------------------------------------------------------------------- +import std.core; +import std.io; +import std.time; +import type.guid; +import type.magic; +import type.time; +import type.base; +import hex.provider; + +import * from DFIR.FAT32 as FAT32Pat; +import * from DFIR.exFAT as EXFATPat; +import * from DFIR.NTFS as NTFSPat; + +// ------------------------------------ +// DISABLED IMPORTS +// REFS - UNTESTED +// EXT4 - GROUP DESC ERRORS +// JPEG/PNG - OFFSET ERRORS +// ------------------------------------ +//import * from fs.ext4 as EXT4Pat; +//import * from fs.refs as REFSPat; +//import * from jpeg as JPEGPat; +//import * from png as PNGPat; + +// ----------------------------------------------------------------------------- +// FWD DECs - GLOBAL +// ----------------------------------------------------------------------------- +bool has_ext = false; +bool has_gpt = false; +u64 partitionOffset = 0; +u64 containerStartOffset = 0; + +u32 mptIndex = 0; +u32 extIndex = 0; +str entryName = ""; + +u32 MPT_Count = 0; +u32 EXT_VolCount = 0; +u32 GPT_Count = 0; + +u32 memory_size = std::mem::size(); +str disk_path = hex::prv::get_information("file_path",""); +u128 sector_size = hex::prv::get_information("sector_size",""); + +// ----------------------------------------------------------------------------- +// REPORT HEADER ** ATTENTION ** +// ----------------------------------------------------------------------------- + +// ---******---*******---vvvv--- | +const bool DISK_REPORT = true; +// ---******---*******---^^^^--- | + +if (DISK_REPORT) { + std::print(" # # # # # # "); + std::print(" # # # "); + std::print(" # # # "); + std::print(" # # # # # # # "); + std::print(" I m H e x "); + std::print(" "); + std::print("-----------------------------------------"); + std::print(" "); + std::print(" ENTITY: _____________________"); + std::print(" "); + std::print("EXAMINER: _____________________"); + std::print(" "); + u128 timestamp = std::time::epoch(); + std::time::Time local_ts = std::time::to_local(timestamp); + std::time::Time utc_ts = std::time::to_utc(timestamp); + std::print("-----------------------------------------"); + std::print(" LOCAL: {}", + std::format("{:02}/{:02}/{:04} @ {:02}:{:02}:{:02}", + local_ts.mon + 1, + local_ts.mday, + local_ts.year + 1900, + local_ts.hour, + local_ts.min, + local_ts.sec + )); + std::print(" UTC: {}", + std::format("{:02}/{:02}/{:04} @ {:02}:{:02}:{:02}", + utc_ts.mon + 1, + utc_ts.mday, + utc_ts.year + 1900, + utc_ts.hour, + utc_ts.min, + utc_ts.sec + )); + std::print("-----------------------------------------"); + std::print(" "); +} + +// ----------------------------------------------------------------------------- +// SIGNATURE HELPER +// ----------------------------------------------------------------------------- +enum MBRSignature : u16 { + MBR_SIG = 0xAA55 // 0x55AA -> Read LE +}; + +// ----------------------------------------------------------------------------- +// CHS HELPER +// ----------------------------------------------------------------------------- +bitfield CHS_Decoder { + head : 8; + sector : 6; + cylinder : 10; +} [[format("chs_formatter")]]; + +fn chs_formatter(CHS_Decoder CHS) { + return std::format("({:X}, {:X}, {:X}) | 0x{:X}", CHS.cylinder, CHS.head, CHS.sector, (CHS.cylinder * 16 + CHS.head) * 63 + (CHS.sector - 1)); +}; + +// ----------------------------------------------------------------------------- +// TIMESTAMP HELPER +// ----------------------------------------------------------------------------- +struct DiskTimeStamp { + u8 seconds, minutes, hours; +}; + +// ----------------------------------------------------------------------------- +// DISK PROTECTION HELPER +// ----------------------------------------------------------------------------- +enum DiskProtection : u16 { + NotProtected = 0x0000, + CopyProtected = 0x5A5A +}; + +// ----------------------------------------------------------------------------- +// PARTITION STATUS HELPER +// ----------------------------------------------------------------------------- +enum PartitionStatus : u8 { + Not_Active = 0x00, // not_bootable + Active = 0x80 // bootable +}; + +enum MPTPartLabel : u8 { + UNUSED_OR_HIDDEN_ENTRY = 0x00, + PRIMARY = 0x07, + PRIMARY_F32_SMALL = 0x0B, + PRIMARY_0C_BIG = 0x0C, + EXTENDED_CONT_SMALL = 0x05, + EXTENDED_CONT_BIG = 0x0F, + LEGACY_MBR = 0xEE +}; + +// ----------------------------------------------------------------------------- +// PARTITION TYPE HELPER +// ----------------------------------------------------------------------------- +enum PartitionTypeCode : u8 { + UNUSED_ENTRY = 0x00, + FAT12_HDD = 0x01, + FAT12_HIDDEN = 0x11, + FAT16_SMALL = 0x04, + FAT16_SMALL_HIDDEN = 0x14, + FAT16_BIG = 0x06, + FAT16_BIG_HIDDEN = 0x16, + FAT32_SMALL = 0x0B, + FAT32_SMALL_HIDDEN = 0x1B, + FAT32_BIG = 0x0C, + FAT32_BIG_HIDDEN = 0x1C, + EXT_PART_SMALL = 0x05, + EXT_PART_SMALL_HIDDEN = 0x15, + EXT_PART_BIG = 0x0F, + EXT_PART_BIG_HIDDEN = 0x1F, + NTFS_EXFAT = 0x07, + NTFS_EXFAT_HIDDEN = 0x17, + WINDOWS_RECOVERY = 0x27, + NTFS_VOL_SET_1 = 0x86, + NTFS_VOL_SET_2 = 0x87, + macOSX = 0xA8, + OS2_HIDDEN_CDRIVE = 0x84, + LINUX_EXT = 0x83, + LINUX_EXT2 = 0x85, + LINUX_LVM = 0x8E, + LINUX_PA_RISC = 0xF0, + LINUX_RAID = 0xFD, + FREE_BSD = 0xA5, + OPEN_BSD = 0xA6, + QNX_1 = 0x4D, + QNX_2 = 0x4E, + QNX_3 = 0x4F, + GPT_DISK_STD = 0xEE, + GPT_DISK_SYS = 0xEF, + UNKNOWN = 0xFF, +}; + +// ----------------------------------------------------------------------------- +// GUID PARTITION TABLE (GPT) PARTIONING SCHEME RELATED +// ----------------------------------------------------------------------------- +// V V V V V V V V V V +// ----------------------------------------------------------------------------- +// GPT PARTITION LABEL HELPER +// ----------------------------------------------------------------------------- +enum GUIDPartLabel : u128 { + // ---------------- COMMON ---------------- + UNUSED_ENTRY = 0x00000000000000000000000000000000, + EFI_SYSTEM_PART = 0x3BC93EC9A0004BBA11D2F81FC12A7328, + APPLE_APFS_CONT = 0xACEC4365300011AA11AA00007C3457EF, + APPLE_HFS_PLUS_PART = 0xACEC4365300011AA11AA000048465300, + MICROSOFT_RESERVED_PART = 0xAE1502F92DF97D81B84D5C0BE3E3C9E3, + WINDOWS_REC_ENVIRONMENT = 0xACD67901D5BF6AA1404DD106A4BB94DE, + BASIC_DATA_PART = 0xC79926B7B668C0874433B9E5EBD0A0A2, + + // ---------------- LINUX ---------------- + LINUX_FILE_SYSTEM = 0xE47D47D8693D798E477284830FC63DAF, + RAID_PART = 0x1E91840F3F7406A04D3B05FCA19D880F, + ROOT_PART_X86 = 0x8A45F0D531D1F79A41B2F29744479540, + ROOT_PART_X86_64 = 0x09B784F9CAFBE7964DB1E8CD4F68BCE3, + ROOT_PART_ARM = 0xD3BE9AD4A1216CB14E3C2CE469DAD710, + ROOT_PART_ARM_64 = 0xAE3F0D286F4C44AF41C31DF0B921B045, + BOOT_PART = 0x72716FFD75B252A3426259E6BC13C2FF, + SWAP_PART = 0x4F4F4BC83309E58443C4A4AB0657FD6D, + LOGICAL_VOLUME_MGR_PART = 0x28F93D2A8F233CA244C2F507E6D6D379, + HOME_PART = 0x15F9AEE2140E44B84F132EB4933AC7E1, + SRV_SERVER_DATA_PART = 0xE8986FA7251A7F904F3B20E03B8F8425, + PLAIN_DMCRYPT_PART = 0xB786550AA13E418949B72D007FFEC5C9, + LUKS_PART = 0xCC59605342171C864C5363EDCA7D7CCB, + + // ---------------- APPLE ---------------- + APPLE_UFS_CONT = 0xACEC4365300011AA11AA000055465300, + APPLE_ZFS = 0x316673200008A69911B21DD26A898CC3, + APPLE_RAID_PART = 0xACEC4365300011AA11AA000052414944, + APPLE_RAID_PART_OFFLINE = 0xACEC4365300011AA11AA5F4F52414944, + APPLE_BOOT_PART_REC_HD = 0xACEC4365300011AA11AA0000426F6F74, + APPLE_LABEL = 0xACEC4365300011AA11AA6C004C616265, + APPLE_TV_RECOVERY_PART = 0xACEC4365300011AA11AA76655265636F, + APPLE_CORE_STORAGE_CONT = 0xACEC4365300011AA11AA616753746F72, + HFS_FILEVAULT_VOLUME_CONT = 0xACEC4365300011AA11AA616753746F72, + APPLE_APFS_PREBOOT_PART = 0xACEC4365300011AA11AA006769646961, + APPLE_APFS_RECOVERY_PART = 0xACEC4365300011AA11AA007972637652, + + // ---------------- WINDOWS ---------------- + LOGICAL_DISK_MGR_META_PART = 0xB3CF34E104E1D28542E08F7EAAC80858, + LOGICAL_DISK_MGR_DATA_PART = 0xAD694A71113368BC4F621431A0609BAF, + IBM_GENERAL_PARALLEL_FILE_SYS_PART = 0x74B155E07A2DC3914E4EEF7D90FFAA37, + STORAGE_SPACES_PART = 0x2DECF6E501B0A3AFEE4CF6808FAF5CE7, + STORAGE_REPLICA_PART = 0xD123292BD147C8AAC043A1ACC58D4355, +}; +// ----------------------------------------------------------------------------- +// BASIC DATA PARTITION ATTRIBUTES +// ----------------------------------------------------------------------------- +bitfield GPT_BDP_Attributes { + bool platform_required : 1 [[comment("Bit 0: RequiredPartition - Volume must be preserved")]]; + bool io_ignore : 1 [[comment("Bit 1: NoBlockIOProtocol - EFI ignores this Volume, no FS Mapping")]]; + bool legacy_flag : 1 [[comment("Bit 2: LegacyBIOSBootable - Active/Bootable under BIOS")]]; + reserved_UEFI : 45 [[comment("Bits 3–47: Reserved for UEFI")]]; + reserved_MS : 12 [[comment("Bits 48–59: Reserved for Microsoft")]]; + bool read_only : 1 [[comment("Bit 60: BasicDataPart - Read-Only Volume")]]; + bool shadow_copy : 1 [[comment("Bit 61: BasicDataPart - Shadow Copy Volume")]]; + bool hidden : 1 [[comment("Bit 62: BasicDataPart - Hidden Volume")]]; + bool no_drive_letter : 1 [[comment("Bit 63: BasicDataPart - Do not Auto-Assign Drive Letter")]]; +} [[bitfield_order( + std::core::BitfieldOrder::LeastToMostSignificant, 64)]]; + +// ----------------------------------------------------------------------------- +// GPT ENTRIES PARSER +// LBA2-LBA33 +// EACH ENTRY IS 128 BYTES (DESCRIBES A VOLUME) +// ----------------------------------------------------------------------------- +union PartitionUnion { + le type::GUID PartTypeGUID; // HUMAN READABLE GUID + GUIDPartLabel PartTypeLabel [[name(std::format("PartTypeLabel (DERIVED)"))]]; // OBJECT LABEL +}; + +struct GPT_PartitionEntry { + PartitionUnion Type [[comment("Known Partition Type GUID: Global Identifier")]]; + le type::GUID Unique_GUID [[comment("Unique Partition GUID: Every Volume has its own Unique GUID")]]; + u64 Start_LBA [[comment("The first Sector of the Volume (Offset by 1)")]]; + u64 End_LBA [[comment("The last Sector of the Volume (Offset by 1)")]]; + GPT_BDP_Attributes ATTR [[comment("ATTRs for a Basic Data Partition may not be the same as a Microsoft Reserved Partition")]]; + char16 PartName[36] [[comment("Partition Name: Based on Known Partition Type GUID, except for Disk Images")]]; + + if (Type.PartTypeLabel != GUIDPartLabel::UNUSED_ENTRY) { + GPT_Count += 1; + } + + u64 GPTpartitionOffset = Start_LBA * sector_size + [[name(std::format("VOL_OFFSET {} | 0x{:02X} (DERIVED)", Start_LBA * sector_size, Start_LBA * sector_size)), + export]]; + + match (Type.PartTypeLabel) { + (GUIDPartLabel::UNUSED_ENTRY): + continue; + + (GUIDPartLabel::EFI_SYSTEM_PART): + FAT32Pat EFI_SYS_VOL @ GPTpartitionOffset; + + (GUIDPartLabel::BASIC_DATA_PART | + GUIDPartLabel::WINDOWS_REC_ENVIRONMENT): { + char gpt_fat32_magic[8] @ GPTpartitionOffset + 82 [[hidden]]; + char gpt_ntfs_magic[8] @ GPTpartitionOffset + 3 [[hidden]]; + char gpt_exfat_magic[8] @ GPTpartitionOffset + 3 [[hidden]]; + + if (gpt_fat32_magic == "FAT32 ") + FAT32Pat FAT32_VOL @ GPTpartitionOffset; + if (gpt_ntfs_magic == "NTFS ") + NTFSPat NTFS_VOL @ GPTpartitionOffset; + else if (gpt_exfat_magic == "EXFAT ") + EXFATPat EXFAT_VOL @ GPTpartitionOffset; + } + // EXT4 PATTERN WAS INOP WHEN TESTED + //(GUIDPartLabel::LINUX_FILE_SYSTEM): + //EXT4Pat EXT4_VOL @ 0x100400; + } +} [[name(std::format("GPT_ENTRY [{}]", std::core::array_index()))]]; + +// ----------------------------------------------------------------------------- +// GPT HEADER PARSER +// LBA1 OFFSETS 0-91 (92 bytes of 512 bytes used) +// ----------------------------------------------------------------------------- +struct GPT_Header { + type::Magic<"EFI PART"> signature [[comment("Signature (EFI PART)")]]; + u32 revision [[comment("Header Revision Value")]]; + u32 header_size [[comment("Size of Header - 92 Bytes")]]; + type::Hex header_crc32 [[comment("GPT Header Checksum")]]; + u32 reserved [[comment("Zeros")]]; + u64 current_lba [[comment("Current LBA - GPT Header Location")]]; + u64 backup_lba [[comment("Location of Backup - Header & GPT")]]; + u64 first_usable_lba [[comment("1st Sector Available for Logical VOL")]]; + u64 last_usable_lba [[comment("Last Sector Available for Logical VOL")]]; + type::GUID disk_guid [[comment("Unique Disk GUID")]]; + u64 partition_entries_lba [[comment("1st Sector of GPT")]]; + u32 num_partition_entries [[comment("Total Number of Partition Entries Available - 128 on Windows")]]; + u32 size_of_partition_entry [[comment("Size in Bytes of each GPT Entry")]]; + type::Hex partition_entries_crc32 [[comment("GPT Array Checksum")]]; +}; + +// ----------------------------------------------------------------------------- +// MASTER BOOT RECORD (MBR) PARTIONING SCHEME RELATED +// ----------------------------------------------------------------------------- +// V V V V V V V V V V +// ----------------------------------------------------------------------------- +// MASTER PARTITION TABLE (MPT) +// LBA0 > 0FFSETS 446-509 +// Each Entry Describes a Logical Volume (type/start_loc/size) +// ----------------------------------------------------------------------------- +union MBRPartitionUnion { + PartitionTypeCode Part_Type; + MPTPartLabel PartTypeLabel; // overlay for 0x00 +}; + +struct PartitionTableEntry { + // Standard partition fields + PartitionStatus ActiveFlag; + CHS_Decoder Starting_CHS; + MBRPartitionUnion Type; + CHS_Decoder Ending_CHS; + u32 Start_LBA; + u32 Total_Sectors; + + if (Type.PartTypeLabel != MPTPartLabel::UNUSED_OR_HIDDEN_ENTRY) { + // Track Count of Logical Volumes in the Extended Container + //MPT_Count += 1; + if (containerStartOffset == 0) { + // top-level MBR entry + MPT_Count = MPT_Count + 1; + } else { + // a logical inside an extended container + EXT_VolCount = EXT_VolCount + 1; + } + } + + partitionOffset = containerStartOffset + (Start_LBA * sector_size); + + match (Type.PartTypeLabel) { + (PartitionTypeCode::UNUSED_ENTRY): continue; + (PartitionTypeCode::FAT32_SMALL | PartitionTypeCode::FAT32_BIG): { + FAT32Pat FAT32_VOL @ partitionOffset; + } + (PartitionTypeCode::NTFS_EXFAT): { + char magic[8] @ partitionOffset + 3; + if (magic == "NTFS ") + NTFSPat NTFS_VOL @ partitionOffset; + else + EXFATPat EXFAT_VOL @ partitionOffset; + } + (PartitionTypeCode::EXT_PART_SMALL | PartitionTypeCode::EXT_PART_BIG): { + // Save parent state + bool parent_has_ext = has_ext; + has_ext = true; + + containerStartOffset = partitionOffset; + + // Parse first two entries of the extended partition + PartitionTableEntry EXTENDED_PARTITION[2] @ partitionOffset + 446; + + has_ext = parent_has_ext; + + } + (PartitionTypeCode::GPT_DISK_STD | PartitionTypeCode::GPT_DISK_SYS): + // Set global flag + has_gpt = true; + } + if (!has_ext) { + entryName = std::format("MPT_ENTRY [{}]", mptIndex); + mptIndex += 1; + } else { + if (std::core::array_index() <= 0) { + entryName = std::format("LOGICAL_VOL (EXT) [{}]", extIndex); + } else if (std::core::array_index() == 1) { + entryName = "NEXT VOL POINTER (EXT)"; + } else { + entryName = std::format("LOGICAL_VOL (EXT) [{}]", extIndex); + } + extIndex += 1; + } +} [[name(entryName)]]; + +// ----------------------------------------------------------------------------- +// MBR PARSER +// LBA0 > OFFSETS 0-511 (512 bytes) +// ----------------------------------------------------------------------------- +struct MasterBootRecord { + u8 bootstrapCodeArea1[218] [[comment("Boot Strapping Code")]]; + padding[2] [[comment("Zeros")]]; + u8 originalPhysicalDrive [[comment("???")]]; + DiskTimeStamp diskTimeStamp [[comment("Timestamp of Disk OG Partitioning")]]; + u8 bootstrapCodeArea2[216] [[comment("Boot Strapping Code")]]; + u32 diskSignature [[comment("Disk Signature")]]; + DiskProtection diskProtection [[comment("Disk Protection - 0x0000=Not | 0x5A5A=Prot")]]; + PartitionTableEntry PT[4] [[comment("Master Partition Table (MPT) Offset 446-509")]]; + MBRSignature MBR_SIG [[comment("End of MBR - 0x55AA")]]; +}; + +// ----------------------------------------------------------------------------- +// DISK PARSER +// ----------------------------------------------------------------------------- +struct DiskRoot { + // Master Boot Record at LBA 0 (1st physical sector) + MasterBootRecord MBR @ 0x00; + + if (has_gpt) { + // GPT Header at LBA 1 (2nd physical sector) + GPT_Header GPT_HEADER @ 0x200; + // The GPT (table) at LBA 2 (3rd physical sector) to LBA 33 (34th physical sector) + // 32 sectors total (Windows) that can define up to 128 - (primary) logical volumes + GPT_PartitionEntry GPT_ENTRIES[GPT_HEADER.num_partition_entries] @ (GPT_HEADER.partition_entries_lba * 512); + } +}; + +// ----------------------------------------------------------------------------- +// ROOT OBJECT +// ----------------------------------------------------------------------------- +// --- +DiskRoot DISK @ 0x0; +// --- + + +// ------------------------------ +// DISK REPORT +// ------------------------------ + +if (DISK_REPORT) { + std::print("-----------------------------------------"); + std::print("-------------- DISK_REPORT --------------"); + std::print("-----------------------------------------"); + + // Disk Basics + std::print("DISK_PATH = {}", disk_path); + std::print("SECTOR_SIZE = {} BYTES", sector_size); + std::print("DISK_SIZE = {} SECTORS", memory_size / sector_size); + std::print("DISK_SIZE = {:.4f} GB @ 1000", memory_size / 1000.0 / 1000.0 / 1000.0); + std::print("DISK_SIZE = {:.4f} GiB @ 1024", memory_size / 1024.0 / 1024.0 / 1024.0); + + // Disk Protection + str diskProtectionStr; + if (DISK.MBR.diskProtection == DiskProtection::NotProtected) { + diskProtectionStr = "NOT_COPY_PROTECTED"; + } else if (DISK.MBR.diskProtection == DiskProtection::CopyProtected) { + diskProtectionStr = "COPY_PROTECTED"; + } else { + diskProtectionStr = "UNKNOWN"; + } + std::print("DISK_PROTECT = {}", diskProtectionStr); + + // Partition Scheme + if (MPT_Count >= 1 && GPT_Count == 0) { + std::print("PART_SCHEME = MBR"); + } else if (GPT_Count >= 1 && MPT_Count == 0) { + std::print("PART_SCHEME = GPT"); + } else if (GPT_Count >= 1 && MPT_Count >= 1) { + std::print("PART_SCHEME = HYBRID (MBR + GPT)"); + } else { + std::print("PART_SCHEME = UNKNOWN"); + } + + // MBR MPT Partitions + for (u32 i = 0, i < MPT_Count, i = i + 1) { + std::print("-----------------------------------------"); + std::print("-------------- MBR_MPT [{}] --------------", i); + std::print("-----------------------------------------"); + + // STATUS + str statusStr; + if (DISK.MBR.PT[i].ActiveFlag == PartitionStatus::Active) { + statusStr = "ACTIVE/BOOTABLE"; + } else if (DISK.MBR.PT[i].ActiveFlag == PartitionStatus::Not_Active) { + statusStr = "INACTIVE/NOT_BOOTABLE"; + } else { + statusStr = "UNKNOWN"; + } + std::print(" STATUS = {}", statusStr); + + // TYPE_CODE + str typeStr; + if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::FAT32_SMALL) { + typeStr = "FAT32 (CHS) (0x0B)"; + } else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::FAT32_BIG) { + typeStr = "FAT32 (LBA) (0x0C)"; + } else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::NTFS_EXFAT) { + typeStr = "NTFS/EXFAT (0x07)"; + } else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::GPT_DISK_STD) { + typeStr = "GPT_PROTECTIVE (0xEE)"; + } else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::EXT_PART_BIG) { + typeStr = "EXTENDED (0x0F)"; + } else { + typeStr = "OTHER/UNKNOWN"; + } + std::print(" TYPE_CODE = {}", typeStr); + + // LBA and size + std::print(" FIRST_LBA = {:02}", DISK.MBR.PT[i].Start_LBA); + std::print(" LAST_LBA = {:02}", DISK.MBR.PT[i].Start_LBA + DISK.MBR.PT[i].Total_Sectors - 1); + std::print(" VOL_SIZE = {:02} SECTORS", DISK.MBR.PT[i].Total_Sectors); + std::print(" VOL_SIZE = {:.4f} GB", (DISK.MBR.PT[i].Total_Sectors * sector_size) / 1000.0 / 1000.0 / 1000.0); + std::print(" VOL_SIZE = {:.4f} GiB", (DISK.MBR.PT[i].Total_Sectors * sector_size) / 1024.0 / 1024.0 / 1024.0); + + if (DISK.MBR.PT[i].Type.PartTypeLabel == MPTPartLabel::EXTENDED_CONT_SMALL || + DISK.MBR.PT[i].Type.PartTypeLabel == MPTPartLabel::EXTENDED_CONT_BIG) { + + u32 logicalCount = std::core::member_count(DISK.MBR.PT[i].EXTENDED_PARTITION); + //u32 logicalCount = std::mem::size(DISK.MBR.PT[i].EXTENDED_PARTITION); + + for (u32 e = 0, e < logicalCount, e = e + 1) { + if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.PartTypeLabel == MPTPartLabel::UNUSED_OR_HIDDEN_ENTRY) + continue; + + std::print("-----------------------------------------"); + std::print("---------- LOGICAL (EXT) [{}] ------------", e); + std::print("-----------------------------------------"); + + // STATUS + str EXTstatusStr; + if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].ActiveFlag == PartitionStatus::Active) { + EXTstatusStr = "ACTIVE/BOOTABLE"; + } else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].ActiveFlag == PartitionStatus::Not_Active) { + EXTstatusStr = "INACTIVE/NOT_BOOTABLE"; + } else { + EXTstatusStr = "UNKNOWN"; + } + std::print(" STATUS = {}", EXTstatusStr); + + // TYPE_CODE + str EXTtypeStr; + if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::FAT32_SMALL) { + EXTtypeStr = "FAT32 (CHS) (0x0B)"; + } else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::FAT32_BIG) { + EXTtypeStr = "FAT32 (LBA) (0x0C)"; + } else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::NTFS_EXFAT) { + EXTtypeStr = "NTFS/EXFAT (0x07)"; + } else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::GPT_DISK_STD) { + EXTtypeStr = "GPT_PROTECTIVE (0xEE)"; + } else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::EXT_PART_BIG) { + EXTtypeStr = "EXTENDED (0x0F)"; + } else { + EXTtypeStr = "OTHER/UNKNOWN"; + } + std::print(" TYPE_CODE = {}", EXTtypeStr); + + std::print(" FIRST_LBA = {}", DISK.MBR.PT[i].EXTENDED_PARTITION[e].Start_LBA); + std::print(" LAST_LBA = {}", DISK.MBR.PT[i].EXTENDED_PARTITION[e].Start_LBA + + DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors - 1); + std::print(" VOL_SIZE = {} SECTORS", DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors); + std::print(" VOL_SIZE = {:.4f} GB", + (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors * sector_size) / 1000.0 / 1000.0 / 1000.0); + std::print(" VOL_SIZE = {:.4f} GiB", + (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors * sector_size) / 1024.0 / 1024.0 / 1024.0); + } + } + } + + // GPT Header + if (GPT_Count >= 1) { + std::print("-----------------------------------------"); + std::print("-------------- GPT_HEADER ---------------"); + std::print("-----------------------------------------"); + std::print("SIGNATURE = {}", DISK.GPT_HEADER.signature); + std::print("REVISION = 0x{:02X}", DISK.GPT_HEADER.revision); + std::print("GPT_HDR_CRC = 0x{:02X}", DISK.GPT_HEADER.header_crc32); + std::print("GPT_HDR_BACKUP_LBA = {}", DISK.GPT_HEADER.backup_lba); + std::print("DISK_GUID = {}", DISK.GPT_HEADER.disk_guid); + std::print("FIRST_USABLE_LBA = {}", DISK.GPT_HEADER.first_usable_lba); + std::print("LAST_USABLE_LBA = {}", DISK.GPT_HEADER.last_usable_lba); + std::print("MAX_GPT_ENTRIES = {:02}", DISK.GPT_HEADER.num_partition_entries); + std::print("GPT_ENTRY_SIZE = {:02} BYTES", DISK.GPT_HEADER.size_of_partition_entry); + std::print("GPT_ARRAY_CRC = 0x{:02X}", DISK.GPT_HEADER.partition_entries_crc32); + + // GPT Partitions + for (u32 j = 0, j < GPT_Count, j = j + 1) { + std::print("-----------------------------------------"); + std::print("------------- GPT_PART [{}] --------------", j); + std::print("-----------------------------------------"); + std::print(" PART_TYPE_LABEL = {}", DISK.GPT_ENTRIES[j].Type.PartTypeLabel); + std::print(" PART_TYPE_GUID = {}", DISK.GPT_ENTRIES[j].Type.PartTypeGUID); + std::print(" UNIQUE_PART_GUID = {}", DISK.GPT_ENTRIES[j].Unique_GUID); + std::print(" FIRST_LBA = {:02}", DISK.GPT_ENTRIES[j].Start_LBA); + std::print(" LAST_LBA = {:02}", DISK.GPT_ENTRIES[j].End_LBA); + + bool _any = false; + std::print(" ATTR_FLAGS |"); + if(DISK.GPT_ENTRIES[j].ATTR.platform_required) { + std::print(" |- - - - > PlatformRequired"); + _any = true; + } + if(DISK.GPT_ENTRIES[j].ATTR.io_ignore) { + std::print(" |- - - - > NO_FS_MAP"); + _any = true; + } + if(DISK.GPT_ENTRIES[j].ATTR.legacy_flag) { + std::print(" |- - - - > LEGACY_BOOT"); + _any = true; + } + if(DISK.GPT_ENTRIES[j].Type.PartTypeLabel == GUIDPartLabel::BASIC_DATA_PART) { + if(DISK.GPT_ENTRIES[j].ATTR.read_only) { + std::print(" |- - - - > READ_ONLY"); + _any = true; + } + if(DISK.GPT_ENTRIES[j].ATTR.shadow_copy) { + std::print(" |- - - - > SHADOW_COPY"); + _any = true; + } + if(DISK.GPT_ENTRIES[j].ATTR.hidden) { + std::print(" |- - - - > HIDDEN"); + _any = true; + } + if(DISK.GPT_ENTRIES[j].ATTR.no_drive_letter) { + std::print(" |- - - - > NO_AUTO_MOUNT"); + _any = true; + } + } + // if nothing was printed, say "NONE" + if (!_any) { + //std::print(" |> NONE"); + std::print(" |- - - - > NONE"); + } + std::print(" PART_TYPE_NAME = {}", DISK.GPT_ENTRIES[j].PartName); + } + } + std::print("-----------------------------------------"); + std::print("------------------ END ------------------"); + std::print("-----------------------------------------"); + std::print(" "); +} diff --git a/patterns/DFIR/FAT32.hexpat b/patterns/DFIR/FAT32.hexpat new file mode 100644 index 00000000..c83d672b --- /dev/null +++ b/patterns/DFIR/FAT32.hexpat @@ -0,0 +1,788 @@ +#pragma author Formula Zero One Technologies +#pragma description FAT32 File System (FAT32_v2.0) +#pragma MIME application/x-ima +#pragma endian little + +// ----------------------------------------------------------------------------- +// CREDIT +// ----------------------------------------------------------------------------- +// OG AUTHOR: WerWolv +// OG DESC: fs/fat32.hexpat_v1.0 +// ----------------------------------------------------------------------------- +// NOTES FOR v2.0 ** GLOBALS NEED YOUR INPUT ** +// ----------------------------------------------------------------------------- +// Imported by DISK_PARSER.hexpat +// Added section separators for organization +// Added recursive parsing for Root Dir and a next level +// Added D/T conversions +// Show filenames on hover +// Added comments to DFIR fields of interest +// Changed pattern output naming/structure. +// Parse FAT1/FAT2 +// Show SFN <-> Starting Cluster Relation Overlay +// ----------------------------------------------------------------------------- +// TODO +// ----------------------------------------------------------------------------- +// Parse all SFN/LFN entries, not just Root + 1 +// ----------------------------------------------------------------------------- +// IMPORTS +// ----------------------------------------------------------------------------- +import std.core; +import std.io; +import std.mem; +import std.time; +import std.string; +import type.time; + +// ----------------------------------------------------------------------------- +// FORWARD DECS/GLOBALS +// ----------------------------------------------------------------------------- +// *** ATTENTION *** +// SET MAXIMUM NUMBER OF 4 BYTE CHUNKS TO PARSE FROM FAT1 +// DEFAULT IS 4096 +// Choose a value greater than 1 and less than 65536 OR increase the Array size limit with "#define... " + +// -------**************---vvvv--- | +const u64 MAX_FAT_CHUNKS = 4096; +// -------**************---^^^^--- | + +// *** ATTENTION *** +// SET MAXIMUM NUMBER OF SFN = STARTING CLUSTER TO PROCESS +// DEFAULT IS 100 (2 LEVELS DEEP | ROOT DIR + 1) +// Choose a value greater than 1 and less than 65536 OR increase the Array size limit with "#define... " + +// ---**************---************---vvv--- | +const u64 MAX_SFN_CLUSTER_RELATIONS = 100; +// ---**************---************---^^^--- | + +// ---*******---*******----vvvv--- | +const bool VOLUME_REPORT = true; +// ---*******---*******----^^^^--- | + +u64 bytesPerCluster = 0; +u64 rootDirSectors = 0; +u64 firstDataSector = 0; +u64 dataRegionStart = 0; +u64 sfn_count = 0; +u64 sfn_del_count = 0; +u64 lfn_count = 0; +u64 lfn_del_count = 0; +u64 start_index = 0; +u64 root_dir_start = 0; +u64 allocated_file_count = 0; + +u64 VBR_OFFSET = 0; +u64 FAT1_start_offset = 0; +u64 FAT2_start_offset = 0; +u64 FAT_ClusterHeap_Count = 0; + +u64 abs_FAT1_start_offset = 0; +u64 abs_FAT2_start_offset = 0; +u64 abs_rootDirStart_offset = 0; + +// ----------------------------------------------------------------------------- +// FILE ALLOCATION TABLE RELATED +// ----------------------------------------------------------------------------- +// V V V V V V V V V V +// ----------------------------------------------------------------------------- + +// ------------------------------ +// SFN <-> CLUSTER OVERLAY +// ------------------------------ +struct INFO_Overlay { + u64 index = std::core::array_index(); + u64 start_location = FAT1_start_offset + 8 + (index * 4); + u32 current_cluster = 2 + index; + str filename = overlay_func_name(current_cluster); + + if (filename != "") { + char hover_label[4] @ start_location [[ + name(std::format( + "SFN: {} | CLUSTER {}", + filename, + current_cluster + ))]]; + } +} [[inline]]; + +fn overlay_func_name(u32 cluster_num) { + str fname = ""; + str ext = ""; + str combo = ""; + + // Loop through all ROOT_DIR_ENTRIES + for (u32 i = 0, i < std::core::member_count(ROOT_DIR_ENTRIES), i = i + 1) { + + // Check SFN_ALIAS and SFN_ENTRY in root entries + if (std::core::has_member(ROOT_DIR_ENTRIES[i], "SFN_ALIAS")) { + if (ROOT_DIR_ENTRIES[i].SFN_ALIAS.first_cluster == cluster_num) { + combo = std::format("{}.{}", + ROOT_DIR_ENTRIES[i].SFN_ALIAS.fileName, + ROOT_DIR_ENTRIES[i].SFN_ALIAS.extension); + return combo; + } + } else if (std::core::has_member(ROOT_DIR_ENTRIES[i], "SFN_ENTRY")) { + if (ROOT_DIR_ENTRIES[i].SFN_ENTRY.first_cluster == cluster_num) { + combo = std::format("{}.{}", + ROOT_DIR_ENTRIES[i].SFN_ENTRY.fileName, + ROOT_DIR_ENTRIES[i].SFN_ENTRY.extension); + return combo; + } + } + + // Loop through all SUB_DIR_INDEX arrays for this root entry + if (std::core::has_member(ROOT_DIR_ENTRIES[i], "SUB_DIR_INDEX")) { + for (u32 j = 0, j < std::core::member_count(ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX), j = j + 1) { + + if (std::core::has_member(ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j], "SFN_ALIAS")) { + if (ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ALIAS.first_cluster == cluster_num) { + combo = std::format("{}.{}", + ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ALIAS.fileName, + ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ALIAS.extension); + return combo; + } + + } else if (std::core::has_member(ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j], "SFN_ENTRY")) { + if (ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ENTRY.first_cluster == cluster_num) { + combo = std::format("{}.{}", + ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ENTRY.fileName, + ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ENTRY.extension); + return combo; + } + } + } + } + } + return ""; // no match found +}; + +// ----------------------------------------------------------------------------- +// FAT32 FILE ALLOCATION TABLE (FAT) PARSER +// ----------------------------------------------------------------------------- + +const u32 CLUSTER_SIZE_BYTES = 4; // Each FAT32 entry = 4 bytes +const u32 FAT32_EOF = 0x0FFFFFFF; // End-of-file marker +const u32 FAT32_BAD = 0x0FFFFFF7; // Bad cluster marker +const u32 FIRST_ALLOC_CLUSTER = 2; // First usable cluster after reserved + +enum FAT_Flags : u32 { + UNALLOCATED = 0x00000000, + END_OF_FILE = 0x0FFFFFFF, // L.END + BAD_CLUSTER = 0xFFFFFFF7, // L.END + //POINTER = Num >= 1 // INFO +}; + +union FAT_Union { + u32 DECIMAL [[hidden]]; + FAT_Flags FAT_FLAG; +}; + +// ------------------------------ +// Helper function for pointer label +// ------------------------------ +fn cluster_label(u32 val) { + if (val == FAT_Flags::UNALLOCATED) + return "UNALLOCATED"; + if (val == FAT_Flags::BAD_CLUSTER) + return "BAD"; + if (val >= 0x0FFFFFF8) + return "EOF"; + return std::format("{}", val); +}; + +// ------------------------------ +// FAT1/FAT2 HEAPS/CHAINS +// ------------------------------ +struct FAT_Entry { + FAT_Union FAT [[inline]]; + + u32 cluster_num = (FIRST_ALLOC_CLUSTER) + (std::core::array_index()); + + u32 next_cluster = FAT.DECIMAL & 0x0FFFFFFF; + + char hover_label[4] @ $ - 4 [[ + name(std::format( + "Cluster: {} → {}", + cluster_num, + cluster_label(next_cluster) + )) + ]]; + + bool is_eof = next_cluster >= 0x0FFFFFF8; + bool is_bad = next_cluster == FAT32_BAD; + bool is_free = next_cluster == 0; + + if (is_eof) { + allocated_file_count += 1; + } +} [[name(format_fat_entry(FAT.DECIMAL, std::core::array_index(), FIRST_ALLOC_CLUSTER))]]; + +// ------------------------------ +// FAT FORMATTER FUNC +// ------------------------------ +fn format_fat_entry(u32 raw_value, u32 cluster_index, u32 first_alloc_cluster) { + u32 next_cluster = raw_value & 0x0FFFFFFF; + + str next_label; + + if (next_cluster == 0) + next_label = "UNALLOCATED"; + + else if (next_cluster == FAT32_BAD) + next_label = "BAD"; + + else if (next_cluster == 0x0FFFFFFF) + next_label = "EOF"; + + else + next_label = std::format("{}", next_cluster); + + u32 logical_cluster = first_alloc_cluster + cluster_index; + + if (next_label == "UNALLOCATED" || next_label == "BAD" || next_label == "EOF") + return std::format("Cluster {}: {}", logical_cluster, next_label); + else + return std::format("Cluster {} → {}", logical_cluster, next_label); +}; + +// ------------------------------ +// MEDIA DESCRIPTOR HELPER +// ------------------------------ +enum Media_Descriptor : u8 { + SINGLE_SIDE_FLOPPY = 0xF0, + DOUBLE_SIDE_FLOPPY = 0xF9, + HARD_DISK_DRIVE = 0xF8, +}; + +// ------------------------------ +// FAT1/FAT2 HEADER PARSER +// ------------------------------ +struct FAT_Header { + Media_Descriptor mediaDescriptor [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]];; + u8 FAT32_FAT_HEADER[7] [[comment("8 BYTES TOTAL: 4 BYTES REPRESENT PSUEDO CLUSTER 0 (SYSTEM) | 4 BYTES REPRESENT PSUEDO CLUSTER 1 (SYSTEM)(EOF)")]]; + char root_dir_label[4] @ $ [[ + name(std::format( + "ROOT_DIRECTORY" + )) + ]]; + // WHICH IS WHY THE ROOT DIRECTORY (FIRST DATA AREA ITEM) STARTS IN CLUSTER 2 +}; + +// ----------------------------------------------------------------------------- +// ROOT DIRECTORY RELATED +// ----------------------------------------------------------------------------- +// V V V V V V V V V V +// ----------------------------------------------------------------------------- +// ------------------------------ +// ACTIVE LFN SEQUENCE NUMBER BITFIELD +// * EXCEPT DELETED ENTRIES - 0xE5 * +// ------------------------------ +bitfield LFN_Sequence { + padding : 1; + IS_LAST_ENTRY : 1 [[name("IS_LAST_ENTRY: [0=NO | 1=YES] ==")]]; + padding : 1; + LFN_SEQ_NUM : 5; +} [[bitfield_order( + std::core::BitfieldOrder::MostToLeastSignificant, 8)]]; + +// ------------------------------ +// DIRECTORY ENTRY STATUS/SEQUENCE HELPERS +// ------------------------------ +enum Entry_Status : u8 { + EMPTY_ENTRY = 0x00, + DOT_ENTRY = 0x2E, + DELETED_ENTRY = 0xE5, + + ACTIVE_1ST_ENTRY = 0x01, + ACTIVE_2ND_ENTRY = 0x02, + ACTIVE_3RD_ENTRY = 0x03, + ACTIVE_4TH_ENTRY = 0x04, + ACTIVE_5TH_ENTRY = 0x05, + ACTIVE_6TH_ENTRY = 0x06, + ACTIVE_7TH_ENTRY = 0x07, + ACTIVE_8TH_ENTRY = 0x08, + ACTIVE_9TH_ENTRY = 0x09, + ACTIVE_10TH_ENTRY = 0x0A, + ACTIVE_11TH_ENTRY = 0x0B, + ACTIVE_12TH_ENTRY = 0x0C, + ACTIVE_13TH_ENTRY = 0x0D, + ACTIVE_14TH_ENTRY = 0x0E, + ACTIVE_15TH_ENTRY = 0x0F, + ACTIVE_16TH_ENTRY = 0x10, + ACTIVE_17TH_ENTRY = 0x11, + ACTIVE_18TH_ENTRY = 0x12, + ACTIVE_19TH_ENTRY = 0x13, + ACTIVE_20TH_ENTRY = 0x14, + ACTIVE_1ST_ENTRY_LAST = 0x41, + ACTIVE_2ND_ENTRY_LAST = 0x42, + ACTIVE_3RD_ENTRY_LAST = 0x43, + ACTIVE_4TH_ENTRY_LAST = 0x44, + ACTIVE_5TH_ENTRY_LAST = 0x45, + ACTIVE_6TH_ENTRY_LAST = 0x46, + ACTIVE_7TH_ENTRY_LAST = 0x47, + ACTIVE_8TH_ENTRY_LAST = 0x48, + ACTIVE_9TH_ENTRY_LAST = 0x49, + ACTIVE_10TH_ENTRY_LAST = 0x4A, + ACTIVE_11TH_ENTRY_LAST = 0x4B, + ACTIVE_12TH_ENTRY_LAST = 0x4C, + ACTIVE_13TH_ENTRY_LAST = 0x4D, + ACTIVE_14TH_ENTRY_LAST = 0x4E, + ACTIVE_15TH_ENTRY_LAST = 0x4F, + ACTIVE_16TH_ENTRY_LAST = 0x50, + ACTIVE_17TH_ENTRY_LAST = 0x51, + ACTIVE_18TH_ENTRY_LAST = 0x52, + ACTIVE_19TH_ENTRY_LAST = 0x53, + ACTIVE_20TH_ENTRY_LAST = 0x54, +}; + +// ------------------------------ +// HELPER FOR LFN FIRST BYTE +// ------------------------------ +union LFNEntry_FirstByte { + Entry_Status status; + LFN_Sequence seq_num; +}; + +// ------------------------------ +// SFN ATTRIBUTE HELPER +// ------------------------------ +bitfield Attributes { + readOnly : 1; + hidden : 1; + systemFile : 1; + volumeLabel : 1; + subDirectory : 1; + archive : 1; + padding : 2; +} [[bitfield_order( + std::core::BitfieldOrder::LeastToMostSignificant, 8)]]; + +// ------------------------------ +// ROOT DIRECTORY ENTRY FUNC +// ------------------------------ +fn dir_entry_marker(u64 abs_off) { + u8 first @ abs_off; + return first; +}; + +// ------------------------------ +// ROOT DIRECTORY ENTRY FUNC +// ------------------------------ +fn dir_entry_attr(u64 abs_off) { + u8 attr @ abs_off + 0x0B; + return attr; +}; + +// ------------------------------ +// DATES AND TIMES FUNC +// ------------------------------ +fn format_dos_time_field(std::time::DOSTime t) { + return std::time::format_dos_time(t, "{:02}:{:02}:{:02}"); +}; + +fn format_dos_date_field(std::time::DOSDate d) { + return std::time::format_dos_date(d, "{1:02}-{0:02}-{2:04}"); +}; + +// ------------------------------ +// SHORT FILE NAME ALIAS PARSER +// ------------------------------ +struct SFN_Entry_Alias { + char fileName[8] [[name("SFN"), comment("Short File Name (8dot3)")]]; + char extension[3] [[name("EXT"), comment("File Extension (8dot3)")]]; + Attributes attributes [[name("RASH ATTR"), comment("Read-Only | Archive | System | Hidden | SubDir...")]]; + u8 reserved [[comment("Zeros")]]; + u8 milliseconds [[comment("Add to Times for Refinement")]]; + std::time::DOSTime Created_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Created_Date [[format("format_dos_date_field")]]; + std::time::DOSDate Accessed_Date [[format("format_dos_date_field")]]; + u16 Cluster_Hi [[comment("High Cluster if Needed")]]; + std::time::DOSTime Modified_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Modified_Date [[format("format_dos_date_field")]]; + u16 Cluster_Lo [[comment("Starting Cluster or Combine with Cluster_Hi")]]; + u32 fileSize [[name("FILE_SIZE"), comment("File Size in Bytes")]]; + + u32 first_cluster = (Cluster_Hi << 16) | Cluster_Lo; + + u8 FILE_DATA[fileSize] @ dataRegionStart + (first_cluster -2) * bytesPerCluster [[comment("Pointer to the Files Content")]]; + + sfn_count += 1; + + if (fileName[0] == 0xE5) { + sfn_del_count += 1; + } +}; + +// ------------------------------ +// LOOOONG FILE NAME PARSER +// ------------------------------ +struct LFN_Entry { + u64 curr_first_byte = $; + u8 curr_attr = dir_entry_attr(curr_first_byte); + + LFNEntry_FirstByte SeqByte [[name("SEQUENCE_NUM"), comment("0x01-0x20 | Add 0x40 to Last LFN Entry")]]; + char16 NAME_1[5] [[comment("First 5 Characters of LFN")]]; + Attributes attributes [[name("LFN_ATTR"), comment("0x0F = LFN")]]; + padding[1] [[comment("Zeros")]]; + u8 nameChecksum [[name("Checksum"), comment("Checksum Calculated on SFN_ALIAS")]]; + char16 NAME_2[6] [[comment("Next 6 Characters of LFN")]]; + padding[2] [[comment("Zeros")]]; + char16 NAME_3[2] [[comment("Next 2 Characters of LFN")]]; + + // ATTEMPT TO CLEANUP UNICODE LFN... DOES NOT ACCOUNT FOR MULTI LFN ENTRIES + if (curr_attr == 0x0F) { + char display_name[32] @ $ - 32 [[ + name( + (NAME_1[0] >= 0x20 && NAME_1[0] <= 0x7E ? std::string::to_string(NAME_1[0]) : "") + + (NAME_1[1] >= 0x20 && NAME_1[1] <= 0x7E ? std::string::to_string(NAME_1[1]) : "") + + (NAME_1[2] >= 0x20 && NAME_1[2] <= 0x7E ? std::string::to_string(NAME_1[2]) : "") + + (NAME_1[3] >= 0x20 && NAME_1[3] <= 0x7E ? std::string::to_string(NAME_1[3]) : "") + + (NAME_1[4] >= 0x20 && NAME_1[4] <= 0x7E ? std::string::to_string(NAME_1[4]) : "") + + + (NAME_2[0] >= 0x20 && NAME_2[0] <= 0x7E ? std::string::to_string(NAME_2[0]) : "") + + (NAME_2[1] >= 0x20 && NAME_2[1] <= 0x7E ? std::string::to_string(NAME_2[1]) : "") + + (NAME_2[2] >= 0x20 && NAME_2[2] <= 0x7E ? std::string::to_string(NAME_2[2]) : "") + + (NAME_2[3] >= 0x20 && NAME_2[3] <= 0x7E ? std::string::to_string(NAME_2[3]) : "") + + (NAME_2[4] >= 0x20 && NAME_2[4] <= 0x7E ? std::string::to_string(NAME_2[4]) : "") + + (NAME_2[5] >= 0x20 && NAME_2[5] <= 0x7E ? std::string::to_string(NAME_2[5]) : "") + + + (NAME_3[0] >= 0x20 && NAME_3[0] <= 0x7E ? std::string::to_string(NAME_3[0]) : "") + + (NAME_3[1] >= 0x20 && NAME_3[1] <= 0x7E ? std::string::to_string(NAME_3[1]) : "") + ) + ]]; + } + lfn_count += 1; + + if (SeqByte.status == Entry_Status::DELETED_ENTRY) { + lfn_del_count += 1; + } +}; + +// ------------------------------ +// SHORT FILE NAME PARSER +// ------------------------------ +struct SFN_Entry { + char fileName[8] [[name("SFN"), comment("Short File Name (8dot3)")]]; + char extension[3] [[name("EXT"), comment("File Extension (8dot3)")]]; + Attributes attributes [[name("RASH ATTR"), comment("Read-Only | Archive | System | Hidden | SubDir...")]]; + u8 reserved [[comment("Zeros")]]; + u8 milliseconds [[comment("Add to Times for Refinement")]]; + std::time::DOSTime Created_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Created_Date [[format("format_dos_date_field")]]; + std::time::DOSDate Accessed_Date [[format("format_dos_date_field")]]; + u16 Cluster_Hi [[comment("High Cluster if Needed")]]; + std::time::DOSTime Modified_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Modified_Date [[format("format_dos_date_field")]]; + u16 Cluster_Lo [[comment("Starting Cluster or Combine with Cluster_Hi")]]; + u32 fileSize [[name("FILE_SIZE"), comment("File Size in Bytes")]]; + + u32 first_cluster = (Cluster_Hi << 16) | Cluster_Lo; + + u8 FILE_DATA[fileSize] @ dataRegionStart + (first_cluster -2) * bytesPerCluster [[comment("Pointer to the File Content")]]; + + sfn_count += 1; + + if (fileName[0] == 0xE5) { + sfn_del_count += 1; + } +}; + +// ------------------------------ +// SUBDIRECTORY PARSER | LEVEL 2 +// ------------------------------ +struct SubDirParser { + u8 first = dir_entry_marker($); + u8 attr = dir_entry_attr($); + + u64 next_first_byte = $ + 32; + u8 next_attr = dir_entry_attr(next_first_byte); // current offset plus 12 bytes (offset 0x0B of entry) + + if (first != 0x00 && attr == 0x0F) { + LFN_Entry LFN_ENTRY; + + if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr == 0x0F) { + LFN_Entry next_LFN_ENTRY; + SFN_Entry_Alias SFN_ALIAS; + } + + } else if (first != 0x00 && attr != 0x0F) { + SFN_Entry SFN_ENTRY; + } +}; + +// ------------------------------ +// ROOT DIRECTORY ENTRY PARSER +// ROUGH METHOD OF PARSING SFN/LFN/SFN_ALIAS/SUBDIR TWO LEVELS DEEP +// IF THE PATTERN CRASHES - THIS IS LIKELY WHY +// ------------------------------ +struct RootDirParser { + u64 curr_first_byte = $; + u8 curr_attr = dir_entry_attr(curr_first_byte); // current offset plus 12 bytes (offset 0x0B of entry) + + u64 next_first_byte = $ + 32; + u8 next_attr = dir_entry_attr(next_first_byte); // current offset plus 12 bytes (offset 0x0B of entry) + + bool is_subdir = false; + + if (curr_first_byte != 0x00 && curr_first_byte != 0xE5 && curr_attr == 0x0F) { + LFN_Entry LFN_ENTRY; + + if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr == 0x0F) { + LFN_Entry next_LFN_ENTRY; + SFN_Entry_Alias SFN_ALIAS; + + is_subdir = SFN_ALIAS.attributes.subDirectory; + + if (SFN_ALIAS.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) { + is_subdir = SFN_ALIAS.attributes.subDirectory; + + u64 dir_start_addr = dataRegionStart + (SFN_ALIAS.first_cluster - 2) * bytesPerCluster; + SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr; + } + } + + if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr != 0x0F) { + SFN_Entry_Alias SFN_ALIAS; + + is_subdir = SFN_ALIAS.attributes.subDirectory; + + if (SFN_ALIAS.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) { + is_subdir = SFN_ALIAS.attributes.subDirectory; + + u64 dir_start_addr = dataRegionStart + (SFN_ALIAS.first_cluster - 2) * bytesPerCluster; + SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr; + } + + } + + } else if (curr_first_byte != 0x00 && curr_first_byte != 0xE5 && curr_attr != 0x0F) { + SFN_Entry SFN_ENTRY; + is_subdir = SFN_ENTRY.attributes.subDirectory; + + if (SFN_ENTRY.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) { + is_subdir = SFN_ENTRY.attributes.subDirectory; + + u64 dir_start_addr = dataRegionStart + (SFN_ENTRY.first_cluster - 2) * bytesPerCluster; + SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr; + } + + } else if (curr_first_byte != 0x00 && current_first_byte == 0xE5) { + + if (next_first_byte != 0x00 && next_attr == 0x0F) { + LFN_Entry LFN_ENTRY; + + if (next_first_byte != 0x00 && next_first_byte != 0xE5) { + + if (next_attr != 0x0F) { + SFN_Entry_Alias SFN_ALIAS; + + is_subdir = SFN_ALIAS.attributes.subDirectory; + + if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr != 0x0F) { + SFN_Entry_Alias SFN_ALIAS2; // otherwise switch to SFN + + is_subdir = SFN_ALIAS.attributes.subDirectory; + + if (SFN_ALIAS.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) { + is_subdir = SFN_ALIAS.attributes.subDirectory; + + u64 dir_start_addr = dataRegionStart + (SFN_ALIAS.first_cluster - 2) * bytesPerCluster; + SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr; + } + } + } + } + } else { + SFN_Entry SFN_ENTRY; + is_subdir = SFN_ENTRY.attributes.subDirectory; + } + } +} [[name(format_element($, start_index, is_subdir)), comment("FILE/DIR [INDX #]")]]; + +// ------------------------------ +// NAME FORMATTER +// ------------------------------ +fn format_element(auto v, u64 offset, bool subdir) { + if (subdir) { + return std::format("SUB_DIR [{:02}]", std::core::array_index() + offset); + } else { + return std::format("FILE [{:02}]", std::core::array_index() + offset); + } +}; + +// ------------------------------ +// ROOT DIRECTORY HEADER PARSER +// ------------------------------ +struct RootDirHeader { + char VolumeName[11] [[comment("User Defined Name of the VOL")]]; + u8 VolumeLabelFlag [[comment("Indicates the Preceding VOL LABEL")]]; + padding[10] [[comment("Zeros")]]; + std::time::DOSTime Created_Time [[format("format_dos_time_field"), comment("Last Write Time - Typically when Created/Formatted, but NOT ALWAYS...(DISK IMAGE/FAT DRIVERS)")]]; + std::time::DOSDate Created_Date [[format("format_dos_date_field"), comment("Last Write Date - Typically when Created/Formatted, but NOT ALWAYS...(DISK IMAGE/FAT DRIVERS)")]]; + padding[6] [[comment("Zeros")]]; +}; + +// ------------------------------ +// VBR SIGNATURE HELPER +// ------------------------------ +enum VBRSignature : u16 { + VBR_SIG = 0xAA55 +}; + +// ------------------------------ +// FILE SYSTEM INFO BLOCK +// ------------------------------ +struct FSInfo { + u32 leadSignature [[comment("RRaA")]]; + padding[480] [[comment("Zeros")]]; + u32 structSignature [[comment("FSINFO Signature")]]; + u32 freeClusterCount [[comment("Approximate Free Cluster Count")]]; + u32 nextFreeCluster [[comment("FAT1: Suggested Starting Point")]]; + padding[14] [[comment("Zeros")]]; + VBRSignature VBR_SIG [[comment("0x55AA")]]; +}; + +// ------------------------------ +// FAT12/16/32 BIOS PARAMETER BLOCK (BPB) +// ------------------------------ +struct BPB_Common { + u8 jmp_boot[3] [[comment("Assembly Instructions to Jump to Boot Code")]]; + char oem_name[8] [[comment("MSDOS/BSD")]]; + u16 bytes_per_sector [[comment("512,1024,2048,4096")]]; + u8 sectors_per_cluster [[comment("Under 32K - Must be a power of 2")]]; + u16 reserved_sectors [[comment("Size of Reserved Area in Sectors")]]; + u8 num_fats [[comment("Typically 2, but can be 1 for Small Volumes")]]; + u16 root_entry_count [[comment("Max Num of Entries -- 0 for FAT32| 512 for FAT16")]]; + u16 total_sectors16 [[comment("if 0, use total_sectors32")]]; + u8 media_type [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]]; + u16 fat_size16 [[comment("Size of each FAT in Sectors for FAT12/16; 0 for FAT32")]]; + u16 sectors_per_track [[comment("Legacy")]]; + u16 num_heads [[comment("Legacy")]]; + u32 hidden_sectors [[comment("Num of Sectors before the Volume")]]; + u32 total_sectors32 [[comment("32bit Value of Total Num of Sectors in Volume")]]; +// ----------------------vvv----- +// FAT32 EXTENDED +// ----------------------vvv----- + u32 FAT_Sector_Count [[comment("Total Sectors per FAT")]]; + u16 ext_flags [[comment("16bit Value: BIT_7 = 1 == 1 FAT USED | Otherwise both FATs USED")]]; + u16 fs_version [[comment("Major and Minor | None")]]; + u32 root_cluster [[comment("Cluster Num of Root Dir")]]; + u16 fs_info_sector [[comment("FS_INFO Location")]]; + u16 backup_boot_sector [[comment("VBR Backup Location")]]; + u8 reserved[12] [[comment("Zeros")]]; + u8 drive_number [[comment("BIOS INT13h Drive Num")]]; + u8 reserved1 [[comment("Zeros")]]; + u8 boot_signature [[comment("Extended Boot Sig = 0x29")]]; + u32 volume_id [[comment("Volume Serial Number - Based on Created Date/Time")]]; + char volume_label[11] [[comment("No Name | User Defined Name | Check Root Dir")]]; + char fs_type[8] [[comment("FAT32 ")]]; + u8 bootstrap[420] [[comment("Until Signature")]]; + VBRSignature VBR_SIG [[comment("0x55AA")]]; +// ----------------------vvv----- +// UPDATE CONSTANTS/GLOBALS +// ----------------------vvv----- + bytesPerCluster = sectors_per_cluster * bytes_per_sector; + rootDirSectors = ((root_entry_count * 32) + (bytes_per_sector - 1)) / bytes_per_sector; + firstDataSector = reserved_sectors + (num_fats * FAT_Sector_Count) + rootDirSectors; + dataRegionStart = firstDataSector * bytes_per_sector; +}; + +// ----------------------------------------------------------------------------- +// FAT32 MAIN RELATED +// ----------------------------------------------------------------------------- +// V V V V V V V V V V +// ----------------------------------------------------------------------------- +// ------------------------------ +// FAT32 VOLUME BOOT RECORD +// ------------------------------ +BPB_Common F32_VBR @ $; +VBR_OFFSET = F32_VBR.hidden_sectors * F32_VBR.bytes_per_sector; + +/// ------------------------------ +// FILE SYSTEM INFO BLOCK +// ------------------------------ +FSInfo FS_INFO @ F32_VBR.fs_info_sector * F32_VBR.bytes_per_sector; +root_dir_start = dataRegionStart + ((F32_VBR.root_cluster - 2) * bytesPerCluster) + 32; + +// ------------------------------ +// FILE ALLOCATION TABLE +// *** HAS GLOBAL AT TOP *** +// ------------------------------ +FAT1_start_offset = F32_VBR.reserved_sectors * F32_VBR.bytes_per_sector; +FAT2_start_offset = FAT1_start_offset + (F32_VBR.FAT_Sector_Count * F32_VBR.bytes_per_sector); +FAT_ClusterHeap_Count = F32_VBR.FAT_Sector_Count * F32_VBR.bytes_per_sector / CLUSTER_SIZE_BYTES; + +FAT_Header FAT1_HEADER @ FAT1_start_offset; +FAT_Entry FAT1[MAX_FAT_CHUNKS] @ FAT1_start_offset + 8; + +FAT_Header FAT2_HEADER @ FAT2_start_offset; +FAT_Entry FAT2[MAX_FAT_CHUNKS] @ FAT2_start_offset + 8; + +// ------------------------------ +// ROOT DIRECTORY HEADER +// ------------------------------ +RootDirHeader ROOT_DIR_HEADER @ dataRegionStart + ((F32_VBR.root_cluster - 2) * bytesPerCluster); + +// ----*-----*------*------*----- +// * * ROOT DIRECTORY PARSER * * +// ----*-----*------*------*----- +RootDirParser ROOT_DIR_ENTRIES[while(std::mem::read_unsigned($, 1) != 0x00)] @ root_dir_start; + +// ------------------------------ +// SFN <-> CLUSTER RELATION OVERLAY +// *** HAS GLOBAL AT TOP *** +// ------------------------------ +INFO_Overlay SFN_CLUSTER_LIST[MAX_SFN_CLUSTER_RELATIONS] @ FAT1_start_offset [[name("SFN <-> CLUSTER (DERIVED)")]]; + +// ------------------------------ +// FAT32 VOLUME REPORT +// *** HAS GLOBAL AT TOP *** +// ------------------------------ +abs_FAT1_start_offset = VBR_OFFSET + (F32_VBR.reserved_sectors * F32_VBR.bytes_per_sector); +abs_FAT2_start_offset = abs_FAT1_start_offset + (F32_VBR.FAT_Sector_Count * F32_VBR.bytes_per_sector); +abs_rootDirStart_offset = VBR_OFFSET + dataRegionStart; + +if (VOLUME_REPORT) { + std::print(" "); + std::print("-----------------------------------------"); + std::print("---------- FAT32 VOLUME_REPORT ----------"); + std::print("-----------------------------------------"); + std::print("VOL_LABEL = {}", F32_VBR.volume_label); + std::print("FILE_SYSTEM = {}", F32_VBR.fs_type); + std::print("SERIAL_NUMBER = 0x{:X}", F32_VBR.volume_id); + + std::print("-----------------------------------------"); + std::print("BYTES/SECTOR = {:02}", F32_VBR.bytes_per_sector); + std::print("SECTORS/CLUSTER = {:02}", F32_VBR.sectors_per_cluster); + std::print("BYTES/CLUSTER = {:02}", bytesPerCluster); + std::print("ROOT_ENTRIES = {:02}", F32_VBR.root_entry_count); + std::print("CLUSTER_COUNT = {:02}", (F32_VBR.total_sectors32 - firstDataSector) / F32_VBR.sectors_per_cluster); + + std::print("-----------------------------------------"); + std::print("VOLUME_SIZE = {:02} SECTORS", F32_VBR.total_sectors32); + std::print("VOLUME_SIZE = {:.4f} GB @ 1000", (F32_VBR.total_sectors32 * F32_VBR.bytes_per_sector) / 1000.0 / 1000.0 / 1000.0); + std::print("VOLUME_SIZE = {:.4f} GiB @ 1024", (F32_VBR.total_sectors32 * F32_VBR.bytes_per_sector) / 1024.0 / 1024.0 / 1024.0); + + std::print("-----------------------------------------"); + std::print("RESERVED_SECTORS = {:02}", F32_VBR.reserved_sectors); + std::print("FAT_COUNT = {:02}", F32_VBR.num_fats); + std::print("FAT_SIZE = {:02} SECTORS", F32_VBR.FAT_Sector_Count); + std::print("FAT1_START_OFF = {} | 0x{:02X}", abs_FAT1_start_offset, abs_FAT1_start_offset); + std::print("FAT2_START_OFF = {} | 0x{:02X}", abs_FAT2_start_offset, abs_FAT2_start_offset); + std::print("ROOT_DIR_CLUSTER = {:02}", F32_VBR.root_cluster); + std::print("ROOT_DIR_OFFSET = {} | 0x{:02X}", abs_rootDirStart_offset, abs_rootDirStart_offset); + + std::print("-----------------------------------------"); + if (sfn_del_count >= 1) { + std::print("SFN_DEL(xE5) = DETECTED"); + } + if (lfn_del_count >= 1) { + std::print("LFN_DEL(xE5) = DETECTED"); + } + std::print("FAT1_EOF_COUNT = {:02}", allocated_file_count / 2); // divided by 2 (FAT1/FAT2) + + std::print("-----------------------------------------"); + std::print("------------------ END ------------------"); + std::print("-----------------------------------------"); + std::print(" "); +} diff --git a/patterns/DFIR/NTFS.hexpat b/patterns/DFIR/NTFS.hexpat new file mode 100644 index 00000000..7afee959 --- /dev/null +++ b/patterns/DFIR/NTFS.hexpat @@ -0,0 +1,1571 @@ +#pragma author MODIFIED BY: Formula Zero One Technologies +#pragma description NT File System (NTFS_v2.0) +#pragma endian little + +// ----------------------------------------------------------------------------- +// CREDIT +// ----------------------------------------------------------------------------- +// OG AUTHOR: Hrant Tadevosyan (Axcient, now ConnectWise) +// OG DESC: ntfs.hexpat_v1.0 +// So much work went into this pattern; NICE JOB!! +// ----------------------------------------------------------------------------- +// NOTES FOR v2.0 +// ----------------------------------------------------------------------------- +// Imported by DISK_PARSER.hexpat +// Added recursive parsing for all $MFT records +// Added D/T conversions +// Changed most ntfschar to char16 to show filenames on hover +// Added comments to DFIR fields of interest +// Changed pattern output naming/structure. Similar to RunTime's DiskExplorer -- FREntry > FRHeader > ATTRName > ATTRHeader > ATTRBody > ATTREnd +// Reconfigured x50 ATTR and ACL processing +// Modified RunList - File Content Pointer +// ----------------------------------------------------------------------------- +// NTFS MANTRAS FROM NW3C... +// ----------------------------------------------------------------------------- +// --- 1 --- EVERYTHING IN NTFS IS A FILE (RECORD) (INCLUDING DIRs) +// --- 2 --- EVERY FILE (& DIR) HAS AN ENTRY IN THE $MFT, INCLUDING THE $MFT ITSELF +// --- 3 --- EVERY FILE (RECORD) IS MADE UP OF ATTRIBUTES +// --------- FILES = x10/x30/x80 (minimum) +// --------- DIRs = x10/x30/x90 (minimum) +// ----------------------------------------------------------------------------- +// ------------------------------------------------------------------------- +// IMPORTS +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +import std.core; +import std.array; +import std.ptr; +import std.io; +import std.mem; +import std.string; +import std.time; +import type.magic; +import type.time; +import type.guid; +import type.base; + +// ------------------------------------------------------------------------- +// FWD DECs - GLOBALS +// ------------------------------------------------------------------------- + +// *** ATTENTION *** + +// ---*******---*******----vvvv--- | +const bool VOLUME_REPORT = true; +// ---*******---*******----^^^^--- | + +u64 VBR_OFFSET; +u64 prev_lcn = 0; +u32 MFT_Count; +u32 Unused_Count; +u8 NT_Major; +u8 NT_Minor; +type::Hex mft_num; +type::Hex seq_num; +str Vol_Label; +u8 PRENT_MFT; +u8 PRENT_SEQ; + +// ------------------------------------------------------------------------- +// GENERAL HELPERS RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +using ntfschar = u16; +using leVCN = u64; +using leLCN = u64; +using leLSN = u64; +using leMFT_REF = u64; + +bitfield RUNLIST_HEADER { + unsigned SE_Offset : 4; + unsigned Length : 4; +} [[bitfield_order(std::core::BitfieldOrder::MostToLeastSignificant, 8)]]; + +// ------------------------------------------------------------------------- +// RUNLIST +// ------------------------------------------------------------------------- +struct RUNLIST { + RUNLIST_HEADER RunList_Header + [[comment("Left Nibble = S.E. Bytes | Right Nibble = S.E. Length")]]; + + // Highlight the Stream Extent bytes + char run_extent[RunList_Header.SE_Offset + RunList_Header.Length] @ $ + [[name(std::format("Run_Extent: {} + {} = {} Bytes ", + RunList_Header.SE_Offset, + RunList_Header.Length, + RunList_Header.SE_Offset + RunList_Header.Length)), + comment("Start/Run Extent | Derived from Header")]]; + + // Get number of clusters as a string + str NoC_value = std::string::to_string( + std::mem::read_unsigned($, RunList_Header.Length)); + + // Highlight exactly RunList_Header.Length bytes + char Num_of_Clusters[RunList_Header.Length] + [[name(std::format("Num_of_Clusters = {} ", NoC_value)), + comment("Number of Clusters in a Run")]]; + + // Needed for mft_get_dat_attr_lcn function only + u8 Logical_Cluster_Num[RunList_Header.SE_Offset] [[hidden]]; + + if (RunList_Header.SE_Offset > 0) { + // Read run length (little-endian) + u64 num_clusters = std::mem::read_unsigned( + $ - (RunList_Header.SE_Offset + RunList_Header.Length), + RunList_Header.Length); + + // Read signed LCN delta + s64 lcn_delta = std::mem::read_signed( + $ - RunList_Header.SE_Offset, + RunList_Header.SE_Offset); + + u64 data_size = num_clusters * cluster_size; + u64 content_offset = VBR_OFFSET + (lcn_delta * cluster_size); + + // Show both decimal LCN and absolute byte offset in the label + char Start_LCN[RunList_Header.SE_Offset] @ $ - RunList_Header.SE_Offset + [[name(std::format("Start_LCN {} | Offset 0x{:X} ", + lcn_delta, + content_offset)), + comment("Starting Logical Cluster Number")]]; + + // Pointer to the file content + char MAGIC_BYTES[4] @ content_offset - VBR_OFFSET //u8 as needed... + [[comment("Pointer to Start Cluster"), static, sealed]]; + + // Select all allocated clusters for easy Section View + // May error - array grew past end of data.... + //u8 CLUSTER_CONTENT[data_size] @ content_offset - VBR_OFFSET + // [[comment("Pointer to Cluster Content"), static, sealed]]; + //if (MAGIC_BYTES == 0xFFD8FF) { + // IMPORT AND CALL jpeg.hexpat + //} + } + + // Look ahead at the next byte to detect another run + u8 next_byte @ $ [[hidden]]; + + if (next_byte != 0x00) { + RUNLIST Next_Run; // recursively parse next run + } +} [[inline]]; + +fn calc_lcn_str(u64 start_offset, u32 len) { + if (len == 0) + return "0"; + return std::mem::read_unsigned(start_offset, len); +}; + +// ------------------------------------------------------------------------- +// SIGNATURE HELPER +// ------------------------------------------------------------------------- +enum VBRSignature : u16 { + VBR_SIG = 0xAA55 +}; + +// ------------------------------------------------------------------------- +// WINDOWS FILETIME FUNC +// ------------------------------------------------------------------------- +fn parse_filetime(u64 raw_ft) { + // Convert raw FILETIME to Unix time + u64 unix_time = type::impl::format_filetime_as_unix(raw_ft); + + // Convert Unix time to structured UTC time + std::time::Time ts = std::time::to_utc(unix_time); + + // Format as string + str formatted = std::time::format(ts, "%Y-%m-%d %H:%M:%S"); + + return formatted; +}; + +// ------------------------------------------------------------------------- +// NTFS VOLUME BOOT RECORD RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +struct BIOS_PARAMETER_BLOCK { + u16 bytes_per_sector [[comment("Bytes per Sector")]]; + u8 sectors_per_cluster [[comment("Sectors per Cluster")]]; + u16 reserved_sectors [[comment("Reserved")]];; + u8 fats [[comment("Zeros")]];; + u16 root_entries [[comment("Unused")]]; + u16 sectors [[comment("Unused")]]; + u8 media_descriptor [[comment("Always F8-2003+")]]; + u16 sectors_per_fat [[comment("Legacy")]]; + u16 sectors_per_track [[comment("Legacy")]]; + u16 heads [[comment("Legacy")]]; + u32 hidden_sectors [[comment("Sectors before Volume")]]; + u32 large_sectors [[comment("Legacy")]]; +}; + +// ------------------------------------------------------------------------- +// FILE RECORD ENTRY RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +enum NTFS_RECORD_TYPES : u32 { + magic_FILE = 0x454c4946, + magic_INDX = 0x58444e49, + magic_HOLE = 0x454c4f48, + magic_RSTR = 0x52545352, + magic_RCRD = 0x44524352, + magic_CHKD = 0x444b4843, + magic_BAAD = 0x44414142, + magic_empty = 0xffffffff, +}; + +// For other records, such as Index +struct NTFS_RECORD { + NTFS_RECORD_TYPES magic [[comment("File Record Signature 'FILE'")]];; + u16 usa_ofs [[comment("Offset to Fix Up Array")]]; + u16 usa_count [[comment("# of 2 Byte Entries in FUA")]]; +}; + +enum MFT_RECORD_FLAGS : u16 { + MFT_RECORD_NOT_IN_USE = 0x0000, + MFT_RECORD_IN_USE = 0x0001, + MFT_RECORD_IS_DIRECTORY = 0x0002, + MFT_RECORD_IS_4 = 0x0004, + MFT_RECORD_IS_VIEW_INDEX = 0x0008, + MFT_REC_SPACE_FILLER = 0xffff, +}; + +// ------------------------------------------------------------------------- +// FILE RECORD ATTRIBUTE RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +enum ATTR_TYPES : u32 { + AT_UNUSED = 0x00, + AT_STANDARD_INFORMATION = 0x10, + AT_ATTRIBUTE_LIST = 0x20, + AT_FILE_NAME = 0x30, + AT_OBJECT_ID = 0x40, + AT_SECURITY_DESCRIPTOR = 0x50, + AT_VOLUME_NAME = 0x60, + AT_VOLUME_INFORMATION = 0x70, + AT_DATA = 0x80, + AT_INDEX_ROOT = 0x90, + AT_INDEX_ALLOCATION = 0xa0, + AT_BITMAP = 0xb0, + AT_REPARSE_POINT = 0xc0, + AT_EA_INFORMATION = 0xd0, + AT_EA = 0xe0, + AT_PROPERTY_SET = 0xf0, + AT_LOGGED_UTILITY_STREAM = 0x100, + AT_FIRST_USER_DEFINED_ATTRIBUTE = 0x1000, + AT_END = 0xffffffff, +}; + +enum ATTR_DEF_FLAGS : u32 { + ATTR_DEF_INDEXABLE = 0x02, + ATTR_DEF_MULTIPLE = 0x04, + ATTR_DEF_NOT_ZERO = 0x08, + ATTR_DEF_INDEXED_UNIQUE = 0x10, + ATTR_DEF_NAMED_UNIQUE = 0x20, + ATTR_DEF_RESIDENT = 0x40, + ATTR_DEF_ALWAYS_LOG = 0x80, +}; + +enum COLLATION_RULES : u32 { + COLLATION_BINARY = 0, + COLLATION_FILE_NAME = 1, + COLLATION_UNICODE_STRING = 2, + COLLATION_NTOFS_ULONG = 16, + COLLATION_NTOFS_SID = 17, + COLLATION_NTOFS_SECURITY_HASH = 18, + COLLATION_NTOFS_ULONGS = 19, +}; + +struct ATTR_DEF { + //ntfschar name[0x40]; + char16 name[0x40]; + ATTR_TYPES type; + u32 display_rule; + COLLATION_RULES collation_rule; + ATTR_DEF_FLAGS flags; + u64 min_size; + u64 max_size; +}; + +enum ATTR_FLAGS : u16 { + ATTR_IS_COMPRESSED = 0x0001, + ATTR_COMPRESSION_MASK = 0x00ff, + ATTR_IS_ENCRYPTED = 0x4000, + ATTR_IS_SPARSE = 0x8000, +}; + +enum RESIDENT_ATTR_FLAGS : u8 { + RESIDENT_ATTR_IS_INDEXED = 0x01, + RESIDENT_ATTR_NOT_INDEXED = 0x00, +}; + +enum FILE_ATTR_FLAGS : u32 { + FILE_ATTR_READONLY = 0x00000001, + FILE_ATTR_HIDDEN = 0x00000002, + FILE_ATTR_SYSTEM = 0x00000004, + FILE_ATTR_DIRECTORY = 0x00000010, + FILE_ATTR_ARCHIVE = 0x00000020, + FILE_ATTR_DEVICE = 0x00000040, + FILE_ATTR_NORMAL = 0x00000080, + FILE_ATTR_TEMPORARY = 0x00000100, + FILE_ATTR_SPARSE_FILE = 0x00000200, + FILE_ATTR_REPARSE_POINT = 0x00000400, + FILE_ATTR_COMPRESSED = 0x00000800, + FILE_ATTR_OFFLINE = 0x00001000, + FILE_ATTR_NOT_CONTENT_INDEXED = 0x00002000, + FILE_ATTR_ENCRYPTED = 0x00004000, + FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x00040000, + FILE_ATTR_VALID_FLAGS = 0x00047fb7, + FILE_ATTR_VALID_SET_FLAGS = 0x000031a7, + FILE_ATTR_I30_INDEX_PRESENT = 0x10000000, + FILE_ATTR_VIEW_INDEX_PRESENT = 0x20000000, +}; + +enum FILE_NAME_TYPE_FLAGS : u8 { + FILE_NAME_POSIX = 0x00, + FILE_NAME_WIN32 = 0x01, + FILE_NAME_DOS = 0x02, + FILE_NAME_WIN32_AND_DOS = 0x03, +}; + +// ------------------------------------------------------------------------- +// STANDARD INFO ATTRIBUTE RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +struct STANDARD_INFORMATION_BODY_OLD { + u64 Created_DT_UTC [[format("parse_filetime"), comment("Created DT UTC")]]; + u64 Modified_DT_UTC [[format("parse_filetime"), comment("Modified DT UTC")]]; + u64 MFT_Mod_DT_UTC [[format("parse_filetime"), comment("$MFT Mod DT UTC")]]; + u64 Accessed_DT_UTC [[format("parse_filetime"), comment("Accessed DT UTC")]]; + FILE_ATTR_FLAGS file_attributes [[comment("RASH ATTR")]];; +} [[static]]; + +struct STANDARD_INFORMATION_OLD { + u64 Created_DT_UTC [[format("parse_filetime"), comment("Created DT UTC")]]; + u64 Modified_DT_UTC [[format("parse_filetime"), comment("Modified DT UTC")]]; + u64 MFT_Mod_DT_UTC [[format("parse_filetime"), comment("$MFT Mod DT UTC")]]; + u64 Accessed_DT_UTC [[format("parse_filetime"), comment("Accessed DT UTC")]]; + FILE_ATTR_FLAGS file_attributes [[comment("RASH ATTR")]];; + u8 reserved12[12]; +} [[static]]; + +struct STANDARD_INFORMATION { + u64 Created_DT_UTC [[format("parse_filetime"), comment("Created DT UTC")]]; + u64 Modified_DT_UTC [[format("parse_filetime"), comment("Modified DT UTC")]]; + u64 MFT_Mod_DT_UTC [[format("parse_filetime"), comment("$MFT Mod DT UTC")]]; + u64 Accessed_DT_UTC [[format("parse_filetime"), comment("Accessed DT UTC")]]; + FILE_ATTR_FLAGS file_attributes [[comment("RASH ATTR")]];; + + u32 maximum_versions [[comment("Maximum Versions Allowed: Disabled = 0x00")]];; + u32 version_number [[comment("File Version Number")]];; + u32 class_id [[comment("Class ID")]];; + u32 owner_id [[comment("Owner ID - $Quota")]];; + u32 security_id [[comment("Security Permission Settings")]];; + u64 quota_charged [[comment("Number of Bytes Towards User Quota")]];; + u64 usn [[comment("Update Sequence Number - $USNJournal Index")]];; +} [[static]]; + +struct FILE_NAME_ATTR_PACKED { + u16 packed_ea_size [[comment("Size of xE0 ATTR")]];; + u16 reserved [[comment("Reserved")]];; +} [[static]]; + +union FILE_NAME_ATTR_FORM { + FILE_NAME_ATTR_PACKED packed; // [[inline]]; + u32 reparse_point_tag [[comment("Reparse Point Tag")]];; +} [[static]]; + +struct FILE_NAME_ATTR { + PRENT_MFT = 0x00; + PRENT_SEQ = 0x00; + + u8 MFT_Parent_Dir[6] [[comment("Parent Directory $MFT File Record Number")]]; + u16 Parent_Seq_Num [[comment("Parent Directory $MFT Sequence Number")]]; + u64 Created_DT_UTC [[format("parse_filetime"), comment("Created DT in UTC")]]; + u64 Modified_DT_UTC [[format("parse_filetime"), comment("Modified DT in UTC")]]; + u64 MFT_Mod_DT_UTC [[format("parse_filetime"), comment("MFT Mod DT in UTC")]]; + u64 Accessed_DT_UTC [[format("parse_filetime"), comment("Accessed DT in UTC")]]; + u64 allocated_size [[comment("Size on Disk")]]; + u64 data_size [[comment("Logical File Size")]]; + FILE_ATTR_FLAGS file_attributes [[comment("RASH ATTR")]]; + FILE_NAME_ATTR_FORM form [[inline, comment("Reparse Value")]]; + u8 file_name_length [[comment("File Name Length")]]; + FILE_NAME_TYPE_FLAGS file_name_type [[comment("Namespace Type: POSIX = 0x00 | Win32 = 0x01 | DOS_SFN = 0x02 | Win32&DOS = 0x03")]]; + char16 file_name[file_name_length] [[comment("The Actual File Name")]]; + + PRENT_MFT = MFT_Parent_Dir; + PRENT_SEQ = Parent_Seq_Num; +}; + +// ------------------------------------------------------------------------- +// SECURITY DESC | OBJECT ID RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +struct OBJECT_ID_ATTR_INFO { + type::GUID birth_volume_id; + type::GUID birth_object_id; + type::GUID domain_id; +}; + +union OBJECT_ID_ATTR_FORM { + OBJECT_ID_ATTR_INFO info [[inline]]; + u8 extended_info[48]; +}; + +struct OBJECT_ID_ATTR { + type::GUID object_id; + OBJECT_ID_ATTR_FORM form [[inline]]; +}; + +enum VOLUME_FLAGS : u16 { + VOLUME_IS_DIRTY = 0x0001, + VOLUME_RESIZE_LOG_FILE = 0x0002, + VOLUME_UPGRADE_ON_MOUNT = 0x0004, + VOLUME_MOUNTED_ON_NT4 = 0x0008, + VOLUME_DELETE_USN_UNDERWAY = 0x0010, + VOLUME_REPAIR_OBJECT_ID = 0x0020, + VOLUME_CHKDSK_UNDERWAY = 0x4000, + VOLUME_MODIFIED_BY_CHKDSK = 0x8000, + VOLUME_FLAGS_MASK = 0xc03f, +}; + +struct VOLUME_INFORMATION { + u64 reserved; + u8 major_ver; + u8 minor_ver; + VOLUME_FLAGS flags; + NT_Major = major_ver; + NT_Minor = minor_ver; +} [[static]]; + +enum SECURITY_DESCRIPTOR_CONTROL : u16 { + SE_OWNER_DEFAULTED = 0x0001, + SE_GROUP_DEFAULTED = 0x0002, + SE_DACL_PRESENT = 0x0004, + SE_DACL_DEFAULTED = 0x0008, + SE_SACL_PRESENT = 0x0010, + SE_SACL_DEFAULTED = 0x0020, + SE_DACL_AUTO_INHERIT_REQ = 0x0100, + SE_SACL_AUTO_INHERIT_REQ = 0x0200, + SE_DACL_AUTO_INHERITED = 0x0400, + SE_SACL_AUTO_INHERITED = 0x0800, + SE_DACL_PROTECTED = 0x1000, + SE_SACL_PROTECTED = 0x2000, + SE_RM_CONTROL_VALID = 0x4000, + SE_SELF_RELATIVE = 0x8000, +}; + + +// ------------------------------ +// Enums +// ------------------------------ +enum ACE_TYPES : u8 { + ACCESS_MIN_MS_ACE_TYPE = 0, + ACCESS_ALLOWED_ACE_TYPE = 0, + ACCESS_DENIED_ACE_TYPE = 1, + SYSTEM_AUDIT_ACE_TYPE = 2, + SYSTEM_ALARM_ACE_TYPE = 3, + ACCESS_MAX_MS_V2_ACE_TYPE = 3, + ACCESS_ALLOWED_COMPOUND_ACE_TYPE = 4, + ACCESS_MAX_MS_V3_ACE_TYPE = 4, + ACCESS_MIN_MS_OBJECT_ACE_TYPE = 5, + ACCESS_ALLOWED_OBJECT_ACE_TYPE = 5, + ACCESS_DENIED_OBJECT_ACE_TYPE = 6, + SYSTEM_AUDIT_OBJECT_ACE_TYPE = 7, + SYSTEM_ALARM_OBJECT_ACE_TYPE = 8, + ACCESS_MAX_MS_OBJECT_ACE_TYPE = 8, + ACCESS_MAX_MS_V4_ACE_TYPE = 8, + ACCESS_MAX_MS_ACE_TYPE = 8, + ACCESS_ALLOWED_CALLBACK_ACE_TYPE = 9, + ACCESS_DENIED_CALLBACK_ACE_TYPE = 10, + ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 11, + ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = 12, + SYSTEM_AUDIT_CALLBACK_ACE_TYPE = 13, + SYSTEM_ALARM_CALLBACK_ACE_TYPE = 14, + SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = 15, + SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = 16, + SYSTEM_MANDATORY_LABEL_ACE_TYPE = 17, + SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE = 18, + SYSTEM_SCOPED_POLICY_ID_ACE_TYPE = 19, + SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE = 20, +}; + +enum ACE_FLAGS : u8 { + OBJECT_INHERIT_ACE = 0x01, + CONTAINER_INHERIT_ACE = 0x02, + NO_PROPAGATE_INHERIT_ACE = 0x04, + INHERIT_ONLY_ACE = 0x08, + INHERITED_ACE = 0x10, + VALID_INHERIT_FLAGS = 0x1f, + SUCCESSFUL_ACCESS_ACE_FLAG = 0x40, + FAILED_ACCESS_ACE_FLAG = 0x80, +}; + +enum ACCESS_MASK : u32 { + FILE_READ_DATA = 0x00000001, + FILE_LIST_DIRECTORY = 0x00000001, + FILE_WRITE_DATA = 0x00000002, + FILE_ADD_FILE = 0x00000002, + FILE_APPEND_DATA = 0x00000004, + FILE_ADD_SUBDIRECTORY = 0x00000004, + FILE_READ_EA = 0x00000008, + FILE_WRITE_EA = 0x00000010, + FILE_EXECUTE = 0x00000020, + FILE_TRAVERSE = 0x00000020, + FILE_DELETE_CHILD = 0x00000040, + FILE_READ_ATTRIBUTES = 0x00000080, + FILE_WRITE_ATTRIBUTES = 0x00000100, + DELETE = 0x00010000, + READ_CONTROL = 0x00020000, + WRITE_DAC = 0x00040000, + WRITE_OWNER = 0x00080000, + SYNCHRONIZE = 0x00100000, + STANDARD_RIGHTS_READ = 0x00020000, + STANDARD_RIGHTS_WRITE = 0x00020000, + STANDARD_RIGHTS_EXECUTE = 0x00020000, + STANDARD_RIGHTS_REQUIRED = 0x000f0000, + STANDARD_RIGHTS_ALL = 0x001f0000, + ACCESS_SYSTEM_SECURITY = 0x01000000, + MAXIMUM_ALLOWED = 0x02000000, + GENERIC_ALL = 0x10000000, + GENERIC_EXECUTE = 0x20000000, + GENERIC_WRITE = 0x40000000, + GENERIC_READ = 0x80000000, +}; + +enum OBJECT_ACE_FLAGS : u32 { + ACE_OBJECT_TYPE_PRESENT = 1, + ACE_INHERITED_OBJECT_TYPE_PRESENT = 2, +}; + +// ------------------------------ +// ACE/SID +// ------------------------------ +struct ACE_HEADER { + ACE_TYPES type; + ACE_FLAGS flags; + u16 size; +}; + +struct SID { + u8 revision; + u8 sub_authority_count; + u8 identifier_authority[6]; + u32 sub_authority[sub_authority_count]; +}; + +struct ACCESS_ALLOWED_ACE { + ACE_HEADER header [[inline]]; + ACCESS_MASK mask; + SID sid; +}; + +using ACCESS_DENIED_ACE = ACCESS_ALLOWED_ACE; +using SYSTEM_ALARM_ACE = ACCESS_ALLOWED_ACE; +using SYSTEM_AUDIT_ACE = ACCESS_ALLOWED_ACE; + +struct ACCESS_ALLOWED_OBJECT_ACE { + ACE_HEADER header [[inline]]; + ACCESS_MASK mask; + OBJECT_ACE_FLAGS object_flags; + type::GUID object_type; + type::GUID inherited_object_type; + SID sid; +}; + +using ACCESS_DENIED_OBJECT_ACE = ACCESS_ALLOWED_OBJECT_ACE; +using SYSTEM_AUDIT_OBJECT_ACE = ACCESS_ALLOWED_OBJECT_ACE; +using SYSTEM_ALARM_OBJECT_ACE = ACCESS_ALLOWED_OBJECT_ACE; + +// ------------------------------ +// ACE HELPER +// ------------------------------ +union ACE { + ACE_HEADER hdr [[hidden]]; + + match (hdr.type) { + (ACE_TYPES::ACCESS_ALLOWED_ACE_TYPE): ACCESS_ALLOWED_ACE allowed; + (ACE_TYPES::ACCESS_DENIED_ACE_TYPE): ACCESS_DENIED_ACE denied; + (ACE_TYPES::SYSTEM_AUDIT_ACE_TYPE): SYSTEM_AUDIT_ACE audit; + (ACE_TYPES::SYSTEM_ALARM_ACE_TYPE): SYSTEM_ALARM_ACE alarm; + + (ACE_TYPES::ACCESS_ALLOWED_OBJECT_ACE_TYPE): ACCESS_ALLOWED_OBJECT_ACE obj_allowed; + (ACE_TYPES::ACCESS_DENIED_OBJECT_ACE_TYPE): ACCESS_DENIED_OBJECT_ACE obj_denied; + (ACE_TYPES::SYSTEM_AUDIT_OBJECT_ACE_TYPE): SYSTEM_AUDIT_OBJECT_ACE obj_audit; + (ACE_TYPES::SYSTEM_ALARM_OBJECT_ACE_TYPE): SYSTEM_ALARM_OBJECT_ACE obj_alarm; + } + + // Consume remaining bytes as raw padding + padding[hdr.size - sizeof(ACE_HEADER)]; +}; + +// ------------------------------ +// ACL +// ------------------------------ +struct ACL { + u8 revision; + u8 alignment1; + u16 size; + u16 ace_count; + u16 alignment2; + + ACE aces[ace_count]; +}; + +// ------------------------------ +// SECURITY DESCRIPTOR +// ------------------------------ +#define SECURITY_DESCRIPTOR_RELATIVE_SIZE (20) +struct SECURITY_DESCRIPTOR_RELATIVE { + u32 struct_start = $; + u8 revision; + u8 alignment; + u16 control; // SECURITY_DESCRIPTOR_CONTROL + u32 owner; + u32 group; + u32 sacl; + u32 dacl; + + if (control & SECURITY_DESCRIPTOR_CONTROL::SE_DACL_PRESENT) { + if (dacl != 0) { + // Skip to the relative offset + if ($ - struct_start < dacl) { + padding[dacl - ($ - struct_start)]; + } + ACL dacl_acl; + } + } + if (control & SECURITY_DESCRIPTOR_CONTROL::SE_SACL_PRESENT) { + if (sacl != 0) { + if ($ - struct_start < sacl) { + padding[sacl - ($ - struct_start)]; + } + ACL sacl_acl; + } + } + + if (owner > 0) { + u32 owner_bytes = $ - struct_start; + if (owner > owner_bytes) { + padding[owner - owner_bytes]; + } + SID owner_sid; + } + + if (group > 0) { + u32 group_bytes = $ - struct_start; + if (group > group_bytes) { + padding[group - group_bytes]; + } + SID group_sid; + } +}; + +// ------------------------------------------------------------------------- +// INDEX RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +// ------------------------------ +// INDEX HEADER HELPER +// ------------------------------ +enum INDEX_HEADER_FLAGS : u8 { + SMALL_INDEX = 0, + LARGE_INDEX = 1, + LEAF_NODE = 0, + INDEX_NODE = 1, + NODE_MASK = 1, +}; + +// ------------------------------ +// INDEX HEADER PARSER +// ------------------------------ +struct INDEX_HEADER { + u32 entries_offset; + u32 index_length; + u32 allocated_size; + INDEX_HEADER_FLAGS ih_flags; + u8 reserved[3]; +}; + +// ------------------------------ +// REPARSE INDEX KEY PARSER +// ------------------------------ +struct REPARSE_INDEX_KEY { + u32 reparse_tag; + leMFT_REF file_id; +}; + +// ------------------------------ +// SDS_ENTRY PARSER +// ------------------------------ +struct SDS_ENTRY { + u32 hash; + u32 security_id; + u64 offset; + u32 length; + SECURITY_DESCRIPTOR_RELATIVE sid; +}; + +using SII_INDEX_KEY = u32 ; + +// ------------------------------ +// SDH INDEX KEY PARSER +// ------------------------------ +struct SDH_INDEX_KEY { + u32 hash; + u32 security_id; +}; + +// ------------------------------ +// INDEX ENTRY FLAG HELPER +// ------------------------------ +enum INDEX_ENTRY_FLAGS : u16 { + INDEX_ENTRY_NODE = 1, + INDEX_ENTRY_END = 2, + INDEX_ENTRY_SPACE_FILLER = 0xffff, +}; + +// ------------------------------ +// INDEX HEADER POINTER +// ------------------------------ +struct INDEX_ENTR_HEADER_PTR { + u16 data_offset; + u16 data_length; + u32 reservedV; +}; + +// ------------------------------ +// INDEX HEADER HELPER +// ------------------------------ +union INDEX_ENTR_HEADER_REF { + leMFT_REF indexed_file; + INDEX_ENTR_HEADER_PTR ptr; +}; + +// ------------------------------ +// INDEX HEADER PARSER +// ------------------------------ +struct INDEX_ENTRY_HEADER { + INDEX_ENTR_HEADER_REF file_ref; + u16 length; + u16 key_length; + u16 flags; // INDEX_ENTRY_FLAGS + u16 reserved; +}; + +// ------------------------------ +// INDEX ENTRY FN ATTR +// Duplicated so that parent mft# and parent seq# tracking doesn't break. +// ------------------------------ +struct IDX_FILE_NAME_ATTR{ + u8 MFT_Parent_Dir[6] [[comment("Parent Directory $MFT File Record Number")]]; + u16 Parent_Seq_Num [[comment("Parent Directory $MFT Sequence Number")]]; + u64 Created_DT_UTC [[format("parse_filetime"), comment("Created DT in UTC")]]; + u64 Modified_DT_UTC [[format("parse_filetime"), comment("Modified DT in UTC")]]; + u64 MFT_Mod_DT_UTC [[format("parse_filetime"), comment("MFT Mod DT in UTC")]]; + u64 Accessed_DT_UTC [[format("parse_filetime"), comment("Accessed DT in UTC")]]; + u64 allocated_size [[comment("Size on Disk")]]; + u64 data_size [[comment("Logical File Size")]]; + FILE_ATTR_FLAGS file_attributes [[comment("RASH ATTR")]]; + FILE_NAME_ATTR_FORM form [[inline, comment("Reparse Value")]]; + u8 file_name_length [[comment("File Name Length")]]; + FILE_NAME_TYPE_FLAGS file_name_type [[comment("Namespace Type: POSIX = 0x00 | Win32 = 0x01 | DOS_SFN = 0x02 | Win32&DOS = 0x03")]]; + char16 file_name[file_name_length] [[comment("The Actual File Name")]]; +}; + +// ------------------------------ +// INDEX ENTRY FN PARSER +// ------------------------------ +struct INDEX_ENTRY_FILE_NAME { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + // Workaround -- Using new structure for IDX FNs -- Allows for setting parent info on non-IDX items + IDX_FILE_NAME_ATTR key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX ENTRY SII PARSER +// ------------------------------ +struct INDEX_ENTRY_SII { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + SII_INDEX_KEY key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX ENTRY SDH PARSER +// ------------------------------ +struct INDEX_ENTRY_SDH { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + SDH_INDEX_KEY key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX ENTRY OBJECT ID PARSER +// ------------------------------ +struct INDEX_ENTRY_OBJ_ID { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + type::GUID key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX ENTRY REPARSE POINT PARSER +// ------------------------------ +struct INDEX_ENTRY_REPARSE { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + REPARSE_INDEX_KEY key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX ENTRY SID PARSER +// ------------------------------ +struct INDEX_ENTRY_SID { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + SID key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX ENTRY OWNER ID PARSER +// ------------------------------ +struct INDEX_ENTRY_OWNER_ID { + INDEX_ENTRY_HEADER header; + + if (!(header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END)) { + u64 key; + } + + if (header.flags & INDEX_ENTRY_FLAGS::INDEX_ENTRY_NODE) { + leVCN vcn; + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// INDEX FLAGS FUNC +// ------------------------------ +fn index_get_flags(u64 offset) { + INDEX_ENTRY_HEADER index_entry_header @ offset; + return index_entry_header.flags; +}; + +// ------------------------------ +// INDEX BLOCK PARSER +// ------------------------------ +struct INDEX_BLOCK { + NTFS_RECORD header [[inline]]; + leLSN lsn; + leVCN index_block_vcn; + + u32 index_head = $; + INDEX_HEADER index; + + if (index.entries_offset > sizeof (index)) { + padding[index.entries_offset - sizeof (index)]; + } + + INDEX_ENTRY_FILE_NAME ents[while(!(index_get_flags($) & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END))]; + INDEX_ENTRY_FILE_NAME ent_end; + + u32 index_used = $ - index_head; + if (index.index_length > index_used) { + padding[index.index_length - index_used]; + } +}; + +// ------------------------------ +// INDEX ROOT PARSER +// ------------------------------ +struct INDEX_ROOT { + ATTR_TYPES type; + COLLATION_RULES collation_rule; + u32 index_block_size; + s8 clusters_per_index_block; + u8 reserved[3] [[static, sealed]]; + + u32 index_head = $; + INDEX_HEADER index; + + if (index.entries_offset > sizeof (index)) { + padding[index.entries_offset - sizeof (index)]; + } + + match (type) { + (ATTR_TYPES::AT_FILE_NAME): { + INDEX_ENTRY_FILE_NAME ents[while(!(index_get_flags($) & INDEX_ENTRY_FLAGS::INDEX_ENTRY_END))]; + INDEX_ENTRY_FILE_NAME ent_end; + } + } + + u32 index_used = $ - index_head; + if (index.index_length > index_used) { + padding[index.index_length - index_used]; + } +}; + +// ------------------------------------------------------------------------- +// RESTART AREA RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +// ------------------------------ +// RESTART AREA HEADER PARSER +// ------------------------------ +struct RESTART_PAGE_HEADER { + NTFS_RECORD_TYPES magic; + u16 usa_ofs; + u16 usa_count; + leLSN chkdsk_lsn; + u32 system_page_size; + u32 log_page_size; + u16 restart_area_offset; + u16 minor_ver; + u16 major_ver; + u16 usn; +}; + +// ------------------------------ +// RESTART AREA FLAG PARSER +// ------------------------------ +enum RESTART_AREA_FLAGS : u16 { + RESTART_VOLUME_IS_CLEAN = 0x0002, + RESTART_SPACE_FILLER = 0xffff, +}; + +// ------------------------------ +// RESTART AREA PARSER +// ------------------------------ +struct RESTART_AREA { + leLSN current_lsn; + u16 log_clients; + u16 client_free_list; + u16 client_in_use_list; + RESTART_AREA_FLAGS flags; + u32 seq_number_bits; + u16 restart_area_length; + u16 client_array_offset; + u64 file_size; + u32 last_lsn_data_length; + u16 log_record_header_length; + u16 log_page_data_offset; + u32 restart_log_open_count; + u32 reserved; +}; + +// ------------------------------------------------------------------------- +// LOG CLIENT RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +// ------------------------------ +// LOG CLIENT PARSER +// ------------------------------ +struct LOG_CLIENT_RECORD { + leLSN oldest_lsn; + leLSN client_restart_lsn; + u16 prev_client; + u16 next_client; + u16 seq_number; + u8 reserved[6]; + u32 client_name_length; + //ntfschar client_name[64]; + char16 client_name[64]; +}; + +// ------------------------------------------------------------------------- +// ATTRIBUTE RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +// ------------------------------ +// xD0 ATTRIBUTE PARSER +// ------------------------------ +struct EA_INFO_ENTRY { + u16 EA_packed_size [[comment("Size of the Packed Extended Attributes")]]; + u16 EA_Count [[comment("Number of Extended Attributes which have NEED_EA set")]]; + u32 EA_unpacked_size [[comment("Size of the Unpacked Extended Attributes")]]; +}; + +// ------------------------------ +// xE0 ATTRIBUTE PARSER +// ------------------------------ +struct EA_ENTRY { + u32 entry_size [[comment("EA Size: Offset to Next Extended Attribute")]]; + u8 flags [[comment("Flags: 0x80 = Needs EA")]]; + u8 name_length [[comment("EA Name Size")]]; + u16 value_length [[comment("EA Value Size")]]; + char name[name_length] [[comment("EA Name: LXUID/LXGID/LXMOD/ETC")]]; + padding[2]; + char value[value_length] [[comment("EA Value: Related to HPFS+ Compatibility")]]; +}; + +// ------------------------------ +// ATTRIBUTE DEFS +// ------------------------------ +#define ATTR_RECORD_HEADER_SIZE (16) +#define ATTR_RECORD_RESIDENT_SIZE (16 + 8) +#define ATTR_RECORD_NONRESIDENT_SIZE (16 + 48) +#define ATTR_RECORD_NONRESIDENT_CMPR_SIZE (16 + 48 + 8) + +// ------------------------------ +// ATTRIBUTE HEADER +// ------------------------------ +struct ATTRHeader { + ATTR_TYPES type [[comment("Attribute Identifier")]]; + u32 length [[comment("Size of this ATTR")]]; + u8 non_resident [[comment("0 = Resident | 1 = Non-Resident")]]; + u8 name_length [[comment("Size of the ATTR Stream Name, if any")]]; + u16 name_offset [[comment("Offset to the ATTR Stream Name")]]; + u16 flags [[comment("0x0001 = Compressed | 0x4000 = Encrypted | 0x8000 = Sparse")]]; // ATTR_FLAGS + u16 instance [[comment("Sequential Order: 0x0000 = 1st")]]; + + u32 name_offset_delta = 0; + + if (!non_resident) { + u32 value_length [[comment("Size of Resident Data")]]; + u16 value_offset [[comment("Offset to Resident Data")]]; + RESIDENT_ATTR_FLAGS resident_flags [[comment("Indexed Flag: 0x01 = Indexed | 0x00 = Not Indexed")]]; + s8 reservedR [[comment("Reserved")]]; + + //if (name_offset > ATTR_RECORD_RESIDENT_SIZE) { + // name_offset_delta = name_offset - ATTR_RECORD_RESIDENT_SIZE; + // padding[name_offset_delta]; + //} + + } +}; + +// ------------------------------ +// ATTRIBUTE BODY NON_RESIDENT +// ------------------------------ +struct ATTRBody { + leVCN lowest_vcn [[comment("Start Virtual Cluster Number")]]; + leVCN highest_vcn [[comment("End Virtual Cluster Number")]]; + u16 mapping_pairs_offset [[comment("Offset to RunList")]]; + u8 compression_unit [[comment("Compression Unit Size")]]; + u8 reserved1[5] [[comment("Reserved")]]; + u64 allocated_size [[comment("Size on Disk")]]; + u64 data_size [[comment("Logical File Size")]]; + u64 initialized_size [[comment("Initialized Size: Typical for Downloads")]]; +}; + +// ------------------------------ +// ATTRIBUTE RECORD BODY RELATED +// ------------------------------ +struct ATTR_RECORD { + ATTRHeader ATTR_HEADER [[name("Header")]]; + + if (ATTR_HEADER.non_resident) { + ATTRBody x80_xB0_Body [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + + if (ATTR_HEADER.flags & ATTR_FLAGS::ATTR_IS_COMPRESSED) { + u64 compressed_size [[comment("Compressed Size")]]; + + if (ATTR_HEADER.name_offset > ATTR_RECORD_NONRESIDENT_CMPR_SIZE) { + name_offset_delta = ATTR_HEADER.name_offset - ATTR_RECORD_NONRESIDENT_CMPR_SIZE; + padding[name_offset_delta]; + } + } else { + if (ATTR_HEADER.name_offset > ATTR_RECORD_NONRESIDENT_SIZE) { + name_offset_delta = ATTR_HEADER.name_offset - ATTR_RECORD_NONRESIDENT_SIZE; + padding[name_offset_delta]; + } + } + } + + u32 name_length_bytes = ATTR_HEADER.name_length * sizeof (ntfschar); + + if (ATTR_HEADER.name_length > 0) { + char16 name[ATTR_HEADER.name_length]; + } + + if (ATTR_HEADER.non_resident) { + if (ATTR_HEADER.flags & ATTR_FLAGS::ATTR_IS_COMPRESSED) { + if (x80_xB0_Body.mapping_pairs_offset > (ATTR_RECORD_NONRESIDENT_CMPR_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes)) { + padding[x80_xB0_Body.mapping_pairs_offset - (ATTR_RECORD_NONRESIDENT_CMPR_SIZE + name_length_bytes)]; + } + } else { + if (x80_xB0_Body.mapping_pairs_offset > (ATTR_RECORD_NONRESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes)) { + padding[x80_xB0_Body.mapping_pairs_offset - (ATTR_RECORD_NONRESIDENT_SIZE + name_length_bytes)]; + } + } + + RUNLIST x80_xB0_RUNLIST[] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_RUNLIST"), comment("$DATA or $BITMAP Run")]]; + } else { + + u32 value_offset_delta = 0; + if (ATTR_HEADER.value_offset > (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes)) { + value_offset_delta = ATTR_HEADER.value_offset - (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes); + padding[value_offset_delta]; + } + + match (ATTR_HEADER.type) { + (ATTR_TYPES::AT_UNUSED): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_STANDARD_INFORMATION): { + if (ATTR_HEADER.value_length > sizeof(STANDARD_INFORMATION_OLD)) { + STANDARD_INFORMATION x10_Body [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + + } else { + STANDARD_INFORMATION_OLD x10_Body [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + } + + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta + sizeof (x10_Body)); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_ATTRIBUTE_LIST): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_FILE_NAME): FILE_NAME_ATTR x30_Body [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + (ATTR_TYPES::AT_OBJECT_ID): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_SECURITY_DESCRIPTOR): { + SECURITY_DESCRIPTOR_RELATIVE x50_Body [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta + sizeof (x50_Body)); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + //(ATTR_TYPES::AT_VOLUME_NAME): char16 vol_name[ATTR_HEADER.value_length / sizeof (ntfschar)] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body"), comment("User Defined Volume Name")]]; + (ATTR_TYPES::AT_VOLUME_NAME): { char16 vol_name[ATTR_HEADER.value_length / sizeof (ntfschar)] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body"), comment("User Defined Volume Name")]]; + Vol_Label = str(vol_name); + } + + (ATTR_TYPES::AT_VOLUME_INFORMATION): VOLUME_INFORMATION vol_info; + (ATTR_TYPES::AT_DATA): { + char x80_xB0_Stream[ATTR_HEADER.value_length] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta + sizeof (x80_xB0_Stream)); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_INDEX_ROOT): { + INDEX_ROOT x90_Body [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + } + (ATTR_TYPES::AT_INDEX_ALLOCATION): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_BITMAP): u8 buffer[ATTR_HEADER.value_length] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + (ATTR_TYPES::AT_REPARSE_POINT): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_EA_INFORMATION): { + u64 attr_start = $ - (ATTR_HEADER.non_resident ? ATTR_RECORD_NONRESIDENT_SIZE : ATTR_RECORD_RESIDENT_SIZE); + EA_INFO_ENTRY xD0_Body[while($ - (attr_start + ATTR_HEADER.value_offset) < ATTR_HEADER.value_length)] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + } + (ATTR_TYPES::AT_EA): { + u64 attr_start = $ - (ATTR_HEADER.non_resident ? ATTR_RECORD_NONRESIDENT_SIZE : ATTR_RECORD_RESIDENT_SIZE); + EA_ENTRY xE0_Body[while($ - (attr_start + ATTR_HEADER.value_offset) < ATTR_HEADER.value_length)] [[name(attr_type_name(u32(ATTR_HEADER.type)) + "_Body")]]; + } + (ATTR_TYPES::AT_PROPERTY_SET): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_LOGGED_UTILITY_STREAM): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_FIRST_USER_DEFINED_ATTRIBUTE): { + u32 attr_sum = (ATTR_RECORD_RESIDENT_SIZE + ATTR_HEADER.name_offset_delta + name_length_bytes + value_offset_delta); + if (ATTR_HEADER.length > attr_sum) { + padding[ATTR_HEADER.length - attr_sum]; + } + } + (ATTR_TYPES::AT_END): { + //length is no longer valid + } + } + } + std::mem::AlignTo<8>; +}; + +// ------------------------------ +// ATTRIBUTE TYPE FUNC +// ------------------------------ +fn attr_get_type(u64 offset) { + ATTR_RECORD ATTR @ offset; + return ATTR.ATTR_HEADER.type; +}; + +// ------------------------------------------------------------------------- +// FILE RECORD ENTRY RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- + +// ------------------------------ +// PRIMARY FILE RECORD HEADER PARSER +// ------------------------------ +struct NTFS_RECORD_HDR { + NTFS_RECORD_TYPES magic [[comment("File Record Signature 'FILE'")]];; + u16 usa_ofs [[comment("Offset to Fix Up Array")]]; + u16 usa_count [[comment("# of 2 Byte Entries in FUA")]]; + leLSN lsn [[comment("$LogFile Sequence #")]]; + u16 sequence_number [[comment("Count of FR Entry Usage: 1=Once/Not Del")]]; + u16 link_count [[comment("(HARDLINK) Count of File Name ATTR")]]; + u16 attr_offset [[comment("Offset to 1st ATTR")]]; + MFT_RECORD_FLAGS flags [[comment("Allocation Status Flags: 0x00=Del File||0x01=Alloc.File||0x02=Del DIR||0x03=Alloc.DIR")]];; + u32 bytes_in_use [[comment("Logical Size of FR Entry")]]; + u32 bytes_allocated [[comment("Phys (Alloc.) Size of FR Entry")]]; + leMFT_REF base_mft_record [[comment("Ref to additional FR Entry, if any")]]; + u16 next_attr_instance [[comment("Count of ATTRs")]]; + u16 FU_Array [[comment("Fix Up Array and ATTR-NT3.0")]]; // 2 bytes NTFS 3.0 (LEGACY) + + if (FU_Array <= 0) { // FU_Array not in use -- NTFS 3.1+ + u32 mft_record_number [[comment("MFT File Record Num")]]; // 4 bytes + mft_num = mft_record_number; + seq_num = sequence_number; + + if (usa_count > 0) { + u16 update_sequence[usa_count] [[comment("Fix Up Array and ATTR-NT3.1+")]]; + } + } else { + padding[4]; + mft_num += 0x01; + } +}; + +// ------------------------------ +// QUICK ATTRIBUTE CHECK +// ------------------------------ +fn attr_type_name(u32 type) { + if (type == ATTR_TYPES::AT_STANDARD_INFORMATION) return "STANDARD_INFORMATION"; + if (type == ATTR_TYPES::AT_ATTRIBUTE_LIST) return "ATTRIBUTE_LIST"; + if (type == ATTR_TYPES::AT_FILE_NAME) return "FILE_NAME"; + if (type == ATTR_TYPES::AT_OBJECT_ID) return "OBJECT_ID"; + if (type == ATTR_TYPES::AT_SECURITY_DESCRIPTOR) return "SECURITY_DESCRIPTOR"; + if (type == ATTR_TYPES::AT_VOLUME_NAME) return "VOLUME_NAME"; + if (type == ATTR_TYPES::AT_VOLUME_INFORMATION) return "VOLUME_INFORMATION"; + if (type == ATTR_TYPES::AT_DATA) return "DATA"; + if (type == ATTR_TYPES::AT_INDEX_ROOT) return "INDEX_ROOT"; + if (type == ATTR_TYPES::AT_INDEX_ALLOCATION) return "INDEX_ALLOCATION"; + if (type == ATTR_TYPES::AT_BITMAP) return "BITMAP"; + if (type == ATTR_TYPES::AT_REPARSE_POINT) return "REPARSE_POINT"; + if (type == ATTR_TYPES::AT_EA_INFORMATION) return "EA_INFORMATION"; + if (type == ATTR_TYPES::AT_EA) return "EA"; + if (type == ATTR_TYPES::AT_PROPERTY_SET) return "PROPERTY_SET"; + if (type == ATTR_TYPES::AT_LOGGED_UTILITY_STREAM) return "LOGGED_UTILITY_STREAM"; + return "UNKNOWN"; +}; + +fn peek_attr_type(u64 offset) { + return std::mem::read_unsigned(offset, 4); // first 4 bytes of ATTRHeader +}; + +// ------------------------------ +// ATTRIBUTE WRAPPER +// ------------------------------ +struct ATTR_WRAPPER { + ATTR_RECORD ATTR [[name(attr_type_name(peek_attr_type($)))]]; +} [[inline, name(attr_type_name(peek_attr_type($)))]]; + +// ------------------------------ +// PATTERN NAMING FUNC +// ------------------------------ +fn get_file_name(auto ATTRS) { + str fname = ""; + + for (u32 i = 0, i < std::core::member_count(ATTRS), i = i + 1) { + + if (ATTRS[i].ATTR.ATTR_HEADER.type == ATTR_TYPES::AT_FILE_NAME) { + u8 len = ATTRS[i].ATTR.x30_Body.file_name_length; + + for (u32 j = 0, j < len, j = j + 1) { + char16 cu = ATTRS[i].ATTR.x30_Body.file_name[j]; + u8 c = u8(cu); // Cast to low byte + fname = std::format("{}{}", fname, char(c)); + } + break; + } + } + return fname; +}; + +// ------------------------------ +// PRIMARY FILE RECORD PARSER +// ------------------------------ +u64 start_index = 12; // First 12 are hard-coded - We're parsing the rest of the MFT Entries + +struct MFT_RECORD { + u64 record_start_offset = $; + NTFS_RECORD_HDR FR_HEADER; + + if (FR_HEADER.flags == 0x00 || FR_HEADER.flags == 0x02) { + Unused_Count += 1; + } + if (FR_HEADER.flags == 0x01 || FR_HEADER.flags == 0x03) { + MFT_Count += 1; + } + + std::mem::AlignTo<8>; + ATTR_WRAPPER ATTRS[while(attr_get_type($) != ATTR_TYPES::AT_END)] [[inline]]; + str filename = get_file_name(ATTRS); + + if (filename == "") { + PRENT_MFT = 0x00; + PRENT_SEQ = 0x00; + filename = "FILE_REC"; + } + + ATTR_RECORD ATTR_END [[static, name("ATTR_END")]] ; + std::mem::AlignTo; + +} [[name(format_element(filename, $, start_index)), + comment("DEC:[MFT#][SEQ#] | HEX:[MFT#][SEQ#] | PARENT_HEX:[MFT#][SEQ#]")]]; + +fn format_element(auto filename, auto v, u64 offset) { + return std::format("{} [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", filename, mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ); +}; + + +// ------------------------------------------------------------------------- +// VOLUME BOOT RECORD RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +// ------------------------------ +// NTFS VBR +// ------------------------------ +struct NTFS_BOOT_SECTOR { + u8 jmp_boot[3] [[comment("Jump Instructions")]]; + char oem_id[8] [[comment("FS IDENTIFIER")]]; + BIOS_PARAMETER_BLOCK bpb; + u8 physical_drive [[comment("Legacy")]]; + u8 current_head [[comment("Legacy")]]; + u8 extended_boot_signature [[comment("Legacy")]]; + u8 reserved1 [[comment("Legacy")]]; + u64 total_sectors [[comment("Volume Size in Sectors")]]; + u64 mft_lcn [[comment("MFT Start Cluster")]]; + u64 mftmirr_lcn [[comment("MFTMirror Start Cluster")]]; + u8 clusters_per_mft_record [[comment("Size of MFT/Mirror in Clusters")]]; + u8 reserved2[3] [[comment("Unused")]]; + u8 clusters_per_index_buffer [[comment("Size of Index Buffer")]]; + u8 reserved3[3] [[comment("Unused")]]; + u64 volume_serial [[comment("Volume Serial #")]]; + u32 checksum [[comment("VBR Sector CRC32")]]; + u8 bootstrap[426] [[comment("Boot-strapping Instructions")]]; + VBRSignature VBR_SIG [[comment("End of VBR - 0x55AA")]]; +}; + +// ------------------------------ +// $DATA FUNC HELPER FOR INDEXES +// ------------------------------ +fn mft_get_dat_attr_lcn(MFT_RECORD record, ATTR_TYPES type = ATTR_TYPES::AT_DATA) { + u64 lcn = 0; + + for (u64 i = 0, i < record.FR_HEADER.next_attr_instance, i += 1) { + if (record.ATTRS[i].ATTR.ATTR_HEADER.type == type) { + for (s64 j = record.ATTRS[i].ATTR.x80_xB0_RUNLIST[0].RunList_Header.SE_Offset - 1, j >= 0, j -= 1) { + lcn |= record.ATTRS[i].ATTR.x80_xB0_RUNLIST[0].Logical_Cluster_Num[j] << (8 * j); + } + break; + } + } + return lcn; +}; + +// ------------------------------------------------------------------------- +// MAIN ENTRY RELATED +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- + +// ============= VBR ============================================================================================= +NTFS_BOOT_SECTOR nbs @ $ [[name("NTFS_VBR")]]; +u32 cluster_size = nbs.bpb.bytes_per_sector * nbs.bpb.sectors_per_cluster; +u32 mft_rec_size = 1 << -nbs.clusters_per_mft_record; + +VBR_OFFSET = $ + (nbs.bpb.hidden_sectors - 1) * nbs.bpb.bytes_per_sector; + +// ============= $MFT ============================================================================================ +MFT_RECORD mft @ nbs.mft_lcn * cluster_size + 0 * mft_rec_size + [[name(std::format("$MFT [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +MFT_RECORD mftmirr @ nbs.mftmirr_lcn * cluster_size + [[name(std::format("$MFT COPY (DERIVED FROM $MFTMirr)"))]]; + +// ============= $MFTMirr ======================================================================================== +MFT_RECORD mft_mirr @ nbs.mft_lcn * cluster_size + 1 * mft_rec_size + [[name(std::format("$MFT Mirror [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $LogFile ======================================================================================== +MFT_RECORD mft_log @ nbs.mft_lcn * cluster_size + 2 * mft_rec_size + [[name(std::format("$LogFile [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $Volume ========================================================================================= +MFT_RECORD mft_vol @ nbs.mft_lcn * cluster_size + 3 * mft_rec_size + [[name(std::format("$Volume [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $AttrDef ======================================================================================== +MFT_RECORD mft_adef @ nbs.mft_lcn * cluster_size + 4 * mft_rec_size + [[name(std::format("$AttrDef [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +ATTR_DEF adef_dat_buf[16] @ mft_get_dat_attr_lcn(mft_adef) * cluster_size + [[name("$AttrDef ARRAY (DERIVED)")]]; + +// ============= $I30 (Root) ===================================================================================== +MFT_RECORD mft_root @ nbs.mft_lcn * cluster_size + 5 * mft_rec_size + [[name(std::format("$I30 (Root) [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; +INDEX_BLOCK root_index_block @ mft_get_dat_attr_lcn(mft_root, ATTR_TYPES::AT_INDEX_ALLOCATION) * cluster_size + [[name(std::format("$I30 (Root) INDEX_BLOCK (DERIVED)"))]]; + +// ============= $Bitmap ========================================================================================= +MFT_RECORD mft_bm @ nbs.mft_lcn * cluster_size + 6 * mft_rec_size + [[name(std::format("$Bitmap [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $Boot =========================================================================================== +MFT_RECORD mft_boot @ nbs.mft_lcn * cluster_size + 7 * mft_rec_size + [[name(std::format("$Boot [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $BadClus ======================================================================================== +MFT_RECORD mft_badclus @ nbs.mft_lcn * cluster_size + 8 * mft_rec_size + [[name(std::format("$BadClus [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $Secure ($Quota for NTFS 1.2) =================================================================== +MFT_RECORD mft_sec @ nbs.mft_lcn * cluster_size + 9 * mft_rec_size + [[name(std::format("$Secure [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +// ============= $UpCase ========================================================================================= +MFT_RECORD mft_uc @ nbs.mft_lcn * cluster_size + 10 * mft_rec_size + [[name(std::format("$UpCase [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; +u8 uc_tbl[1] @ mft_get_dat_attr_lcn(mft_uc) * cluster_size [[name("$UpCase TABLE (DERIVED)")]]; + +// ============= $Extend ========================================================================================= +MFT_RECORD mft_ext @ nbs.mft_lcn * cluster_size + 11 * mft_rec_size + [[name(std::format("$Extend [{:02}][{:02}] | [x{:02X}][x{:02X}] | PRENT:[x{:02X}][x{:02X}]", + mft_num, seq_num, mft_num, seq_num, PRENT_MFT, PRENT_SEQ))]]; + +//============== RESERVED / UNUSED================================================================================ +// RESERVED/UNUSED FILE RECORD ENTRIES 12-23 | 0x0C-0x17 (13th-24th) + +// ============= NEXT RECORDS ==================================================================================== +// ---- FUNC TO CHECK FILE RECORD MAGIC +fn fr_magic(u64 abs_off) { + NTFS_RECORD_TYPES magic @ abs_off; + return magic; +}; + +// ---- CALL THE NEXT RECORDS ARRAY +MFT_RECORD NEXT_RECORDS[while(fr_magic($) == NTFS_RECORD_TYPES::magic_FILE)] @ (nbs.mft_lcn * cluster_size + 12 * mft_rec_size) [[inline, static]]; + +// ============= REPORT ========================================================================================== +// ------------------------------ +// NTFS VOLUME REPORT +// *** HAS GLOBAL AT TOP *** +// ------------------------------ +u64 mft_offset = VBR_OFFSET + nbs.mft_lcn * cluster_size + 0 * mft_rec_size; +u64 mft_mrr_offset = VBR_OFFSET + nbs.mftmirr_lcn * cluster_size; +u64 rootDir_offset = VBR_OFFSET + mft_get_dat_attr_lcn(mft_root, ATTR_TYPES::AT_INDEX_ALLOCATION) * cluster_size; + +if (VOLUME_REPORT) { + std::print("-----------------------------------------"); + std::print("------------- VOLUME_REPORT -------------"); + std::print("-----------------------------------------"); + std::print("VOL_LABEL = {}", Vol_Label); + std::print("NTFS_VERS = {}.{}", NT_Major, NT_Minor); + std::print("SECTOR_SIZE = {:02} BYTES", nbs.bpb.bytes_per_sector); + std::print("CLUSTER_SIZE = {:02} BYTES", cluster_size); + std::print("VOLUME_SIZE = {:02} SECTORS", nbs.total_sectors); + std::print("VOLUME_SIZE = {:.4f} GB @ 1000", (nbs.total_sectors * nbs.bpb.bytes_per_sector) / 1000.0 / 1000.0 / 1000.0); + std::print("VOLUME_SIZE = {:.4f} GiB @ 1024", (nbs.total_sectors * nbs.bpb.bytes_per_sector) / 1024.0 / 1024.0 / 1024.0); + + std::print("-----------------------------------------"); + std::print("MFT_OFFSET = {} | 0x{:02X}", mft_offset, mft_offset); + std::print("MFT_RECORDS = ~{:02} IN USE", MFT_Count); + std::print("MFT_RECORDS = ~{:02} NOT IN USE", Unused_Count); + + std::print("-----------------------------------------"); + std::print("MFT_COPY_OFFSET = {} | 0x{:02X}", mft_mrr_offset, mft_mrr_offset); + std::print("ROOT_DIR_OFFSET = {} | 0x{:02X}", rootDir_offset, rootDir_offset); + std::print("-----------------------------------------"); + std::print("------------------ END ------------------"); + std::print("-----------------------------------------"); + std::print(" "); +} \ No newline at end of file diff --git a/patterns/DFIR/exFAT.hexpat b/patterns/DFIR/exFAT.hexpat new file mode 100644 index 00000000..79995a88 --- /dev/null +++ b/patterns/DFIR/exFAT.hexpat @@ -0,0 +1,621 @@ +#pragma author Formula Zero One Technologies +#pragma description exFAT Filesystem Structures +#pragma MIME application/x-ima +#pragma endian little + +// ----------------------------------------------------------------------------- +// CREDIT +// ----------------------------------------------------------------------------- +// Based on /fs/exfat.hexpat by WerWolv +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// NOTES +// ----------------------------------------------------------------------------- +// Imported by DISK_PARSER.hexpat +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// TODO +// ----------------------------------------------------------------------------- +// Recursive parsing of Root Directory / SubDirs +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// IMPORTS +// ----------------------------------------------------------------------------- +import std.core; +import std.io; +import std.time; +import std.mem; +import type.guid; +import type.magic; +import type.base; + +// ------------------------------ +// FORWARD DECS/GLOBALS +// ------------------------------ + +// *** ATTENTION *** +// SET MAXIMUM NUMBER OF 4 BYTE CHUNKS TO PARSE FROM FAT1 +// SET MAXIMUM NUMBER OF DIRECTORY ENTRIES TO PARSE FROM ROOT DIRECTORY +// DEFAULTS ARE 4096 | 2500 +// Choose a value greater than 1 and less than 65536 OR increase the Array size limit with "#define... " + +// -------**************---vvvv--- | +const u64 MAX_FAT_CHUNKS = 4096; +// -------**************---^^^^--- | + +// -------**************---vvvv--- | +const u64 MAX_DIR_ENTRIES = 2500; +// -------**************---^^^^--- | + +// *** ATTENTION *** +// ---*******---*******----vvvv--- | +const bool VOLUME_REPORT = true; +// ---*******---*******----^^^^--- | + +u64 allocated_file_count; +u64 rdc; +u32 entry_size = 32; + +// -------------------------- +// exFAT DIRECTORY ENTRY HELPER +// -------------------------- +enum EntryType : u8 { + UNUSED_ENTRY = 0x00, + ACTIVE_VOLUME_GUID_ENTRY = 0xA0, + INACTIVE_VOLUME_GUID_ENTRY = 0x20, + ACTIVE_TEXFAT_ENTRY = 0xA1, + INACTIVE_TEXFAT_ENTRY = 0x21, + ACTIVE_ACCESS_CONTROL_ENTRY = 0xA2, + INACTIVE_ACCESS_CONTROL_ENTRY = 0x22, + ACTIVE_VOLUME_LABEL_ENTRY = 0x83, + INACTIVE_VOLUME_LABEL_ENTRY = 0x03, + ACTIVE_ALLOCATION_BITMAP_ENTRY = 0x81, + INACTIVE_ALLOCATION_BITMAP_ENTRY = 0x01, + ACTIVE_UPCASE_TABLE_ENTRY = 0x82, + INACTIVE_UPCASE_TABLE_ENTRY = 0x02, + ACTIVE_FILE_INFO_ENTRY = 0x85, + INACTIVE_FILE_INFO_ENTRY = 0x05, + ACTIVE_STREAM_ENTRY = 0xC0, + INACTIVE_STREAM_ENTRY = 0x40, + ACTIVE_FILENAME_ENTRY = 0xC1, + INACTIVE_FILENAME_ENTRY = 0x41, +}; + +// ------------------------------ +// DATES AND TIMES FUNC +// ------------------------------ +fn format_dos_time_field(std::time::DOSTime t) { + return std::time::format_dos_time(t, "{:02}:{:02}:{:02}"); +}; + +fn format_dos_date_field(std::time::DOSDate d) { + return std::time::format_dos_date(d, "{1:02}-{0:02}-{2:04}"); +}; + +// ------------------------------ +// BITFIELD HELPERS +// ------------------------------ +bitfield Entry_Flags { + unsigned TypeCode : 5; + unsigned Importance : 1; + unsigned Category : 1; + unsigned InUse : 1; +} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]]; + +bitfield Bitmap_Flags { + unsigned Bitmap_1 : 1; + unsigned Bitmap_2 : 1; +} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]]; + +bitfield General_Primary_Flags { + unsigned Allocation_Possible : 1; + unsigned No_FAT_Chain : 1; + unsigned Reserved : 6; +} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]]; + +bitfield General_Secondary_Flags { + unsigned Allocation_Possible : 1; + unsigned No_FAT_Chain : 1; + unsigned Reserved : 6; +} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]]; + +bitfield File_Attr_Flags { + unsigned Read_Only : 1; + unsigned Hidden : 1; + unsigned System : 1; + unsigned Directory : 1; + unsigned Archive : 1; + Reserved : 11; +} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 16)]]; + +// -------------------------- +// exFAT DIRECTORY ENTRY STRUCTURES +// -------------------------- +// xA0 / x20 = Volume GUID Entry +struct VolumeGUID_Entry { + Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]]; + u8 SecondaryCount[3] [[comment("COUNT OF SUBSEQUENT ENTRIES")]]; + type::Hex SetChecksum [[comment("16bit CHECKSUM")]]; + General_Primary_Flags PrimaryFlags; + type::GUID GUID; + u8 Reserved_1[9]; +}; + +// xA1 / x21 = TexFAT / Padding Entry +struct TexFATPadding_Entry { + Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]]; + u8 Reserved_1[31]; +}; + +// xA2 / x22 = Access Control Entry +struct AccessControl_Entry { + Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]]; + u8 Reserved[31]; +}; + +// x83 / x03 = Volume Label Entry +struct VolumeLabel_Entry { + Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]]; + u8 LabelLength [[comment("NUMBER OF UTF-16 CHARACTERS")]]; + char16 Label[LabelLength] [[comment("VOLUME LABEL: UTF-16")]]; + u8 Reserved[32-2-(LabelLength * 2)]; +}; + +// x81 / x01 = Allocation Bitmap Entry +struct AllocationBitmap_Entry { + Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]]; + Bitmap_Flags BitmapFlags; + u8 Reserved_1[18]; + u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]]; + u64 DataLength [[comment("DATA SIZE")]]; +}; + +// x82 / x02 = UpCase Table Entry +struct UpCaseTable_Entry { + Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]]; + u8 Reserved_1[3]; + type::Hex TableChecksum [[comment("16bit CHECKSUM")]]; + u8 Reserved_2[12]; + u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]]; + u64 DataLength [[comment("DATA SIZE")]]; +}; + +// x85 / x05 = File Info Entry +struct FileInfo_Entry { + Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]]; + u8 SecondaryCount [[comment("COUNT OF SUBSEQUENT ENTRIES")]]; + type::Hex SetChecksum [[comment("16bit CHECKSUM")]]; + File_Attr_Flags AttrFlags [[comment("FILE ATTRS: RASH")]]; + u16 Reserved_1; + std::time::DOSTime Created_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Created_Date [[format("format_dos_date_field")]]; + std::time::DOSTime Accessed_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Accessed_Date [[format("format_dos_date_field")]]; + std::time::DOSTime Modified_Time [[format("format_dos_time_field")]]; + std::time::DOSDate Modified_Date [[format("format_dos_date_field")]]; + u8 Created_10ms_Increments [[comment("Add to Times for Refinement")]]; + u8 Modified_10ms_Increments [[comment("Add to Times for Refinement")]]; + s8 Created_UTC_Diff [[comment("Add to Times for Refinement")]]; + s8 Modified_UTC_Diff [[comment("Add to Times for Refinement")]]; + s8 Accessed_UTC_Diff [[comment("Add to Times for Refinement")]]; + u8 Reserved[7]; +}; + +// xC1 / x41 = File Name Entry +struct FileName_Entry { + Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]]; + General_Secondary_Flags SecondaryFlags [[comment("COUNT OF SUBSEQUENT ENTRIES")]]; + char16 FileName[15] [[comment("FILE NAME: UTF-16")]]; +}; + +// xC0 / x40 = Stream Extension Entry +struct Stream_Entry { + Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]];; + General_Secondary_Flags SecondaryFlags [[comment("COUNT OF SUBSEQUENT ENTRIES")]]; + u8 Reserved_1; + u8 NameLength [[comment("STREAM LENGTH")]]; + type::Hex NameHash [[comment("16bit QUICK HASH: USED FOR FILE SEARCHING")]]; + u16 Reserved_2; + u64 InitSize [[comment("INITIALIZED SIZE")]]; + u32 Reserved_3; + u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]]; + u64 ActualSize [[comment("PHYSICAL DATA SIZE")]];; + u8 FILE_DATA[InitSize] @ temp_root_location + (FirstCluster - exFAT_VBR.root_dir_cluster) * bytesPerCluster [[comment("POINTER TO THE CLUSTER CONTENT")]]; +}; + +// -------------------------- +// exFAT ROOT DIRECTORY +// -------------------------- +struct RootDir { + EntryType Type; + padding[31]; + + match (Type) { + (EntryType::UNUSED_ENTRY): { + continue; + } + (EntryType::ACTIVE_VOLUME_GUID_ENTRY | EntryType::INACTIVE_VOLUME_GUID_ENTRY):{ + VolumeGUID_Entry VOLUME_GUID_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_TEXFAT_ENTRY | EntryType::INACTIVE_TEXFAT_ENTRY):{ + TexFATPadding_Entry TEXFAT_PADDING_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_ACCESS_CONTROL_ENTRY | EntryType::INACTIVE_ACCESS_CONTROL_ENTRY):{ + AccessControl_Entry ACCESS_CONTROL_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_VOLUME_LABEL_ENTRY | EntryType::INACTIVE_VOLUME_LABEL_ENTRY):{ + VolumeLabel_Entry VOLUME_LABEL_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_ALLOCATION_BITMAP_ENTRY | EntryType::INACTIVE_ALLOCATION_BITMAP_ENTRY):{ + AllocationBitmap_Entry ALLOCATION_BITMAP_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_UPCASE_TABLE_ENTRY | EntryType::INACTIVE_UPCASE_TABLE_ENTRY):{ + UpCaseTable_Entry UPCASE_TABLE_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_FILE_INFO_ENTRY | EntryType::INACTIVE_FILE_INFO_ENTRY):{ + FileInfo_Entry FILE_INFO_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_STREAM_ENTRY | EntryType::INACTIVE_STREAM_ENTRY):{ + Stream_Entry STREAM_EXT_ENTRY @ $ - 32; + } + (EntryType::ACTIVE_FILENAME_ENTRY | EntryType::INACTIVE_FILENAME_ENTRY):{ + FileName_Entry FILE_NAME_ENTRY @ $ - 32; + } + } +} [[name(format_entry_name(std::mem::read_unsigned($-32, 1), std::core::array_index()))]]; + +fn format_entry_name(auto entry_type, u64 idx) { + return std::format("{}[{}]", type_name(entry_type), idx); +}; + +// ------------------------------ +// TYPE RE-NAMER +// ------------------------------ +fn type_name(u32 type) { + if (type == EntryType::UNUSED_ENTRY) return "UNUSED_ENTRY"; + if (type == EntryType::ACTIVE_VOLUME_GUID_ENTRY) return "ACTIVE_VOLUME_GUID_ENTRY"; + if (type == EntryType::INACTIVE_VOLUME_GUID_ENTRY) return "INACTIVE_VOLUME_GUID_ENTRY"; + if (type == EntryType::ACTIVE_TEXFAT_ENTRY) return "ACTIVE_TEXFAT_ENTRY"; + if (type == EntryType::INACTIVE_TEXFAT_ENTRY) return "INACTIVE_TEXFAT_ENTRY"; + if (type == EntryType::ACTIVE_ACCESS_CONTROL_ENTRY) return "ACTIVE_ACCESS_CONTROL_ENTRY"; + if (type == EntryType::INACTIVE_ACCESS_CONTROL_ENTRY) return "INACTIVE_ACCESS_CONTROL_ENTRY"; + if (type == EntryType::ACTIVE_VOLUME_LABEL_ENTRY) return "ACTIVE_VOLUME_LABEL_ENTRY"; + if (type == EntryType::INACTIVE_VOLUME_LABEL_ENTRY) return "INACTIVE_VOLUME_LABEL_ENTRY"; + if (type == EntryType::ACTIVE_ALLOCATION_BITMAP_ENTRY) return "ACTIVE_ALLOCATION_BITMAP_ENTRY"; + if (type == EntryType::INACTIVE_ALLOCATION_BITMAP_ENTRY) return "INACTIVE_ALLOCATION_BITMAP_ENTRY"; + if (type == EntryType::ACTIVE_UPCASE_TABLE_ENTRY) return "ACTIVE_UPCASE_TABLE_ENTRY"; + if (type == EntryType::INACTIVE_UPCASE_TABLE_ENTRY) return "INACTIVE_UPCASE_TABLE_ENTRY"; + if (type == EntryType::ACTIVE_FILE_INFO_ENTRY) return "ACTIVE_FILE_INFO_ENTRY"; + if (type == EntryType::INACTIVE_FILE_INFO_ENTRY) return "INACTIVE_FILE_INFO_ENTRY"; + if (type == EntryType::ACTIVE_STREAM_ENTRY) return "ACTIVE_STREAM_ENTRY"; + if (type == EntryType::INACTIVE_STREAM_ENTRY) return "INACTIVE_STREAM_ENTRY"; + if (type == EntryType::ACTIVE_FILENAME_ENTRY) return "ACTIVE_FILENAME_ENTRY"; + if (type == EntryType::INACTIVE_FILENAME_ENTRY) return "INACTIVE_FILENAME_ENTRY"; + return "UNKNOWN"; +}; + +// ----------------------------------------------------------------------------- +// exFAT FILE ALLOCATION TABLE (FAT1) PARSER +// ----------------------------------------------------------------------------- + +const u32 CLUSTER_SIZE_BYTES = 4; // Each FAT32 entry = 4 bytes +const u32 FAT_EOF = 0x0FFFFFFF; // End-of-file marker +const u32 FAT_BAD = 0x0FFFFFF7; // Bad cluster marker +const u32 FIRST_ALLOC_CLUSTER = 2; // First usable cluster after reserved + +enum FAT_Flags : u32 { + UNALLOCATED = 0x00000000, + END_OF_FILE = 0xFFFFFFFF, // L.END + BAD_CLUSTER = 0xFFFFFFF7, // L.END +}; + +union FAT_Union { + u32 DECIMAL [[hidden]]; + FAT_Flags FAT_FLAG; +}; + +// ------------------------------ +// Helper function for pointer label +// ------------------------------ +fn cluster_label(u32 val) { + if (val == FAT_Flags::UNALLOCATED) + return "UNALLOCATED"; + if (val == FAT_Flags::BAD_CLUSTER) + return "BAD"; + if (val >= 0x0FFFFFF8) + return "EOF"; + return std::format("{}", val); +}; + +// ------------------------------ +// FAT1 HEAPS/CHAINS +// ------------------------------ +struct FAT_Entry { + FAT_Union FAT [[inline]]; + + u32 cluster_num = (FIRST_ALLOC_CLUSTER) + (std::core::array_index()); + + u32 next_cluster = FAT.DECIMAL & 0x0FFFFFFF; + + char hover_label[4] @ $ - 4 [[ + name(std::format( + "Cluster: {} → {}", + cluster_num, + cluster_label(next_cluster) + )) + ]]; + + bool is_eof = next_cluster >= 0x0FFFFFF8; + bool is_bad = next_cluster == FAT_BAD; + bool is_free = next_cluster == 0; + + if (is_eof) { + allocated_file_count += 1; + } +} [[name(format_fat_entry(FAT.DECIMAL, std::core::array_index(), FIRST_ALLOC_CLUSTER))]]; + +// ------------------------------ +// FAT FORMATTER FUNC +// ------------------------------ +fn format_fat_entry(u32 raw_value, u32 cluster_index, u32 first_alloc_cluster) { + u32 next_cluster = raw_value & 0x0FFFFFFF; + + str next_label; + + if (next_cluster == 0) + next_label = "UNALLOCATED"; + + else if (next_cluster == FAT_BAD) + next_label = "BAD"; + + else if (next_cluster == 0x0FFFFFFF) + next_label = "EOF"; + + else + next_label = std::format("{}", next_cluster); + + u32 logical_cluster = first_alloc_cluster + cluster_index; + + if (next_label == "UNALLOCATED" || next_label == "BAD" || next_label == "EOF") + return std::format("Cluster {}: {}", logical_cluster, next_label); + else + return std::format("Cluster {} → {}", logical_cluster, next_label); +}; + +// ------------------------------ +// MEDIA DESCRIPTOR HELPER +// ------------------------------ +enum Media_Descriptor : u8 { + SINGLE_SIDE_FLOPPY = 0xF0, + DOUBLE_SIDE_FLOPPY = 0xF9, + HARD_DISK_DRIVE = 0xF8, +}; + +// ------------------------------ +// FAT HEADER PARSER +// ------------------------------ +struct FAT_Header { + Media_Descriptor mediaDescriptor [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]];; + u8 exFAT_FAT_HEADER[7] [[comment("8 BYTES TOTAL: 4 BYTES REPRESENT PSUEDO CLUSTER 0 (SYSTEM) | 4 BYTES REPRESENT PSUEDO CLUSTER 1 (SYSTEM)(EOF)")]]; + + char dolla_BITMAP_label[4] @ $ [[ + name(std::format( + "$Bitmap" + )) + ]]; + + char dolla_UPCASE_label[4] @ $ + 4 [[ + name(std::format( + "$UpCase" + )) + ]]; + + char root_dir_label[4] @ $ + ((rdc - 2) * 4) [[ + name(std::format( + "ROOT_DIRECTORY" + )) + ]]; +}; + +// ------------------------------ +//SIGNATURE HELPER +// ------------------------------ +enum VBRSignature : u16 { + VBR_SIG = 0xAA55 +}; + +// ------------------------------ +// EXTENDED BOOT REGION +// ------------------------------ +struct ExtendedBoot { + u8 Extended_Boot_Sector[1 * bytesPerSector]; + VBRSignature VBR_SIG @ $ - 2; +}; + +// ------------------------------ +// BOOT SECTOR BITFIELD FLAGS +// ------------------------------ +bitfield VolumeFlags { + unsigned Active : 1; + unsigned VolumeDirty : 1; + unsigned Media_Failure : 1; + unsigned Clear_to_Zero : 1; + Rserved : 12; +} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 16)]]; + +// ------------------------------ +// EXFAT VOLUME BOOT RECORD +// ------------------------------ +struct exFAT_BootSector { + u8 jmp_boot[3]; + char fs_name[8]; // "EXFAT " + u8 must_be_zero[53]; + u64 partition_offset; // in sectors + u64 volume_length; // in sectors + u32 fat_offset; // in sectors + u32 fat_length; // in sectors + u32 cluster_heap_offset; // in sectors + u32 cluster_count; + u32 root_dir_cluster; + u32 volume_serial; + u16 fs_revision; + VolumeFlags volume_flags; + u8 bytes_per_sector_shift; // 2^n + u8 sectors_per_cluster_shift; // 2^n + u8 number_of_fats; + u8 drive_select; + u8 percent_in_use; + u8 reserved[7]; + u8 bootstrap[390]; + VBRSignature VBR_SIG; // 0x55AA + + rdc = root_dir_cluster; +}; + +// ------------------------------------------------------------------------- +// MAIN +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- + +exFAT_BootSector exFAT_VBR @ 0x0; + +// ------------------------------------------------------------------------- +// DERIVED CONSTANTS +// ------------------------------------------------------------------------- + +// ============= SIZES =================================================================== +u32 bytesPerSector = 1 << exFAT_VBR.bytes_per_sector_shift; +u32 bytesPerCluster = bytesPerSector << exFAT_VBR.sectors_per_cluster_shift; + +// ============= OFFSETS ================================================================= +u64 volumeStartSector = exFAT_VBR.partition_offset; +u64 volumeStartOffset = volumeStartSector * bytesPerSector; +u64 volumeSize = exFAT_VBR.volume_length * bytesPerSector; + +u64 FAT1_start_offset = exFAT_VBR.fat_offset * bytesPerSector; + +//For printing absolute offset +u64 RootDir_Offset = (exFAT_VBR.cluster_heap_offset + + ((exFAT_VBR.root_dir_cluster - 2) << exFAT_VBR.sectors_per_cluster_shift)) + * bytesPerSector + volumeStartOffset; + +// ============= CLUSTERS ================================================================ +u32 clusterSize = bytesPerCluster; +u32 clusterCount = exFAT_VBR.cluster_count; +u64 clusterHeapOffset = exFAT_VBR.cluster_heap_offset * bytesPerSector; + +// ------------------------------------------------------------------------- +// SECONDARY +// ------------------------------------------------------------------------- +// V V V V V V V V V +// ------------------------------------------------------------------------- +// ============= USAGE =================================================================== +u8 percentInUse = exFAT_VBR.percent_in_use; + +// ============= EBS ===================================================================== +ExtendedBoot Extended_Boot_Sectors[8] @ $; + +// ============= OEM ===================================================================== +u8 OEM_Parameters[1 * bytesPerSector] @ $; + +// ============= ER ====================================================================== +u8 Extended_Reserved[1 * bytesPerSector] @ $; + +// ============= BCS ===================================================================== +u8 Boot_Checksum[1 * bytesPerSector] @ $; + +// ============= BBS ===================================================================== +exFAT_BootSector Backup_Boot_Sector @ $; + +// ============= BEBS ==================================================================== +ExtendedBoot Backup_Extended_Boot_Sectors[8] @ $; + +// ============= BOEM ==================================================================== +u8 Backup_OEM_Parameters[1 * bytesPerSector] @ $; + +// ============= BER ===================================================================== +u8 Backup_Extended_Reserved[1 * bytesPerSector] @ $; + +// ============= BBCS ==================================================================== +u8 Backup_Boot_Checksum[1 * bytesPerSector] @ $; + +// ============= FAT ===================================================================== +// *** HAS GLOBAL AT TOP *** + +FAT_Header FAT1_HEADER @ FAT1_start_offset; +FAT_Entry FAT1[MAX_FAT_CHUNKS] @ FAT1_start_offset + 8; + +// ============= ROOT ==================================================================== +// ROOT DIRECTORY +// *** HAS GLOBAL AT TOP *** + +// for locating root directory within memory +u64 temp_root_location = (exFAT_VBR.root_dir_cluster - 2) * clusterSize + clusterHeapOffset; +RootDir ROOT_DIRECTORY[MAX_DIR_ENTRIES] @ temp_root_location; + + +// ============= REPORT ================================================================== +// VOLUME REPORT +// *** HAS GLOBAL AT TOP *** + +if (VOLUME_REPORT) { + std::print(" "); + std::print("-----------------------------------------"); + std::print("---------- EXFAT VOLUME_REPORT ----------"); + std::print("-----------------------------------------"); + std::print("FILE_SYSTEM = {}", exFAT_VBR.fs_name); + std::print("SERIAL_NUMBER = 0x{:X}", exFAT_VBR.volume_serial); + std::print("FS_REVISION = {}.{}", (exFAT_VBR.fs_revision >> 8) & 0xFF, exFAT_VBR.fs_revision & 0xFF); + + bool _any = false; + if(exFAT_VBR.volume_flags.Active) { + std::print("FAT_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Active); + _any = true; + } + if(exFAT_VBR.volume_flags.VolumeDirty) { + std::print("DIRTY_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Volume_Dirty); + _any = true; + } + if(exFAT_VBR.volume_flags.Media_Failure) { + std::print("FAILURE_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Media_Failure); + _any = true; + } + if(exFAT_VBR.volume_flags.Clear_to_Zero) { + std::print("CLEAR_TO_ZERO_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Clear_to_Zero); + _any = true; + } + if (!_any){ + std::print("VOL_FLAGS = NONE"); + } + + std::print("-----------------------------------------"); + std::print("BYTES/SECTOR = {}", bytesPerSector); + std::print("SECTORS/CLUSTER = {}", 1 << exFAT_VBR.sectors_per_cluster_shift); + std::print("BYTES/CLUSTER = {}", bytesPerCluster); + std::print("CLUSTER_COUNT = {}", clusterCount); + + std::print("-----------------------------------------"); + std::print("VOLUME_SIZE = {} SECTORS", exFAT_VBR.volume_length); + std::print("VOLUME_SIZE = {:.4f} GB @ 1000", volumeSize / 1000.0 / 1000.0 / 1000.0); + std::print("VOLUME_SIZE = {:.4f} GiB @ 1024", volumeSize / 1024.0 / 1024.0 / 1024.0); + + std::print("-----------------------------------------"); + std::print("VOLUME_START_SEC = {}", volumeStartSector); + std::print("VOLUME_START_OFF = 0x{:X}", volumeStartOffset); + + std::print("FAT1_START_OFF = 0x{:02X}", FAT1_start_offset); + std::print("CLUSTER_HEAP_OFF = 0x{:02X}", clusterHeapOffset); + std::print("ROOT_DIR_CLUSTER = {:02}", exFAT_VBR.root_dir_cluster); + std::print("ROOT_DIR_OFFSET = 0x{:02X}", RootDir_Offset); + + std::print("-----------------------------------------"); + std::print("PERCENT_IN_USE = {:02} %", percentInUse); + std::print("NUMBER_OF_FATS = {:02}", exFAT_VBR.number_of_fats); + std::print("DRIVE_SELECT = 0x{:02X}", exFAT_VBR.drive_select); + + std::print("-----------------------------------------"); + std::print("------------------ END ------------------"); + std::print("-----------------------------------------"); + std::print(" "); +} \ No newline at end of file From d60bfd9b398c6951980d2b472d3d62e972fad0d3 Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:03:10 -0400 Subject: [PATCH 02/10] DFIR_README.md --- patterns/DFIR/DFIR_README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/patterns/DFIR/DFIR_README.md b/patterns/DFIR/DFIR_README.md index 9bd88513..ab18c9e3 100644 --- a/patterns/DFIR/DFIR_README.md +++ b/patterns/DFIR/DFIR_README.md @@ -15,7 +15,7 @@ Use: - EXAMPLE: /dev/disk6 - Import Pattern File - EXAMPLE: DISK_PARSER.hexpat -[Pattern_Selection (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/2-DISK_PARSER-Pattern.png) +- [Pattern_Selection (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/2-DISK_PARSER-Pattern.png) - DISK_PARSER.hexpat - Recognize MBR/GPT Disks and parse MPT/GPT @@ -23,8 +23,8 @@ Use: - Auto load file system patterns for FAT32, exFAT, NTFS formatted volumes - Optional Disk Report -[DISK > MBR/GPT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3-DISK-HYBRID.png) -[DISK > MBR > MPT > 3 Primaries | 2 Logicals in an Extended (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3a-DISK-MBR.png) +- [DISK > MBR/GPT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3-DISK-HYBRID.png) +- [DISK > MBR > MPT > 3 Primaries | 2 Logicals in an Extended (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3a-DISK-MBR.png) - FAT32.hexpat - Auto loaded by DISK_PARSER.hexpat @@ -36,9 +36,9 @@ Use: - D/T Conversions - Optional FAT32 Volume Report -[VOLUME > FAT32 > FAT1 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/4-FAT32-1_SMALL_TXT.png) -[VOLUME > FAT32 > Root Dir (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/5-FAT32_ROOT_DIR.png) -[VOLUME > FAT32 > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/6-FAT32_SFN_POINTER.png) +- [VOLUME > FAT32 > FAT1 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/4-FAT32-1_SMALL_TXT.png) +- [VOLUME > FAT32 > Root Dir (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/5-FAT32_ROOT_DIR.png) +- [VOLUME > FAT32 > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/6-FAT32_SFN_POINTER.png) - exFAT.hexpat - Auto loaded by DISK_PARSER.hexpat @@ -49,9 +49,9 @@ Use: - D/T Conversions - Optional exFAT Volume Report -[VOLUME > exFAT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/7-exFAT-1.png) -[VOLUME > exFAT > Root Dir > xC0 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/8-exFAT_xC0.png) -[VOLUME > exFAT > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/9-exFAT-Data_Pointer.png) +- [VOLUME > exFAT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/7-exFAT-1.png) +- [VOLUME > exFAT > Root Dir > xC0 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/8-exFAT_xC0.png) +- [VOLUME > exFAT > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/9-exFAT-Data_Pointer.png) - NTFS.hexpat - Auto loaded by DISK_PARSER.hexpat @@ -63,9 +63,9 @@ Use: - D/T Conversions - Optional NTFS Volume Report -[VOLUME > NTFS > $MFT > D/T Conversion (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/10-NTFS-DT.png) -[VOLUME > NTFS > $MFT > x80 Run List (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/11-NTFS-DATA_RUN.png) -[VOLUME > NTFS > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/12-NTFS-DATA_POINTER.png) +- [VOLUME > NTFS > $MFT > D/T Conversion (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/10-NTFS-DT.png) +- [VOLUME > NTFS > $MFT > x80 Run List (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/11-NTFS-DATA_RUN.png) +- [VOLUME > NTFS > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/12-NTFS-DATA_POINTER.png) - Optional Reports - Simply copy the console output to a file... @@ -77,9 +77,9 @@ Use: - "false" = disabled Example Report: GPT > FAT32|exFAT -[exFAT_Report](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/exFAT_Report.txt) +- [exFAT_Report](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/exFAT_Report.txt) Example Report: MBR > 5 Logical Volumes (2 in an Extended) > All FAT32 Volumes -[MBR_5_VOLs](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/MBR_5_VOLs.txt) +- [MBR_5_VOLs](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/MBR_5_VOLs.txt) From 9e021111c8dcf0e32544297966d317b37521927f Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:05:09 -0400 Subject: [PATCH 03/10] DFIR_README.md --- patterns/DFIR/DFIR_README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/patterns/DFIR/DFIR_README.md b/patterns/DFIR/DFIR_README.md index ab18c9e3..a1e64af8 100644 --- a/patterns/DFIR/DFIR_README.md +++ b/patterns/DFIR/DFIR_README.md @@ -1,5 +1,5 @@ -ImHex Pattern Files - Digital Forensics - - [ImHex-DFIR-Patterns](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns) +ImHex Pattern Files - Digital Forensics: + - [ImHex-DFIR-Patterns](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns) Enhanced features of the stock Disk/Filesystem pattern files for forensic review of disk content. - [ImHex](https://github.com/WerWolv/ImHex) @@ -15,7 +15,7 @@ Use: - EXAMPLE: /dev/disk6 - Import Pattern File - EXAMPLE: DISK_PARSER.hexpat -- [Pattern_Selection (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/2-DISK_PARSER-Pattern.png) + - [Pattern_Selection (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/2-DISK_PARSER-Pattern.png) - DISK_PARSER.hexpat - Recognize MBR/GPT Disks and parse MPT/GPT @@ -23,8 +23,8 @@ Use: - Auto load file system patterns for FAT32, exFAT, NTFS formatted volumes - Optional Disk Report -- [DISK > MBR/GPT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3-DISK-HYBRID.png) -- [DISK > MBR > MPT > 3 Primaries | 2 Logicals in an Extended (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3a-DISK-MBR.png) + - [DISK > MBR/GPT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3-DISK-HYBRID.png) + - [DISK > MBR > MPT > 3 Primaries | 2 Logicals in an Extended (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3a-DISK-MBR.png) - FAT32.hexpat - Auto loaded by DISK_PARSER.hexpat @@ -36,9 +36,9 @@ Use: - D/T Conversions - Optional FAT32 Volume Report -- [VOLUME > FAT32 > FAT1 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/4-FAT32-1_SMALL_TXT.png) -- [VOLUME > FAT32 > Root Dir (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/5-FAT32_ROOT_DIR.png) -- [VOLUME > FAT32 > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/6-FAT32_SFN_POINTER.png) + - [VOLUME > FAT32 > FAT1 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/4-FAT32-1_SMALL_TXT.png) + - [VOLUME > FAT32 > Root Dir (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/5-FAT32_ROOT_DIR.png) + - [VOLUME > FAT32 > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/6-FAT32_SFN_POINTER.png) - exFAT.hexpat - Auto loaded by DISK_PARSER.hexpat @@ -49,9 +49,9 @@ Use: - D/T Conversions - Optional exFAT Volume Report -- [VOLUME > exFAT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/7-exFAT-1.png) -- [VOLUME > exFAT > Root Dir > xC0 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/8-exFAT_xC0.png) -- [VOLUME > exFAT > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/9-exFAT-Data_Pointer.png) + - [VOLUME > exFAT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/7-exFAT-1.png) + - [VOLUME > exFAT > Root Dir > xC0 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/8-exFAT_xC0.png) + - [VOLUME > exFAT > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/9-exFAT-Data_Pointer.png) - NTFS.hexpat - Auto loaded by DISK_PARSER.hexpat @@ -63,9 +63,9 @@ Use: - D/T Conversions - Optional NTFS Volume Report -- [VOLUME > NTFS > $MFT > D/T Conversion (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/10-NTFS-DT.png) -- [VOLUME > NTFS > $MFT > x80 Run List (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/11-NTFS-DATA_RUN.png) -- [VOLUME > NTFS > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/12-NTFS-DATA_POINTER.png) + - [VOLUME > NTFS > $MFT > D/T Conversion (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/10-NTFS-DT.png) + - [VOLUME > NTFS > $MFT > x80 Run List (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/11-NTFS-DATA_RUN.png) + - [VOLUME > NTFS > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/12-NTFS-DATA_POINTER.png) - Optional Reports - Simply copy the console output to a file... @@ -77,9 +77,9 @@ Use: - "false" = disabled Example Report: GPT > FAT32|exFAT -- [exFAT_Report](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/exFAT_Report.txt) + - [exFAT_Report](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/exFAT_Report.txt) Example Report: MBR > 5 Logical Volumes (2 in an Extended) > All FAT32 Volumes -- [MBR_5_VOLs](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/MBR_5_VOLs.txt) + - [MBR_5_VOLs](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/MBR_5_VOLs.txt) From dbc4403f25da6f6302bdea38b1f651bf8cbbd38d Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:06:16 -0400 Subject: [PATCH 04/10] DFIR_README.md --- patterns/DFIR/DFIR_README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/patterns/DFIR/DFIR_README.md b/patterns/DFIR/DFIR_README.md index a1e64af8..6c518ddb 100644 --- a/patterns/DFIR/DFIR_README.md +++ b/patterns/DFIR/DFIR_README.md @@ -1,15 +1,11 @@ ImHex Pattern Files - Digital Forensics: + - [ImHex-DFIR-Patterns](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns) Enhanced features of the stock Disk/Filesystem pattern files for forensic review of disk content. - [ImHex](https://github.com/WerWolv/ImHex) - [ImHex Patterns](https://github.com/WerWolv/ImHex-Patterns) -Install: - - Create a new folder called "DFIR" - - Add these updated pattern files to "DFIR" -[Folder_Structure (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/1-Folder_Structure.png) - Use: - Open a physical disk via Raw Provider (read-only) - EXAMPLE: /dev/disk6 From 517ff18d243624f09d0eb032d0abecc8b3d67c2a Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:24:42 -0400 Subject: [PATCH 05/10] DISK_PARSER.hexpat --- patterns/DFIR/DISK_PARSER.hexpat | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/patterns/DFIR/DISK_PARSER.hexpat b/patterns/DFIR/DISK_PARSER.hexpat index d01fcb2a..e91443e5 100644 --- a/patterns/DFIR/DISK_PARSER.hexpat +++ b/patterns/DFIR/DISK_PARSER.hexpat @@ -1,9 +1,7 @@ #pragma author Formula Zero One Technologies -#pragma description Disk / Volume / Filesystem Structures (MBR, GPT, FAT32, exFAT, NTFS) +#pragma description DFIR_DISK_PARSER_v2.0 #pragma MIME application/x-ima #pragma endian little -#pragma loop_limit 16000 - // ----------------------------------------------------------------------------- // CREDIT @@ -12,9 +10,6 @@ // ----------------------------------------------------------------------------- // TODO // ----------------------------------------------------------------------------- -// Auto apply pattern?... -// EXTENDED PARTITION and logical volumes in the container -// Other File Systems for GPT Entries // Refine File System Detection/Match // ----------------------------------------------------------------------------- // IMPORTS @@ -28,6 +23,7 @@ import type.time; import type.base; import hex.provider; +// WORKING IMPORTS import * from DFIR.FAT32 as FAT32Pat; import * from DFIR.exFAT as EXFATPat; import * from DFIR.NTFS as NTFSPat; @@ -36,8 +32,13 @@ import * from DFIR.NTFS as NTFSPat; // DISABLED IMPORTS // REFS - UNTESTED // EXT4 - GROUP DESC ERRORS +// APFS - PARTIALLY WORKS + // Comment out "using uuid_t = type::GUID" + // Replace all instances of "uuid_t" with "type::GUID" + // Comment out line 1456-EOF // JPEG/PNG - OFFSET ERRORS // ------------------------------------ +//import * from fs.apfs as APFSPat; //import * from fs.ext4 as EXT4Pat; //import * from fs.refs as REFSPat; //import * from jpeg as JPEGPat; @@ -318,9 +319,12 @@ struct GPT_PartitionEntry { else if (gpt_exfat_magic == "EXFAT ") EXFATPat EXFAT_VOL @ GPTpartitionOffset; } + // --------- DISABLED ----------------- // EXT4 PATTERN WAS INOP WHEN TESTED //(GUIDPartLabel::LINUX_FILE_SYSTEM): - //EXT4Pat EXT4_VOL @ 0x100400; + //EXT4Pat EXT4_VOL @ GPTpartitionOffset; + //(GUIDPartLabel::APPLE_APFS_CONT): + // APFSPat APFS_VOL @ GPTpartitionOffset; } } [[name(std::format("GPT_ENTRY [{}]", std::core::array_index()))]]; @@ -360,7 +364,7 @@ union MBRPartitionUnion { }; struct PartitionTableEntry { - // Standard partition fields + // partition table fields PartitionStatus ActiveFlag; CHS_Decoder Starting_CHS; MBRPartitionUnion Type; @@ -405,7 +409,6 @@ struct PartitionTableEntry { PartitionTableEntry EXTENDED_PARTITION[2] @ partitionOffset + 446; has_ext = parent_has_ext; - } (PartitionTypeCode::GPT_DISK_STD | PartitionTypeCode::GPT_DISK_SYS): // Set global flag From b84a2816274bcec312da53f8ee12f11dcf108d54 Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:40:58 -0400 Subject: [PATCH 06/10] DISK_PARSER.hexpat --- patterns/DFIR/DISK_PARSER.hexpat | 1 + 1 file changed, 1 insertion(+) diff --git a/patterns/DFIR/DISK_PARSER.hexpat b/patterns/DFIR/DISK_PARSER.hexpat index e91443e5..aa2222fe 100644 --- a/patterns/DFIR/DISK_PARSER.hexpat +++ b/patterns/DFIR/DISK_PARSER.hexpat @@ -3,6 +3,7 @@ #pragma MIME application/x-ima #pragma endian little + // ----------------------------------------------------------------------------- // CREDIT // ----------------------------------------------------------------------------- From 32ca333b22f3147544ad47eb80cb9e091eb5b61f Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:42:14 -0400 Subject: [PATCH 07/10] FAT32.hexpat --- patterns/DFIR/FAT32.hexpat | 1 + 1 file changed, 1 insertion(+) diff --git a/patterns/DFIR/FAT32.hexpat b/patterns/DFIR/FAT32.hexpat index c83d672b..c43b2797 100644 --- a/patterns/DFIR/FAT32.hexpat +++ b/patterns/DFIR/FAT32.hexpat @@ -3,6 +3,7 @@ #pragma MIME application/x-ima #pragma endian little + // ----------------------------------------------------------------------------- // CREDIT // ----------------------------------------------------------------------------- From e79937aad6f2f8363fa88082079e8aa78a31753d Mon Sep 17 00:00:00 2001 From: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:44:12 -0400 Subject: [PATCH 08/10] exFAT.hexpat --- patterns/DFIR/exFAT.hexpat | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/patterns/DFIR/exFAT.hexpat b/patterns/DFIR/exFAT.hexpat index 79995a88..3f8975a1 100644 --- a/patterns/DFIR/exFAT.hexpat +++ b/patterns/DFIR/exFAT.hexpat @@ -1,5 +1,5 @@ #pragma author Formula Zero One Technologies -#pragma description exFAT Filesystem Structures +#pragma description exFAT Filesystem (exFAT_v2.0) #pragma MIME application/x-ima #pragma endian little @@ -54,7 +54,6 @@ const bool VOLUME_REPORT = true; u64 allocated_file_count; u64 rdc; -u32 entry_size = 32; // -------------------------- // exFAT DIRECTORY ENTRY HELPER @@ -168,6 +167,7 @@ struct AllocationBitmap_Entry { u8 Reserved_1[18]; u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]]; u64 DataLength [[comment("DATA SIZE")]]; + u8 FILE_DATA[DataLength] @ temp_root_location + (FirstCluster - exFAT_VBR.root_dir_cluster) * bytesPerCluster [[comment("POINTER TO THE CLUSTER CONTENT")]]; }; // x82 / x02 = UpCase Table Entry @@ -178,6 +178,7 @@ struct UpCaseTable_Entry { u8 Reserved_2[12]; u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]]; u64 DataLength [[comment("DATA SIZE")]]; + u8 FILE_DATA[DataLength] @ temp_root_location + (FirstCluster - exFAT_VBR.root_dir_cluster) * bytesPerCluster [[comment("POINTER TO THE CLUSTER CONTENT")]]; }; // x85 / x05 = File Info Entry @@ -248,9 +249,13 @@ struct RootDir { } (EntryType::ACTIVE_ALLOCATION_BITMAP_ENTRY | EntryType::INACTIVE_ALLOCATION_BITMAP_ENTRY):{ AllocationBitmap_Entry ALLOCATION_BITMAP_ENTRY @ $ - 32; + u64 bitmap_cluster = ALLOCATION_BITMAP_ENTRY.FirstCluster; + char dolla_BITMAP_label[4] @ FAT1_start_offset + (bitmap_cluster * 4) [[name(std::format("$Bitmap"))]]; } (EntryType::ACTIVE_UPCASE_TABLE_ENTRY | EntryType::INACTIVE_UPCASE_TABLE_ENTRY):{ UpCaseTable_Entry UPCASE_TABLE_ENTRY @ $ - 32; + u64 upcase_cluster = UPCASE_TABLE_ENTRY.FirstCluster; + char dolla_UPCASE_label[4] @ FAT1_start_offset + (upcase_cluster * 4) [[name(std::format("$UpCase"))]]; } (EntryType::ACTIVE_FILE_INFO_ENTRY | EntryType::INACTIVE_FILE_INFO_ENTRY):{ FileInfo_Entry FILE_INFO_ENTRY @ $ - 32; @@ -398,17 +403,7 @@ struct FAT_Header { Media_Descriptor mediaDescriptor [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]];; u8 exFAT_FAT_HEADER[7] [[comment("8 BYTES TOTAL: 4 BYTES REPRESENT PSUEDO CLUSTER 0 (SYSTEM) | 4 BYTES REPRESENT PSUEDO CLUSTER 1 (SYSTEM)(EOF)")]]; - char dolla_BITMAP_label[4] @ $ [[ - name(std::format( - "$Bitmap" - )) - ]]; - - char dolla_UPCASE_label[4] @ $ + 4 [[ - name(std::format( - "$UpCase" - )) - ]]; + //Bitmap and UpCase overlays handled in RootDir parser char root_dir_label[4] @ $ + ((rdc - 2) * 4) [[ name(std::format( @@ -618,4 +613,4 @@ if (VOLUME_REPORT) { std::print("------------------ END ------------------"); std::print("-----------------------------------------"); std::print(" "); -} \ No newline at end of file +} From 2177815e6786e596b233e914bc6a8e27561fae70 Mon Sep 17 00:00:00 2001 From: F01TECH <59177844+F01TECH@users.noreply.github.com> Date: Wed, 17 Sep 2025 19:19:30 -0400 Subject: [PATCH 09/10] README.md Added DFIR related hexpats to table. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e20e8b76..6f4758e4 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | DDS | `image/vnd-ms.dds` | [`patterns/dds.hexpat`](patterns/dds.hexpat) | DirectDraw Surface | | DEX | | [`patterns/dex.hexpat`](patterns/dex.hexpat) | Dalvik EXecutable Format | | DICOM | `application/dicom` | [`patterns/dicom.hexpat`](patterns/dicom.hexpat) | DICOM image format | +| DISK_PARSER (DFIR) | `application/x-ima` | ['patterns/DFIR/DISK_PARSER.hexpat'](patterns/DFIR/DISK_PARSER.hexpat) | Recursive Disk/Volume/Filesystem parsing | | DMG | | [`patterns/dmg.hexpat`](patterns/dmg.hexpat) | Apple Disk Image Trailer (DMG) | | DMP | | [`patterns/dmp64.hexpat`](patterns/dmp64.hexpat) | Windows Kernel Dump(DMP64) | | DOTNET_BinaryFormatter | | [`patterns/dotnet_binaryformatter.hexpat`](patterns/dotnet_binaryformatter.hexpat) | .NET BinaryFormatter | @@ -72,9 +73,11 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | ELF | `application/x-executable` | [`patterns/elf.hexpat`](patterns/elf.hexpat) | ELF header in elf binaries | | EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log | | EXFAT | | [`patterns/fs/exfat.hexpat`](patterns/fs/exfat.hexpat) | Extensible File Allocation Table (exFAT) | +| EXFAT (DFIR) | | ['patterns/DFIR/exFAT.hexpat'](patterns/DFIR/exFAT.hexpat) | Imported by DISK_PARSER.hexpat | | EXT4 | | [`patterns/fs/ext4.hexpat`](patterns/fs/ext4.hexpat) | Ext4 File System | | FAS | | [`patterns/fas_oskasoftware.hexpat`](patterns/fas_oskasoftware.hexpat) [`patterns/fas_oskasoftware_old.hexpat`](patterns/fas_oskasoftware_old.hexpat) (Old versions of Oska DeskMate) | Oska Software DeskMates FAS (Frames and Sequences) file | | FAT32 | | [`patterns/fs/fat32.hexpat`](patterns/fs/fat32.hexpat) | FAT32 File System | +| FAT32 (DFIR) | | ['patterns/DFIR/FAT32.hexpat'](patterns/DFIR/FAT32.hexpat) | Imported by DISK_PARSER.hexpat | | FBX | | [`patterns/fbx.hexpat`](patterns/fbx.hexpat) | Kaydara FBX Binary | | FDT | | [`patterns/fdt.hexpat`](patterns/fdt.hexpat) | Flat Linux Device Tree blob | | FFX | | [`patterns/ffx/*`](https://gitlab.com/EvelynTSMG/imhex-ffx-pats) | Various Final Fantasy X files | @@ -127,6 +130,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | NRO | | [`patterns/nro.hexpat`](patterns/nro.hexpat) | Nintendo Switch NRO files | | NTAG | | [`patterns/ntag.hexpat`](patterns/ntag.hexpat) | NTAG213/NTAG215/NTAG216, NFC Forum Type 2 Tag compliant IC | | NTFS | | [`patterns/fs/ntfs.hexpat`](patterns/fs/ntfs.hexpat) | NTFS (NT File System) | +| NTFS (DFIR) | | ['patterns/DFIR/NTFS.hexpat'](patterns/DFIR/NTFS.hexpat) | Imported by DISK_PARSER.hexpat | | OGG | `audio/ogg` | [`patterns/ogg.hexpat`](patterns/ogg.hexpat) | OGG Audio format | | ORP / ORS | | [`patterns/orp.hexpat`](patterns/orp.hexpat) | OpenRGB profile format | | PACK | | [`patterns/roblox_pack.hexpat`](patterns/roblox_pack.hexpat) | Roblox shader archive format | From c32b23c07324448fab95eaf10b4eebdb7f41391a Mon Sep 17 00:00:00 2001 From: F01TECH <59177844+F01TECH@users.noreply.github.com> Date: Wed, 17 Sep 2025 19:24:59 -0400 Subject: [PATCH 10/10] README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6f4758e4..012cf1ed 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | DDS | `image/vnd-ms.dds` | [`patterns/dds.hexpat`](patterns/dds.hexpat) | DirectDraw Surface | | DEX | | [`patterns/dex.hexpat`](patterns/dex.hexpat) | Dalvik EXecutable Format | | DICOM | `application/dicom` | [`patterns/dicom.hexpat`](patterns/dicom.hexpat) | DICOM image format | -| DISK_PARSER (DFIR) | `application/x-ima` | ['patterns/DFIR/DISK_PARSER.hexpat'](patterns/DFIR/DISK_PARSER.hexpat) | Recursive Disk/Volume/Filesystem parsing | +| DISK_PARSER (DFIR) | `application/x-ima` | [`patterns/DFIR/DISK_PARSER.hexpat`](patterns/DFIR/DISK_PARSER.hexpat) | Recursive Disk/Volume/Filesystem parsing | | DMG | | [`patterns/dmg.hexpat`](patterns/dmg.hexpat) | Apple Disk Image Trailer (DMG) | | DMP | | [`patterns/dmp64.hexpat`](patterns/dmp64.hexpat) | Windows Kernel Dump(DMP64) | | DOTNET_BinaryFormatter | | [`patterns/dotnet_binaryformatter.hexpat`](patterns/dotnet_binaryformatter.hexpat) | .NET BinaryFormatter | @@ -73,11 +73,11 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | ELF | `application/x-executable` | [`patterns/elf.hexpat`](patterns/elf.hexpat) | ELF header in elf binaries | | EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log | | EXFAT | | [`patterns/fs/exfat.hexpat`](patterns/fs/exfat.hexpat) | Extensible File Allocation Table (exFAT) | -| EXFAT (DFIR) | | ['patterns/DFIR/exFAT.hexpat'](patterns/DFIR/exFAT.hexpat) | Imported by DISK_PARSER.hexpat | +| EXFAT (DFIR) | | [`patterns/DFIR/exFAT.hexpat`](patterns/DFIR/exFAT.hexpat) | Imported by DISK_PARSER.hexpat | | EXT4 | | [`patterns/fs/ext4.hexpat`](patterns/fs/ext4.hexpat) | Ext4 File System | | FAS | | [`patterns/fas_oskasoftware.hexpat`](patterns/fas_oskasoftware.hexpat) [`patterns/fas_oskasoftware_old.hexpat`](patterns/fas_oskasoftware_old.hexpat) (Old versions of Oska DeskMate) | Oska Software DeskMates FAS (Frames and Sequences) file | | FAT32 | | [`patterns/fs/fat32.hexpat`](patterns/fs/fat32.hexpat) | FAT32 File System | -| FAT32 (DFIR) | | ['patterns/DFIR/FAT32.hexpat'](patterns/DFIR/FAT32.hexpat) | Imported by DISK_PARSER.hexpat | +| FAT32 (DFIR) | | [`patterns/DFIR/FAT32.hexpat`](patterns/DFIR/FAT32.hexpat) | Imported by DISK_PARSER.hexpat | | FBX | | [`patterns/fbx.hexpat`](patterns/fbx.hexpat) | Kaydara FBX Binary | | FDT | | [`patterns/fdt.hexpat`](patterns/fdt.hexpat) | Flat Linux Device Tree blob | | FFX | | [`patterns/ffx/*`](https://gitlab.com/EvelynTSMG/imhex-ffx-pats) | Various Final Fantasy X files | @@ -130,7 +130,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | NRO | | [`patterns/nro.hexpat`](patterns/nro.hexpat) | Nintendo Switch NRO files | | NTAG | | [`patterns/ntag.hexpat`](patterns/ntag.hexpat) | NTAG213/NTAG215/NTAG216, NFC Forum Type 2 Tag compliant IC | | NTFS | | [`patterns/fs/ntfs.hexpat`](patterns/fs/ntfs.hexpat) | NTFS (NT File System) | -| NTFS (DFIR) | | ['patterns/DFIR/NTFS.hexpat'](patterns/DFIR/NTFS.hexpat) | Imported by DISK_PARSER.hexpat | +| NTFS (DFIR) | | [`patterns/DFIR/NTFS.hexpat`](patterns/DFIR/NTFS.hexpat) | Imported by DISK_PARSER.hexpat | | OGG | `audio/ogg` | [`patterns/ogg.hexpat`](patterns/ogg.hexpat) | OGG Audio format | | ORP / ORS | | [`patterns/orp.hexpat`](patterns/orp.hexpat) | OpenRGB profile format | | PACK | | [`patterns/roblox_pack.hexpat`](patterns/roblox_pack.hexpat) | Roblox shader archive format |