Skip to content

Commit

Permalink
Add intel me structures from CSS (linuxboot#334)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

Move intel me file from pkg/me to pkg/intel/me

Signed-off-by: Christopher Meis <[email protected]>

Get rid of 9elements/converged-security-suite dependency

Signed-off-by: Christopher Meis <[email protected]>

correct formatting verbs

Signed-off-by: Christopher Meis <[email protected]>

Remove spaces and correct return statements

Signed-off-by: Christopher Meis <[email protected]>

* Refactor new me parsing
Add tests for me parsing

Signed-off-by: Christopher Meis <[email protected]>

Co-authored-by: Patrick Rudolph <[email protected]>
  • Loading branch information
ChriMarMe and PatrickRudolph authored Sep 14, 2021
1 parent 75b23bb commit 43cb739
Show file tree
Hide file tree
Showing 3 changed files with 367 additions and 0 deletions.
153 changes: 153 additions & 0 deletions pkg/intel/me/me.go
Original file line number Diff line number Diff line change
@@ -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
}
82 changes: 82 additions & 0 deletions pkg/intel/me/me_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
132 changes: 132 additions & 0 deletions pkg/intel/me/structures.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 43cb739

Please sign in to comment.