From 43cb7391010ac6cb416ab6f641a3a5465b5f524e Mon Sep 17 00:00:00 2001 From: Christopher Meis <30509262+ChriMarMe@users.noreply.github.com> Date: Tue, 14 Sep 2021 08:23:42 +0200 Subject: [PATCH] Add intel me structures from CSS (#334) * pkg/me: Add parser for the Intel ME Flash Partition Table The flash partition table $FPT describes the partitions found in the ME region. The new API allows basic detection, enumeration and modification support for those partitions. To be used to patch the UEP partition with the KM hash. Based on Igor Skochinsky talk "Rootkit in your laptop" and ME Analyzer written by Plato Mavropoulos. Signed-off-by: Patrick Rudolph Move intel me file from pkg/me to pkg/intel/me Signed-off-by: Christopher Meis Get rid of 9elements/converged-security-suite dependency Signed-off-by: Christopher Meis correct formatting verbs Signed-off-by: Christopher Meis Remove spaces and correct return statements Signed-off-by: Christopher Meis * Refactor new me parsing Add tests for me parsing Signed-off-by: Christopher Meis Co-authored-by: Patrick Rudolph --- pkg/intel/me/me.go | 153 +++++++++++++++++++++++++++++++++++++ pkg/intel/me/me_test.go | 82 ++++++++++++++++++++ pkg/intel/me/structures.go | 132 ++++++++++++++++++++++++++++++++ 3 files changed, 367 insertions(+) create mode 100644 pkg/intel/me/me.go create mode 100644 pkg/intel/me/me_test.go create mode 100644 pkg/intel/me/structures.go diff --git a/pkg/intel/me/me.go b/pkg/intel/me/me.go new file mode 100644 index 00000000..c720f345 --- /dev/null +++ b/pkg/intel/me/me.go @@ -0,0 +1,153 @@ +// Copyright 2021 the LinuxBoot Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package me + +import ( + "bytes" + "encoding/binary" + "io" +) + +var ( + Signature = [4]byte{0x24, 0x46, 0x50, 0x54} +) + +func parseLegacyFlashPartitionTableHeader(r io.Reader) (*LegacyFlashPartitionTableHeader, error) { + var header LegacyFlashPartitionTableHeader + var scrap [12]byte + if err := binary.Read(r, binary.LittleEndian, &scrap); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.Marker); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.NumFptEntries); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.HeaderVersion); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.EntryVersion); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.HeaderLength); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.HeaderChecksum); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.TicksToAdd); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.TokensToAdd); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.UMASize); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.Flags); err != nil { + return nil, err + } + return &header, nil + +} + +func parseFlashPartitionTableHeader(r io.Reader) (*FlashPartitionTableHeader, error) { + var header FlashPartitionTableHeader + // Set Signature + header.Marker = Signature + + if err := binary.Read(r, binary.LittleEndian, &header.NumFptEntries); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.HeaderVersion); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.EntryVersion); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.HeaderLength); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.HeaderChecksum); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.TicksToAdd); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.TokensToAdd); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.UMASizeOrReserved); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.FlashLayoutOrFlags); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.FitcMajor); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.FitcMinor); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.FitcHotfix); err != nil { + return nil, err + } + if err := binary.Read(r, binary.LittleEndian, &header.FitcBuild); err != nil { + return nil, err + } + + return &header, nil +} + +func parseEntry(r io.Reader) (*FlashPartitionTableEntry, error) { + var entry FlashPartitionTableEntry + if err := binary.Read(r, binary.LittleEndian, &entry); err != nil { + return nil, err + } + return &entry, nil +} + +// ParseIntelFirmware parses the Intel firmware image by uefi.Firmware interface` +func ParseIntelME(r io.Reader) (*IntelME, error) { + var me IntelME + me.legacy = false + var numEntries uint32 + + // Read first 4 byte, we catch the marker as prefix or suffix + var markerarea [4]byte + if err := binary.Read(r, binary.LittleEndian, &markerarea); err != nil { + return nil, err + } + + // Check on new header + if bytes.HasPrefix(markerarea[:], Signature[:]) { + hdr, err := parseFlashPartitionTableHeader(r) + if err != nil { + return nil, err + } + me.hdr = hdr + numEntries = hdr.NumFptEntries + } else { + me.legacy = true + hdr, err := parseLegacyFlashPartitionTableHeader(r) + if err != nil { + return nil, err + } + me.legacyhdr = hdr + numEntries = hdr.NumFptEntries + } + + partitions := make([]FlashPartitionTableEntry, 0) + for i := uint32(0); i < numEntries; i++ { + entry, err := parseEntry(r) + if err != nil { + return nil, err + } + partitions = append(partitions, *entry) + } + me.partitions = partitions + return &me, nil +} diff --git a/pkg/intel/me/me_test.go b/pkg/intel/me/me_test.go new file mode 100644 index 00000000..0223b5e5 --- /dev/null +++ b/pkg/intel/me/me_test.go @@ -0,0 +1,82 @@ +// Copyright 2021 the LinuxBoot Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package me + +import ( + "bytes" + "errors" + "fmt" + "testing" +) + +var ( + validLegacyHeaderPadding = []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + validME = []byte{ + 0x24, 0x46, 0x50, 0x54, 0x02, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x20, 0x30, 0x00, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // first entry + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + invalidMEEntryNumber = []byte{ + 0x24, 0x46, 0x50, 0x54, 0x07, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x20, 0x30, 0x00, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // first entry + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } +) + +func TestParseIntelME(t *testing.T) { + for _, tt := range []struct { + name string + data []byte + wantErr error + }{ + { + name: "Test Lagacy ME", + data: append(validLegacyHeaderPadding, validME...), + }, + { + name: "Test ME", + data: validME, + }, + { + name: "Invalid Entry number", + data: invalidMEEntryNumber, + wantErr: errors.New("EOF"), + }, + } { + t.Run(tt.name, func(t *testing.T) { + _, gotErr := ParseIntelME(bytes.NewReader(tt.data)) + if gotErrorString, wantErrorString := fmt.Sprint(gotErr), fmt.Sprint(tt.wantErr); gotErrorString != wantErrorString { + t.Errorf("ParseIntelME() got err %q; want err %q", + gotErrorString, + wantErrorString) + } + }) + } +} diff --git a/pkg/intel/me/structures.go b/pkg/intel/me/structures.go new file mode 100644 index 00000000..e5d05495 --- /dev/null +++ b/pkg/intel/me/structures.go @@ -0,0 +1,132 @@ +// Copyright 2021 the LinuxBoot Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package me + +import ( + "fmt" + "strings" +) + +// LegacyFlashPartitionTableHeader describes the old flash partition table header +// in Intel ME binaries. +type LegacyFlashPartitionTableHeader struct { + Padding [16]byte // 16 zeros + Marker [4]byte // Always $FPT + NumFptEntries uint32 + HeaderVersion uint8 + EntryVersion uint8 + HeaderLength uint8 // Usually 0x30 + HeaderChecksum uint8 + TicksToAdd uint16 + TokensToAdd uint16 + UMASize uint32 + Flags uint32 +} + +func (h LegacyFlashPartitionTableHeader) String() string { + var b strings.Builder + b.WriteString("Flash partition table:\n") + fmt.Fprintf(&b, " Entries : %d\n", h.NumFptEntries) + fmt.Fprintf(&b, " HeaderVersion : 0x%x\n", h.HeaderVersion) + fmt.Fprintf(&b, " EntryVersion : 0x%x\n", h.EntryVersion) + fmt.Fprintf(&b, " HeaderLength : 0x%x\n", h.HeaderLength) + fmt.Fprintf(&b, " HeaderChecksum: 0x%x\n", h.HeaderChecksum) + fmt.Fprintf(&b, " TicksToAdd : 0x%x\n", h.TicksToAdd) + fmt.Fprintf(&b, " TokensToAdd : 0x%x\n", h.TokensToAdd) + fmt.Fprintf(&b, " UMASize : 0x%x\n", h.UMASize) + fmt.Fprintf(&b, " Flags : 0x%x\n", h.Flags) + + return b.String() +} + +// FlashPartitionTableHeader describes the new flash partition table header +// in Intel ME binaries. +type FlashPartitionTableHeader struct { + Marker [4]byte // Always $FPT + NumFptEntries uint32 + HeaderVersion uint8 // Only support 2.0 + EntryVersion uint8 + HeaderLength uint8 // Usually 0x20 + HeaderChecksum uint8 + TicksToAdd uint16 + TokensToAdd uint16 + UMASizeOrReserved uint32 + FlashLayoutOrFlags uint32 + // Not Present in ME version 7 + FitcMajor uint16 + FitcMinor uint16 + FitcHotfix uint16 + FitcBuild uint16 +} + +func (h FlashPartitionTableHeader) String() string { + var b strings.Builder + + b.WriteString("Flash partition table:\n") + fmt.Fprintf(&b, " Entries : %d\n", h.NumFptEntries) + fmt.Fprintf(&b, " HeaderVersion : 0x%x\n", h.HeaderVersion) + fmt.Fprintf(&b, " EntryVersion : 0x%x\n", h.EntryVersion) + fmt.Fprintf(&b, " HeaderLength : 0x%x\n", h.HeaderLength) + fmt.Fprintf(&b, " HeaderChecksum : 0x%x\n", h.HeaderChecksum) + fmt.Fprintf(&b, " TicksToAdd : 0x%x\n", h.TicksToAdd) + fmt.Fprintf(&b, " TokensToAdd : 0x%x\n", h.TokensToAdd) + fmt.Fprintf(&b, " UMASizeOrReserved : 0x%x\n", h.UMASizeOrReserved) + fmt.Fprintf(&b, " FlashLayoutOrFlags : 0x%x\n", h.FlashLayoutOrFlags) + fmt.Fprintf(&b, " Fitc Version : %d.%d.%d.%d\n", h.FitcMajor, h.FitcMinor, h.FitcHotfix, h.FitcBuild) + + return b.String() +} + +type name [4]byte + +func (n *name) String() string { + return string(n[:]) +} + +// FlashPartitionTableEntry describes information of a flash partition table entry. +type FlashPartitionTableEntry struct { + Name name + Owner name + Offset uint32 + Length uint32 + StartTokens uint32 + MaxTokens uint32 + ScratchSectors uint32 + Flags uint32 +} + +func (e FlashPartitionTableEntry) String() string { + var b strings.Builder + b.WriteString("Flash partition entry:\n") + fmt.Fprintf(&b, " Name : %s\n", e.Name.String()) + fmt.Fprintf(&b, " Owner : %s\n", e.Owner.String()) + fmt.Fprintf(&b, " Offset : 0x%x\n", e.Offset) + fmt.Fprintf(&b, " Length : 0x%x\n", e.Length) + fmt.Fprintf(&b, " StartTokens : 0x%x\n", e.StartTokens) + fmt.Fprintf(&b, " MaxTokens : 0x%x\n", e.MaxTokens) + fmt.Fprintf(&b, " ScratchSectors: 0x%x\n", e.ScratchSectors) + fmt.Fprintf(&b, " Flags : 0x%x\n", e.Flags) + + if e.Flags>>24 == 0xff { + b.WriteString(" Valid : No\n") + } else { + b.WriteString(" Valid : yes\n") + } + if e.Flags&1 > 0 { + b.WriteString(" Partition : Data\n") + } else { + b.WriteString(" Partition : Code\n") + } + + return b.String() +} + +// IntelME abstracts the ME/CSME/SPS firmware found on intel platforms +type IntelME struct { + hdr *FlashPartitionTableHeader + legacyhdr *LegacyFlashPartitionTableHeader + legacy bool + partitions []FlashPartitionTableEntry +}