Reading PCR value from UEFI

machinehunter

MachineHunter

Posted on July 25, 2022

Reading PCR value from UEFI

PCR is one of the most-used TPM functionality in UEFI security. Measured boot which ensures integrity of UEFI firmware is a good example of the use of PCR. In this post, I will explain how to read PCR value from UEFI module.
If you are not familiar with TPM programming from UEFI, it might be better to start with my Accessing TPM2.0 from UEFI module Series.

PCR explanaition

PCRs (Platform Configuration Registers) in TPM holds measurements of software states. UEFI uses this value to ensure if none of the code during the bootphase are modified. Value in PCR is actually a hash and this can only be updated by an operation called extend (or system reset). Before executing the next program (e.g. DxeCore, bootloader), the caller extend the PCR value by calculating PCR[i] = H(PCR[i]||m). PCR[i] is the i th PCR's value, H() is the hash function and m is the new measurement of the next program going to be executed.

TPM allows you to control access towards entities depending on PCR value. For example, if you want to have a key which is only accessible during specific part in the bootphase, you can authenticate the key with a policy which only allows reading operation when PCR has specific value.

There are at least 24 PCRs on any platform. Each PCRs are meant to store different software's measurements, and typical example is like below.
Image description
(Image from A Practical Guide to TPM 2.0)

The actual allocation depends on PC client, but in this post, I'll read the value of PCR[0].

PCR bank is a group of PCR with same hash algorithm. Simple example is like below.



PCR bank 0 (sha1)
  * PCR[0]: sha1 value
  * ...
  * PCR[23]: sha1 value

PCR bank 1 (sha256)
  * PCR[0]: sha256 value
  * ...
  * PCR[23]: sha256 value


Enter fullscreen mode Exit fullscreen mode

This is to handle issues like compatibility for old application only using old hash, or new application restricting to use only strong hash. When extending PCR[i] value, TPM should extend each bank's PCR[i] if that PCR is present in bank. There are cases when PCR[i] is implemented in bank0 but not in bank1. I will be reading from PCR bank with sha256 hash.

For further description of PCR, you can refer to TCG spec part1.

Implementation

I will be using EDK2 to build the UEFI module. There are actually two ways to access PCR from UEFI.

  1. Using Tcg2Protocol.GetActivePcrBanks
  2. Using Tcg2Protocol.SubmitCommand to send TPM2_PCR_Read command I will be using 2 in this post.

TPM2_PCR_Read format

Looking at TCG spec part3, you can check the command's format as follows.
Image description

TPML_PCR_SELECTION

Structure's definition can be found at TCG spec part2, and the actual definition of the structure in EDK2 can be found in MdePkg/Include/IndustryStandard/Tpm20.h. To summarize the TPML_PCR_SELECTION structure, it is like follows.



* [TPML_PCR_SELECTION] pcrSelectionIn/pcrSelectionOut
   * [UINT32]             count
   * [TPMS_PCR_SELECTION] pcrSelections[count] (max: HASH_COUNT)
      * [TPMI_ALG_HASH][TPM_ALG_ID][UINT16] hash
      * [UINT8]                             sizeofSelect (min: PCR_SELECT_MIN)
      * [BYTE]                              pcrSelect[sizeofSelect] (max: PCR_SELECT_MAX)


Enter fullscreen mode Exit fullscreen mode

Below are the related constants.



HASH_COUNT         = 5;
PCR_SELECT_MIN     = ((PLATFORM_PCR + 7) / 8) = 3;
PLATFORM_PCR       = 24;
PCR_SELECT_MAX     = ((IMPLEMENTATION_PCR + 7) / 8) = 3;
IMPLEMENTATION_PCR = 24;


Enter fullscreen mode Exit fullscreen mode

IMPLEMENTATION_PCR maybe the actual number of PCR in your PC client, and since EDK2 doesn't know it, you have to change it to the appropriate value. But since we're only accessing only PCR[0], it doesn't matter to keep it this way.

Each pcrSelections specify PCR bank, and each bit in pcrSelect specify PCR. You might wonder why PCR_SELECT_MIN and PCR_SELECT_MAX are 3, but BYTE is 8bit and 3*8=24, which means, each bit specifies the PCR index you want to read. So for example, if you want PCR[0] and PCR[10], you will set pcrSelect for pcrSelectIn like the following (Omitting SwapBytes for simplicity).



pcrSelect[0] = 00000001 = 1
pcrSelect[1] = 00000100 = 4
pcrSelect[2] = 00000000 = 0


Enter fullscreen mode Exit fullscreen mode

TPML_DIGEST

This is where you get the actual value of PCR specified in the pcrSelectionIn. This structure is way simpler than TPML_PCR_SELECTION because it is just a list of digest. But, it might be confusing if you requested multiple PCR values: what value is in what index of digest. This order is actually noted in the description of TPM2_PCR_Read in the spec.

  • TPM will process the list of TPMS_PCR_SELECTION in pcrSelectionIn in order
  • Within each TPMS_PCR_SELECTION, the TPM will process the bits in the pcrSelect array in ascending PCR order

Source code

Here is the whole source code getting PCR[0] value from sha256 PCR bank.



#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/Tpm20.h>

