def __init__(self): BaseModule.__init__(self) self.interrupts = Interrupts( self.cs ) self.is_check_memory = True self.test_ptr_in_buffer = False self.fill_byte = _MEM_FILL_VALUE self.fill_size = _MEM_FILL_SIZE
def run(self): if len(self.argv) < 3: print (SMICommand.__doc__) return try: interrupts = Interrupts( self.cs ) except RuntimeError as msg: print (msg) return op = self.argv[2] t = time.time() if 'count' == op: self.logger.log( "[CHIPSEC] SMI count:" ) for tid in range(self.cs.msr.get_cpu_thread_count()): smi_cnt = self.cs.read_register_field('MSR_SMI_COUNT', 'Count', cpu_thread=tid) self.logger.log( " CPU{:d}: {:d}".format(tid,smi_cnt) ) else: SMI_code_port_value = 0xF SMI_data_port_value = 0x0 if len(self.argv) > 4: thread_id = int(self.argv[2],16) SMI_code_port_value = int(self.argv[3],16) SMI_data_port_value = int(self.argv[4],16) self.logger.log( "[CHIPSEC] Sending SW SMI (code: 0x{:02X}, data: 0x{:02X})..".format(SMI_code_port_value, SMI_data_port_value) ) if 5 == len(self.argv): interrupts.send_SMI_APMC( SMI_code_port_value, SMI_data_port_value ) elif 11 == len(self.argv): _rax = int(self.argv[5],16) _rbx = int(self.argv[6],16) _rcx = int(self.argv[7],16) _rdx = int(self.argv[8],16) _rsi = int(self.argv[9],16) _rdi = int(self.argv[10],16) self.logger.log( " RAX: 0x{:016X} (AX will be overwridden with values of SW SMI ports B2/B3)".format(_rax) ) self.logger.log( " RBX: 0x{:016X}".format(_rbx) ) self.logger.log( " RCX: 0x{:016X}".format(_rcx) ) self.logger.log( " RDX: 0x{:016X} (DX will be overwridden with 0x00B2)".format(_rdx) ) self.logger.log( " RSI: 0x{:016X}".format(_rsi) ) self.logger.log( " RDI: 0x{:016X}".format(_rdi) ) ret = interrupts.send_SW_SMI( thread_id, SMI_code_port_value, SMI_data_port_value, _rax, _rbx, _rcx, _rdx, _rsi, _rdi ) if not ret is None: self.logger.log( "Return values") self.logger.log( " RAX: {:16X}".format(ret[1]) ) self.logger.log( " RBX: {:16X}".format(ret[2]) ) self.logger.log( " RCX: {:16X}".format(ret[3]) ) self.logger.log( " RDX: {:16X}".format(ret[4]) ) self.logger.log( " RSI: {:16X}".format(ret[5]) ) self.logger.log( " RDI: {:16X}".format(ret[6]) ) else: print (SMICommand.__doc__) else: self.logger.error( "unknown command-line option '{:32}'".format(op) ) print (SMICommand.__doc__) return self.logger.log( "[CHIPSEC] (smi) time elapsed {:.3f}".format(time.time()-t) )
def run(self): try: interrupts = Interrupts( self.cs ) except RuntimeError as msg: print (msg) return t = time.time() self.logger.log( "[CHIPSEC] Sending NMI#.." ) interrupts.send_NMI() self.logger.log( "[CHIPSEC] (nmi) time elapsed {:.3f}".format(time.time()-t) )
def __init__(self): BaseModule.__init__(self) self._interrupts = Interrupts(self.cs) # SMI code to be written to I/O port 0xB2 self.smic_start = 0x00 self.smic_end = SMI_CODE_LIMIT # SMI data to be written to I/O port 0xB3 self.smid_start = 0x00 self.smid_end = SMI_DATA_LIMIT # SMI handler "function" often supplied in ECX register self.smif_start = 0x00 self.smif_end = SMI_FUNC_LIMIT # SMM communication buffer often supplied in EBX register self.comm = 0x00 self.reloc_mmio = None
def nmi(argv): if 2 < len(argv): print usage try: interrupts = Interrupts( chipsec_util._cs ) except RuntimeError, msg: print msg return
def run(self): if len(self.argv) < 3: print SMICommand.__doc__ return try: interrupts = Interrupts(self.cs) except RuntimeError, msg: print msg return
def __init__(self): BaseModule.__init__(self) self._interrupts = Interrupts( self.cs ) self.generate_smi = False self.bar_names = [] self.bars = {} self.bars_diff = {} # SMI code to be written to I/O port 0xB2 self.smic_start = 0x00 self.smic_end = SMI_CODE_LIMIT # SMI data to be written to I/O port 0xB3 self.smid_start = 0x00 self.smid_end = SMI_DATA_LIMIT # SMI handler "function" often supplied in ECX register self.smif_start = 0x00 self.smif_end = SMI_FUNC_LIMIT # SMM communication buffer often supplied in EBX register self.comm = 0x00
def run(self): try: self.interrupts = Interrupts(self.cs) except RuntimeError as msg: self.logger.log(msg) return t = time.time() self.func() self.logger.log( "[CHIPSEC] (smi) time elapsed {:.3f}".format(time.time() - t))
def smi(argv): """ >>> chipsec_util smi <thread_id> <SMI_code> <SMI_data> [RAX] [RBX] [RCX] [RDX] [RSI] [RDI] Examples: >>> chipsec_util smi 0x0 0xDE 0x0 >>> chipsec_util smi 0x0 0xDE 0x0 0xAAAAAAAAAAAAAAAA .. """ try: interrupts = Interrupts(chipsec_util._cs) except RuntimeError, msg: print msg return
def nmi(argv): """ >>> chipsec_util nmi Examples: >>> chipsec_util nmi """ if 2 < len(argv): print nmi.__doc__ try: interrupts = Interrupts(chipsec_util._cs) except RuntimeError, msg: print msg return
def __init__(self): BaseModule.__init__(self) self._interrupts = Interrupts( self.cs ) # SMI code to be written to I/O port 0xB2 self.smic_start = 0x00 self.smic_end = SMI_CODE_LIMIT # SMI data to be written to I/O port 0xB3 self.smid_start = 0x00 self.smid_end = SMI_DATA_LIMIT # SMI handler "function" often supplied in ECX register self.smif_start = 0x00 self.smif_end = SMI_FUNC_LIMIT # SMM communication buffer often supplied in EBX register self.comm = 0x00 self.reloc_mmio = None
class rogue_mmio_bar(BaseModule): def __init__(self): BaseModule.__init__(self) self._interrupts = Interrupts( self.cs ) # SMI code to be written to I/O port 0xB2 self.smic_start = 0x00 self.smic_end = SMI_CODE_LIMIT # SMI data to be written to I/O port 0xB3 self.smid_start = 0x00 self.smid_end = SMI_DATA_LIMIT # SMI handler "function" often supplied in ECX register self.smif_start = 0x00 self.smif_end = SMI_FUNC_LIMIT # SMM communication buffer often supplied in EBX register self.comm = 0x00 self.reloc_mmio = None def smi_mmio_range_fuzz(self, thread_id, b, d, f, bar_off, is64bit, bar, new_bar, base, size): # copy all registers from MMIO range to new location in memory # we do that once rather than before every SMI since we return after first change detected self.logger.log( "[*] copying BAR 0x%X > 0x%X" % (base,self.reloc_mmio) ) orig_mmio = self.copy_bar(base, self.reloc_mmio, size) if self.logger.VERBOSE: self.cs.mmio.dump_MMIO(base, size) file.write_file('mmio_mem.orig', orig_mmio) for smi_code in xrange(self.smic_start,self.smic_end+1): for smi_data in xrange(self.smid_start,self.smid_end+1): for ecx in xrange(self.smif_start,self.smif_end+1): self.logger.log( "> SMI# %02X: data %02X, func (ECX) %X" % (smi_code,smi_data,ecx) ) if FLUSH_OUTPUT_AFTER_SMI: self.logger.flush() # point MMIO range to new location (relocate MMIO range) self.logger.log( " relocating BAR 0x%X" % bar ) if not self.modify_bar(b, d, f, bar_off, is64bit, bar, new_bar): continue # generate SW SMI self._interrupts.send_SW_SMI(thread_id, smi_code, smi_data, _FILL_VALUE_QWORD, self.comm, ecx, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD) # restore original location of MMIO range self.restore_bar(b, d, f, bar_off, is64bit, bar) self.logger.log( " restored BAR with 0x%X" % bar ) # check the contents at the address range used to relocate MMIO BAR buf = self.cs.mem.read_physical_mem( self.reloc_mmio, size ) diff = DIFF(orig_mmio, buf, size) self.logger.log(" checking relocated MMIO") if len(diff) > 0: self.logger.log_important("changes found at 0x%X +%s" % (self.reloc_mmio, diff)) if self.logger.VERBOSE: file.write_file('mmio_mem.new', buf) return True return False def copy_bar(self, bar_base, bar_base_mem, size): for off in xrange(0,size,4): r = self.cs.mem.read_physical_mem_dword(bar_base + off) self.cs.mem.write_physical_mem_dword(bar_base_mem + off, r) return self.cs.mem.read_physical_mem(bar_base_mem, size) """ def copy_bar_name(self, name, bar_base_mem): bar_regs = self.cs.mmio.read_MMIO_BAR(bar_name) size = len(bar_regs) off = 0 for r in bar_regs: self.cs.mem.write_physical_mem_dword(bar_base_mem + off, r) off += 4 return self.cs.mem.read_physical_mem(bar_base_mem, size) """ def modify_bar(self, b, d, f, off, is64bit, bar, new_bar): # Modify MMIO BAR address if is64bit: #self.cs.pci.write_dword(b, d, f, off, 0x0) self.cs.pci.write_dword(b, d, f, off + pci.PCI_HDR_BAR_STEP, ((new_bar>>32)&0xFFFFFFFF)) self.cs.pci.write_dword(b, d, f, off, (new_bar&0xFFFFFFFF)) # Check that the MMIO BAR has been modified correctly. Restore original and skip if not l = self.cs.pci.read_dword(b, d, f, off) if l != (new_bar&0xFFFFFFFF): self.restore_bar(b, d, f, off, is64bit, bar) self.logger.log(" skipping (%X != %X)" % (l,new_bar)) return False self.logger.log(" new BAR: 0x%X" % l) return True def restore_bar(self, b, d, f, off, is64bit, bar): if is64bit: #self.cs.pci.write_dword(b, d, f, off, 0x0) self.cs.pci.write_dword(b, d, f, off + pci.PCI_HDR_BAR_STEP, ((bar>>32)&0xFFFFFFFF)) self.cs.pci.write_dword(b, d, f, off, (bar&0xFFFFFFFF)) return True def run( self, module_argv ): self.logger.start_test( "experimental tool to help checking for SMM MMIO BAR issues" ) pcie_devices = [] if len(module_argv) > 0: smic_arr = module_argv[0].split(':') self.smic_start = int(smic_arr[0],16) self.smic_end = int(smic_arr[1],16) if len(module_argv) > 1: try: b,df = module_argv[1].split(':') d,f = df.split('.') pcie_devices = [ (int(b,16),int(d,16),int(f,16),0,0) ] except: self.logger.error("incorrect b:d.f format\nUsage:\nchipsec_main -m tools.smm.rogue_mmio_bar [-a <smi_start:smi_end>,<b:d.f>]") else: self.logger.log("[*] discovering PCIe devices..") pcie_devices = self.cs.pci.enumerate_devices() self.logger.log("[*] testing MMIO of PCIe devices:") for (b,d,f,_,_) in pcie_devices: self.logger.log(" %02X:%02X.%X" % (b,d,f)) # allocate a page or SMM communication buffer (often supplied in EBX register) _, self.comm = self.cs.mem.alloc_physical_mem(0x1000, defines.BOUNDARY_4GB-1) #self.cs.mem.write_physical_mem( self.comm, 0x1000, chr(0)*0x1000 ) # allocate range in physical memory (should cover all MMIO ranges including GTTMMADR) bsz = 2*MAX_MMIO_RANGE_SIZE (va, pa) = self.cs.mem.alloc_physical_mem( bsz, defines.BOUNDARY_4GB-1 ) self.logger.log( "[*] allocated memory range : 0x%016X (0x%X bytes)" % (pa,bsz) ) self.cs.mem.write_physical_mem(pa, bsz, _MEM_FILL_VALUE*bsz) # align at the MAX_MMIO_RANGE_SIZE boundary within allocated range self.reloc_mmio = pa & (~(MAX_MMIO_RANGE_SIZE-1)) if self.reloc_mmio < pa: self.reloc_mmio += MAX_MMIO_RANGE_SIZE self.logger.log("[*] MMIO relocation address: 0x%016X\n" % self.reloc_mmio) for (b, d, f, vid, did) in pcie_devices: self.logger.log("[*] enumerating device %02X:%02X.%X MMIO BARs.." % (b, d, f)) device_bars = self.cs.pci.get_device_bars(b, d, f, True) for (base, isMMIO, is64bit, bar_off, bar, size) in device_bars: if isMMIO and size <= MAX_MMIO_RANGE_SIZE: self.logger.flush() self.logger.log( "[*] found MMIO BAR +0x%02X (base 0x%016X, size 0x%X)" % (bar_off,base,size) ) new_bar = ((self.reloc_mmio & pci.PCI_HDR_BAR_BASE_MASK_MMIO64)|(bar & pci.PCI_HDR_BAR_CFGBITS_MASK)) if self.smi_mmio_range_fuzz(0, b, d, f, bar_off, is64bit, bar, new_bar, base, size): return ModuleResult.FAILED return ModuleResult.PASSED
class rogue_mmio_bar(BaseModule): def __init__(self): BaseModule.__init__(self) self._interrupts = Interrupts(self.cs) # SMI code to be written to I/O port 0xB2 self.smic_start = 0x00 self.smic_end = SMI_CODE_LIMIT # SMI data to be written to I/O port 0xB3 self.smid_start = 0x00 self.smid_end = SMI_DATA_LIMIT # SMI handler "function" often supplied in ECX register self.smif_start = 0x00 self.smif_end = SMI_FUNC_LIMIT # SMM communication buffer often supplied in EBX register self.comm = 0x00 self.reloc_mmio = None def smi_mmio_range_fuzz(self, thread_id, b, d, f, bar_off, is64bit, bar, new_bar, base, size): # copy all registers from MMIO range to new location in memory # we do that once rather than before every SMI since we return after first change detected self.logger.log("[*] copying BAR 0x%X > 0x%X" % (base, self.reloc_mmio)) orig_mmio = self.copy_bar(base, self.reloc_mmio, size) if self.logger.DEBUG: self.cs.mmio.dump_MMIO(base, size) file.write_file('mmio_mem.orig', orig_mmio) for smi_code in xrange(self.smic_start, self.smic_end + 1): for smi_data in xrange(self.smid_start, self.smid_end + 1): for ecx in xrange(self.smif_start, self.smif_end + 1): self.logger.log("> SMI# %02X: data %02X, func (ECX) %X" % (smi_code, smi_data, ecx)) if FLUSH_OUTPUT_AFTER_SMI: self.logger.flush() # point MMIO range to new location (relocate MMIO range) self.logger.log(" relocating BAR 0x%X" % bar) if not self.modify_bar(b, d, f, bar_off, is64bit, bar, new_bar): continue # generate SW SMI self._interrupts.send_SW_SMI(thread_id, smi_code, smi_data, _FILL_VALUE_QWORD, self.comm, ecx, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD) # restore original location of MMIO range self.restore_bar(b, d, f, bar_off, is64bit, bar) self.logger.log(" restored BAR with 0x%X" % bar) # check the contents at the address range used to relocate MMIO BAR buf = self.cs.mem.read_physical_mem(self.reloc_mmio, size) diff = DIFF(orig_mmio, buf, size) self.logger.log(" checking relocated MMIO") if len(diff) > 0: self.logger.log_important("changes found at 0x%X +%s" % (self.reloc_mmio, diff)) if self.logger.DEBUG: file.write_file('mmio_mem.new', buf) return True return False def copy_bar(self, bar_base, bar_base_mem, size): for off in xrange(0, size, 4): r = self.cs.mem.read_physical_mem_dword(bar_base + off) self.cs.mem.write_physical_mem_dword(bar_base_mem + off, r) return self.cs.mem.read_physical_mem(bar_base_mem, size) """ def copy_bar_name(self, name, bar_base_mem): bar_regs = self.cs.mmio.read_MMIO_BAR(bar_name) size = len(bar_regs) off = 0 for r in bar_regs: self.cs.mem.write_physical_mem_dword(bar_base_mem + off, r) off += 4 return self.cs.mem.read_physical_mem(bar_base_mem, size) """ def modify_bar(self, b, d, f, off, is64bit, bar, new_bar): # Modify MMIO BAR address if is64bit: #self.cs.pci.write_dword(b, d, f, off, 0x0) self.cs.pci.write_dword(b, d, f, off + pci.PCI_HDR_BAR_STEP, ((new_bar >> 32) & 0xFFFFFFFF)) self.cs.pci.write_dword(b, d, f, off, (new_bar & 0xFFFFFFFF)) # Check that the MMIO BAR has been modified correctly. Restore original and skip if not l = self.cs.pci.read_dword(b, d, f, off) if l != (new_bar & 0xFFFFFFFF): self.restore_bar(b, d, f, off, is64bit, bar) self.logger.log(" skipping (%X != %X)" % (l, new_bar)) return False self.logger.log(" new BAR: 0x%X" % l) return True def restore_bar(self, b, d, f, off, is64bit, bar): if is64bit: #self.cs.pci.write_dword(b, d, f, off, 0x0) self.cs.pci.write_dword(b, d, f, off + pci.PCI_HDR_BAR_STEP, ((bar >> 32) & 0xFFFFFFFF)) self.cs.pci.write_dword(b, d, f, off, (bar & 0xFFFFFFFF)) return True def run(self, module_argv): self.logger.start_test( "experimental tool to help checking for SMM MMIO BAR issues") pcie_devices = [] if len(module_argv) > 0: smic_arr = module_argv[0].split(':') self.smic_start = int(smic_arr[0], 16) self.smic_end = int(smic_arr[1], 16) if len(module_argv) > 1: try: b, df = module_argv[1].split(':') d, f = df.split('.') pcie_devices = [(int(b, 16), int(d, 16), int(f, 16), 0, 0)] except: self.logger.error( "incorrect b:d.f format\nUsage:\nchipsec_main -m tools.smm.rogue_mmio_bar [-a <smi_start:smi_end>,<b:d.f>]" ) else: self.logger.log("[*] discovering PCIe devices..") pcie_devices = self.cs.pci.enumerate_devices() self.logger.log("[*] testing MMIO of PCIe devices:") for (b, d, f, _, _) in pcie_devices: self.logger.log(" %02X:%02X.%X" % (b, d, f)) # allocate a page or SMM communication buffer (often supplied in EBX register) _, self.comm = self.cs.mem.alloc_physical_mem(0x1000, defines.BOUNDARY_4GB - 1) #self.cs.mem.write_physical_mem( self.comm, 0x1000, chr(0)*0x1000 ) # allocate range in physical memory (should cover all MMIO ranges including GTTMMADR) bsz = 2 * MAX_MMIO_RANGE_SIZE (va, pa) = self.cs.mem.alloc_physical_mem(bsz, defines.BOUNDARY_4GB - 1) self.logger.log("[*] allocated memory range : 0x%016X (0x%X bytes)" % (pa, bsz)) self.cs.mem.write_physical_mem(pa, bsz, _MEM_FILL_VALUE * bsz) # align at the MAX_MMIO_RANGE_SIZE boundary within allocated range self.reloc_mmio = pa & (~(MAX_MMIO_RANGE_SIZE - 1)) if self.reloc_mmio < pa: self.reloc_mmio += MAX_MMIO_RANGE_SIZE self.logger.log("[*] MMIO relocation address: 0x%016X\n" % self.reloc_mmio) for (b, d, f, vid, did) in pcie_devices: self.logger.log("[*] enumerating device %02X:%02X.%X MMIO BARs.." % (b, d, f)) device_bars = self.cs.pci.get_device_bars(b, d, f, True) for (base, isMMIO, is64bit, bar_off, bar, size) in device_bars: if isMMIO and size <= MAX_MMIO_RANGE_SIZE: self.logger.flush() self.logger.log( "[*] found MMIO BAR +0x%02X (base 0x%016X, size 0x%X)" % (bar_off, base, size)) new_bar = ( (self.reloc_mmio & pci.PCI_HDR_BAR_BASE_MASK_MMIO64) | (bar & pci.PCI_HDR_BAR_CFGBITS_MASK)) if self.smi_mmio_range_fuzz(0, b, d, f, bar_off, is64bit, bar, new_bar, base, size): return ModuleResult.FAILED return ModuleResult.PASSED
from struct import pack, unpack from crc_spoof import * import chipsec.chipset from chipsec.hal.interrupts import Interrupts PAGE_SIZE = 0x1000 SMI_USB_RUNTIME = 0x31 cs = chipsec.chipset.cs() cs.init(None, True, True) intr = Interrupts(cs) SMRAM = cs.cpu.get_SMRAM()[0] mem_read = cs.helper.read_physical_mem mem_write = cs.helper.write_physical_mem mem_alloc = cs.helper.alloc_physical_mem io_read = cs.helper.read_io_port # check if system is in ACPI mode # assert (io_read(0x1804, 1) & 1) == 0, "this system is in ACPI mode now" # locate EFI_USB_PROTOCOL and usb_data in the memory for addr in xrange(SMRAM / PAGE_SIZE - 1, 0, -1): if mem_read(addr * PAGE_SIZE, 4) == 'USBP': usb_protocol = addr * PAGE_SIZE usb_data = unpack("<Q", mem_read(addr * PAGE_SIZE + 8, 8))[0] break assert usb_protocol != 0, "can't find EFI_USB_PROTOCOL structure"
def smi(argv): try: interrupts = Interrupts( _cs ) except RuntimeError, msg: print msg return
def __init__(self): BaseModule.__init__(self) self.interrupts = Interrupts(self.cs) self.is_check_memory = True
class smm_ptr(BaseModule): def __init__(self): BaseModule.__init__(self) self.interrupts = Interrupts( self.cs ) self.is_check_memory = True def is_supported(self): return True def get_SMRAM( self ): msr_smrrbase = chipsec.chipset.read_register( self.cs, 'IA32_SMRR_PHYSBASE' ) msr_smrrmask = chipsec.chipset.read_register( self.cs, 'IA32_SMRR_PHYSMASK' ) smrrbase = chipsec.chipset.get_register_field( self.cs, 'IA32_SMRR_PHYSBASE', msr_smrrbase, 'PhysBase', True ) smrrmask = chipsec.chipset.get_register_field( self.cs, 'IA32_SMRR_PHYSMASK', msr_smrrmask, 'PhysMask', True ) return (smrrbase,smrrmask) def fill_memory( self, _addr, _addr1, _fill_byte, _fill_size ): # # Fill in contents at PA = _addr with known pattern to check later if any SMI handler modifies them # self.logger.log( "[*] Filling in %d bytes at PA 0x%016X with '%c'.." % (_fill_size, _addr, _fill_byte) ) self.cs.mem.write_physical_mem( _addr, _fill_size, _fill_byte*_MEM_FILL_SIZE ) if MODE_SECOND_ORDER_BUFFER: self.logger.log( "[*] Filling in %d bytes at PA 0x%016X with '%c'.." % (_fill_size, _addr1, _fill_byte) ) self.cs.mem.write_physical_mem( _addr1, _fill_size, _fill_byte*_MEM_FILL_SIZE ) return True def send_smi( self, smi_code, smi_data, name, desc, rax, rbx, rcx, rdx, rsi, rdi ): # # Invoke SW SMI# # self.logger.log( "[*] Sending SMI# 0x%02X (data = 0x%02X) %s (%s).." % (smi_code, smi_data, name, desc) ) self.logger.log( " RAX: 0x%016X (AX will be overwridden with values of SW SMI ports B2/B3)" % rax ) self.logger.log( " RBX: 0x%016X" % rbx ) self.logger.log( " RCX: 0x%016X" % rcx ) self.logger.log( " RDX: 0x%016X (DX will be overwridden with 0x00B2)" % rdx ) self.logger.log( " RSI: 0x%016X" % rsi ) self.logger.log( " RDI: 0x%016X" % rdi ) self.interrupts.send_SW_SMI( smi_code, smi_data, rax, rbx, rcx, rdx, rsi, rdi ) return True def check_memory( self, _addr, _addr1, _fill_byte, _fill_size ): # # Check if contents have changed at physical address passed in GPRs to SMI handler # If changed, SMI handler might have written to that address # _changed = False self.logger.log( "[*] Checking contents at PA 0x%016X.." % _addr ) buf = self.cs.mem.read_physical_mem( _addr, _fill_size ) i = 0 for c in buf: if _fill_byte != c: _changed = True break i = i + 1 if _changed: self.logger.log_important( "Detected: contents at PA 0x%016X (+ 0x%X) have changed" % (_addr,i) ) if DUMP_MEMORY_ON_DETECT: _f = os.path.join( _pth, '%s_addr%X_after.dmp' % (name,_addr) ) write_file( _f, buf ) else: self.logger.log_good( "Contents at PA 0x%016X have not changed" % _addr ) _changed1 = False if MODE_SECOND_ORDER_BUFFER: self.logger.log( "[*] Checking contents at PA 0x%016X.." % _addr1 ) buf1 = self.cs.mem.read_physical_mem( _addr1, _fill_size ) i = 0 for c in buf1: if _fill_byte != c: _changed1 = True break i = i + 1 if _changed1: self.logger.log_important( "Detected: contents at PA 0x%016X (+ 0x%X) have changed" % (_addr1,i) ) if DUMP_MEMORY_ON_DETECT: _f = os.path.join( _pth, '%s_addr%X_after.dmp' % (name,_addr1) ) write_file( _f, buf1 ) else: self.logger.log_good( "Contents at PA 0x%016X have not changed" % _addr1 ) return (_changed or _changed1) def run( self, module_argv ): self.logger.start_test( "A tool to test SMI handlers for pointer validation vulnerabilies" ) self.logger.log( "Usage: chipsec_main -m tools.smm.smm_ptr [ -a <fill_byte>,<size>,<config_file>,<address> ]" ) self.logger.log( " address physical address of memory buffer to pass in GP regs to SMI handlers" ) self.logger.log( " ='smram' pass address of SMRAM base (system may hang in this mode!)" ) self.logger.log( " config_file path to a file describing interfaces to SMI handlers (template: smm_config.ini)" ) self.logger.log( " size size of the memory buffer" ) self.logger.log( " fill_byte byte to fill the memory buffer with\n" ) _smi_config_fname = 'chipsec/modules/tools/smm/smm_config.ini' _addr = 0x0 _wr_val = _FILL_VALUE_BYTE _fill_byte = chr(int(module_argv[0],16)) if len(module_argv) > 0 else _MEM_FILL_VALUE _fill_size = int(module_argv[1],16) if len(module_argv) > 1 else _MEM_FILL_SIZE if len(module_argv) > 2: _smi_config_fname = module_argv[2] if len(module_argv) > 3: if 'smram' == module_argv[3]: (smrrbase,smrrmask) = self.get_SMRAM() _addr = smrrbase & smrrmask self.is_check_memory = False self.logger.log( "[*] Using SMRAM base address (0x%016X) to pass to SMI handlers" % _addr ) else: _addr = int(module_argv[3],16) self.logger.log( "[*] Using address from command-line (0x%016X) to pass to SMI handlers" % _addr ) else: (va, _addr) = self.cs.mem.alloc_physical_mem( _fill_size, _MAX_ALLOC_PA ) self.logger.log( "[*] Allocated new memory buffer (0x%016X) to pass to SMI handlers" % _addr ) _b = ord(_fill_byte) _addr1 = 0xFFFFFFFFFFFFFFFF & ((_b<<24) | (_b<<16) | (_b<<8) | _b) # # @TODO: Need to check that SW/APMC SMI is enabled # self.logger.log( "[*] Configuration:" ) self.logger.log( " SMI config file : %s" % _smi_config_fname ) self.logger.log( " Register default value : 0x%016X" % _FILL_VALUE_QWORD ) self.logger.log( " Memory address : 0x%016X (passed in GP regs to SMI)" % _addr ) self.logger.log( " Pointers within buffer? : %s" % ('ON' if MODE_SECOND_ORDER_BUFFER else 'OFF') ) if MODE_SECOND_ORDER_BUFFER: self.logger.log( " Pointer (address) in memory buffer (32b): 0x%016X" % _addr1 ) self.logger.log( " Filling/checking memory? : %d" % self.is_check_memory ) if self.is_check_memory: self.logger.log( " Byte to fill with : 0x%X" % _b ) self.logger.log( " Number of bytes to fill : 0x%X" % _fill_size ) # # Parse SMM config file describing SMI handlers and their call arguments # Then invoke SMI handlers # fcfg = open( _smi_config_fname, 'r' ) keys = {} if DUMP_MEMORY_ON_DETECT and not os.path.exists( _pth ): os.makedirs( _pth ) self.logger.set_always_flush( FLUSH_OUTPUT_ALWAYS ) self.logger.log('') self.logger.log( "[*] Fuzzing SMI handlers defined in '%s'.." % _smi_config_fname ) _failed = False for line in fcfg: if '' == line.strip(): # Fill memory buffer if not in 'No Fill' mode if self.is_check_memory: self.fill_memory( _addr, _addr1, _fill_byte, _fill_size ) # Invoke SW SMI handler self.send_smi( keys['smi_code'], keys['smi_data'], \ keys['name'], keys['desc'], \ keys['rax'], keys['rbx'], keys['rcx'], keys['rdx'], keys['rsi'], keys['rdi'] ) # Check memory buffer if not in 'No Fill' mode if self.is_check_memory: _failed = _failed or self.check_memory( _addr, _addr1, _fill_byte, _fill_size ) else: name, var = line.strip().partition('=')[::2] _n = name.strip().lower() if 'name' == _n or 'desc' == _n: keys[ _n ] = var elif 'smi_code' == _n or 'smi_data' == _n: keys[ _n ] = int(var,16) if '*'!=var else _SMI_CODE_DATA else: keys[ _n ] = ( _addr if 'PTR'==var else (_wr_val if 'VAL'==var else int(var,16)) ) if '*'!=var else _FILL_VALUE_QWORD res = ModuleResult.FAILED if _failed else ModuleResult.PASSED return res
class smm_ptr(BaseModule): def __init__(self): BaseModule.__init__(self) self.interrupts = Interrupts( self.cs ) self.is_check_memory = True self.test_ptr_in_buffer = False self.fill_byte = _MEM_FILL_VALUE self.fill_size = _MEM_FILL_SIZE def is_supported(self): return True def fill_memory( self, _addr, is_ptr_in_buffer, _ptr, _ptr_offset, _sig, _sig_offset ): # # Fill in contents at PA = _addr with known pattern to check later if any SMI handler modifies them # fill_buf = FILL_BUFFER( self.fill_byte, self.fill_size, is_ptr_in_buffer, _ptr, _ptr_offset, _sig, _sig_offset ) s = "[*] writing 0x%X bytes at 0x%016X" % (self.fill_size, _addr) if is_ptr_in_buffer: s += " -> PTR at +0x%X" % _ptr_offset if _sig is not None: s += " -> SIG at +0x%X" % _sig_offset self.logger.log( s ) self.cs.mem.write_physical_mem( _addr, self.fill_size, fill_buf ) if self.logger.VERBOSE: self.logger.log( "filling in contents at PA 0x%016X:" % _addr ) chipsec.logger.print_buffer( fill_buf ) if is_ptr_in_buffer and _ptr is not None: self.logger.log( "[*] writing buffer at PA 0x%016X with 0x%X bytes '%c'" % (_ptr, self.fill_size, self.fill_byte) ) self.cs.mem.write_physical_mem( _ptr, self.fill_size, self.fill_byte*self.fill_size ) return True def send_smi( self, thread_id, smi_code, smi_data, name, desc, rax, rbx, rcx, rdx, rsi, rdi ): self.logger.log( " > SMI %02X (data: %02X)" % (smi_code,smi_data) ) if DUMP_GPRS_EVERY_SMI: self.logger.log( " RAX: 0x%016X\n RBX: 0x%016X\n RCX: 0x%016X\n RDX: 0x%016X\n RSI: 0x%016X\n RDI: 0x%016X" % (rax,rbx,rcx,rdx,rsi,rdi) ) self.interrupts.send_SW_SMI( thread_id, smi_code, smi_data, rax, rbx, rcx, rdx, rsi, rdi ) return True def check_memory( self, _addr, _smi_desc, fn, restore_contents=False ): _ptr = _smi_desc.ptr filler = self.fill_byte*self.fill_size # # Check if contents have changed at physical address passed in GPRs to SMI handler # If changed, SMI handler might have written to that address # self.logger.log( " < checking buffers" ) expected_buf = FILL_BUFFER( self.fill_byte, self.fill_size, _smi_desc.ptr_in_buffer, _smi_desc.ptr, _smi_desc.ptr_offset, _smi_desc.sig, _smi_desc.sig_offset ) buf = self.cs.mem.read_physical_mem( _addr, self.fill_size ) differences = DIFF( expected_buf, buf, self.fill_size ) _changed = (len(differences) > 0) if self.logger.VERBOSE: self.logger.log( "checking contents at PA 0x%016X:" % _addr ) chipsec.logger.print_buffer( buf ) self.logger.log( "expected contents:" ) chipsec.logger.print_buffer( expected_buf ) if _changed: self.logger.log( " contents changed at 0x%016X +%s" % (_addr,differences) ) if restore_contents: self.logger.log( " restoring 0x%X bytes at 0x%016X" % (self.fill_size, _addr) ) self.cs.mem.write_physical_mem( _addr, self.fill_size, expected_buf ) if DUMP_MEMORY_ON_DETECT: _pth_smi = os.path.join( _pth, '%X_%s'% (_smi_desc.smi_code,_smi_desc.name) ) if not os.path.exists( _pth_smi ): os.makedirs( _pth_smi ) _f = os.path.join( _pth_smi, fn + '.dmp' ) self.logger.log( " dumping buffer to '%s'" % _f ) write_file( _f, buf ) _changed1 = False expected_buf = filler if _smi_desc.ptr_in_buffer and _ptr is not None: buf1 = self.cs.mem.read_physical_mem( _ptr, self.fill_size ) differences1 = DIFF( expected_buf, buf1, self.fill_size ) _changed1 = (len(differences1) > 0) if self.logger.VERBOSE: self.logger.log( "checking contents at PA 0x%016X:" % _ptr ) chipsec.logger.print_buffer( buf1 ) if _changed1: self.logger.log( " contents changed at 0x%016X +%s" % (_ptr,differences1) ) if restore_contents: self.logger.log( " restoring 0x%X bytes at PA 0x%016X" % (self.fill_size, _ptr) ) self.cs.mem.write_physical_mem( _ptr, self.fill_size, expected_buf ) if DUMP_MEMORY_ON_DETECT: _pth_smi = os.path.join( _pth, '%X_%s'% (_smi_desc.smi_code,_smi_desc.name) ) if not os.path.exists( _pth_smi ): os.makedirs( _pth_smi ) _f = os.path.join( _pth_smi, fn + ('_ptr%X.dmp' % _smi_desc.ptr_offset) ) self.logger.log( " dumping buffer to '%s'" % _f ) write_file( _f, buf1 ) return (_changed or _changed1) def smi_fuzz_iter( self, thread_id, _addr, _smi_desc, fill_contents=True, restore_contents=False ): # # Fill memory buffer if not in 'No Fill' mode # if self.is_check_memory and fill_contents: self.fill_memory( _addr, _smi_desc.ptr_in_buffer, _smi_desc.ptr, _smi_desc.ptr_offset, _smi_desc.sig, _smi_desc.sig_offset ) # # Invoke SW SMI Handler # _rax = _smi_desc.gprs['rax'] _rbx = _smi_desc.gprs['rbx'] _rcx = _smi_desc.gprs['rcx'] _rdx = _smi_desc.gprs['rdx'] _rsi = _smi_desc.gprs['rsi'] _rdi = _smi_desc.gprs['rdi'] self.send_smi( thread_id, _smi_desc.smi_code, _smi_desc.smi_data, _smi_desc.name, _smi_desc.desc, _rax, _rbx, _rcx, _rdx, _rsi, _rdi ) # # Check memory buffer if not in 'No Fill' mode # contents_changed = False if self.is_check_memory: fn = '%X-a%X_b%X_c%X_d%X_si%X_di%X' % (_smi_desc.smi_data,_rax,_rbx,_rcx,_rdx,_rsi,_rdi) contents_changed = self.check_memory( _addr, _smi_desc, fn, restore_contents ) if contents_changed: msg = "DETECTED: SMI# %X data %X (rax=%X rbx=%X rcx=%X rdx=%X rsi=%X rdi=%X)" % (_smi_desc.smi_code,_smi_desc.smi_data,_rax,_rbx,_rcx,_rdx,_rsi,_rdi) self.logger.log_important( msg ) if FUZZ_BAIL_ON_1ST_DETECT: raise BadSMIDetected, msg if FLUSH_OUTPUT_AFTER_SMI: self.logger.flush() return contents_changed def test_config( self, thread_id, _smi_config_fname, _addr, _addr1 ): # # Parse SMM config file describing SMI handlers and their call arguments # Then invoke SMI handlers # fcfg = open( _smi_config_fname, 'r' ) self.logger.log( "\n[*] >>> Testing SMI handlers defined in '%s'.." % _smi_config_fname ) bad_ptr_cnt = 0 _smi_desc = smi_desc() for line in fcfg: if '' == line.strip(): self.logger.log( "\n[*] testing SMI# 0x%02X (data: 0x%02X) %s (%s)" % (_smi_desc.smi_code,_smi_desc.smi_data,_smi_desc.name,_smi_desc.desc) ) if self.smi_fuzz_iter( thread_id, _addr, _smi_desc ): bad_ptr_cnt += 1 _smi_desc = None _smi_desc = smi_desc() else: name, var = line.strip().partition('=')[::2] _n = name.strip().lower() if 'name' == _n: _smi_desc.name = var elif 'desc' == _n: _smi_desc.desc = var elif 'smi_code' == _n: _smi_desc.smi_code = int(var,16) if '*'!=var else _SMI_CODE_DATA elif 'smi_data' == _n: _smi_desc.smi_data = int(var,16) if '*'!=var else _SMI_CODE_DATA elif 'ptr_offset' == _n: _smi_desc.ptr_in_buffer = True _smi_desc.ptr_offset = int(var,16) _smi_desc.ptr = _addr1 elif 'sig' == _n: _smi_desc.sig = str( bytearray.fromhex( var ) ) elif 'sig_offset' == _n: _smi_desc.sig_offset = int(var,16) else: _smi_desc.gprs[ _n ] = ( _addr if 'PTR'==var else (_FILL_VALUE_BYTE if 'VAL'==var else int(var,16)) ) if '*'!=var else _FILL_VALUE_QWORD return bad_ptr_cnt def test_fuzz( self, thread_id, smic_start, smic_end, _addr, _addr1 ): gpr_value = ((_addr<<32)|_addr) if GPR_2ADDR else _addr gprs_addr = {'rax' : gpr_value, 'rbx' : gpr_value, 'rcx' : gpr_value, 'rdx' : gpr_value, 'rsi' : gpr_value, 'rdi' : gpr_value} gprs_fill = {'rax' : _FILL_VALUE_QWORD, 'rbx' : _FILL_VALUE_QWORD, 'rcx' : _FILL_VALUE_QWORD, 'rdx' : _FILL_VALUE_QWORD, 'rsi' : _FILL_VALUE_QWORD, 'rdi' : _FILL_VALUE_QWORD} self.logger.log( "\n[*] >>> Fuzzing SMI handlers.." ) self.logger.log( "[*] AX in RAX will be overwridden with values of SW SMI ports 0xB2/0xB3" ) self.logger.log( " DX in RDX will be overwridden with value 0x00B2" ) bad_ptr_cnt = 0 _smi_desc = smi_desc() _smi_desc.gprs = gprs_addr if PTR_IN_ALL_GPRS else gprs_fill self.logger.log( "\n[*] Setting values of general purpose registers to 0x%016X" % _smi_desc.gprs['rax'] ) max_ptr_off = 1 if self.is_check_memory and self.test_ptr_in_buffer: _smi_desc.ptr_in_buffer = True _smi_desc.ptr = _addr1 max_ptr_off = MAX_PTR_OFFSET_IN_BUFFER+1 # if we are not in fuzzmore mode, i.e. we are not testing the pointer within memory buffer # then this outer loop will only have 1 iteration for off in range(max_ptr_off): _smi_desc.ptr_offset = off self.logger.log( "\n[*] reloading buffer with PTR at offset 0x%X.." % off ) if self.is_check_memory: self.fill_memory( _addr, _smi_desc.ptr_in_buffer, _smi_desc.ptr, _smi_desc.ptr_offset, None, None ) for smi_code in range(smic_start, smic_end + 1, 1): _smi_desc.smi_code = smi_code for smi_data in range(MAX_SMI_DATA): _smi_desc.smi_data = smi_data self.logger.log( "\n[*] fuzzing SMI# 0x%02X (data: 0x%02X)" % (smi_code,smi_data) ) if FUZZ_SMI_FUNCTIONS_IN_ECX: for _rcx in range(MAX_SMI_FUNCTIONS): self.logger.log( " >> function (RCX): 0x%016X" % _rcx ) _smi_desc.gprs['rcx'] = _rcx if PTR_IN_ALL_GPRS: if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 else: self.logger.log( " RBX: 0x%016X" % _addr ) _smi_desc.gprs['rbx'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rbx'] = _FILL_VALUE_QWORD self.logger.log( " RSI: 0x%016X" % _addr ) _smi_desc.gprs['rsi'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rsi'] = _FILL_VALUE_QWORD self.logger.log( " RDI: 0x%016X" % _addr ) _smi_desc.gprs['rdi'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rdi'] = _FILL_VALUE_QWORD else: if PTR_IN_ALL_GPRS: if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 else: self.logger.log( " RBX: 0x%016X" % _addr ) _smi_desc.gprs['rbx'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rbx'] = _FILL_VALUE_QWORD self.logger.log( " RCX: 0x%016X" % _addr ) _smi_desc.gprs['rcx'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rcx'] = _FILL_VALUE_QWORD self.logger.log( " RSI: 0x%016X" % _addr ) _smi_desc.gprs['rsi'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rsi'] = _FILL_VALUE_QWORD self.logger.log( " RDI: 0x%016X" % _addr ) _smi_desc.gprs['rdi'] = gpr_value if self.smi_fuzz_iter( thread_id, _addr, _smi_desc, False, True ): bad_ptr_cnt += 1 _smi_desc.gprs['rdi'] = _FILL_VALUE_QWORD return bad_ptr_cnt def run( self, module_argv ): self.logger.start_test( "A tool to test SMI handlers for pointer validation vulnerabilies" ) self.logger.log( "Usage: chipsec_main -m tools.smm.smm_ptr [ -a <mode>,<config_file>|<smic_start:smic_end>,<size>,<address> ]" ) self.logger.log( " mode SMI handlers testing mode" ) self.logger.log( " = config use SMI configuration file <config_file>" ) self.logger.log( " = fuzz fuzz all SMI handlers with code in the range <smic_start:smic_end>" ) self.logger.log( " = fuzzmore fuzz mode + pass '2nd-order' pointers within buffer to SMI handlers") self.logger.log( " size size of the memory buffer (in Hex)" ) self.logger.log( " address physical address of memory buffer to pass in GP regs to SMI handlers (in Hex)" ) self.logger.log( " = smram pass address of SMRAM base (system may hang in this mode!)\n" ) test_mode = 'config' _smi_config_fname = 'chipsec/modules/tools/smm/smm_config.ini' _addr = None _addr1 = None thread_id = 0x0 global DUMP_GPRS_EVERY_SMI if len(module_argv) > 1: test_mode = module_argv[0].lower() if 'config' == test_mode: _smi_config_fname = module_argv[1] elif 'fuzz' == test_mode or 'fuzzmore' == test_mode: smic_arr = module_argv[1].split(':') smic_start = int(smic_arr[0],16) smic_end = int(smic_arr[1],16) if 'fuzzmore' == test_mode: self.test_ptr_in_buffer = True DUMP_GPRS_EVERY_SMI = False else: self.logger.error( "Unknown fuzzing mode '%s'" % module_argv[0] ) return ModuleResult.ERROR if len(module_argv) > 2: self.fill_size = int(module_argv[2],16) if len(module_argv) > 3: if 'smram' == module_argv[3]: (_addr, smram_limit, smram_size) = self.cs.cpu.get_SMRAM() self.is_check_memory = False self.logger.log( "[*] Using SMRAM base address (0x%016X) to pass to SMI handlers" % _addr ) else: _addr = int(module_argv[3],16) self.logger.log( "[*] Using address from command-line (0x%016X) to pass to SMI handlers" % _addr ) else: (va, _addr) = self.cs.mem.alloc_physical_mem( self.fill_size, _MAX_ALLOC_PA ) self.logger.log( "[*] Allocated memory buffer (to pass to SMI handlers) : 0x%016X" % _addr ) if self.is_check_memory: (va1, _addr1) = self.cs.mem.alloc_physical_mem( self.fill_size, _MAX_ALLOC_PA ) self.logger.log( "[*] Allocated 2nd buffer (address will be in the 1st buffer): 0x%016X" % _addr1 ) # # @TODO: Need to check that SW/APMC SMI is enabled # self.logger.log( "\n[*] Configuration" ) self.logger.log( " SMI testing mode : %s" % test_mode ) if 'config' == test_mode: self.logger.log( " Config file : %s" % _smi_config_fname ) else: self.logger.log( " Range of SMI codes (B2) : 0x%02X:0x%02X" % (smic_start,smic_end) ) self.logger.log( " Memory buffer pointer : 0x%016X (address passed in GP regs to SMI)" % _addr ) self.logger.log( " Filling/checking memory? : %s" % ('YES' if self.is_check_memory else 'NO')) if self.is_check_memory: self.logger.log( " Second buffer pointer : 0x%016X (address written to memory buffer)" % _addr1 ) self.logger.log( " Number of bytes to fill : 0x%X" % self.fill_size ) self.logger.log( " Byte to fill with : 0x%X" % ord(self.fill_byte) ) self.logger.log( " Additional options (can be changed in the source code):" ) self.logger.log( " Fuzzing SMI functions in ECX? : %d" % FUZZ_SMI_FUNCTIONS_IN_ECX ) self.logger.log( " Max value of SMI function in ECX : 0x%X" % MAX_SMI_FUNCTIONS ) self.logger.log( " Max value of SMI data (B3) : 0x%X" % MAX_SMI_DATA ) self.logger.log( " Max offset of the pointer in the buffer: 0x%X" % MAX_PTR_OFFSET_IN_BUFFER ) self.logger.log( " Passing pointer in all GP registers? : %d" % PTR_IN_ALL_GPRS ) self.logger.log( " Default values of the registers : 0x%016X" % _FILL_VALUE_QWORD ) self.logger.log( " Dump all register values every SMI : %d" % DUMP_GPRS_EVERY_SMI ) self.logger.log( " Bail on first detection : %d" % FUZZ_BAIL_ON_1ST_DETECT ) self.logger.set_always_flush( FLUSH_OUTPUT_ALWAYS ) if DUMP_MEMORY_ON_DETECT and not os.path.exists( _pth ): os.makedirs( _pth ) bad_ptr_cnt = 0 try: if 'config' == test_mode: bad_ptr_cnt = self.test_config( thread_id, _smi_config_fname, _addr, _addr1 ) elif 'fuzz' == test_mode or 'fuzzmore' == test_mode: bad_ptr_cnt = self.test_fuzz ( thread_id, smic_start, smic_end, _addr, _addr1 ) except BadSMIDetected, msg: bad_ptr_cnt = 1 self.logger.log_important( "Potentially bad SMI detected! Stopped fuzing (see FUZZ_BAIL_ON_1ST_DETECT option)" ) if bad_ptr_cnt > 0: self.logger.log_bad( "<<< Done: found %d potential occurrences of unchecked input pointers" % bad_ptr_cnt ) else: self.logger.log_good( "<<< Done: didn't find unchecked input pointers in tested SMI handlers" ) res = ModuleResult.FAILED if (bad_ptr_cnt > 0) else ModuleResult.PASSED return res
def run(self): try: interrupts = Interrupts(self.cs) except RuntimeError, msg: print msg return
def run(self): if len(self.argv) < 3: print(SMICommand.__doc__) return try: interrupts = Interrupts(self.cs) except RuntimeError as msg: print(msg) return op = self.argv[2] t = time.time() if 'count' == op: self.logger.log("[CHIPSEC] SMI count:") for tid in range(self.cs.msr.get_cpu_thread_count()): smi_cnt = self.cs.read_register_field('MSR_SMI_COUNT', 'Count', cpu_thread=tid) self.logger.log(" CPU{:d}: {:d}".format(tid, smi_cnt)) elif 'smmc' == op: if len(self.argv) < 8: print(SMICommand.__doc__) return RTC_start = int(self.argv[3], 16) RTC_end = int(self.argv[4], 16) guid = self.argv[5] payload_loc = int(self.argv[6], 16) payload = self.argv[7] if os.path.isfile(payload): f = open(payload, 'rb') payload = f.read() f.close() self.logger.log( "Searching for \'smmc\' in range 0x{:x}-0x{:x}".format( RTC_start, RTC_end)) # scan for SMM_CORE_PRIVATE_DATA smmc signature smmc_loc = interrupts.find_smmc(RTC_start, RTC_end) if smmc_loc == 0: self.logger.log(" Couldn't find smmc signature") return self.logger.log( "Found \'smmc\' structure at 0x{:x}".format(smmc_loc)) ReturnStatus = interrupts.send_smmc_SMI(smmc_loc, guid, payload, payload_loc) #TODO Translate ReturnStatus to EFI_STATUS enum self.logger.log("ReturnStatus: {:x}".format(ReturnStatus)) else: SMI_data_port_value = 0x0 if len(self.argv) > 4: thread_id = int(self.argv[2], 16) SMI_code_port_value = int(self.argv[3], 16) SMI_data_port_value = int(self.argv[4], 16) self.logger.log( "[CHIPSEC] Sending SW SMI (code: 0x{:02X}, data: 0x{:02X}).." .format(SMI_code_port_value, SMI_data_port_value)) if 5 == len(self.argv): interrupts.send_SMI_APMC(SMI_code_port_value, SMI_data_port_value) elif 11 == len(self.argv): _rax = int(self.argv[5], 16) _rbx = int(self.argv[6], 16) _rcx = int(self.argv[7], 16) _rdx = int(self.argv[8], 16) _rsi = int(self.argv[9], 16) _rdi = int(self.argv[10], 16) self.logger.log( " RAX: 0x{:016X} (AX will be overwridden with values of SW SMI ports B2/B3)" .format(_rax)) self.logger.log(" RBX: 0x{:016X}".format(_rbx)) self.logger.log(" RCX: 0x{:016X}".format(_rcx)) self.logger.log( " RDX: 0x{:016X} (DX will be overwridden with 0x00B2)" .format(_rdx)) self.logger.log(" RSI: 0x{:016X}".format(_rsi)) self.logger.log(" RDI: 0x{:016X}".format(_rdi)) ret = interrupts.send_SW_SMI(thread_id, SMI_code_port_value, SMI_data_port_value, _rax, _rbx, _rcx, _rdx, _rsi, _rdi) if not ret is None: self.logger.log("Return values") self.logger.log(" RAX: {:16X}".format(ret[1])) self.logger.log(" RBX: {:16X}".format(ret[2])) self.logger.log(" RCX: {:16X}".format(ret[3])) self.logger.log(" RDX: {:16X}".format(ret[4])) self.logger.log(" RSI: {:16X}".format(ret[5])) self.logger.log(" RDI: {:16X}".format(ret[6])) else: print(SMICommand.__doc__) else: self.logger.error( "unknown command-line option '{:32}'".format(op)) print(SMICommand.__doc__) return self.logger.log( "[CHIPSEC] (smi) time elapsed {:.3f}".format(time.time() - t))
def __init__(self): BaseModule.__init__(self) self.interrupts = Interrupts( self.cs ) self.is_check_memory = True
def smi(argv): try: interrupts = Interrupts( chipsec_util._cs ) except RuntimeError, msg: print msg return
class smm_ptr(BaseModule): def __init__(self): BaseModule.__init__(self) self.interrupts = Interrupts(self.cs) self.is_check_memory = True def is_supported(self): return True def get_SMRAM(self): msr_smrrbase = chipsec.chipset.read_register(self.cs, 'IA32_SMRR_PHYSBASE') msr_smrrmask = chipsec.chipset.read_register(self.cs, 'IA32_SMRR_PHYSMASK') smrrbase = chipsec.chipset.get_register_field(self.cs, 'IA32_SMRR_PHYSBASE', msr_smrrbase, 'PhysBase', True) smrrmask = chipsec.chipset.get_register_field(self.cs, 'IA32_SMRR_PHYSMASK', msr_smrrmask, 'PhysMask', True) return (smrrbase, smrrmask) def fill_memory(self, _addr, _addr1, _fill_byte, _fill_size): # # Fill in contents at PA = _addr with known pattern to check later if any SMI handler modifies them # self.logger.log("[*] Filling in %d bytes at PA 0x%016X with '%c'.." % (_fill_size, _addr, _fill_byte)) self.cs.mem.write_physical_mem(_addr, _fill_size, _fill_byte * _MEM_FILL_SIZE) if MODE_SECOND_ORDER_BUFFER: self.logger.log( "[*] Filling in %d bytes at PA 0x%016X with '%c'.." % (_fill_size, _addr1, _fill_byte)) self.cs.mem.write_physical_mem(_addr1, _fill_size, _fill_byte * _MEM_FILL_SIZE) return True def send_smi(self, smi_code, smi_data, name, desc, rax, rbx, rcx, rdx, rsi, rdi): # # Invoke SW SMI# # self.logger.log("[*] Sending SMI# 0x%02X (data = 0x%02X) %s (%s).." % (smi_code, smi_data, name, desc)) self.logger.log( " RAX: 0x%016X (AX will be overwridden with values of SW SMI ports B2/B3)" % rax) self.logger.log(" RBX: 0x%016X" % rbx) self.logger.log(" RCX: 0x%016X" % rcx) self.logger.log( " RDX: 0x%016X (DX will be overwridden with 0x00B2)" % rdx) self.logger.log(" RSI: 0x%016X" % rsi) self.logger.log(" RDI: 0x%016X" % rdi) self.interrupts.send_SW_SMI(smi_code, smi_data, rax, rbx, rcx, rdx, rsi, rdi) return True def check_memory(self, _addr, _addr1, _fill_byte, _fill_size): # # Check if contents have changed at physical address passed in GPRs to SMI handler # If changed, SMI handler might have written to that address # _changed = False self.logger.log("[*] Checking contents at PA 0x%016X.." % _addr) buf = self.cs.mem.read_physical_mem(_addr, _fill_size) i = 0 for c in buf: if _fill_byte != c: _changed = True break i = i + 1 if _changed: self.logger.log_important( "Detected: contents at PA 0x%016X (+ 0x%X) have changed" % (_addr, i)) if DUMP_MEMORY_ON_DETECT: _f = os.path.join(_pth, '%s_addr%X_after.dmp' % (name, _addr)) write_file(_f, buf) else: self.logger.log_good("Contents at PA 0x%016X have not changed" % _addr) _changed1 = False if MODE_SECOND_ORDER_BUFFER: self.logger.log("[*] Checking contents at PA 0x%016X.." % _addr1) buf1 = self.cs.mem.read_physical_mem(_addr1, _fill_size) i = 0 for c in buf1: if _fill_byte != c: _changed1 = True break i = i + 1 if _changed1: self.logger.log_important( "Detected: contents at PA 0x%016X (+ 0x%X) have changed" % (_addr1, i)) if DUMP_MEMORY_ON_DETECT: _f = os.path.join(_pth, '%s_addr%X_after.dmp' % (name, _addr1)) write_file(_f, buf1) else: self.logger.log_good( "Contents at PA 0x%016X have not changed" % _addr1) return (_changed or _changed1) def run(self, module_argv): self.logger.start_test( "A tool to test SMI handlers for pointer validation vulnerabilies") self.logger.log( "Usage: chipsec_main -m tools.smm.smm_ptr [ -a <fill_byte>,<size>,<config_file>,<address> ]" ) self.logger.log( " address physical address of memory buffer to pass in GP regs to SMI handlers" ) self.logger.log( " ='smram' pass address of SMRAM base (system may hang in this mode!)" ) self.logger.log( " config_file path to a file describing interfaces to SMI handlers (template: smm_config.ini)" ) self.logger.log(" size size of the memory buffer") self.logger.log( " fill_byte byte to fill the memory buffer with\n") _smi_config_fname = 'chipsec/modules/tools/smm/smm_config.ini' _addr = 0x0 _wr_val = _FILL_VALUE_BYTE _fill_byte = chr(int(module_argv[0], 16)) if len(module_argv) > 0 else _MEM_FILL_VALUE _fill_size = int(module_argv[1], 16) if len(module_argv) > 1 else _MEM_FILL_SIZE if len(module_argv) > 2: _smi_config_fname = module_argv[2] if len(module_argv) > 3: if 'smram' == module_argv[3]: (smrrbase, smrrmask) = self.get_SMRAM() _addr = smrrbase & smrrmask self.is_check_memory = False self.logger.log( "[*] Using SMRAM base address (0x%016X) to pass to SMI handlers" % _addr) else: _addr = int(module_argv[3], 16) self.logger.log( "[*] Using address from command-line (0x%016X) to pass to SMI handlers" % _addr) else: (va, _addr) = self.cs.mem.alloc_physical_mem(_fill_size, _MAX_ALLOC_PA) self.logger.log( "[*] Allocated new memory buffer (0x%016X) to pass to SMI handlers" % _addr) _b = ord(_fill_byte) _addr1 = 0xFFFFFFFFFFFFFFFF & ((_b << 24) | (_b << 16) | (_b << 8) | _b) # # @TODO: Need to check that SW/APMC SMI is enabled # self.logger.log("[*] Configuration:") self.logger.log(" SMI config file : %s" % _smi_config_fname) self.logger.log(" Register default value : 0x%016X" % _FILL_VALUE_QWORD) self.logger.log( " Memory address : 0x%016X (passed in GP regs to SMI)" % _addr) self.logger.log(" Pointers within buffer? : %s" % ('ON' if MODE_SECOND_ORDER_BUFFER else 'OFF')) if MODE_SECOND_ORDER_BUFFER: self.logger.log( " Pointer (address) in memory buffer (32b): 0x%016X" % _addr1) self.logger.log(" Filling/checking memory? : %d" % self.is_check_memory) if self.is_check_memory: self.logger.log(" Byte to fill with : 0x%X" % _b) self.logger.log(" Number of bytes to fill : 0x%X" % _fill_size) # # Parse SMM config file describing SMI handlers and their call arguments # Then invoke SMI handlers # fcfg = open(_smi_config_fname, 'r') keys = {} if DUMP_MEMORY_ON_DETECT and not os.path.exists(_pth): os.makedirs(_pth) self.logger.set_always_flush(FLUSH_OUTPUT_ALWAYS) self.logger.log('') self.logger.log("[*] Fuzzing SMI handlers defined in '%s'.." % _smi_config_fname) _failed = False for line in fcfg: if '' == line.strip(): # Fill memory buffer if not in 'No Fill' mode if self.is_check_memory: self.fill_memory(_addr, _addr1, _fill_byte, _fill_size) # Invoke SW SMI handler self.send_smi( keys['smi_code'], keys['smi_data'], \ keys['name'], keys['desc'], \ keys['rax'], keys['rbx'], keys['rcx'], keys['rdx'], keys['rsi'], keys['rdi'] ) # Check memory buffer if not in 'No Fill' mode if self.is_check_memory: _failed = _failed or self.check_memory( _addr, _addr1, _fill_byte, _fill_size) else: name, var = line.strip().partition('=')[::2] _n = name.strip().lower() if 'name' == _n or 'desc' == _n: keys[_n] = var elif 'smi_code' == _n or 'smi_data' == _n: keys[_n] = int(var, 16) if '*' != var else _SMI_CODE_DATA else: keys[_n] = (_addr if 'PTR' == var else (_wr_val if 'VAL' == var else int(var, 16) )) if '*' != var else _FILL_VALUE_QWORD res = ModuleResult.FAILED if _failed else ModuleResult.PASSED return res
class bar(BaseModule): def __init__(self): BaseModule.__init__(self) self._interrupts = Interrupts( self.cs ) self.generate_smi = False self.bar_names = [] self.bars = {} self.bars_diff = {} # SMI code to be written to I/O port 0xB2 self.smic_start = 0x00 self.smic_end = SMI_CODE_LIMIT # SMI data to be written to I/O port 0xB3 self.smid_start = 0x00 self.smid_end = SMI_DATA_LIMIT # SMI handler "function" often supplied in ECX register self.smif_start = 0x00 self.smif_end = SMI_FUNC_LIMIT # SMM communication buffer often supplied in EBX register self.comm = 0x00 def mmio_diff(self, bar_name, _regs=None): regs = _regs if _regs else self.bars[bar_name] n = len(regs) new_regs = self.cs.mmio.read_MMIO_BAR(bar_name) diff = DIFF(new_regs, regs, n) return (len(diff) > 0), diff def check_mmio_noSMI(self): for bar_name in self.bars.keys(): self.logger.log("[*] '%s' normal difference (%d diffs):" % (bar_name,3*NO_DIFFS)) self.logger.flush() diff = [] for i in range(3*NO_DIFFS): if i > 0 and (i % NO_DIFFS) == 0: self.logger.log("sleeping 1 sec..") time.sleep(1) regs = self.cs.mmio.read_MMIO_BAR(bar_name) _,d = self.mmio_diff(bar_name, regs) self.logger.log(" diff%d: %d regs %s" % (i,len(d),d)) diff = list(set(diff)|set(d)) self.bars_diff[bar_name] = diff self.logger.log(" %d regs changed: %s" % (len(diff),sorted(diff))) def read_BARs(self): bars = {} for bar_name in self.bar_names: self.logger.log(" reading '%s'" % bar_name) bars[bar_name] = self.cs.mmio.read_MMIO_BAR(bar_name) return bars def check_BARs(self, bars, bars1): changed = False for bar_name in bars.keys(): n = len(bars[bar_name]) self.logger.log(" diffing '%s' (%d regs)" % (bar_name,n)) diff = DIFF(bars1[bar_name], bars[bar_name], n) if len(diff) > 0: self.logger.log(" %d regs changed: %s" % (len(diff),sorted(diff))) normal_diff = self.bars_diff[bar_name] new = [r for r in diff if r not in normal_diff] self.logger.log(" new regs: %s" % sorted(new)) if len(new) > 0: changed = True else: self.logger.log(" no changes") return changed def smi_mmio_check(self, thread_id): for smi_code in xrange(self.smic_start,self.smic_end+1): if smi_code in EXCLUDE_SMI: continue for smi_data in xrange(self.smid_start,self.smid_end+1): for ecx in xrange(self.smif_start,self.smif_end+1): self.logger.log("[*] SMI# %02X: data %02X, func (ECX) 0x%08X" % (smi_code,smi_data,ecx) ) bars = self.read_BARs() self.logger.log(" generating SMI" ) self.logger.flush() self._interrupts.send_SW_SMI(thread_id, smi_code, smi_data, _FILL_VALUE_QWORD, self.comm, ecx, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD) bars_after = self.read_BARs() if self.check_BARs(bars, bars_after): self.logger.log_important( "New changes found!" ) if REPEAT_SMI_ON_NEW_CHANGED_REGS: self.logger.log(" repeating SMI") self._interrupts.send_SW_SMI(thread_id, smi_code, smi_data, _FILL_VALUE_QWORD, self.comm, ecx, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD, _FILL_VALUE_QWORD) bars1 = self.read_BARs() self.check_BARs(bars_after,bars1) return ModuleResult.PASSED def run( self, module_argv ): self.logger.start_test( "Monitors changes in MMIO ranges done by SMI handlers" ) if len(module_argv) > 0: self.bar_names = module_argv[0].split(':') if len(self.bar_names) == 0: self.logger.error("MMIO BAR names were not specified") self.logger.log("Example: chipsec_main.py -m tools.smm.bar -a RCBA:MCHBAR,smi,0:1") return ModuleResult.SKIPPED if len(module_argv) > 1 and module_argv[1] == 'smi': self.generate_smi = True if len(module_argv) > 2: smic_arr = module_argv[2].split(':') self.smic_start = int(smic_arr[0],16) self.smic_end = int(smic_arr[1],16) self.logger.log("[*] Configuration:") self.logger.log(" MMIO BAR names: %s" % self.bar_names) self.logger.log(" Generate SMI: %s" % ('True' if self.generate_smi else 'False')) self.logger.log(" SMI codes: [0x%02x:0x%02x]" % (self.smic_start,self.smic_end)) # allocate a page or SMM communication buffer (often supplied in EBX register) (va, self.comm) = self.cs.mem.alloc_physical_mem( 0x1000, defines.BOUNDARY_4GB-1 ) self.logger.log( "[*] SMM comm buffer (EBX) : 0x%016X" % self.comm ) self.cs.mem.write_physical_mem( self.comm, 0x1000, chr(0)*0x1000 ) for bar_name in self.bar_names: if self.cs.mmio.is_MMIO_BAR_defined(bar_name): (base,size) = self.cs.mmio.get_MMIO_BAR_base_address(bar_name) self.logger.log("[*] MMIO BAR '%s': base = 0x%016X, size = 0x%08X" % (bar_name,base,size)) else: self.bar_names.remove(bar_name) self.logger.warn("'%s' BAR is not defined. ignoring.." % bar_name) self.logger.log("[*] reading contents of MMIO BARs %s" % self.bar_names) self.bars = self.read_BARs() self.logger.flush() self.logger.log("[*] calculating normal MMIO BAR differences..") self.check_mmio_noSMI() self.logger.flush() if self.generate_smi: self.logger.log("[*] fuzzing SMIs..") self.res = self.smi_mmio_check(0) return self.res
import struct import chipsec.chipset import hexdump from chipsec.hal.interrupts import Interrupts from crc32_spoof import get_buffer_crc32, modify_buffer_crc32 PAGE_SIZE = 0x1000 SMI_NUM = 0x31 cs = chipsec.chipset.cs() cs.init(None, True, True) intr = Interrupts(cs) SMRAM = cs.cpu.get_SMRAM()[0] mem_read = cs.helper.read_physical_mem mem_write = cs.helper.write_physical_mem mem_alloc = cs.helper.alloc_physical_mem io_read = cs.helper.read_io_port class UsbRtExpl: def __init__(self): # Print platform information print("[+] Platform: {}".format(cs.longname)) # Structures controlled by the attacker