Translate

Archives

Image of Android Wireless Application Development
Image of RHCE Red Hat Certified Engineer Linux Study Guide (Exam RH302) (Certification Press)
Image of Beginning Google Maps API 3
Image of Linux Kernel Development (3rd Edition)

Accessing ACPI SLIC From UEFI Shell

Those who follow my work in the UEFI Shell space are aware that I usually develop a number of new, and hopefully useful, UEFI shell utilities each year. This year, I plan to write three or four new utilities and enhance a number of existing utilities. This is the first of these new utilities.

In this post, I describe the ShowSLIC utility. It is the first of my new utilities and came about from license and booting issues caused by a disk failure on a friend’s laptop that was running Windows 7.

ShowSLIC is designed to enable you to retrieve SLIC (System License Internal Certificate) information from a UEFI-based Microsoft Windows PC or laptop. Such information is accessible (exposed) via the ACPI (Advanced Configuration and Power Interface) SLIP table.

Some history first. Licensing was easy to bypass (defeat) in Windows XP and earlier versions of Microsoft Windows. Windows Vista was the first version of Microsoft Windows whose royalty OEM licensing mechanism was harder to defeat. Basically, the BIOS was modified by an OEM to contain specific licensing information. This information was exposed to the operating system via the ACPI SLIC table.

When Windows Vista was installed by a royalty OEM, it would be given certificate files with appropriate OEM information and public keys. The certificates themselves were signed by a Microsoft private key to resist tampering. The Royalty OEM process was triggered by use of a generic OEM serial number (product key), common to all computers sold by a particular OEM. When the product key was entered, the system firmware was checked for a SLIC table, and the contents compared to any and all OEM certificates that had been loaded by Windows. If a SLIC table was present, and matched a valid OEM certificate, Windows Vista was offline activated.

The problem with this activation mechanism was that a royalty OEM generally had one key for each major version of Windows Vista (Home, Professional, etc), and you could change versions of Windows Vista by simply using a different OEM key.

Windows 7 OEM activation was exactly the same as for Windows Vista, just the certificate version was increased from SLIC 2.0 to SLIC 2.1. Windows 8 and Windows 8.1 could also use SLIC activation but the version of SLIC was rev’ed to SLIC 3.0.

Windows 8 was also the first version of Microsoft Windows to use a new royalty OEM licensing mechanism based on an ACPI table called MSDM. This table contains a hardware hash that matches the machine it is installed on, along with a full OEM Windows key, which is specific to the machine it is installed on. Activation via a generic OEM product key is no longer used, instead each OEM computer requires a unique activation product key. By the way, my ShowMSDM utility can be used to retrieve full MSDM information from the UEFI shell.

SLIC should not be confused with SLP (System Locked Pre-installation), which was a entirely different mechanism used by the major computer manufacturers to pre-activate Microsoft Windows computers before distribution. See this Wikipedia article for more information.

Here is the source code for the ShowSLIC utility:

//
//  Copyright (c) 2018  Finnbarr P. Murphy.   All rights reserved.
//
//  Display SLIC License Key
//
//  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 <Guid/Acpi.h>

#define UTILITY_VERSION L"20180227"
#undef DEBUG

#pragma pack(1)
// OEM Public Key
typedef struct {
    UINT32    Type;
    UINT32    Length;
    UINT8     KeyType;
    UINT8     Version;
    UINT16    Reserved;
    UINT32    Algorithm;
    CHAR8     Magic[4];
    UINT32    BitLength;
    UINT32    Exponent;
    UINT8     Modulus[128];
} OEM_PUBLIC_KEY;

// Windows Marker
typedef struct {
    UINT32    Type;
    UINT32    Length;
    UINT32    Version;   
    CHAR8     OemId[6];
    CHAR8     OemTableId[8];
    CHAR8     Product[8];
    UINT16    MinorVersion;
    UINT16    MajorVersion;
    UINT8     Signature[144];
} WINDOWS_MARKER;
 
// Software Licensing
typedef struct {
    EFI_ACPI_SDT_HEADER Header;
    OEM_PUBLIC_KEY      PubKey;
    WINDOWS_MARKER      Marker;
} EFI_ACPI_SLIC;
#pragma pack()

static VOID AsciiToUnicodeSize(CHAR8 *, UINT8, CHAR16 *, BOOLEAN);

static VOID
AsciiToUnicodeSize( CHAR8 *String, 
                    UINT8 length, 
                    CHAR16 *UniString,
                    BOOLEAN Quote )
{
    int len = length;
 
    if (Quote)
        *(UniString++) = L'"';
    while (*String != '\0' && len > 0) {
        *(UniString++) = (CHAR16) *(String++);
        len--;
    }
    if (Quote)
        *(UniString++) = L'"';
    *UniString = '\0';
}