#pragma pack(1)
    typedef struct {
        UINT32 count;
        TPMS_PCR_SELECTION pcrSelections[1];
    } ORIG_TPML_PCR_SELECTION;

    typedef struct {
        TPM2_COMMAND_HEADER Header;
        ORIG_TPML_PCR_SELECTION pcrSelectionIn;
    } TPM2_PCR_READ_COMMAND;

    typedef struct {
        TPM2_RESPONSE_HEADER Header;
        UINT32 pcrUpdateCounter;
        ORIG_TPML_PCR_SELECTION pcrSelectionOut;
        TPML_DIGEST pcrValues;
    } TPM2_PCR_READ_RESPONSE;
#pragma pack()


EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
    UINT32 i,j;
    EFI_TCG2_PROTOCOL *Tcg2Protocol;
    SystemTable->BootServices->LocateProtocol(&gEfiTcg2ProtocolGuid, NULL, (VOID**)&Tcg2Protocol);

    ORIG_TPML_PCR_SELECTION pcrSelectionIn;
    pcrSelectionIn.count                         = SwapBytes32(1);
    pcrSelectionIn.pcrSelections[0].hash         = SwapBytes16(TPM_ALG_SHA256);
    pcrSelectionIn.pcrSelections[0].sizeofSelect = PCR_SELECT_MIN;

    for(i=0; i<PCR_SELECT_MIN; i++)
        pcrSelectionIn.pcrSelections[0].pcrSelect[i] = 0;

    UINT32 pcrId;
    for(pcrId=0; pcrId<PLATFORM_PCR; pcrId++) {
        if(pcrId==0)
            pcrSelectionIn.pcrSelections[0].pcrSelect[pcrId/8] |= (1<<(pcrId%8));
    }


    TPM2_PCR_READ_COMMAND CmdBuffer;
    UINT32 CmdBufferSize;
    TPM2_PCR_READ_RESPONSE RecvBuffer;
    UINT32 RecvBufferSize;

    CmdBuffer.Header.tag         = SwapBytes16(TPM_ST_NO_SESSIONS);
    CmdBuffer.Header.commandCode = SwapBytes32(TPM_CC_PCR_Read);
    CmdBuffer.pcrSelectionIn     = pcrSelectionIn;
    CmdBufferSize = sizeof(CmdBuffer.Header) + sizeof(CmdBuffer.pcrSelectionIn);
    CmdBuffer.Header.paramSize = SwapBytes32(CmdBufferSize);

    Print(L"sending TPM command...\r\n");

    RecvBufferSize = sizeof(RecvBuffer);
    EFI_STATUS stats = Tcg2Protocol->SubmitCommand(Tcg2Protocol, CmdBufferSize, (UINT8*)&CmdBuffer, RecvBufferSize, (UINT8*)&RecvBuffer);
    if(stats==EFI_SUCCESS)
        Print(L"SubmitCommand Success!\r\n");
    else
        Print(L"stats: 0x%x (EFI_DEVICE_ERROR:0x%x, EFI_INVALID_PARAMETER:0x%x, EFI_BUFFER_TOO_SMALL:0x%x)\r\n", stats, EFI_DEVICE_ERROR, EFI_INVALID_PARAMETER, EFI_BUFFER_TOO_SMALL);


    // parse response
    UINT16 res = SwapBytes32(RecvBuffer.Header.responseCode);
    Print(L"ResponseCode is %d (0x%X)\r\n", res, res);

    UINT32 pcrUpdateCounter = SwapBytes32(RecvBuffer.pcrUpdateCounter);
    Print(L"Pcr Update Counter: %d (0x%X)\r\n", pcrUpdateCounter, pcrUpdateCounter);

    UINT32 cntSelectionOut = SwapBytes32(RecvBuffer.pcrSelectionOut.count);
    Print(L"pcrSelectionOut.count (should be 1): %d\r\n", cntSelectionOut);

    TPMS_PCR_SELECTION* pcrSelections = (TPMS_PCR_SELECTION*)RecvBuffer.pcrSelectionOut.pcrSelections;
    for(i=0; i<cntSelectionOut; i++) {
        UINT8 sizeofSelect = pcrSelections[i].sizeofSelect;
        Print(L"pcrSelections[%d].sizeofSelect (should be 3): %d\r\n", i, sizeofSelect);
        for(j=0; j<sizeofSelect; j++) {
            Print(L"pcrSelections[%d].pcrSelect[%d]: %1x\r\n", i, j, pcrSelections[i].pcrSelect[j]);
        }
    }

    UINT32 cntDigest = SwapBytes32(RecvBuffer.pcrValues.count);
    Print(L"Number of digests: %d\r\n", cntDigest);

    TPM2B_DIGEST* digests = (TPM2B_DIGEST*)RecvBuffer.pcrValues.digests;
    for(i=0; i<cntDigest; i++) {
        UINT16 size = SwapBytes16(digests[i].size);
        BYTE buf[size];
        for(j=0; j<size; j++)
            buf[size-j-1] = digests[i].buffer[j];

        Print(L"pcrValues.digest[%d] (size %d):\r\n", i, size);
        for(j=0; j<size; j++) {
            Print(L"%2X", buf[j]);
        }
        Print(L"\r\n");
        digests = (TPM2B_DIGEST*)(((void*)digests)+sizeof(UINT16)+size);
    }

    while(1);
    return 0;
}


Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
machinehunter
MachineHunter

Posted on July 25, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related