Translate

Image of Modern Operating Systems (3rd Edition)
Image of Linux Kernel Development (3rd Edition)
Image of XSLT 2.0 and XPath 2.0 Programmer's Reference (Programmer to Programmer)
Image of Advanced Programming in the UNIX Environment, Second Edition (Addison-Wesley Professional Computing Series)

Retrieve Microsoft Windows Product Key From UEFI Shell

With Microsoft Windows v3.0 activation, the product key is now stored in the UEFI firmware instead of being printed on a COA sticker. Specifically, on UEFI firmware, the individualized product key is stored in the ACPI (Advanced Configuration and Power Interface) table call MSDM ( Microsoft Data Management). This table contains the information necessary to enable individualized OEM activation.

Note that generic activation information is stored in a separate ACPI table named SLIC (Software Licensing). I will not be discussing that particular table in this post.

This utility is quite simple. It locates a pointer to the list of ACPI tables, looks for a table with a MSDM signature in the table header and, if found, prints out either the full table information or just the product key.

Here is the source code for the utility:

//
//  Copyright (c) 2015  Finnbarr P. Murphy.   All rights reserved.
//
//  Display MSDM table information including Windows product key
//
//  License: BSD License
//

#include <efi.h>
#include <efilib.h>


#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 }} 
#define EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION 0x02

#define EFI_SHELL_INTERFACE_GUID \
    {0x47c7b223, 0xc42a, 0x11d2, {0x8e,0x57,0x00,0xa0,0xc9,0x69,0x72,0x3b}}
#define SHELL_VARIABLE_GUID \
    {0x158def5a, 0xf656, 0x419c, {0xb0,0x27,0x7a,0x31,0x92,0xc0,0x79,0xd2}}

#undef DEBUG


typedef struct {
    CHAR8   Signature[8];
    UINT8   Checksum;
    UINT8   OemId[6];
    UINT8   Revision;
    UINT32  RsdtAddress;
    UINT32  Length;
    UINT64  XsdtAddress;
    UINT8   ExtendedChecksum;
    UINT8   Reserved[3];
} EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER;
 
// There are many kinds of SDT. All SDT may be split into two parts. 
// A common header and data which is different for each table.
typedef struct {
    CHAR8   Signature[4];
    UINT32  Length;
    UINT8   Revision;
    UINT8   Checksum;
    CHAR8   OemId[6];
    CHAR8   OemTableId[8];
    UINT32  OemRevision;
    CHAR8   CreatorId[4];
    UINT32  CreatorRevision;
} EFI_ACPI_SDT_HEADER;

// 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;

typedef enum {
    ARG_NO_ATTRIB         = 0x0,
    ARG_IS_QUOTED         = 0x1,
    ARG_PARTIALLY_QUOTED  = 0x2,
    ARG_FIRST_HALF_QUOTED = 0x4,
    ARG_FIRST_CHAR_IS_ESC = 0x8
} EFI_SHELL_ARG_INFO_TYPES;

struct _EFI_SHELL_ARG_INFO {
    UINT32 Attributes;
} __attribute__((packed)) __attribute__((aligned (1)));
typedef struct _EFI_SHELL_ARG_INFO EFI_SHELL_ARG_INFO;

struct _EFI_SHELL_INTERFACE {
    EFI_HANDLE           ImageHandle;
    EFI_LOADED_IMAGE    *Info;
    CHAR16             **Argv;
    UINTN                Argc;
    CHAR16             **RedirArgv;
    UINTN                RedirArgc;
    EFI_FILE            *StdIn;
    EFI_FILE            *StdOut;
    EFI_FILE            *StdErr;
    EFI_SHELL_ARG_INFO  *ArgInfo;
    BOOLEAN              EchoOn;
} __attribute__((packed)) __attribute__((aligned (1)));
typedef struct _EFI_SHELL_INTERFACE EFI_SHELL_INTERFACE;


