Merge pull request #245 from 9elements/meparser

pkg/me: Add parser for the Intel ME Flash Partition Table
This commit is contained in:
Zaolin 2021-06-22 14:22:03 +02:00 committed by GitHub
commit 0031ac7344
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 277 additions and 0 deletions

277
pkg/me/me.go Normal file
View File

@ -0,0 +1,277 @@
package me
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"github.com/9elements/converged-security-suite/v2/pkg/ostools"
)
type LegacyFlashPartitionTableHeader struct {
Padding [16]uint8 // 16 zeros
Marker uint32 // 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()
}
type FlashPartitionTableHeader struct {
Marker uint32 // 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 FlashPartitionTableEntry struct {
Name [4]uint8
Owner [4]uint8
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", []byte{e.Name[0], e.Name[1], e.Name[2], e.Name[3]})
fmt.Fprintf(&b, " Owner : %s\n", []byte{e.Owner[0], e.Owner[1], e.Owner[2], e.Owner[3]})
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
image []byte
// Offset in image to $FPT
fptoffset uint32
}
// ParseIntelFirmwareFile parses the Intel firmware image by path `imagePath`
func ParseIntelFirmwareFile(imagePath string) (*IntelME, error) {
imageBytes, err := ostools.FileToBytes(imagePath)
if err != nil {
return nil, fmt.Errorf(`unable to get the content of file "%s": %w`,
imagePath, err)
}
uefi, err := ParseIntelFirmwareBytes(imageBytes)
if err != nil {
return nil, fmt.Errorf("unable to parse UEFI image file '%s': %w", imagePath, err)
}
return uefi, nil
}
// ParseIntelFirmwareBytes parses the Intel firmware image from bytes
func ParseIntelFirmwareBytes(imageBytes []byte) (me *IntelME, err error) {
legacy := false
fptoffset := -1
// Search for the Flash partition table
for i := 0; i < len(imageBytes); i += 0x1000 {
// New Header
if bytes.HasPrefix(imageBytes[i:], []byte(`$FPT`)) {
fptoffset = i
break
}
// Legacy Header
if bytes.HasPrefix(imageBytes[i:], append(make([]byte, 16), []byte(`$FPT`)...)) {
legacy = true
fptoffset = i
break
}
}
if fptoffset == -1 {
err = fmt.Errorf("FlashPartitionTable not found")
return
}
me = &IntelME{image: imageBytes, legacy: legacy, fptoffset: uint32(fptoffset)}
reader := bytes.NewReader(imageBytes[fptoffset:])
offset := 0
entries := 0
if legacy {
if err = binary.Read(reader, binary.LittleEndian, &me.legacyhdr); err != nil {
return
}
if me.legacyhdr.HeaderVersion != 0x20 {
err = fmt.Errorf("Unsupported header version. Got 0x%x\n", me.legacyhdr.HeaderVersion)
return
}
if int(me.legacyhdr.HeaderLength) > len(imageBytes)-fptoffset {
err = fmt.Errorf("Invalid header length. Got 0x%x\n", me.legacyhdr.HeaderLength)
return
}
offset = int(me.legacyhdr.HeaderLength)
entries = int(me.legacyhdr.NumFptEntries)
} else {
if err = binary.Read(reader, binary.LittleEndian, &me.hdr); err != nil {
return
}
if me.hdr.HeaderVersion != 0x20 {
err = fmt.Errorf("Unsupported header version. Got 0x%x\n", me.hdr.HeaderVersion)
return
}
if int(me.hdr.HeaderLength) > len(imageBytes)-fptoffset {
err = fmt.Errorf("Invalid header length. Got 0x%x\n", me.hdr.HeaderLength)
return
}
offset = int(me.hdr.HeaderLength)
entries = int(me.hdr.NumFptEntries)
}
reader = bytes.NewReader(imageBytes[fptoffset+offset:])
for i := 0; i < entries; i++ {
var e FlashPartitionTableEntry
if err = binary.Read(reader, binary.LittleEndian, &e); err != nil {
return
}
me.partitions = append(me.partitions, e)
}
return me, nil
}
// ImageBytes just returns the image as `[]byte`.
func (m *IntelME) ImageBytes() []byte {
return m.image
}
// PrintInfo prints the ME partitions in human readable format
func (m *IntelME) PrintInfo() string {
ret := ""
if m.legacy {
ret += m.legacyhdr.String()
} else {
ret += m.hdr.String()
}
for i := range m.partitions {
ret += m.partitions[i].String()
}
return ret
}
// WritePartition writes new data into specified partition.
// Must be equal in size to current parition
func (m *IntelME) WritePartition(id string, data []byte) (err error) {
for i := range m.partitions {
name := m.partitions[i].Name
if string(bytes.Trim([]byte{name[0], name[1], name[2], name[3]}, "\x00")) == id {
if uint32(len(data)) != m.partitions[i].Length {
err = fmt.Errorf("Invalid length")
return
}
m.image = append(append(m.image[:m.partitions[i].Offset+m.fptoffset], data...),
m.image[m.partitions[i].Offset+m.partitions[i].Length+m.fptoffset:]...)
return
}
}
err = fmt.Errorf("not found")
return
}
// ReadPartition reads data from specified partition.
func (m *IntelME) ReadPartition(id string) (data []byte, err error) {
for i := range m.partitions {
name := m.partitions[i].Name
if string(bytes.Trim([]byte{name[0], name[1], name[2], name[3]}, "\x00")) == id {
data = m.image[m.partitions[i].Offset+m.fptoffset : m.partitions[i].Offset+m.partitions[i].Length+m.fptoffset]
return
}
}
err = fmt.Errorf("not found")
return
}
// LsPartitions list all partition found in image
func (m *IntelME) LsPartitions() (part []string) {
for i := range m.partitions {
name := m.partitions[i].Name
part = append(part, string(bytes.Trim([]byte{name[0], name[1], name[2], name[3]}, "\x00")))
}
return
}