From df0aa1cf300fcb04f388dabc6da86d67117cc636 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Thu, 10 Jun 2021 12:08:04 +0200 Subject: [PATCH] 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 --- pkg/me/me.go | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 pkg/me/me.go diff --git a/pkg/me/me.go b/pkg/me/me.go new file mode 100644 index 0000000..fb582c9 --- /dev/null +++ b/pkg/me/me.go @@ -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 +}