static VOID
PrintHexTable( UINT8 *ptr, 
               int count )
{
    int i = 0;

    Print(L"  ");
    for (i = 0; i < count; i++ ) {
        if ( i > 0 && i%16 == 0)
            Print(L"\n  ");
        Print(L"0x%02x ", 0xff & *ptr++);
    }
    Print(L"\n");
}


static VOID
PrintAcpiHeader( EFI_ACPI_SDT_HEADER *Ptr )
{
    CHAR16 Buffer[50];

    Print(L"ACPI Standard Header\n");
    AsciiToUnicodeSize((CHAR8 *)&(Ptr->Signature), 4, Buffer, TRUE);
    Print(L"  Signature         : %s\n", Buffer);
    Print(L"  Length            : 0x%08x (%d)\n", Ptr->Length, Ptr->Length);
    Print(L"  Revision          : 0x%02x (%d)\n", Ptr->Revision, Ptr->Revision);
    Print(L"  Checksum          : 0x%02x (%d)\n", Ptr->Checksum, Ptr->Checksum);
    AsciiToUnicodeSize((CHAR8 *)&(Ptr->OemId), 6, Buffer, TRUE);
    Print(L"  OEM ID            : %s\n", Buffer);
    AsciiToUnicodeSize((CHAR8 *)&(Ptr->OemTableId), 8, Buffer, TRUE);
    Print(L"  OEM Table ID      : %s\n", Buffer);
    Print(L"  OEM Revision      : 0x%08x (%d)\n", Ptr->OemRevision, Ptr->OemRevision);
    AsciiToUnicodeSize((CHAR8 *)&(Ptr->CreatorId), 4, Buffer, TRUE);
    Print(L"  Creator ID        : %s\n", Buffer);
    Print(L"  Creator Revision  : 0x%08x (%d)\n", Ptr->CreatorRevision, Ptr->CreatorRevision);
    Print(L"\n");
}


static VOID
PrintOemPublicKey( OEM_PUBLIC_KEY *Ptr,
                   int Verbose )
{
    CHAR16 Buffer[50];

    Print(L"OEM Public Key\n");
    Print(L"  Type              : 0x%08x (%d)\n", Ptr->Type, Ptr->Type);
    Print(L"  Length            : 0x%08x (%d)\n", Ptr->Length, Ptr->Length);
    Print(L"  KeyType           : 0x%02x (%d)\n", Ptr->KeyType, Ptr->KeyType);
    Print(L"  Version           : 0x%02x (%d)\n", Ptr->Version, Ptr->Version);
    Print(L"  Reserved          : 0x%04x (%d)\n", Ptr->Reserved, Ptr->Reserved);
    Print(L"  Algorithm         : 0x%08x (%d)\n", Ptr->Algorithm, Ptr->Algorithm);
    AsciiToUnicodeSize((CHAR8 *)&(Ptr->Magic), 4, Buffer, TRUE);
    Print(L"  Magic             : %s\n", Buffer);
    Print(L"  Bit Length        : 0x%08x (%d)\n", Ptr->BitLength, Ptr->BitLength);
    Print(L"  Exponent          : 0x%08x (%d)\n", Ptr->Exponent, Ptr->Exponent);
    if (Verbose) {
        Print(L"  Modulus:\n");
        PrintHexTable( (UINT8 *)&(Ptr->Modulus), (int)(Ptr->BitLength)/8 );
    }
    Print(L"\n");
}


static VOID
PrintWindowsMarker( WINDOWS_MARKER *Ptr, 
                    int Verbose )
{
    CHAR16 Buffer[50];

    Print(L"Windows Marker\n");
    Print(L"  Type              : 0x%08x (%d)\n", Ptr->Type, Ptr->Type);
    Print(L"  Length            : 0x%08x (%d)\n", Ptr->Length, Ptr->Length);
    Print(L"  Version           : 0x%02x (%d)\n", Ptr->Version, Ptr->Version);
    AsciiToUnicodeSize((CHAR8 *)(Ptr->OemId), 6, Buffer, TRUE);
    Print(L"  OEM ID            : %s\n", Buffer);
    AsciiToUnicodeSize((CHAR8 *)(Ptr->OemTableId), 8, Buffer, TRUE);
    Print(L"  OEM Table ID      : %s\n", Buffer);
    AsciiToUnicodeSize((CHAR8 *)(Ptr->Product), 8, Buffer, TRUE);
    Print(L"  Windows Flag      : %s\n", Buffer);
    Print(L"  SLIC Version      : 0x%04x%04x (%d.%d)\n", Ptr->MajorVersion, Ptr->MinorVersion,
                                                         Ptr->MajorVersion, Ptr->MinorVersion);
    if (Verbose) {
        Print(L"  Signature:\n");
        PrintHexTable( (UINT8 *)&(Ptr->Signature), 144 );   // 144 - Fix up in next version
    }
    Print(L"\n");
}


