diff --git a/README.md b/README.md index d3b6961..d061bf7 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | PE | `application/x-dosexec` `application/x-msdownload` | [`patterns/pe.hexpat`](patterns/pe.hexpat) | PE header, COFF header, Standard COFF fields and Windows Specific fields | | PP | | [`patterns/selinuxpp.hexpat`](patterns/selinuxpp.pat) | SE Linux package | | PFS0 | | [`patterns/pfs0.hexpat`](patterns/pfs0.hexpat) | Nintendo Switch PFS0 archive (NSP files) | +| PF | | [`patterns/pf.hexpat`](patterns/pf.hexpat) | Microsoft uncompressed prefetch files (.pf) | | PIF | `image/pif` | [`patterns/pif.hexpat`](patterns/pif.hexpat) | PIF Image Format | | PKM | | [`patterns/pkm.hexpat`](patterns/pkm.hexpat) | PKM texture format | | PNG | `image/png` | [`patterns/png.hexpat`](patterns/png.hexpat) | PNG image files | diff --git a/patterns/pf.hexpat b/patterns/pf.hexpat new file mode 100644 index 0000000..18beeda --- /dev/null +++ b/patterns/pf.hexpat @@ -0,0 +1,161 @@ +#pragma author MrMcX +#pragma description Parse forensically relevant parts of Windows Prefetch(*.pf) (only uncompressed SCCA version) +/* +The pattern implements the uncompressed SCCA format which is used below Windows 10. +For the newer compressed file format starting with "MAM\x04" has to be decompressed first, +for example using the at https://gist.github.com/dfirfpi/113ff71274a97b489dfd + +Thanks to Joachim Metz for his work that this pattern is heavily based on: +https://github.com/libyal/libscca/blob/main/documentation/Windows%20Prefetch%20File%20(PF)%20format.asciidoc +Unknown values commented with "jm:" refer to Joachim Metz' format analysis +*/ + +#pragma magic [0x54 0x43 0x43 0x41] @ 0x04 + +#pragma endian little +import std.mem; +import std.string; +import type.time; +import type.magic; + +enum WinVer : u32 { + Windows11 = 31, + Windows10 = 30, + Windows8x = 26, + Windows7orVista = 23, + WindowsXPor2003 = 17 +}; + +enum Flag : u32 { + Boot = 0x01, + Application =0x00 +}; + +fn to_HexString32(u32 value) { + return std::string::to_upper(std::format("{:08x}", value)); +}; + +using HexString32 = u32 [[format("to_HexString32")]]; + +struct Header { + WinVer version; + type::Magic<"SCCA"> signature; + u32; + u32 size; + char16 program_name[30]; + HexString32 crc_hash; + Flag flag; +}; + +struct FileInformation { + u32 metrics_offset; + u32 metrics_entry_count; + u32 trace_chains_offset; + u32 trace_chains_entry_count; + u32 filenames_offset; + u32 filenames_size; + u32 volumes_information_offset; + u32 volumes_count; + u32 volumes_information_size; + if(header.version >= WinVer::Windows7orVista) { + padding[8]; + } + // Times are in FILETIME format (now shown as unix timestamp) + type::FILETIME last_execution; + if(header.version >= WinVer::Windows8x) { + type::FILETIME prev_executions[7]; + } + if(header.version <= WinVer::Windows10) { + u128; + } else { + u64; + } + u32 run_count; + u32; + u32 hash_string_offset = 0x0; + u32 hash_string_size = 0; + if(header.version >= WinVer::Windows10) { + u32; + u32 hash_string_offset; + u32 hash_string_size; + padding[76]; + } else if(header.version == WinVer::Windows8x) { + u32; + padding[84]; + } else if(header.version == WinVer::Windows7orVista) { + padding[80]; + } +}; + +struct FileReference { + u48 mft_entry_index; + u16 sequence_number; +}; + +struct FileMetric { + u32; // jm: might be prefetch start time in ms or index into the trace chain array + u32; // jm: might be prefetch duration in ms or number of entries in the trace chain + if(header.version >= WinVer::Windows7orVista) { + u32; // jm: might be Average prefetch duration in ms? + } + u32 filename_string_offset; + u32 filename_string_length; + u32 flags; + if(header.version >= WinVer::Windows7orVista) { + FileReference file_reference; + } +}; + +struct TraceChain { + if(header.version <= WinVer::Windows8x) { + u32 next_array_entry_index; + } + u32 total_block_load_count; + u8; + u8; // jm: might be sample duration in ms + u16; +}; + +struct DirectoryString { + u16 length; + char16 value[length+1]; +}; + +struct VolumeInformation { + auto vi_start = $; + u32 volume_device_path_offset; + u32 volume_device_path_length; + type::FILETIME volume_creation_time; + HexString32 volume_serial_number; + u32 file_references_offset; + u32 file_references_size; + u32 directory_strings_offset; + u32 directory_strings_count; + u32; + if(header.version >= WinVer::Windows7orVista) { + padding[(header.version >= WinVer::Windows10) ? 24 : 28]; + u32; // jm: might be copy of the number of directory strings + padding[(header.version >= WinVer::Windows10) ? 24 : 28]; + u32; + } + $ = vi_start + volume_device_path_offset; + char16 volume_device_path[volume_device_path_length]; + $ = vi_start + file_references_offset; + u32 file_reference_version; + u32 file_reference_count; + if(header.version >= WinVer::Windows7orVista) { + u64; + } + FileReference file_references[file_reference_count]; + $ = vi_start + directory_strings_offset; + DirectoryString directory_strings[directory_strings_count]; +}; + +Header header @ 0x00; +FileInformation info @ 0x54; +FileMetric metrics[info.metrics_entry_count] @ info.metrics_offset; +TraceChain trace_chains[info.trace_chains_entry_count] @ info.trace_chains_offset; +std::string::NullString16 filenames[while(!std::mem::reached(info.hash_string_offset))] @ info.filenames_offset; +// executable_path should be empty below windows 10 +char16 executable_path[(info.hash_string_size / 2) - 1] @ info.hash_string_offset; +VolumeInformation volume_informations[info.volumes_count] @ info.volumes_information_offset;