static EFI_STATUS
get_args(EFI_HANDLE image, UINTN *argc, CHAR16 ***argv)
{
    EFI_STATUS Status;
    EFI_SHELL_INTERFACE *shell;
    EFI_GUID gEfiShellInterfaceGuid = EFI_SHELL_INTERFACE_GUID;

    Status = uefi_call_wrapper(BS->OpenProtocol, 6,
                               image, &gEfiShellInterfaceGuid,
                               (VOID **)&shell, image, NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL);
    if (EFI_ERROR(Status))
        return Status;

    *argc = shell->Argc;
    *argv = shell->Argv;

    Status = uefi_call_wrapper(BS->CloseProtocol, 4, image,
                               &gEfiShellInterfaceGuid, image, NULL);
    return Status; 
}


VOID
Ascii2UnicodeStr(CHAR8 *String, CHAR16 *UniString, UINT8 length)
{
    int len = length;

    while (*String != '\0' && len > 0) {
        *(UniString++) = (CHAR16) *(String++);
        len--;
    }
    *UniString = '\0';
}


VOID
Guid2String(CHAR16 *Buffer, EFI_GUID *Guid)
{
    SPrint(Buffer, 0, L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
           Guid->Data1, Guid->Data2, Guid->Data3,
           Guid->Data4[0], Guid->Data4[1], Guid->Data4[2], Guid->Data4[3], 
           Guid->Data4[4], Guid->Data4[5], Guid->Data4[6], Guid->Data4[7]);
} 


static VOID 
ParseMSDM(EFI_ACPI_MSDM *Msdm, int verbose)
{
    CHAR16 Buffer[100];

    Print(L"\n");
    if (verbose) {
        Ascii2UnicodeStr((CHAR8 *)(Msdm->Header.Signature), Buffer, 4);
        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);
        Ascii2UnicodeStr((CHAR8 *)(Msdm->Header.OemId), Buffer, 6);
        Print(L"Oem ID            : %s\n", Buffer);
        Ascii2UnicodeStr((CHAR8 *)(Msdm->Header.OemTableId), Buffer, 8);
        Print(L"Oem Table ID      : %s\n", Buffer);
        Print(L"Oem Revision      : %d\n", Msdm->Header.OemRevision);
        Ascii2UnicodeStr((CHAR8 *)(Msdm->Header.CreatorId), Buffer, 4);
        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);
    }
    Ascii2UnicodeStr((CHAR8 *)(Msdm->ProductKey), Buffer, 29);
    if (verbose) {
        Print(L"Product Key       : %s\n", Buffer);
    } else {
        Print(L"%s\n", Buffer);
    }
}


static int
ParseRSDP( EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp, CHAR16* GuidStr, int verbose)
{
    EFI_ACPI_SDT_HEADER *Xsdt, *Entry;
    CHAR16 SigStr[20], OemStr[20];
    UINT32 EntryCount;
    UINT64 *EntryPtr;
    int Index;

#ifdef DEBUG 
    Print(L"\n\nACPI GUID: %s\n", GuidStr);
#endif
    Ascii2UnicodeStr((CHAR8 *)(Rsdp->OemId), OemStr, 6);
#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 (strncmpa("XSDT", (CHAR8 *)(VOID *)(Xsdt->Signature), 4)) {
#ifdef DEBUG 
        Print(L"ERROR: Invalid ACPI XSDT table found.\n");
#endif
        return 1;
    }

    Ascii2UnicodeStr((CHAR8 *)(Xsdt->OemId), OemStr, 6);
    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 (Index = 0; Index < EntryCount; Index++, EntryPtr++) {
        Entry = (EFI_ACPI_SDT_HEADER *)((UINTN)(*EntryPtr));
        Ascii2UnicodeStr((CHAR8 *)(Entry->Signature), SigStr, 4);
        if (!strncmpa("MSDM", (CHAR8 *)(Entry->Signature), 4)) {
            ParseMSDM((EFI_ACPI_MSDM *)((UINTN)(*EntryPtr)), verbose);
        }
    }

    return 0;
}


static void
Usage(void)
{
    Print(L"Usage: listmsdm [-v|--verbose]\n");
}


