WSMT
중첩된 포인터에 따른 SMRAM 오염을 설명하기 전 WSMT에 대해 공부를 할 필요가 있었다. WSMT는 Windows SMM Security Mitigation Table의 줄임말로써 지난 시간에 이야기했던 ACPI 테이블 중 하나다. 이는 시스템 펌웨어가 SMM 소프트웨어에서 보안이 잘 지켜졌는지를 확인하도록 OS에게 이야기하는 역할이라고 볼 수 있다.

WSMT의 구조로, 총 3가지 Flag가 있는 것을 확인할 수 있고, 각 플래그들은
FIXED_COMM_BUFFERS : OS가 지정한 고정 CommBuffer만 사용하는가
COMM_BUFFER_NESTED_PTR_PROTECTION : CommBuffer의 중첩 포인터까지 검증하는가
SYSTEM_RESOURCE_PROTECTION : 시스템 주요 설정들을 잘 보호하는가
세 가지를 담고 있다.
여기서 큰 문제가 발생하는데, WSMT는 위 세가지 문제에 대해 “검증”을 하는 것이 아닌 각 제조사가 해당 플래그를 켰는지 “확인”만 한다. 즉 제조사들은 이를 준수하지 않았더라도 Flag를 True로 뒀다면 OS는 그것을 믿고 별도 검증 기능 없이 바로 해당 핸들러를 수행하게 된다.
SMRAM Corruption using Nested Pointer
중첩된 포인터를 이용한 SMRAM 오염은 CommBuffer를 통해 값을 받고, 또 CommBuffer에 값을 저장할 때 이중(또는 그 이상) 포인터를 사용하는 과정에서 생기는 문제를 의미한다. 아래 예제는 CVE-2023-5058 사례로, 후지쯔 펌웨어, 또는 레노버 Yoga Slim 7 Pro에 들어간 UEFI에 발생한 취약점이다.
EFI_STATUS __fastcall ChildSwSmiHandler(
EFI_HANDLE DispatchHandle,
const void *Context,
_QWORD *CommBuffer,
UINTN *CommBufferSize)
{
...
Ptr2 = (CommBuffer[22] + 8);
for ( i = *Ptr2; i != Ptr2; i = *i )
{
i[24] = 0; // unchecked write (SMRAM corruption)
i[4] = 0; // unchecked write (SMRAM corruption)
i[6] = 0; // unchecked write (SMRAM corruption)
}
...
}
코드의 일부다. 위 코드를 보면 Ptr2에 CommBuffer가, 그리고 그 안에 있는 값들을 별도 검사 없이 사용하는 모습을 볼 수 있다. 물론 CommBuffer가 SMRAM에 침범하는지 검사가 이뤄졌을 것이고, 통과가 되어 CommBuffer를 사용했을 것이다. 하지만 만약 해커가 CommBuffer 내부 24, 4, 6번 등에 악의적인 Payload를 심었다면 해당 핸들러는 CommBuffer만 검사하고 내부 요소들은 검사하지 않았으므로 해당 Payload들이 SMM 권한을 얻은 채 실행될 것이다.
본격 UEFI 개발 환경 테스트해보기
아직 앞으로 어떻게 연구가 이뤄질지 모르지만 인터넷과 제미나이 등과 함께 UEFI 개발 환경을 만들어보았다.
환경은
노트북 : MacBook M4 Pro 16
가상환경 : VirtualBox, Ubuntu 24.04.02 LTS
UEFI : Tianocore edk2
기존 계획은 상대적 구형 버전으로 다운받으려고 했지만 오류가 너무나도 많이 터지는 바람에….. 일단은 최신 버전으로 만들어 보았다.
(자료 참고는 여기와 여기를 참고했습니다.)
먼저 터미널에서 의존성 패키지를 설치해준다.
sudo apt install build-essential uuid-dev acpica-tools git nasm python3-setuptools gcc-x86-64-linux-gnu
- build-essential : 빌드 도구(make 등) 모음
- uuid-dev : GUID 식별 라이브러리
- acpica-tools : ACPI 컴파일러
- nasm : 어셈블리 컴파일러
- gcc-x86-64-linux-gnu : ARM 환경에서 x86-64 버전으로 컴파일하기 위해 설치
이후 폴더를 하나 만들고 해당 폴더 안에서 edk2 파일을 clone 해준다.
mkdir uefi_test
cd uefi_test
git clone https://github.com/tianocore/edk2.git
cd edk2
이후 서브모듈을 최신화해주고 빌드 툴을 컴파일해준다.
git submodule --init --recursive
make -C BaseTools
이후 환경설정 파일을 생성한 뒤 빌드를 진행한다.
source edksetup.sh
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t GCC5
(이 과정에서 ARM 기반이라 그런지 오류가 매우 많이 발생했습니다. 이 때 저는 Conf/target.txt를 열어 다운받았던 gcc-x86-64-linux-gnu로 사용 컴파일러들을 바꾸는 등의 작업을 통해 설치할 수 있었습니다.)
이후 빌드가 성공하면 Build/OvmfX64/DEBUG_GCC5/FV 내에 Ovmf.fd라는 파일이 생기게 된다. 이 파일을 QEMU를 통해 실행할 수 있다.
qemu-system-x86_64 \
-bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd \
-net none
실행에 성공하면 다음과 같은 화면이 나오는 것을 볼 수 있다!

한 가지 아쉬운 점으론 취약점 분석을 할 예정인 만큼 최신 버전이 아닌 구버전을 다운받아 보려 했는데 오류가 매우 많이 발생하여 아쉬웠다. 아마 다음 주 목표는 구버전 다운로드를 목표로 하지 않을까 싶다.
(2026.01.26 11시 45분. 가상환경 없이 맥 환경에서 2021년 2월 버전 EDK2 빌드 성공!)
내 UEFI에 간단한 파일 올려보기
이제 여기서 UEFI Shell 부분에 코드를 추가해서 내 맘대로 일부 수정을 해보고 빌드를 해보자. 내가 수정을 해볼 부분은 ShellPkg/Application/Shell 내의 Shell.c 파일로, 해당 파일은 UEFI의 SHell을 담당하고 있는 파일이라고 볼 수 있다. 이 파일 내의 UefiMain() 함수를 찾아준다. UefiMain 함수는 마치 C나 Rust의 main() 처럼 해당 UEFI 파일의 시작점이 되는 부분이라고 볼 수 있다. 이 부분에 Print() 함수를 통해 내가 원하는 것을 출력시킬 것이다.
(Print() 함수는 C의 printf()와 비슷한 EDK2에서 제공하는 출력 함수다. 출력할 땐 L을 붙여 글자당 2바이트임을 알려준다.)

위와 같이 UefiMain 함수 내에 다음과 같이 입력한 뒤 저장하고 다시 빌드를 한 뒤 QEMU로 UEFI를 실행해보자.
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t GCC5
qemu-system-x86_64 -bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd -net none

Shell이 시작될 때 내가 입력한 내용이 출력되는 것을 볼 수 있다!