mirror of
https://github.com/google/go-attestation.git
synced 2024-12-18 20:47:57 +00:00
812 lines
27 KiB
Go
812 lines
27 KiB
Go
// Copyright 2020 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
// use this file except in compliance with the License. You may obtain a copy of
|
|
// the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations under
|
|
// the License.
|
|
|
|
package attest
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"unicode/utf16"
|
|
|
|
"github.com/google/go-attestation/attest/internal"
|
|
)
|
|
|
|
type windowsEvent uint32
|
|
|
|
// SIPA event types
|
|
const (
|
|
sipaTypeMask windowsEvent = 0x000f0000
|
|
sipaContainer windowsEvent = 0x00010000
|
|
sipaInformation windowsEvent = 0x00020000
|
|
sipaError windowsEvent = 0x00030000
|
|
sipaPreOsParameter windowsEvent = 0x00040000
|
|
sipaOSParameter windowsEvent = 0x00050000
|
|
sipaAuthority windowsEvent = 0x00060000
|
|
sipaLoadedModule windowsEvent = 0x00070000
|
|
sipaTrustPoint windowsEvent = 0x00080000
|
|
sipaELAM windowsEvent = 0x00090000
|
|
sipaVBS windowsEvent = 0x000a0000
|
|
trustBoundary windowsEvent = 0x40010001
|
|
elamAggregation windowsEvent = 0x40010002
|
|
loadedModuleAggregation windowsEvent = 0x40010003
|
|
trustpointAggregation windowsEvent = 0xC0010004
|
|
ksrAggregation windowsEvent = 0x40010005
|
|
ksrSignedMeasurementAggregation windowsEvent = 0x40010006
|
|
information windowsEvent = 0x00020001
|
|
bootCounter windowsEvent = 0x00020002
|
|
transferControl windowsEvent = 0x00020003
|
|
applicationReturn windowsEvent = 0x00020004
|
|
bitlockerUnlock windowsEvent = 0x00020005
|
|
eventCounter windowsEvent = 0x00020006
|
|
counterID windowsEvent = 0x00020007
|
|
morBitNotCancelable windowsEvent = 0x00020008
|
|
applicationSVN windowsEvent = 0x00020009
|
|
svnChainStatus windowsEvent = 0x0002000A
|
|
morBitAPIStatus windowsEvent = 0x0002000B
|
|
bootDebugging windowsEvent = 0x00040001
|
|
bootRevocationList windowsEvent = 0x00040002
|
|
osKernelDebug windowsEvent = 0x00050001
|
|
codeIntegrity windowsEvent = 0x00050002
|
|
testSigning windowsEvent = 0x00050003
|
|
dataExecutionPrevention windowsEvent = 0x00050004
|
|
safeMode windowsEvent = 0x00050005
|
|
winPE windowsEvent = 0x00050006
|
|
physicalAddressExtension windowsEvent = 0x00050007
|
|
osDevice windowsEvent = 0x00050008
|
|
systemRoot windowsEvent = 0x00050009
|
|
hypervisorLaunchType windowsEvent = 0x0005000A
|
|
hypervisorPath windowsEvent = 0x0005000B
|
|
hypervisorIOMMUPolicy windowsEvent = 0x0005000C
|
|
hypervisorDebug windowsEvent = 0x0005000D
|
|
driverLoadPolicy windowsEvent = 0x0005000E
|
|
siPolicy windowsEvent = 0x0005000F
|
|
hypervisorMMIONXPolicy windowsEvent = 0x00050010
|
|
hypervisorMSRFilterPolicy windowsEvent = 0x00050011
|
|
vsmLaunchType windowsEvent = 0x00050012
|
|
osRevocationList windowsEvent = 0x00050013
|
|
vsmIDKInfo windowsEvent = 0x00050020
|
|
flightSigning windowsEvent = 0x00050021
|
|
pagefileEncryptionEnabled windowsEvent = 0x00050022
|
|
vsmIDKSInfo windowsEvent = 0x00050023
|
|
hibernationDisabled windowsEvent = 0x00050024
|
|
dumpsDisabled windowsEvent = 0x00050025
|
|
dumpEncryptionEnabled windowsEvent = 0x00050026
|
|
dumpEncryptionKeyDigest windowsEvent = 0x00050027
|
|
lsaISOConfig windowsEvent = 0x00050028
|
|
noAuthority windowsEvent = 0x00060001
|
|
authorityPubKey windowsEvent = 0x00060002
|
|
filePath windowsEvent = 0x00070001
|
|
imageSize windowsEvent = 0x00070002
|
|
hashAlgorithmID windowsEvent = 0x00070003
|
|
authenticodeHash windowsEvent = 0x00070004
|
|
authorityIssuer windowsEvent = 0x00070005
|
|
authoritySerial windowsEvent = 0x00070006
|
|
imageBase windowsEvent = 0x00070007
|
|
authorityPublisher windowsEvent = 0x00070008
|
|
authoritySHA1Thumbprint windowsEvent = 0x00070009
|
|
imageValidated windowsEvent = 0x0007000A
|
|
moduleSVN windowsEvent = 0x0007000B
|
|
quote windowsEvent = 0x80080001
|
|
quoteSignature windowsEvent = 0x80080002
|
|
aikID windowsEvent = 0x80080003
|
|
aikPubDigest windowsEvent = 0x80080004
|
|
elamKeyname windowsEvent = 0x00090001
|
|
elamConfiguration windowsEvent = 0x00090002
|
|
elamPolicy windowsEvent = 0x00090003
|
|
elamMeasured windowsEvent = 0x00090004
|
|
vbsVSMRequired windowsEvent = 0x000A0001
|
|
vbsSecurebootRequired windowsEvent = 0x000A0002
|
|
vbsIOMMURequired windowsEvent = 0x000A0003
|
|
vbsNXRequired windowsEvent = 0x000A0004
|
|
vbsMSRFilteringRequired windowsEvent = 0x000A0005
|
|
vbsMandatoryEnforcement windowsEvent = 0x000A0006
|
|
vbsHVCIPolicy windowsEvent = 0x000A0007
|
|
vbsMicrosoftBootChainRequired windowsEvent = 0x000A0008
|
|
ksrSignature windowsEvent = 0x000B0001
|
|
)
|
|
|
|
type WinCSPAlg uint32
|
|
|
|
// Valid CSP Algorithm IDs.
|
|
const (
|
|
WinAlgMD4 WinCSPAlg = 0x02
|
|
WinAlgMD5 WinCSPAlg = 0x03
|
|
WinAlgSHA1 WinCSPAlg = 0x04
|
|
WinAlgSHA256 WinCSPAlg = 0x0c
|
|
WinAlgSHA384 WinCSPAlg = 0x0d
|
|
WinAlgSHA512 WinCSPAlg = 0x0e
|
|
)
|
|
|
|
// BitlockerStatus describes the status of BitLocker on a Windows system.
|
|
type BitlockerStatus uint8
|
|
|
|
// Valid BitlockerStatus values.
|
|
const (
|
|
BitlockerStatusCached = 0x01
|
|
BitlockerStatusMedia = 0x02
|
|
BitlockerStatusTPM = 0x04
|
|
BitlockerStatusPin = 0x10
|
|
BitlockerStatusExternal = 0x20
|
|
BitlockerStatusRecovery = 0x40
|
|
)
|
|
|
|
// Ternary describes a boolean value that can additionally be unknown.
|
|
type Ternary uint8
|
|
|
|
// Valid Ternary values.
|
|
const (
|
|
TernaryUnknown Ternary = iota
|
|
TernaryTrue
|
|
TernaryFalse
|
|
)
|
|
|
|
// WinEvents describes information from the event log recorded during
|
|
// bootup of Microsoft Windows.
|
|
type WinEvents struct {
|
|
// ColdBoot is set to true if the system was not resuming from hibernation.
|
|
ColdBoot bool
|
|
// BootCount contains the value of the monotonic boot counter. This
|
|
// value is not set for TPM 1.2 devices and some TPMs with buggy
|
|
// implementations of monotonic counters.
|
|
BootCount uint64
|
|
// LoadedModules contains authenticode hashes for binaries which
|
|
// were loaded during boot.
|
|
LoadedModules map[string]WinModuleLoad
|
|
// ELAM describes the configuration of each Early Launch AntiMalware driver,
|
|
// for each AV Vendor key.
|
|
ELAM map[string]WinELAM
|
|
// BootDebuggingEnabled is true if boot debugging was ever reported
|
|
// as enabled.
|
|
BootDebuggingEnabled bool
|
|
// KernelDebugEnabled is true if kernel debugging was recorded as
|
|
// enabled at any point during boot.
|
|
KernelDebugEnabled bool
|
|
// DEPEnabled is true if NX (Data Execution Prevention) was consistently
|
|
// reported as enabled.
|
|
DEPEnabled Ternary
|
|
// CodeIntegrityEnabled is true if code integrity was consistently
|
|
// reported as enabled.
|
|
CodeIntegrityEnabled Ternary
|
|
// TestSigningEnabled is true if test-mode signature verification was
|
|
// ever reported as enabled.
|
|
TestSigningEnabled bool
|
|
// BitlockerUnlocks reports the bitlocker status for every instance of
|
|
// a disk unlock, where bitlocker was used to secure the disk.
|
|
BitlockerUnlocks []BitlockerStatus
|
|
}
|
|
|
|
// WinModuleLoad describes a module which was loaded while
|
|
// Windows booted.
|
|
type WinModuleLoad struct {
|
|
// FilePath represents the path from which the module was loaded. This
|
|
// information is not always present.
|
|
FilePath string
|
|
// AuthenticodeHash contains the authenticode hash of the binary
|
|
// blob which was loaded.
|
|
AuthenticodeHash []byte
|
|
// ImageBase describes all the addresses to which the the blob was loaded.
|
|
ImageBase []uint64
|
|
// ImageSize describes the size of the image in bytes. This information
|
|
// is not always present.
|
|
ImageSize uint64
|
|
// HashAlgorithm describes the hash algorithm used.
|
|
HashAlgorithm WinCSPAlg
|
|
// ImageValidated is set if the post-boot loader validated the image.
|
|
ImageValidated bool
|
|
|
|
// AuthorityIssuer identifies the issuer of the certificate which certifies
|
|
// the signature on this module.
|
|
AuthorityIssuer string
|
|
// AuthorityPublisher identifies the publisher of the certificate which
|
|
// certifies the signature on this module.
|
|
AuthorityPublisher string
|
|
// AuthoritySerial contains the serial of the certificate certifying this
|
|
// module.
|
|
AuthoritySerial []byte
|
|
// AuthoritySHA1 is the SHA1 hash of the certificate thumbprint.
|
|
AuthoritySHA1 []byte
|
|
}
|
|
|
|
// WinELAM describes the configuration of an Early Launch AntiMalware driver.
|
|
// These values represent the 3 measured registry values stored in the ELAM
|
|
// hive for the driver.
|
|
type WinELAM struct {
|
|
Measured []byte
|
|
Config []byte
|
|
Policy []byte
|
|
}
|
|
|
|
// ParseWinEvents parses a series of events to extract information about
|
|
// the bringup of Microsoft Windows. This information is not trustworthy
|
|
// unless the integrity of platform & bootloader events has already been
|
|
// established.
|
|
func ParseWinEvents(events []Event) (*WinEvents, error) {
|
|
var (
|
|
out = WinEvents{
|
|
LoadedModules: map[string]WinModuleLoad{},
|
|
ELAM: map[string]WinELAM{},
|
|
}
|
|
seenSeparator struct {
|
|
PCR12 bool
|
|
PCR13 bool
|
|
}
|
|
)
|
|
|
|
for _, e := range events {
|
|
if e.Index != 12 && e.Index != 13 {
|
|
continue
|
|
}
|
|
|
|
et, err := internal.UntrustedParseEventType(uint32(e.Type))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unrecognised event type: %v", err)
|
|
}
|
|
|
|
digestVerify := e.digestEquals(e.Data)
|
|
|
|
switch e.Index {
|
|
case 12: // 'early boot' events
|
|
switch et {
|
|
case internal.EventTag:
|
|
if seenSeparator.PCR12 {
|
|
continue
|
|
}
|
|
s, err := internal.ParseTaggedEventData(e.Data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
|
|
}
|
|
if digestVerify != nil {
|
|
return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, digestVerify)
|
|
}
|
|
if err := out.readWinEventBlock(s, e.Index); err != nil {
|
|
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
|
|
}
|
|
case internal.Separator:
|
|
if seenSeparator.PCR12 {
|
|
return nil, fmt.Errorf("duplicate WBCL separator at event %d", e.sequence)
|
|
}
|
|
seenSeparator.PCR12 = true
|
|
if !bytes.Equal(e.Data, []byte("WBCL")) {
|
|
return nil, fmt.Errorf("invalid WBCL separator data at event %d: %v", e.sequence, e.Data)
|
|
}
|
|
if digestVerify != nil {
|
|
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unexpected (PCR12) event type: %v", et)
|
|
}
|
|
case 13: // Post 'early boot' events
|
|
switch et {
|
|
case internal.EventTag:
|
|
if seenSeparator.PCR13 {
|
|
continue
|
|
}
|
|
s, err := internal.ParseTaggedEventData(e.Data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
|
|
}
|
|
if digestVerify != nil {
|
|
return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, digestVerify)
|
|
}
|
|
if err := out.readWinEventBlock(s, e.Index); err != nil {
|
|
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
|
|
}
|
|
case internal.Separator:
|
|
if seenSeparator.PCR13 {
|
|
return nil, fmt.Errorf("duplicate WBCL separator at event %d", e.sequence)
|
|
}
|
|
seenSeparator.PCR13 = true
|
|
if !bytes.Equal(e.Data, []byte("WBCL")) {
|
|
return nil, fmt.Errorf("invalid WBCL separator data at event %d: %v", e.sequence, e.Data)
|
|
}
|
|
if digestVerify != nil {
|
|
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unexpected (PCR13) event type: %v", et)
|
|
}
|
|
}
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
type microsoftEventHeader struct {
|
|
Type windowsEvent
|
|
Size uint32
|
|
}
|
|
|
|
// unknownSIPAEvent is returned by parseSIPAEvent if the event type is
|
|
// not handled. Unlike other events in the TCG log, it is safe to skip
|
|
// unhandled SIPA events, as they are embedded within EventTag structures,
|
|
// and these structures should match the event digest.
|
|
var unknownSIPAEvent = errors.New("unknown event")
|
|
|
|
func (w *WinEvents) readBooleanInt64Event(header microsoftEventHeader, r *bytes.Reader) error {
|
|
if header.Size != 8 {
|
|
return fmt.Errorf("payload was %d bytes, want 8", header.Size)
|
|
}
|
|
var num uint64
|
|
if err := binary.Read(r, binary.LittleEndian, &num); err != nil {
|
|
return fmt.Errorf("reading u64: %w", err)
|
|
}
|
|
isSet := num != 0
|
|
|
|
switch header.Type {
|
|
// Boolean signals that latch off if the are ever false (ie: attributes
|
|
// that represent a stronger security state when set).
|
|
case dataExecutionPrevention:
|
|
if isSet && w.DEPEnabled == TernaryUnknown {
|
|
w.DEPEnabled = TernaryTrue
|
|
} else if !isSet {
|
|
w.DEPEnabled = TernaryFalse
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *WinEvents) readBooleanByteEvent(header microsoftEventHeader, r *bytes.Reader) error {
|
|
if header.Size != 1 {
|
|
return fmt.Errorf("payload was %d bytes, want 1", header.Size)
|
|
}
|
|
var b byte
|
|
if err := binary.Read(r, binary.LittleEndian, &b); err != nil {
|
|
return fmt.Errorf("reading byte: %w", err)
|
|
}
|
|
isSet := b != 0
|
|
|
|
switch header.Type {
|
|
// Boolean signals that latch on if they are ever true (ie: attributes
|
|
// that represent a weaker security state when set).
|
|
case osKernelDebug:
|
|
w.KernelDebugEnabled = w.KernelDebugEnabled || isSet
|
|
case bootDebugging:
|
|
w.BootDebuggingEnabled = w.BootDebuggingEnabled || isSet
|
|
case testSigning:
|
|
w.TestSigningEnabled = w.TestSigningEnabled || isSet
|
|
|
|
// Boolean signals that latch off if the are ever false (ie: attributes
|
|
// that represent a stronger security state when set).
|
|
case codeIntegrity:
|
|
if isSet && w.CodeIntegrityEnabled == TernaryUnknown {
|
|
w.CodeIntegrityEnabled = TernaryTrue
|
|
} else if !isSet {
|
|
w.CodeIntegrityEnabled = TernaryFalse
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *WinEvents) readUint32(header microsoftEventHeader, r io.Reader) (uint32, error) {
|
|
if header.Size != 4 {
|
|
return 0, fmt.Errorf("integer size not uint32 (%d bytes)", header.Size)
|
|
}
|
|
|
|
data := make([]uint8, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return 0, fmt.Errorf("reading u32: %w", err)
|
|
}
|
|
i := binary.LittleEndian.Uint32(data)
|
|
|
|
return i, nil
|
|
}
|
|
|
|
func (w *WinEvents) readUint64(header microsoftEventHeader, r io.Reader) (uint64, error) {
|
|
if header.Size != 8 {
|
|
return 0, fmt.Errorf("integer size not uint64 (%d bytes)", header.Size)
|
|
}
|
|
|
|
data := make([]uint8, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return 0, fmt.Errorf("reading u64: %w", err)
|
|
}
|
|
i := binary.LittleEndian.Uint64(data)
|
|
|
|
return i, nil
|
|
}
|
|
|
|
func (w *WinEvents) readBootCounter(header microsoftEventHeader, r *bytes.Reader) error {
|
|
i, err := w.readUint64(header, r)
|
|
if err != nil {
|
|
return fmt.Errorf("boot counter: %v", err)
|
|
}
|
|
|
|
if w.BootCount > 0 && w.BootCount != i {
|
|
return fmt.Errorf("conflicting values for boot counter: %d != %d", i, w.BootCount)
|
|
}
|
|
w.BootCount = i
|
|
return nil
|
|
}
|
|
|
|
func (w *WinEvents) readTransferControl(header microsoftEventHeader, r *bytes.Reader) error {
|
|
i, err := w.readUint32(header, r)
|
|
if err != nil {
|
|
return fmt.Errorf("transfer control: %v", err)
|
|
}
|
|
|
|
// A transferControl event with a value of 1 indicates that bootmngr
|
|
// launched WinLoad. A different (unknown) value is set if WinResume
|
|
// is launched.
|
|
w.ColdBoot = i == 0x1
|
|
return nil
|
|
}
|
|
|
|
func (w *WinEvents) readBitlockerUnlock(header microsoftEventHeader, r *bytes.Reader, pcr int) error {
|
|
if header.Size > 8 {
|
|
return fmt.Errorf("bitlocker data too large (%d bytes)", header.Size)
|
|
}
|
|
data := make([]uint8, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return fmt.Errorf("reading u%d: %w", header.Size<<8, err)
|
|
}
|
|
i, n := binary.Uvarint(data)
|
|
if n <= 0 {
|
|
return fmt.Errorf("reading u%d: invalid varint", header.Size<<8)
|
|
}
|
|
|
|
if pcr == 13 {
|
|
// The bitlocker status is duplicated across both PCRs. As such,
|
|
// we prefer the earlier one, and bail here to prevent duplicate
|
|
// records.
|
|
return nil
|
|
}
|
|
|
|
w.BitlockerUnlocks = append(w.BitlockerUnlocks, BitlockerStatus(i))
|
|
return nil
|
|
}
|
|
|
|
func (w *WinEvents) parseImageValidated(header microsoftEventHeader, r io.Reader) (bool, error) {
|
|
if header.Size != 1 {
|
|
return false, fmt.Errorf("payload was %d bytes, want 1", header.Size)
|
|
}
|
|
var num byte
|
|
if err := binary.Read(r, binary.LittleEndian, &num); err != nil {
|
|
return false, fmt.Errorf("reading u8: %w", err)
|
|
}
|
|
return num == 1, nil
|
|
}
|
|
|
|
func (w *WinEvents) parseHashAlgID(header microsoftEventHeader, r io.Reader) (WinCSPAlg, error) {
|
|
i, err := w.readUint32(header, r)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("hash algorithm ID: %v", err)
|
|
}
|
|
|
|
switch alg := WinCSPAlg(i & 0xff); alg {
|
|
case WinAlgMD4, WinAlgMD5, WinAlgSHA1, WinAlgSHA256, WinAlgSHA384, WinAlgSHA512:
|
|
return alg, nil
|
|
default:
|
|
return 0, fmt.Errorf("unknown algorithm ID: %x", i)
|
|
}
|
|
}
|
|
|
|
func (w *WinEvents) parseAuthoritySerial(header microsoftEventHeader, r io.Reader) ([]byte, error) {
|
|
if header.Size > 128 {
|
|
return nil, fmt.Errorf("authority serial is too long (%d bytes)", header.Size)
|
|
}
|
|
data := make([]byte, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return nil, fmt.Errorf("reading bytes: %w", err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (w *WinEvents) parseAuthoritySHA1(header microsoftEventHeader, r io.Reader) ([]byte, error) {
|
|
if header.Size > 20 {
|
|
return nil, fmt.Errorf("authority thumbprint is too long (%d bytes)", header.Size)
|
|
}
|
|
data := make([]byte, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return nil, fmt.Errorf("reading bytes: %w", err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (w *WinEvents) parseImageBase(header microsoftEventHeader, r io.Reader) (uint64, error) {
|
|
if header.Size != 8 {
|
|
return 0, fmt.Errorf("payload was %d bytes, want 8", header.Size)
|
|
}
|
|
var num uint64
|
|
if err := binary.Read(r, binary.LittleEndian, &num); err != nil {
|
|
return 0, fmt.Errorf("reading u64: %w", err)
|
|
}
|
|
return num, nil
|
|
}
|
|
|
|
func (w *WinEvents) parseAuthenticodeHash(header microsoftEventHeader, r io.Reader) ([]byte, error) {
|
|
if header.Size > 32 {
|
|
return nil, fmt.Errorf("authenticode hash data exceeds the size of any valid hash (%d bytes)", header.Size)
|
|
}
|
|
data := make([]byte, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return nil, fmt.Errorf("reading bytes: %w", err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (w *WinEvents) readLoadedModuleAggregation(rdr *bytes.Reader, header microsoftEventHeader) error {
|
|
var (
|
|
r = &io.LimitedReader{R: rdr, N: int64(header.Size)}
|
|
codeHash []byte
|
|
imgBase, imgSize uint64
|
|
fPath string
|
|
algID WinCSPAlg
|
|
imgValidated bool
|
|
aIssuer, aPublisher string
|
|
aSerial, aSHA1 []byte
|
|
)
|
|
|
|
for r.N > 0 {
|
|
var h microsoftEventHeader
|
|
if err := binary.Read(r, binary.LittleEndian, &h); err != nil {
|
|
return fmt.Errorf("parsing LMA sub-event: %v", err)
|
|
}
|
|
if int64(h.Size) > r.N {
|
|
return fmt.Errorf("LMA sub-event is larger than available data: %d > %d", h.Size, r.N)
|
|
}
|
|
|
|
var err error
|
|
switch h.Type {
|
|
case imageBase:
|
|
if imgBase != 0 {
|
|
return errors.New("duplicate image base data in LMA event")
|
|
}
|
|
if imgBase, err = w.parseImageBase(h, r); err != nil {
|
|
return err
|
|
}
|
|
case authenticodeHash:
|
|
if codeHash != nil {
|
|
return errors.New("duplicate authenticode hash structure in LMA event")
|
|
}
|
|
if codeHash, err = w.parseAuthenticodeHash(h, r); err != nil {
|
|
return err
|
|
}
|
|
case filePath:
|
|
if fPath != "" {
|
|
return errors.New("duplicate file path in LMA event")
|
|
}
|
|
if fPath, err = w.parseUTF16(h, r); err != nil {
|
|
return err
|
|
}
|
|
case imageSize:
|
|
if imgSize != 0 {
|
|
return errors.New("duplicate image size in LMA event")
|
|
}
|
|
if imgSize, err = w.readUint64(h, r); err != nil {
|
|
return err
|
|
}
|
|
case hashAlgorithmID:
|
|
if algID != 0 {
|
|
return errors.New("duplicate hash algorithm ID in LMA event")
|
|
}
|
|
if algID, err = w.parseHashAlgID(h, r); err != nil {
|
|
return err
|
|
}
|
|
case imageValidated:
|
|
if imgValidated {
|
|
return errors.New("duplicate image validated field in LMA event")
|
|
}
|
|
if imgValidated, err = w.parseImageValidated(h, r); err != nil {
|
|
return err
|
|
}
|
|
case authorityIssuer:
|
|
if aIssuer != "" {
|
|
return errors.New("duplicate authority issuer in LMA event")
|
|
}
|
|
if aIssuer, err = w.parseUTF16(h, r); err != nil {
|
|
return err
|
|
}
|
|
case authorityPublisher:
|
|
if aPublisher != "" {
|
|
return errors.New("duplicate authority publisher in LMA event")
|
|
}
|
|
if aPublisher, err = w.parseUTF16(h, r); err != nil {
|
|
return err
|
|
}
|
|
case authoritySerial:
|
|
if aSerial != nil {
|
|
return errors.New("duplicate authority serial in LMA event")
|
|
}
|
|
if aSerial, err = w.parseAuthoritySerial(h, r); err != nil {
|
|
return err
|
|
}
|
|
case authoritySHA1Thumbprint:
|
|
if aSHA1 != nil {
|
|
return errors.New("duplicate authority SHA1 thumbprint in LMA event")
|
|
}
|
|
if aSHA1, err = w.parseAuthoritySHA1(h, r); err != nil {
|
|
return err
|
|
}
|
|
case moduleSVN:
|
|
// Ignore - consume value.
|
|
b := make([]byte, h.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &b); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown event in LMA aggregation: %v", h.Type)
|
|
}
|
|
}
|
|
|
|
var iBase []uint64
|
|
if imgBase != 0 {
|
|
iBase = []uint64{imgBase}
|
|
}
|
|
|
|
l := WinModuleLoad{
|
|
FilePath: fPath,
|
|
AuthenticodeHash: codeHash,
|
|
ImageBase: iBase,
|
|
ImageSize: imgSize,
|
|
ImageValidated: imgValidated,
|
|
HashAlgorithm: algID,
|
|
AuthorityIssuer: aIssuer,
|
|
AuthorityPublisher: aPublisher,
|
|
AuthoritySerial: aSerial,
|
|
AuthoritySHA1: aSHA1,
|
|
}
|
|
hashHex := hex.EncodeToString(l.AuthenticodeHash)
|
|
l.ImageBase = append(l.ImageBase, w.LoadedModules[hashHex].ImageBase...)
|
|
w.LoadedModules[hashHex] = l
|
|
return nil
|
|
}
|
|
|
|
// parseUTF16 decodes data representing a UTF16 string. It is assumed the
|
|
// caller has validated that the data size is within allowable bounds.
|
|
func (w *WinEvents) parseUTF16(header microsoftEventHeader, r io.Reader) (string, error) {
|
|
data := make([]uint16, header.Size/2)
|
|
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSuffix(string(utf16.Decode(data)), "\x00"), nil
|
|
}
|
|
|
|
func (w *WinEvents) readELAMAggregation(rdr io.Reader, header microsoftEventHeader) error {
|
|
var (
|
|
r = &io.LimitedReader{R: rdr, N: int64(header.Size)}
|
|
driverName string
|
|
measured []byte
|
|
policy []byte
|
|
config []byte
|
|
)
|
|
|
|
for r.N > 0 {
|
|
var h microsoftEventHeader
|
|
if err := binary.Read(r, binary.LittleEndian, &h); err != nil {
|
|
return fmt.Errorf("parsing ELAM aggregation sub-event: %v", err)
|
|
}
|
|
if int64(h.Size) > r.N {
|
|
return fmt.Errorf("ELAM aggregation sub-event is larger than available data: %d > %d", h.Size, r.N)
|
|
}
|
|
|
|
var err error
|
|
switch h.Type {
|
|
case elamAggregation:
|
|
w.readELAMAggregation(r, h)
|
|
if r.N == 0 {
|
|
return nil
|
|
}
|
|
case elamKeyname:
|
|
if driverName != "" {
|
|
return errors.New("duplicate driver name in ELAM aggregation event")
|
|
}
|
|
if driverName, err = w.parseUTF16(h, r); err != nil {
|
|
return fmt.Errorf("parsing ELAM driver name: %v", err)
|
|
}
|
|
case elamMeasured:
|
|
if measured != nil {
|
|
return errors.New("duplicate measured data in ELAM aggregation event")
|
|
}
|
|
measured = make([]byte, h.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &measured); err != nil {
|
|
return fmt.Errorf("reading ELAM measured value: %v", err)
|
|
}
|
|
case elamPolicy:
|
|
if policy != nil {
|
|
return errors.New("duplicate policy data in ELAM aggregation event")
|
|
}
|
|
policy = make([]byte, h.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &policy); err != nil {
|
|
return fmt.Errorf("reading ELAM policy value: %v", err)
|
|
}
|
|
case elamConfiguration:
|
|
if config != nil {
|
|
return errors.New("duplicate config data in ELAM aggregation event")
|
|
}
|
|
config = make([]byte, h.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &config); err != nil {
|
|
return fmt.Errorf("reading ELAM config value: %v", err)
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown event in LMA aggregation: %v", h.Type)
|
|
}
|
|
}
|
|
|
|
if driverName == "" {
|
|
return errors.New("ELAM driver name not specified")
|
|
}
|
|
w.ELAM[driverName] = WinELAM{
|
|
Measured: measured,
|
|
Config: config,
|
|
Policy: policy,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *WinEvents) readSIPAEvent(r *bytes.Reader, pcr int) error {
|
|
var header microsoftEventHeader
|
|
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch header.Type {
|
|
case elamAggregation:
|
|
return w.readELAMAggregation(r, header)
|
|
case loadedModuleAggregation:
|
|
return w.readLoadedModuleAggregation(r, header)
|
|
case bootCounter:
|
|
return w.readBootCounter(header, r)
|
|
case bitlockerUnlock:
|
|
return w.readBitlockerUnlock(header, r, pcr)
|
|
case transferControl:
|
|
return w.readTransferControl(header, r)
|
|
|
|
case osKernelDebug, codeIntegrity, bootDebugging, testSigning: // Parse boolean values.
|
|
return w.readBooleanByteEvent(header, r)
|
|
case dataExecutionPrevention: // Parse booleans represented as uint64's.
|
|
return w.readBooleanInt64Event(header, r)
|
|
|
|
default:
|
|
// Event type was not handled, consume the data.
|
|
if int(header.Size) > r.Len() {
|
|
return fmt.Errorf("event data len (%d bytes) larger than event length (%d bytes)", header.Size, r.Len())
|
|
}
|
|
tmp := make([]byte, header.Size)
|
|
if err := binary.Read(r, binary.LittleEndian, &tmp); err != nil {
|
|
return fmt.Errorf("reading unknown data section of length %d: %w", header.Size, err)
|
|
}
|
|
|
|
return unknownSIPAEvent
|
|
}
|
|
}
|
|
|
|
// readWinEventBlock extracts boot configuration from SIPA events contained in
|
|
// the given tagged event.
|
|
func (w *WinEvents) readWinEventBlock(evt *internal.TaggedEventData, pcr int) error {
|
|
r := bytes.NewReader(evt.Data)
|
|
|
|
// All windows information should be sub events in an enclosing SIPA
|
|
// container event.
|
|
if (windowsEvent(evt.ID) & sipaTypeMask) != sipaContainer {
|
|
return fmt.Errorf("expected container event, got %v", windowsEvent(evt.ID))
|
|
}
|
|
|
|
for r.Len() > 0 {
|
|
if err := w.readSIPAEvent(r, pcr); err != nil {
|
|
if errors.Is(err, unknownSIPAEvent) {
|
|
// Unknown SIPA events are okay as all TCG events are verifiable.
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|