static VOID 
PrintSLIC( EFI_ACPI_SLIC *Slic, 
           int Verbose )
{
    Print(L"\n");
    PrintAcpiHeader( (EFI_ACPI_SDT_HEADER *)&(Slic->Header) );
    PrintOemPublicKey( (OEM_PUBLIC_KEY *)&(Slic->PubKey), Verbose );
    PrintWindowsMarker( (WINDOWS_MARKER *)&(Slic->Marker), Verbose );
}


static int
ParseRSDP( EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp, 
           CHAR16* GuidStr, 
           int Verbose )
{
    EFI_ACPI_SDT_HEADER *Xsdt, *Entry;
#ifdef DEBUG 
    CHAR16 OemStr[20];
#endif
    UINT32 EntryCount;
    UINT64 *EntryPtr;

#ifdef DEBUG 
    Print(L"\n\nACPI GUID: %s\n", GuidStr);

    AsciiToUnicodeSize((CHAR8 *)(Rsdp->OemId), 6, OemStr, FALSE);
    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;
    }

    EntryCount = (Xsdt->Length - sizeof (EFI_ACPI_SDT_HEADER)) / sizeof(UINT64);
#ifdef DEBUG 
    AsciiToUnicodeSize((CHAR8 *)(Xsdt->OemId), 6, OemStr, FALSE);
    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 ('S', 'L', 'I', 'C')) {
            PrintSLIC((EFI_ACPI_SLIC *)((UINTN)(*EntryPtr)), Verbose);
        }
    }

    return 0;
}


static void
Usage( void )
{
    Print(L"Usage: ShowSLIC [-v | --verbose]\n");
    Print(L"       ShowSLIC [-V | --version]\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 gAcpi20TableGuid = EFI_ACPI_20_TABLE_GUID;
    EFI_GUID gAcpi10TableGuid = ACPI_10_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;
        } else if (!StrCmp(Argv[1], L"--version") ||
            !StrCmp(Argv[1], L"-V")) {
            Print(L"Version: %s\n", UTILITY_VERSION);
            return Status;
        } else if (!StrCmp(Argv[1], L"--help") ||
            !StrCmp(Argv[1], L"-h")) {
            Usage();
            return Status;
        } else {
            Usage();
            return Status;
        }
    }
    if (Argc > 2) {
        Usage();
        return Status;
    }

    // locate RSDP (Root System Description Pointer) 
    for (int i = 0; i < gST->NumberOfTableEntries; i++) {
        if ((CompareGuid (&(gST->ConfigurationTable[i].VendorGuid), &gAcpi20TableGuid)) ||
            (CompareGuid (&(gST->ConfigurationTable[i].VendorGuid), &gAcpi10TableGuid))) {
            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");
        }
        Status = EFI_NOT_FOUND;
    }

    return Status;
}


There is nothing unusual in the above code so I will not go into details about each function. It was designed to be built using the UDK2017 snapshot of TianoCore EDK2.

Note that I have switched all UEFI utilities that I maintain from GNU-EFI or UDK2015 and GCC to UDK2017 and LLVM Clang. Note also that I no longer use utility version numbers such as “1.0” or “1.1”. Now I use a date string of the form “YYYYMMDD”.

Here is the INF build configuration file used to build this utility:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = ShowSLIC 
  FILE_GUID                      = 4ea87c51-7395-4dcd-0055-747010f3ce51
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib
  VALID_ARCHITECTURES            = X64

[Sources]
  ShowSLIC.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  ShellCEntryLib
  ShellLib
  BaseLib
  BaseMemoryLib
  UefiLib

[Protocols]

[BuildOptions]

[Pcd]


AS usual, I expect that you to know what an .INF is if you are reading this post so no further build instructions are provided.

Here is a binary dump of the SLIC table from a Lenovo T450 laptop which originally came with Windows 7, was upgraded to Windows 8, followed by Windows 8.1 and now runs Windows 10:

Here is the output from this utility when it was run on this laptop:

fs1> ShowSLIC.efi -v

