In a February 2015 post, I published the source code for a simple UEFI shell utility which could retrieve a Microsoft Windows product license key from a system running Windows 8 or later. The code was based on the GNU EFI development and runtime libraries.
There are a number of freely available EFI development environments. GNU EFI is probably the simplest of these and is what I recommend for beginners. It’s easy to install in a Linux environment such as Fedora 23 as both runtime and development RPMs are available from the Fedora repos.
The most sophisticated EFI development environment is the EFI Development Kit, Version 2, commonly known as EDK2. This also is the official UEFI reference source. Validated development snapshots are released from time to time with UDK2015 being the latest.
Here is the modified source code for the utility which successfully builds in UDK2015:
// // Copyright (c) 2015 Finnbarr P. Murphy. All rights reserved. // // Display Microsoft Windows License via MSDM ACPI table // // License: BSD License // #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/ShellLib.h> #include <Library/BaseLib.h> #include <Library/BaseMemoryLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/PrintLib.h> #include <Protocol/EfiShell.h> #include <Protocol/LoadedImage.h> #include <Protocol/AcpiSystemDescriptionTable.h> #include EFI_GUID_DEFINITION (Acpi) #undef DEBUG #define EFI_ACPI_TABLE_GUID \ { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d }} #define EFI_ACPI_20_TABLE_GUID \ { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 }} // Microsoft Data Management table structure typedef struct { EFI_ACPI_SDT_HEADER Header; UINT32 SlsVersion; UINT32 SlsReserved; UINT32 SlsDataType; UINT32 SlsDataReserved; UINT32 SlsDataLength; CHAR8 ProductKey[30]; } EFI_ACPI_MSDM; static VOID AsciiToUnicodeSize(CHAR8 *, UINT8, CHAR16 *); static VOID AsciiToUnicodeSize(CHAR8 *String, UINT8 length, CHAR16 *UniString) { int len = length; while (*String != '\0' && len > 0) { *(UniString++) = (CHAR16) *(String++); len--; } *UniString = '\0'; } static VOID ParseMSDM(EFI_ACPI_MSDM *Msdm, int verbose) { CHAR16 Buffer[100]; Print(L"\n"); if (verbose) { AsciiToUnicodeSize((CHAR8 *)&(Msdm->Header.Signature), 4, Buffer); Print(L"Signature : %s\n", Buffer); Print(L"Length : %d\n", Msdm->Header.Length); Print(L"Revision : %d\n", Msdm->Header.Revision); Print(L"Checksum : %d\n", Msdm->Header.Checksum); AsciiToUnicodeSize((CHAR8 *)(Msdm->Header.OemId), 6, Buffer); Print(L"Oem ID : %s\n", Buffer); AsciiToUnicodeSize((CHAR8 *)(Msdm->Header.OemTableId), 8, Buffer); Print(L"Oem Table ID : %s\n", Buffer); Print(L"Oem Revision : %d\n", Msdm->Header.OemRevision); AsciiToUnicodeSize((CHAR8 *)&(Msdm->Header.CreatorId), 4, Buffer); Print(L"Creator ID : %s\n", Buffer); Print(L"Creator Revision : %d\n", Msdm->Header.CreatorRevision); Print(L"SLS Version : %d\n", Msdm->SlsVersion); Print(L"SLS Reserved : %d\n", Msdm->SlsReserved); Print(L"SLS Data Type : %d\n", Msdm->SlsDataType); Print(L"SLS Data Reserved : %d\n", Msdm->SlsDataReserved); Print(L"SLS Data Length : %d\n", Msdm->SlsDataLength); } AsciiToUnicodeSize((CHAR8 *)(Msdm->ProductKey), 29, Buffer); if (verbose) { Print(L"Product Key : %s\n", Buffer); } else { Print(L"%s\n", Buffer); } Print(L"\n"); } static int ParseRSDP( EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp, CHAR16* GuidStr, int verbose) { EFI_ACPI_SDT_HEADER *Xsdt, *Entry; CHAR16 OemStr[20]; UINT32 EntryCount; UINT64 *EntryPtr; #ifdef DEBUG Print(L"\n\nACPI GUID: %s\n", GuidStr); #endif AsciiToUnicodeSize((CHAR8 *)(Rsdp->OemId), 6, OemStr); #ifdef DEBUG Print(L"\nFound RSDP. Version: %d OEM ID: %s\n", (int)(Rsdp->Revision), OemStr); #endif if (Rsdp->Revision >= EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION) { Xsdt = (EFI_ACPI_SDT_HEADER *)(Rsdp->XsdtAddress); } else { #ifdef DEBUG Print(L"ERROR: No ACPI XSDT table found.\n"); #endif return 1; } if (Xsdt->Signature != SIGNATURE_32 ('X', 'S', 'D', 'T')) { #ifdef DEBUG Print(L"ERROR: Invalid ACPI XSDT table found.\n"); #endif return 1; } AsciiToUnicodeSize((CHAR8 *)(Xsdt->OemId), 6, OemStr); EntryCount = (Xsdt->Length - sizeof (EFI_ACPI_SDT_HEADER)) / sizeof(UINT64); #ifdef DEBUG Print(L"Found XSDT. OEM ID: %s Entry Count: %d\n\n", OemStr, EntryCount); #endif EntryPtr = (UINT64 *)(Xsdt + 1); for (int Index = 0; Index < EntryCount; Index++, EntryPtr++) { Entry = (EFI_ACPI_SDT_HEADER *)((UINTN)(*EntryPtr)); if (Entry->Signature == SIGNATURE_32 ('M', 'S', 'D', 'M')) { ParseMSDM((EFI_ACPI_MSDM *)((UINTN)(*EntryPtr)), verbose); } } return 0; } static void Usage(void) { Print(L"Usage: listmsdm [-v|--verbose]\n"); } INTN EFIAPI ShellAppMain(UINTN Argc, CHAR16 **Argv) { EFI_CONFIGURATION_TABLE *ect = gST->ConfigurationTable; EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp = NULL; EFI_GUID AcpiTableGuid = EFI_ACPI_TABLE_GUID; EFI_GUID Acpi20TableGuid = EFI_ACPI_20_TABLE_GUID; EFI_STATUS Status = EFI_SUCCESS; CHAR16 GuidStr[100]; int Verbose = 0; if (Argc == 2) { if (!StrCmp(Argv[1], L"--verbose") || !StrCmp(Argv[1], L"-v")) { Verbose = 1; } if (!StrCmp(Argv[1], L"--help") || !StrCmp(Argv[1], L"-h") || !StrCmp(Argv[1], L"-?")) { Usage(); return Status; } } // locate RSDP (Root System Description Pointer) for (int i = 0; i < gST->NumberOfTableEntries; i++) { if ((CompareGuid (&(gST->ConfigurationTable[i].VendorGuid), &AcpiTableGuid)) || (CompareGuid (&(gST->ConfigurationTable[i].VendorGuid), &Acpi20TableGuid))) { if (!AsciiStrnCmp("RSD PTR ", (CHAR8 *)(ect->VendorTable), 8)) { UnicodeSPrint(GuidStr, sizeof(GuidStr), L"%g", &(gST->ConfigurationTable[i].VendorGuid)); Rsdp = (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *)ect->VendorTable; ParseRSDP(Rsdp, GuidStr, Verbose); } } ect++; } if (Rsdp == NULL) { if (Verbose) { Print(L"ERROR: Could not find an ACPI RSDP table.\n"); } return EFI_NOT_FOUND; } return Status; }
If you compare the above source code with that which I published in my February 2015 blog post, you will see that significant changes were necessary to port the source code to build in the UDK2015 development environment. Can you have a single source code base which compiles successfully in UDK2015 and GNU EFI development environments? Yes you can, but you will end up with source code containing a lot of compiler preprocessor directives.
Here is the .INF build file for the utility:
[Defines] INF_VERSION = 0x00010006 BASE_NAME = listmsdm FILE_GUID = 4ea87c51-7395-4dcd-0055-747010f3ce51 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = ShellCEntryLib VALID_ARCHITECTURES = X64 [Sources] listmsdm.c [Packages] MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] ShellCEntryLib ShellLib BaseLib BaseMemoryLib UefiLib [Protocols] [BuildOptions] [Pcd]
If you are unfamiliar with the EDK2 module build information (INF) file format, do an Internet search for Intel EDK II INF File Specification. It contains all the gory details. INF files are used by EDK2 utilities to parse module build meta-data files (INF, DEC, DSC and FDF files) in order to generate AutoGen.c and AutoGen.h and Makefile files for the EDK2 build infrastructure.
Here is sample output when the utility is invoked from a UEFI shell:
FS0> listmsdm -h Usage: listmsdm [-v|--verbose] FS0> listmddm NJCF7-T6T5Q-WRYF7-GY44B-VQVQ2 FS0> listmsdm -v Signature : MSDM Length : 85 Revision : 3 Checksum : 65 Oem ID : LENOVO Oem Table ID : TP-JB Oem Revision : 4480 Creator ID : PTEC Creator Revision : 2 SLS Version : 1 SLS Reserved : 0 SLS Data Type : 1 SLS Data Reserved : 0 SLS Data Length : 29 Product Key : NJCF7-T6T5Q-WRYF7-GY44B-VQVQ2
By the way, the product license key string has been altered so do not bother trying to use it!