Files
chipsec-chipsec/chipsec/hal/smbios.py
T
2021-09-28 10:19:52 -07:00

505 lines
22 KiB
Python

#CHIPSEC: Platform Security Assessment Framework
#Copyright (c) 2019-2021, Intel Corporation
#
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; Version 2.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#Contact information:
#chipsec@intel.com
#
"""
HAL component providing access to and decoding of SMBIOS structures
"""
import struct
from collections import namedtuple
from chipsec.defines import BOUNDARY_1MB, bytestostring
from chipsec.hal import hal_base, uefi
from chipsec.logger import logger
SCAN_LOW_LIMIT = 0xF0000
SCAN_SIZE = 0x10000
SMBIOS_2_x_SIG = b"_SM_"
SMBIOS_2_x_ENTRY_SIZE = 0x1F
SMBIOS_2_x_ENTRY_SIZE_OLD = 0x1E
SMBIOS_2_x_MAJOR_VER = 0x02
SMBIOS_2_x_INT_SIG = b"_DMI_"
SMBIOS_2_x_GUID = "EB9D2D31-2D88-11D3-9A16-0090273FC14D"
SMBIOS_2_x_ENTRY_POINT_FMT = "=4sBBBBHB5B5sBHIHB"
SMBIOS_2_x_ENTRY_POINT_SIZE = struct.calcsize(SMBIOS_2_x_ENTRY_POINT_FMT)
SMBIOS_2_x_FORMAT_STRING = """
SMBIOS 2.x Entry Point Structure:
Anchor String : {}
Checksum : 0x{:02X}
Entry Point Length : 0x{:02X}
Entry Point Version : {:d}.{:d}
Max Structure Size : 0x{:04X}
Entry Point Revision : 0x{:02X}
Formatted Area : 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}
Intermediate Anchor String: {}
Intermediate Checksum : 0x{:02X}
Structure Table Length : 0x{:04X}
Structure Talbe Address : 0x{:08X}
SMBIOS Structure Count : 0x{:04X}
SMBIOS BCD Revision : 0x{:02X}
"""
class SMBIOS_2_x_ENTRY_POINT(namedtuple('SMBIOS_2_x_ENTRY_POINT', 'Anchor EntryCs EntryLen MajorVer MinorVer MaxSize EntryRev \
FormatArea0 FormatArea1 FormatArea2 FormatArea3 FormatArea4 IntAnchor IntCs TableLen TableAddr NumStructures BcdRev')):
__slots__ = ()
def __str__(self):
return SMBIOS_2_x_FORMAT_STRING.format(bytestostring(self.Anchor), self.EntryCs, self.EntryLen, self.MajorVer, \
self.MinorVer, self.MaxSize, self.EntryRev, self.FormatArea0, self.FormatArea1, \
self.FormatArea2, self.FormatArea3, self.FormatArea4, bytestostring(self.IntAnchor), \
self.IntCs, self.TableLen, self.TableAddr, self.NumStructures, self.BcdRev)
SMBIOS_3_x_SIG = b"_SM3_"
SMBIOS_3_x_ENTRY_SIZE = 0x18
SMBIOS_3_x_MAJOR_VER = 0x03
SMBIOS_3_x_GUID = "F2FD1544-9794-4A2C-992E-E5BBCF20E394"
SMBIOS_3_x_ENTRY_POINT_FMT = "=5sBBBBBBBIQ"
SMBIOS_3_x_ENTRY_POINT_SIZE = struct.calcsize(SMBIOS_3_x_ENTRY_POINT_FMT)
SMBIOS_3_x_FORMAT_STRING = """
SMBIOS 3.x Entry Point Structure:
Anchor String : {}
Checksum : 0x{:02X}
Entry Point Length : 0x{:02X}
Entry Ponnt Version : {:d}.{:d}
SMBIOS Docrev : 0x{:02X}
Entry Point Revision : 0x{:02X}
Reserved : 0x{:02X}
Max Structure Size : 0x{:08X}
Structure Table Address : 0x{:016X}
"""
class SMBIOS_3_x_ENTRY_POINT(namedtuple('SMBIOS_3_x_ENTRY_POINT', 'Anchor EntryCs EntryLen MajorVer MinorVer Docrev EntryRev \
Reserved MaxSize TableAddr')):
__slots__ = ()
def __str__(self):
return SMBIOS_3_x_FORMAT_STRING.format(bytestostring(self.Anchor), self.EntryCs, self.EntryLen, self.MajorVer, \
self.MinorVer, self.Docrev, self.EntryRev, self.Reserved, self.MaxSize, \
self.TableAddr)
SMBIOS_STRUCT_HEADER_FMT = "=BBH"
SMBIOS_STRUCT_HEADER_SIZE = struct.calcsize(SMBIOS_STRUCT_HEADER_FMT)
SMBIOS_STRUCT_HEADER_FORMAT_STRING = """
SMBIOS Stuct Header:
Type : 0x{0:02X} ({0:d})
Length : 0x{1:02X}
Handle : 0x{2:04X}
"""
class SMBIOS_STRUCT_HEADER(namedtuple('SMBIOS_STRUCT_HEADER', 'Type Length Handle')):
__slots__ = ()
def __str__(self):
return SMBIOS_STRUCT_HEADER_FORMAT_STRING.format(self.Type, self.Length, self.Handle)
SMBIOS_STRUCT_TERM_FMT = "=H"
SMBIOS_STRUCT_TERM_SIZE = struct.calcsize(SMBIOS_STRUCT_TERM_FMT)
SMBIOS_STRUCT_TERM_VAL = 0x0000
SMBIOS_BIOS_INFO_ENTRY_ID = 0
SMBIOS_BIOS_INFO_2_0_ENTRY_FMT = '=BBHBBHBBQ'
SMBIOS_BIOS_INFO_2_0_ENTRY_SIZE = struct.calcsize(SMBIOS_BIOS_INFO_2_0_ENTRY_FMT)
SMBIOS_BIOS_INFO_2_0_FORMAT_STRING = """
SMBIOS BIOS Information:
Type : 0x{0:02X} ({0:d})
Length : 0x{1:02X}
Handle : 0x{2:04X}
Vendor : {3:s}
BIOS Version : {4:s}
BIOS Starting Segment : 0x{5:04X}
BIOS Release Date : {6:s}
BIOS ROM Size : 0x{7:02X}
BIOS Characteristics : 0x{8:016X}
"""
SMBIOS_BIOS_INFO_2_0_FORMAT_STRING_FAILED = """
SMBIOS BIOS Information structure decode failed
"""
class SMBIOS_BIOS_INFO_2_0(namedtuple('SMBIOS_BIOS_INFO_2_0_ENTRY', 'type length handle vendor_str version_str segment \
release_str rom_sz bios_char strings')):
__slots__ = ()
def __str__(self):
str_count = len(self.strings)
ven_str = ''
ver_str = ''
rel_str = ''
if self.vendor_str != 0 and self.vendor_str <= str_count:
ven_str = self.strings[self.vendor_str - 1]
if self.version_str != 0 and self.version_str <= str_count:
ver_str = self.strings[self.version_str - 1]
if self.release_str != 0 and self.release_str <= str_count:
rel_str = self.strings[self.release_str - 1]
return SMBIOS_BIOS_INFO_2_0_FORMAT_STRING.format(self.type, self.length, self.handle, ven_str, \
ver_str, self.segment, rel_str, self.rom_sz, self.bios_char)
SMBIOS_SYSTEM_INFO_ENTRY_ID = 1
SMBIOS_SYSTEM_INFO_2_0_ENTRY_FMT = '=BBHBBBB'
SMBIOS_SYSTEM_INFO_2_0_ENTRY_SIZE = struct.calcsize(SMBIOS_SYSTEM_INFO_2_0_ENTRY_FMT)
SMBIOS_SYSTEM_INFO_2_0_FORMAT_STRING = """
SMBIOS System Information:
Type : 0x{0:02X} ({0:d})
Length : 0x{1:02X}
Handle : 0x{2:04X}
Manufacturer : {3:s}
Product Name : {4:s}
Version : {5:s}
Serial Number : {6:s}
"""
SMBIOS_SYSTEM_INFO_2_0_FORMAT_STRING_FAILED = """
SMBIOS System Information structure decode failed
"""
class SMBIOS_SYSTEM_INFO_2_0(namedtuple('SMBIOS_SYSTEM_INFO_2_0_ENTRY', 'type length handle manufacturer_str product_str \
version_str serial_str strings')):
__slots__ = ()
def __str__(self):
str_count = len(self.strings)
man_str = ''
pro_str = ''
ver_str = ''
ser_str = ''
if self.manufacturer_str != 0 and self.manufacturer_str <= str_count:
man_str = self.strings[self.manufacturer_str - 1]
if self.product_str != 0 and self.product_str <= str_count:
pro_str = self.strings[self.product_str - 1]
if self.version_str != 0 and self.version_str <= str_count:
ver_str = self.strings[self.version_str - 1]
if self.serial_str != 0 and self.serial_str <= str_count:
ser_str = self.strings[self.serial_str - 1]
return SMBIOS_SYSTEM_INFO_2_0_FORMAT_STRING.format(self.type, self.length, self.handle, man_str, \
pro_str, ver_str, ser_str)
struct_decode_tree = {
SMBIOS_BIOS_INFO_ENTRY_ID: {'class': SMBIOS_BIOS_INFO_2_0, 'format': SMBIOS_BIOS_INFO_2_0_ENTRY_FMT},
SMBIOS_SYSTEM_INFO_ENTRY_ID: {'class': SMBIOS_SYSTEM_INFO_2_0, 'format': SMBIOS_SYSTEM_INFO_2_0_ENTRY_FMT}
}
class SMBIOS(hal_base.HALBase):
def __init__(self, cs):
super(SMBIOS, self).__init__(cs)
self.uefi = uefi.UEFI(cs)
self.smbios_2_guid_found = False
self.smbios_2_pa = None
self.smbios_2_ep = None
self.smbios_2_data = None
self.smbios_3_guid_found = False
self.smbios_3_pa = None
self.smbios_3_ep = None
self.smbios_3_data = None
def __get_raw_struct(self, table, start_offset):
"""
Returns a tuple including the raw data and the offset to the next entry. This allows the function
to be called multiple times to process all the entries in a table.
Return Value:
(raw_data, next_offset)
Error/End:
(None, None)
"""
# Check for end of table and remaining size to parse
if table is None:
if logger().HAL: logger().log('- Invalid table')
return (None, None)
table_len = len(table)
if logger().HAL: logger().log('Start Offset: 0x{:04X}, Table Size: 0x{:04X}'.format(start_offset, table_len))
if start_offset >= table_len:
if logger().HAL: logger().log('- Bad table length (table_len): 0x{:04X}'.format(table_len))
return (None, None)
size_left = len(table[start_offset:])
if size_left < SMBIOS_STRUCT_HEADER_SIZE:
if logger().HAL: logger().log('- Table too small (size_left): 0x{:04X}'.format(size_left))
return (None, None)
# Read the header to determine structure fixed size
try:
header = SMBIOS_STRUCT_HEADER(*struct.unpack_from(SMBIOS_STRUCT_HEADER_FMT, \
table[start_offset:start_offset + SMBIOS_STRUCT_HEADER_SIZE]))
except:
if logger().HAL: logger().log('- Unable to unpack data')
return (None, None)
str_offset = start_offset + header.Length
if str_offset + SMBIOS_STRUCT_TERM_SIZE >= table_len:
if logger().HAL: logger().log('- Not enough space for termination (str_offset): 0x{:04X}'.format(str_offset))
return (None, None)
# Process any remaing content (strings)
if logger().HAL: logger().log('String start offset: 0x{:04X}'.format(str_offset))
tmp_offset = str_offset
while (tmp_offset + SMBIOS_STRUCT_TERM_SIZE < table_len):
(value, ) = struct.unpack_from(SMBIOS_STRUCT_TERM_FMT, table[tmp_offset:tmp_offset + SMBIOS_STRUCT_TERM_SIZE])
if value == SMBIOS_STRUCT_TERM_VAL:
if logger().HAL: logger().log('+ Found structure termination')
break
tmp_offset += 1
if tmp_offset >= table_len:
if logger().HAL: logger().log('- End of table reached')
return (None, None)
tmp_offset += SMBIOS_STRUCT_TERM_SIZE
if logger().HAL: logger().log('Structure Size: 0x{:04X}'.format(tmp_offset - start_offset))
return (table[start_offset:tmp_offset], tmp_offset)
def __validate_ep_2_values(self, pa):
# Force a second read of memory so we don't have to worry about it falling outside the
# original buffer.
try:
if logger().HAL: logger().log('Validating 32bit SMBIOS header @ 0x{:08X}'.format(pa))
mem_buffer = self.cs.mem.read_physical_mem(pa, SMBIOS_2_x_ENTRY_POINT_SIZE)
ep_data = SMBIOS_2_x_ENTRY_POINT(*struct.unpack_from(SMBIOS_2_x_ENTRY_POINT_FMT, mem_buffer))
except:
if logger().HAL: logger().log('- Memory read failed')
return None
if ep_data.Anchor != SMBIOS_2_x_SIG:
if logger().HAL: logger().log('- Invalid signature')
return None
if not (ep_data.EntryLen == SMBIOS_2_x_ENTRY_SIZE or ep_data.EntryLen == SMBIOS_2_x_ENTRY_SIZE_OLD):
if logger().HAL: logger().log('- Invalid structure size')
return None
if ep_data.IntAnchor != SMBIOS_2_x_INT_SIG:
if logger().HAL: logger().log('- Invalid intermediate signature')
return None
if ep_data.TableAddr == 0 or ep_data.TableLen == 0:
if logger().HAL: logger().log('- Invalid table address or length')
return None
return ep_data
def __validate_ep_3_values(self, pa):
# Force a second read of memory so we don't have to worry about it falling outside the
# original buffer.
try:
if logger().HAL: logger().log('Validating 64bit SMBIOS header @ 0x{:08X}'.format(pa))
mem_buffer = self.cs.mem.read_physical_mem(pa, SMBIOS_3_x_ENTRY_POINT_SIZE)
ep_data = SMBIOS_3_x_ENTRY_POINT(*struct.unpack_from(SMBIOS_3_x_ENTRY_POINT_FMT, mem_buffer))
except:
if logger().HAL: logger().log('- Memory read failed')
return None
if ep_data.Anchor != SMBIOS_3_x_SIG:
if logger().HAL: logger().log('- Invalid signature')
return None
if not (ep_data.EntryLen == SMBIOS_3_x_ENTRY_SIZE):
if logger().HAL: logger().log('- Invalid structure size')
return None
if ep_data.MaxSize == 0 or ep_data.TableAddr == 0:
if logger().HAL: logger().log('- Invalid table address or maximum size')
return None
return ep_data
def find_smbios_table(self):
# Handle the case were we already found the tables
if self.smbios_2_ep is not None or self.smbios_3_ep is not None:
return True
# Initialize search parameters
entries_to_find = entries_found = 0
# Fist get the configuration table using the UEFI HAL. You may not be able to use the addresses
# in the table because in some cases they have been converted to a VA and are not mapped.
if logger().HAL: logger().log('Chedking UEFI Configuration Table for SMBIOS entry')
(ect_found, ect_pa, ect, ect_buf) = self.uefi.find_EFI_Configuration_Table()
if ect_found:
if logger().HAL: logger().log(ect)
if SMBIOS_2_x_GUID in ect.VendorTables:
if logger().HAL: logger().log('+ Found 32bit SMBIOS entry')
if logger().HAL: logger().log('+ Potential 2.x table address: 0x{:016X}'.format(ect.VendorTables[SMBIOS_2_x_GUID]))
self.smbios_2_guid_found = True
entries_to_find += 1
if SMBIOS_3_x_GUID in ect.VendorTables:
if logger().HAL: logger().log('+ Found 64bit SMBIOS entry')
if logger().HAL: logger().log('+ Potential 3.x table address: 0x{:016X}'.format(ect.VendorTables[SMBIOS_3_x_GUID]))
self.smbios_3_guid_found = True
entries_to_find += 1
# Determine regions to scan
if self.smbios_2_guid_found or self.smbios_3_guid_found:
(smm_base, smm_limit, smm_size) = self.cs.cpu.get_SMRAM()
pa = smm_base - SCAN_SIZE
else:
entries_to_find = 2
pa = BOUNDARY_1MB - SCAN_SIZE
# Scan memory for the signature
if logger().HAL: logger().log('Scanning memory for {:d} signature(s)'.format(entries_to_find))
while (pa >= SCAN_LOW_LIMIT):
mem_buffer = self.cs.mem.read_physical_mem(pa, SCAN_SIZE)
sig_pa = mem_buffer.find(SMBIOS_2_x_SIG) + pa
if sig_pa >= pa and self.smbios_2_pa is None:
if logger().HAL: logger().log('+ Found SMBIOS 2.x signature @ 0x{:08X}'.format(sig_pa))
self.smbios_2_ep = self.__validate_ep_2_values(sig_pa)
if self.smbios_2_ep is not None:
if logger().HAL: logger().log('+ Verified SMBIOS 2.x Entry Point structure')
self.smbios_2_pa = sig_pa
entries_found += 1
sig_pa = mem_buffer.find(SMBIOS_3_x_SIG) + pa
if sig_pa >= pa and self.smbios_3_pa is None:
if logger().HAL: logger().log('+ Found SMBIOS 3.x signature @ 0x{:08X}'.format(sig_pa))
self.smbios_3_ep = self.__validate_ep_3_values(sig_pa)
if self.smbios_3_ep is not None:
if logger().HAL: logger().log('+ Verified SMBIOS 3.x Entry Point structure')
self.smbios_3_pa = sig_pa
entries_found += 1
if entries_found >= entries_to_find:
break
pa -= SCAN_SIZE
# Check to see if we thing we found the structure
if self.smbios_2_pa is None and self.smbios_3_pa is None:
if logger().HAL: logger().log('- Unable to find SMBIOS tables')
return False
# Read the raw data regions
if logger().HAL: logger().log('Reading SMBIOS data tables:')
if self.smbios_2_ep is not None and self.smbios_2_ep.TableAddr != 0 and self.smbios_2_ep.TableLen != 0:
self.smbios_2_data = self.cs.mem.read_physical_mem(self.smbios_2_ep.TableAddr, self.smbios_2_ep.TableLen)
if self.smbios_2_data is None and logger().HAL:
logger().log('- Failed to read 32bit SMBIOS data')
if self.smbios_3_ep is not None and self.smbios_3_ep.TableAddr != 0 and self.smbios_3_ep.MaxSize != 0:
self.smbios_3_data = self.cs.mem.read_physical_mem(self.smbios_3_ep.TableAddr, self.smbios_3_ep.MaxSize)
if self.smbios_3_data is None and logger().HAL:
logger().log('- Failed to read 64bit SMBIOS data')
return True
def get_raw_structs(self, struct_type=None, force_32bit=False):
"""
Returns a list of raw data blobs for each SMBIOS structure. The default is to process the 64bit
entries if available unless specifically specified.
Error:
None
"""
ret_val = []
if self.smbios_3_data is not None and not force_32bit:
if logger().HAL: logger().log('Using 64bit SMBIOS table')
table = self.smbios_3_data
elif self.smbios_2_data is not None:
if logger().HAL: logger().log('Using 32bit SMBIOS table')
table = self.smbios_2_data
else:
if logger().HAL: logger().log('- No SMBIOS data available')
return None
if logger().HAL: logger().log('Getting SMBIOS structures...')
raw_data, next_offset = self.__get_raw_struct(table, 0)
while next_offset is not None:
if struct_type is None:
ret_val.append(raw_data)
else:
header = SMBIOS_STRUCT_HEADER(*struct.unpack_from(SMBIOS_STRUCT_HEADER_FMT, raw_data[:SMBIOS_STRUCT_HEADER_SIZE]))
if header is not None and header.Type == struct_type:
ret_val.append(raw_data)
raw_data, next_offset = self.__get_raw_struct(table, next_offset)
return ret_val
def get_header(self, raw_data):
if logger().HAL: logger().log('Getting generic SMBIOS header information')
if raw_data is None:
if logger().HAL: logger().log('- Raw data pointer is None')
return None
if len(raw_data) < SMBIOS_STRUCT_HEADER_SIZE:
if logger().HAL: logger().log('- Raw data too small for header information')
return None
try:
header = SMBIOS_STRUCT_HEADER(*struct.unpack_from(SMBIOS_STRUCT_HEADER_FMT, raw_data[:SMBIOS_STRUCT_HEADER_SIZE]))
except:
if logger().HAL: logger().log('- Failed to extract information from raw data')
return None
return header
def get_string_list(self, raw_data):
ret_val = []
if logger().HAL: logger().log('Getting strings from structure')
raw_data_size = len(raw_data)
header = self.get_header(raw_data)
if header is None:
return None
if header.Length + SMBIOS_STRUCT_TERM_SIZE > raw_data_size:
if logger().HAL: logger().log('- Data buffer too small for structure')
return None
if header.Length + SMBIOS_STRUCT_TERM_SIZE == raw_data_size:
if logger().HAL: logger().log('+ No strings in this structure')
return ret_val
index = 0
tmp_offset = header.Length
while tmp_offset + index + 1 < raw_data_size:
(value, ) = struct.unpack_from('=B', raw_data[tmp_offset +index:])
if value == 0:
if logger().HAL: logger().log('+ Unpacking string of size {:d}'.format(index))
(string, ) = struct.unpack_from('={:d}s'.format(index), raw_data[tmp_offset:])
string = bytestostring(string)
if logger().HAL: logger().log('+ Found: {:s}'.format(string))
ret_val.append(string)
tmp_offset += index + 1
index = 0
continue
index += 1
if logger().HAL: logger().log('+ Found {:d} strings'.format(len(ret_val)))
return ret_val
def get_decoded_structs(self, struct_type=None, force_32bit=False):
ret_val = []
# Determine if the structure exists in the table
if logger().HAL: logger().log('Getting decoded SMBIOS structures')
structs = self.get_raw_structs(struct_type, force_32bit)
if structs is None:
return None
# Process all the entries
for data in structs:
# Get the structures header information so we can determine the correct decode method
header = self.get_header(data)
if header is None:
if logger().HAL: logger().log('- Could not decode header')
continue
if header.Type not in struct_decode_tree:
if logger().HAL: logger().log('- Structure {:d} not in decode list'.format(header.Type))
continue
# Unpack the structure and then get the strings
tmp_decode = struct_decode_tree[header.Type]
try:
decode_data = struct.unpack_from(tmp_decode['format'], data)
except:
if logger().HAL: logger().log('- Could not decode structure')
continue
if decode_data is None:
if logger().HAL: logger().log('- No structure data was decoded')
continue
strings = self.get_string_list(data)
if strings is not None:
decode_data = decode_data + (strings, )
# Create the actual object
try:
decode_object = tmp_decode['class'](*decode_data)
except:
if logger().HAL: logger().log('- Failed to create structure')
continue
ret_val.append(decode_object)
return ret_val