ACPI Standard Header
  Signature         : "SLIC"
  Length            : 0x00000176 (374)
  Revision          : 0x01 (1)
  Checksum          : 0xF4 (244)
  OEM ID            : "LENOVO"
  OEM Table ID      : "TP-JB   "
  OEM Revision      : 0x00001300 (4864)
  Creator ID        : "PTEC"
  Creator Revision  : 0x00000001 (1)

OEM Public Key
  Type              : 0x00000000 (0)
  Length            : 0x0000009C (156)
  KeyType           : 0x06 (6)
  Version           : 0x02 (2)
  Reserved          : 0x0000 (0)
  Algorithm         : 0x00002400 (9216)
  Magic             : "RSA1"
  Bit Length        : 0x00000400 (1024)
  Exponent          : 0x00010001 (65537)
  Modulus:
  0x69 0x16 0x4A 0x9F 0xB1 0x4B 0x3A 0xFB 0x80 0x20 0xAA 0xAF 0xC4 0xF9 0x3E 0xC1 
  0x80 0x49 0xEE 0x6A 0x65 0x26 0x72 0x1E 0xCD 0xBF 0x5F 0x2F 0x96 0xD6 0xC0 0x0A 
  0x92 0xF5 0x06 0xB5 0x00 0xB2 0x3B 0x29 0x02 0xE2 0x4C 0x8D 0xC2 0xF2 0xBC 0x41 
  0x77 0x9C 0x70 0xF0 0xF3 0x1B 0x09 0xD2 0x63 0x5A 0xDC 0xA8 0x83 0xF8 0x5E 0xC9 
  0x15 0x95 0xF9 0xFA 0xFD 0xDC 0x05 0xB7 0x4D 0x67 0x7F 0x2D 0xB3 0x84 0x33 0x20 
  0xE1 0xD1 0x79 0x2A 0xA7 0x6A 0x77 0xD1 0xB6 0x20 0x2A 0x76 0x42 0xC5 0xD5 0xE9 
  0xB6 0x43 0x40 0x55 0x44 0xC3 0xC9 0x37 0x99 0x5F 0x41 0x97 0x70 0xF3 0xD1 0xF6 
  0x07 0xEC 0x7B 0x1A 0x29 0xA1 0xC1 0xF1 0x91 0xFD 0x48 0x86 0x6E 0x3E 0xCE 0xCB 

Windows Marker
  Type              : 0x00000001 (1)
  Length            : 0x000000B6 (182)
  Version           : 0x20000 (131072)
  OEM ID            : "LENOVO"
  OEM Table ID      : "TP-JB   "
  Windows Flag      : "WINDOWS "
  SLIC Version      : 0x00020001 (2.1)
  Signature:
  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
  0x85 0x15 0x64 0xED 0x0E 0x94 0x48 0xA3 0x86 0xA9 0x2D 0xFE 0x14 0x93 0xD1 0x60 
  0xC0 0xCA 0x07 0x91 0xBF 0x4F 0xE3 0x1E 0x19 0xD1 0x62 0xC3 0xDE 0x0E 0xC9 0xDA 
  0x99 0xF6 0xE3 0x12 0x6F 0x6E 0xF1 0x71 0xD8 0x33 0x59 0xF2 0x15 0x12 0x3A 0xAE 
  0xEF 0xC4 0x72 0x90 0x81 0x7E 0xD2 0xB9 0x77 0x54 0x37 0xFE 0xF9 0x96 0x91 0x45 
  0xD0 0x13 0x2A 0xF2 0x4B 0x38 0x07 0x0E 0x35 0x9F 0xF1 0xC6 0x22 0x8F 0xB9 0x85 
  0xE3 0xC0 0xC1 0xBE 0x50 0x72 0x65 0x59 0xE4 0x26 0x2B 0xB4 0x5A 0x14 0x4C 0x20 
  0x1F 0x89 0x06 0xDE 0x36 0x21 0x04 0xDF 0x03 0x7C 0x41 0x9A 0x76 0x95 0xBA 0x17 
  0x5C 0x2C 0x2F 0x51 0x70 0xBF 0xEF 0xB1 0x4A 0x92 0x3D 0x4E 0x8A 0x5C 0x89 0xA1 

fs1>


Note that OEMID and OEMTableID are not just exposed in the SLIC table. In fact, they are exposed in all system ACPI table headers except FACS and are considered the identifier of a category of physical computers. However, they play an important role in SLIC-based license activation as they are part of the Windows Marker section of a SLIC table.

Finally, I do not know how to extract information from the signature field of the Windows Marker section of the SLIC table. I do know that it is some sort of digital certificate generated by the appropriate version (in this case 2.1) of the Microsoft OATool (OEM Activation Tool).

Enjoy!

Comments are closed.