EFI_STATUS
efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *systab)
{
    EFI_CONFIGURATION_TABLE *ect = systab->ConfigurationTable;
    EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp = NULL;
    EFI_GUID AcpiTableGuid = ACPI_TABLE_GUID;
    EFI_GUID Acpi2TableGuid = ACPI_20_TABLE_GUID;
    CHAR16 GuidStr[100];
    UINTN argc;
    CHAR16 **argv;
    EFI_STATUS Status = EFI_SUCCESS;
    int Index;
    int Verbose = 0;

    InitializeLib(image_handle, systab);

    Status = get_args(image_handle, &argc, &argv);
    if (EFI_ERROR(Status)) {
        Print(L"ERROR: Parsing command line arguments: %d\n", Status);
        return Status;
    }

    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 (Index = 0; Index < systab->NumberOfTableEntries; Index++) {
        if ((CompareGuid (&(systab->ConfigurationTable[Index].VendorGuid), &AcpiTableGuid)) ||
            (CompareGuid (&(systab->ConfigurationTable[Index].VendorGuid), &Acpi2TableGuid))) {
            if (!strncmpa("RSD PTR ", (CHAR8 *)(ect->VendorTable), 8)) {
                Guid2String(GuidStr, &(systab->ConfigurationTable[Index].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;
}


As with other UEFI utilities that I have published on this blog, it uses the GNU EFI library. I assume you are familiar with ACPI tables and UEFI APIs if you are reading this post and thus make no attempt to explain the source code.

Here is a suitable Makefile to build listmsdm.efi:

SRCDIR     = .
PREFIX     := /usr
HOSTARCH   := $(shell uname -m | sed s,i[3456789]86,ia32,)
ARCH       := $(shell uname -m | sed s,i[3456789]86,ia32,)
INCDIR     = -I.
CPPFLAGS   = -DCONFIG_$(ARCH)
CFLAGS     = $(ARCH3264) -g -O0 -fpic -Wall -fshort-wchar -fno-strict-aliasing -fno-merge-constants --std=gnu99 -D_GNU_SOURCE
ASFLAGS    = $(ARCH3264)
LDFLAGS    = -nostdlib
INSTALL    = install

CC         = gcc
AS         = as
LD         = ld.bfd
AR         = ar
RANLIB     = ranlib
OBJCOPY    = objcopy

ifeq ($(ARCH), ia32)
  LIBDIR := $(PREFIX)/lib
  ifeq ($(HOSTARCH), x86_64)
    ARCH3264 := -m32
  endif
endif

ifeq ($(ARCH), x86_64)
  CFLAGS += -mno-red-zone
  LIBDIR := $(PREFIX)/lib64
  ifeq ($(HOSTARCH), ia32)
    ARCH3264 := -m64
  endif
endif

FORMAT=efi-app-$(HOSTARCH)
LDFLAGS = -nostdlib -T $(LIBDIR)/gnuefi/elf_$(HOSTARCH)_efi.lds -shared -Bsymbolic $(LIBDIR)/gnuefi/crt0-efi-$(HOSTARCH).o -L$(LIBDIR)
LIBS=-lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name)
CCLDFLAGS =
CFLAGS = -I/usr/include/efi/ -I/usr/include/efi/$(HOSTARCH)/ -I/usr/include/efi/protocol -fpic -fshort-wchar -fno-reorder-functions -fno-strict-aliasing -fno-merge-constants -mno-red-zone -Wimplicit-function-declaration


TARGETS = listmsdm.efi

all : $(TARGETS)

clean : 
	@rm -rf *.o *.a *.so $(TARGETS)

.PHONY: all clean install


%.efi : %.so
	$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel \
		-j .rela -j .reloc --target=$(FORMAT) $*.so $@

%.so: %.o
	$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)

%.o: %.c
	$(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -c $< -o $@

%.S: %.c
	$(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -S $< -o $@

%.E: %.c
	$(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -E $< -o $@


Note that I have not tested a 32-bit build.

There are a number of utilities available that can retrieve the product key from within Microsoft Windows but as far as I know nobody to date has retrieved this information directly via the UEFI shell using a custom EFI utility. This utility, by the way, has been included in version 0.3 of my UEFI Rescue DVD (URD).