From 9d5839c43ec8408560c9b00c4a5daaed13789a31 Mon Sep 17 00:00:00 2001 From: Marc Jones Date: Fri, 11 Jul 2025 12:38:47 -0600 Subject: [PATCH] Add UEFI Firmare Volume Variable Store pattern Add a pattern for UEFI Firmare Volume Variable store. This file type is commonly used with virtual machine UEFI variable files, like OVMF.fd used with QEMU. You could also extract a UEFI firmware binary from a flash device, search for the FV Variable Store, and set this pattern to the FV address. Signed-off-by: Marc Jones --- README.md | 1 + patterns/uefi_fv_varstore.hexpat | 212 ++++++++++++++++++ .../test_data/uefi_fv_varstore.hexpat.fd | Bin 0 -> 540672 bytes 3 files changed, 213 insertions(+) create mode 100644 patterns/uefi_fv_varstore.hexpat create mode 100644 tests/patterns/test_data/uefi_fv_varstore.hexpat.fd diff --git a/README.md b/README.md index 1f352a42..835b75a1 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | UPK | | [`patterns/upk-ue3.hexpat`](patterns/upk-ue3.hexpat) | Unreal Engine 3 UPK file | | UEFI | | [`patterns/uefi.hexpat`](patterns/uefi.hexpat)` | UEFI structs for parsing efivars | | UEFI Boot Entry | | [`patterns/uefi_boot_entry.hexpat`](patterns/uefi_boot_entry.hexpat) | UEFI Boot Entry (Load option) | +| UEFI Variable Store | | [`patterns/uefi_fv_varstore.hexpat`](patterns/uefi_fv_varstore.hexpat) | UEFI Firmware Volume Variable Store | | UF2 | | [`patterns/uf2.hexpat`](patterns/uf2.hexpat) | [USB Flashing Format](https://github.com/microsoft/uf2) | | Valve VPK | | [`patterns/valve_vpk.hexpat`](valve_vpk.hexpat) | Valve Package File | | VBMeta | | [`patterns/vbmeta.hexpat`](patterns/vbmeta.hexpat) | Android VBMeta image | diff --git a/patterns/uefi_fv_varstore.hexpat b/patterns/uefi_fv_varstore.hexpat new file mode 100644 index 00000000..4fc12ce5 --- /dev/null +++ b/patterns/uefi_fv_varstore.hexpat @@ -0,0 +1,212 @@ +/* + * ImHex Pattern for UEFI Firmware Volume Variable Store + * + * This file type is commonly used with virtual machine UEFI variable files, like OVMF.fd + * used with QEMU. You could also extract a UEFI firmware binary from a flash device, + * search for the FV Variable Store, and set this pattern to the FV address. + * + * A 'custom_vars.fd' can be generated with these tools: + * + * https://gitlab.com/kraxel/virt-firmware + * https://github.com/rhuefi/qemu-ovmf-secureboot/tree/master + * https://github.com/LongSoft/UEFITool + * + * 1. Generate a blank .fd file with ovmfvartool. + * + * $ ovmfvartool generate-blank blank.fd + * + * 2. Enroll the Redhat and Microsoft keys with virt-fw-vars in custom_vars.fd. + * + * $ virt-fw-vars -i blank.fd -o custom_vars.fd --enroll-redhat --secure-boot + * + * 3. Dump custom_vars.fd contents + * + * $virt-fw-vars -i custom_vars.fd -pvx + * + * or + * + * $ uefitool custom_vars.fd + * + * or use this pattern with ImHex! + */ + +#pragma author Marc Jones +#pragma UEFI Firmware Volume Variable Store +// #pragma debug + +import std.core; +import std.mem; +import type.guid; + + +// --- GUIDs --- + +#define NVRAM_FV "{FFF12B8D-7696-4C8B-A985-2747075B4F50}" +#define NVRAM_VARSTORE "{AAF32C78-947B-439A-A180-2E144EC37792}" + + +// --- Enumerations and Bitfields --- + +// Describes the type of a file within the Firmware File System. +enum FfsFileType : u8 { + RAW = 0x01, + FREEFORM = 0x02, + SECURITY_CORE = 0x03, + PEI_CORE = 0x04, + DXE_CORE = 0x05, + PEIM = 0x06, + DRIVER = 0x07, + COMBINED_PEIM_DRIVER = 0x08, + APPLICATION = 0x09, + SMM = 0x0A, + FIRMWARE_VOLUME_IMAGE = 0x0B, + COMBINED_SMM_DXE = 0x0C, + SMM_CORE = 0x0D, + FFS_PAD = 0xF0, +}; + +// Attributes for a UEFI variable, indicating its properties and accessibility. +bitfield VariableAttributes{ + NON_VOLATILE : 1; + BOOTSERVICE_ACCESS : 1; + RUNTIME_ACCESS : 1; + HARDWARE_ERROR_RECORD : 1; + AUTHENTICATED_WRITE_ACCESS : 1; + TIME_BASED_AUTHENTICATED_WRITE_ACCESS : 1; + APPEND_WRITE : 1; + RSVD: 25; +}; + +// +// Variable Store Header Format & State flags +// +enum VariableStoreFormat : u8 { + VARIABLE_STORE_FORMATTED = 0x5a, +}; + +enum VariableStoreState : u8 { + VARIABLE_STORE_HEALTHY = 0xfe, +}; + + +// +// Variable State flags. See https://countchu.blogspot.com/2014/09/the-life-cycle-of-uefi-variable-in.html +// +enum VariableState : u8 { + VAR_IN_DELETED_TRANSITION = 0xfe, + VAR_DELETED = 0xfd, + VAR_HEADER_VALID_ONLY = 0x7f, + VAR_ADDED = 0x3f, + VAR_ADDED__VAR_IN_DELETED_TRANSITION__VAR_DELETED = 0x3c, + VAR_ADDED__VAR_IN_DELETED_TRANSITION = 0x3e, + VAR_ADDED__VAR_DELETED = 0x3d, +}; + + +// --- Other Structures --- + +struct EFI_TIME { + u16 Year; + u8 Month; + u8 Day; + u8 Hour; + u8 Minute; + u8 Second; + u8 Pad1; + u32 Nanosecond; + u16 TimeZone; + u8 Daylight; + u8 Pad2; +}; + + +// --- Firmware Volume Structures --- + +// Header for a block in the firmware volume map. +struct EFI_FV_BLOCK_MAP_ENTRY { + u32 NumBlocks; + u32 Length; +}; + +// The main header for a Firmware Volume. +struct EFI_FIRMWARE_VOLUME_HEADER { + u128 ZeroVector; + type::GUID FileSystemGuid; + u64 FvLength; + u32 Signature; + u32 Attributes; + u16 HeaderLength; + u16 Checksum; + u16 ExtHeaderOffset; + u8 Reserved; + u8 Revision; + EFI_FV_BLOCK_MAP_ENTRY BlockMap[while(std::mem::read_unsigned($, 4) != 0 || std::mem::read_unsigned($ + 4, 4) != 0)]; + EFI_FV_BLOCK_MAP_ENTRY BlockMapTerminator; // After the loop, explicitly parse the (0,0) terminator entry +}[[single_color]]; + + +// --- UEFI Variable Structures --- + +struct VARIABLE_STORE_HEADER { + type::GUID Signature; + u32 Size; + VariableStoreFormat Format; + VariableStoreState State; + u16 Reserved; + u32 Reserved1; +}[[single_color]]; + + +#define VAR_START_ID 0x55AA + +struct VARIABLE_HEADER { + u16 StartId; + VariableState State; + u8 Reserved; + VariableAttributes Attributes; + u32 NameSize; + u32 DataSize; + type::GUID VendorGuid; +}; + +struct AUTHENTICATED_VARIABLE_HEADER { + u16 StartId; + VariableState State; + u8 Reserved; + VariableAttributes Attributes; + u64 MonotonicCount; + EFI_TIME TimeStamp; + u32 PubKeyIndex; + u32 NameSize; + u32 DataSize; + type::GUID VendorGuid; +}; + +struct UEFI_VARIABLE { + AUTHENTICATED_VARIABLE_HEADER Header; // TODO: Check authenticated vs normal variable... + char16 Name[Header.NameSize / 2]; // Name is a UTF-16 string + u8 Data[Header.DataSize]; + // Align to the next 4-byte boundary for the next variable. + u8 pad[std::mem::align_to(4, sizeof(this)) - sizeof(this)]; +} [[name(this.Name), single_color]]; + + +// --- Main Pattern Entry Point --- + +EFI_FIRMWARE_VOLUME_HEADER FV_Header @ 0; + +if (std::core::formatted_value(FV_Header.FileSystemGuid) != "{FFF12B8D-7696-4C8B-A985-2747075B4F50}") { + std::error(std::format("Unknown FV_Header.FileSystemGuid {}", std::core::formatted_value(FV_Header.FileSystemGuid))); +} + +// The next structure should be the Variable Store Header +VARIABLE_STORE_HEADER VarStore @ $; + +if (std::core::formatted_value(VarStore.Signature) != NVRAM_VARSTORE) { + std::error(std::format("Unknown VarStore.Signature {}", std::core::formatted_value(VarStore.Signature))); +} + +// Index through the Uefi variables until we don't find a Variable Signature 0x55AA +UEFI_VARIABLE UefiVars[while(std::mem::read_unsigned($, 2) == VAR_START_ID)] @ $; + +// TODO: grey out the Uefi variables that are in the non-active state, != VAR_ADDED. diff --git a/tests/patterns/test_data/uefi_fv_varstore.hexpat.fd b/tests/patterns/test_data/uefi_fv_varstore.hexpat.fd new file mode 100644 index 0000000000000000000000000000000000000000..b3ee104808ee9a04c2c7aa83feca9f3adde29d42 GIT binary patch literal 540672 zcmeIuF-TQW7zglU3K~U8h!EBik%kgY4T7XTErVA{p+rSdJ*gpkWo1GI4Ml^rXiCo@ z4I5MPzwihqtx2C&^q+6#QMVvk%RhEMtva1ajC7s(bg-#+I1$c=v*BDwht9Aw+Ebw; z{yz6Vnd{~=pD!EzTr%0-oS6xwOJCMD?Ts>yMY&zkvoXp{NBPa+R5%ijhLidHf_#2u zeA9f*78k_7x1eH~WVBvATK%@RZm@ZDYWB^F;~nW#l+zRSH~@6L%!mv@Z3?g+nzD+X#NYf^Jj{=1bSUyFE^ zC;|is5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkKc HkpzANH80}V literal 0 HcmV?d00001