EDK2 빌드 시스템 내에 OverFlowExam이라는 패키지를 생성하고, 공격 수행용 애플리케이션과 취약한 드라이버, 총 두 개의 모듈을 구현했다. 디렉터리 구조는 아래와 같다.
edk2/
└── OverFlowExam/ <-- [패키지 디렉터리]
│
├── OverFlowExam.dsc <-- [Platform Description File]
│
│
├── ExploitSetter/ <-- [모듈 디렉터리 1: 공격용 애플리케이션]
│ ├── ExploitSetter.c <-- [소스 코드]
│ └── ExploitSetter.inf <-- [모듈 정보 파일]
│
│
└── VulnDriver/ <-- [모듈 디렉터리 2: 취약한 드라이버]
├── VulnDriver.c <-- [소스 코드]
└── VulnDriver.inf <-- [모듈 정보 파일]
.dsc 파일은 패키지 단위의 빌드 명세서로, 전체적인 라이브러리 클래스 매핑과 빌드 대상 컴포넌트를 정의한다.
- 도커의 docker-compose.yml 또는 C언어의 MakeFile 처럼 전체 프로젝트를 조율하는 역할
각 모듈 폴더 내에는 소스 코드와 모듈 정보를 담은 .inf 파일이 존재해야 한다. .inf 파일은 해당 모듈의 메타데이터 파일로서 모듈의 진입점, 소스 파일 경로, 필요한 패키지 및 라이브러리 의존성 등을 명시하는 파일이다.
1. ExploitSetter
먼저 NVRAM 변수를 조작하여 공격용 페이로드를 시스템에 주입하는 애플리케이션인 ExploitSetter를 구현했다.
1.1 ExploitSetter.c
NVRAM에 VulnVar 라는 변수를 생성하여 악성 데이터를 심어두는 역할을 한다. 추후 취약한 드라이버가 이 변수를 읽어 들일 때 스택 버퍼 오버플로우가 발생하도록 유도
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
EFI_GUID gGuid = { 0x11111111, 0x2222, 0x3333, { 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB } };
EFI_STATUS EFIAPI UefiMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
){
UINT8 Payload[1000];
SetMem(Payload, sizeof(Payload), 0x41);
EFI_STATUS Status = gRT -> SetVariable(
L"VulnVar",
&gGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
sizeof(Payload),
Payload
);
if(EFI_ERROR(Status)){
Print(L"Error \n");
}else{
Print(L"VulnVar set successfully feel so good \n");
};
return EFI_SUCCESS;
}
- GUID
- GUID는 UEFI 변수의 고유 식별자 역할을 한다.
- 마치 집 주소처럼, 나중에 드라이버가 변수를 찾을 때 동일한 GUID를 제시해야만 해당 변수에 접근할 수 있다.
- 페이로드
UINT8 Payload[1000];
SetMem(Payload, sizeof(Payload), 0x41);
1000바이트 크기의 버퍼를 생성하고, 메모리 변조 확인을 쉽게 하기 위해 A로 가득 채웠다.
- SetVariable
EFI_STATUS Status = gRT -> SetVariable(
L"VulnVar", // 1. 변수 이름
&gGuid, // 2. 벤더 GUID
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, // 3. 속성
sizeof(Payload), // 4. 데이터 크기
Payload // 5. 실제 데이터
);
SetVariable은 NVRAM에 데이터를 저장하는 역할로, 총 5개의 인자를 받는다. 인자는 순서대로 이름, 벤더 GUID, 속성, 데이터 크기, 실제 데이터이다.
설정한 속성의 역할은 다음과 같다.
- BOOTSERVICE_ACCESS: 부팅 과정 중에 접근 가능함.
- RUNTIME_ACCESS: OS가 부팅된 후에도 접근 가능함.
- NON_VOLATILE: 비휘발성, 전원을 껐다 켜도 데이터가 사라지지 않고 NVRAM에 영구 보존됨
1.2. ExploitSetter.inf
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = ExploitSetter
FILE_GUID = 8F7D7B10-22A6-4F35-9596-332306C99501
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
ExploitSetter.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
UefiBootServicesTableLib
UefiRuntimeServicesTableLib
[Defines]: 모듈 정보
- MODULE_TYPE: UEFI_APPLICATION으로 설정하여, 메모리에 상주하는 드라이버가 아니라 사용자가 실행 후 종료되는 응용 프로그램임을 명시
- FILE_GUID: 펌웨어 볼륨 내에서 이 모듈을 식별하는 고유한 ID이다.
- ENTRY_POINT: 프로그램 실행 시 최초로 호출되는 함수를 UefiMain으로 지정하여 소스 코드와 연결하였다.
[Sources]: 모듈을 만들기 위해 컴파일해야 할 소스코드 파일
[Packages]: 필요한 헤더 파일 패키지
- MdePkg/MdePkg.dec는 UEFI 표준 개발 패키지로 Uefi.h, EFI_STATUS 같은 UEFI 표준 자료형과 정의들을 가져올 수 있음. C언어 표준 라이브러리와 비슷한 개념
[LibraryClasses]: 모듈에서 사용하는 라이브러리 나열
- UefiRuntimeServicesTableLib: NVRAM 변수 조작(SetVariable)을 수행하기 위해, UEFI 런타임 서비스(gRT)에 접근할 수 있는 라이브러리를 링크하였다.
- UefiLib: 화면 출력을 위한 Print 등 표준 유틸리티 함수 사용을 위해 선언
- 실제 라이브러리 매핑은 dsc 파일에서 작성한다.
2. VulnDriver
앞서 NVRAM에 만든 버퍼를 읽는 역할을 하며, 스택 버퍼 오버플로우를 발생하도록 구현한 UEFI 드라이버이다.
2.1 VulnDriver.c
#include <Uefi.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DebugLib.h>
EFI_GUID gGuid = { 0x11111111, 0x2222, 0x3333, { 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB } };
EFI_STATUS EFIAPI DriverEntry (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable){
UINT8 Buffer[16];
UINTN DataSize = 1000;
EFI_STATUS Status = gRT -> GetVariable(
L"VulnVar",
&gGuid,
NULL,
&DataSize,
Buffer
);
Print(L"Status: %r\n", Status);
return EFI_SUCCESS;
}
GetVariable
- SetVariable과 반대로 NVRAM에서 변수를 읽어 들이는 역할을 하며, 인자는 순서대로, 변수명, GUID, 속성, 데이터크기, 데이터 버퍼이다.
Phase
- 실제 할당된 로컬 버퍼의 크기는 16바이트이다.
- 하지만 GetVariable 함수에 전달하는 DataSize 인자에는 1000바이트를 입력하여, 시스템이 버퍼의 크기를 1000바이트로 착각하게 한다.
- GetVariable은 입력된 크기를 신뢰하여 NVRAM에 저장된 VulnVar 데이터인 1000바이트를 그대로 복사한다.
- 결과적으로 버퍼 크기를 초과한 나머지 984바이트가 인접한 스택 메모리를 덮어쓰며 스택 버퍼 오버플로우가 발생
2.2.VulnDriver.inf
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = VulnDriver
FILE_GUID = 2E5A8C34-7164-4C23-8A91-56188D4F7299
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = DriverEntry
[Sources]
VulnDriver.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiLib
UefiRuntimeServicesTableLib
DebugLib
VulnDriver.inf도 모듈의 빌드 명세서이며, 앞서 설명한 ExploitSetter.inf와 유사한 구조를 가진다. 다만 이 모듈은 애플리케이션이 아닌 드라이버이므로 약간의 차이가 있다.
- MODULE_TYPE: UEFI_DRIVER로 설정함. 실행 후 종료되는 것이 아니라, 메모리에 상주하며 시스템의 일부로 동작
- ENTRY_POINT: 드라이버의 표준 진입점 함수인 DriverEntry를 지정
- LibraryClasses: 드라이버용 진입점 라이브러리인 UefiDriverEntryPoint를 사용했다.
3. OverFlowExam.dsc
마지막으로 두 모듈을 패키지 단위로 묶어 동시에 빌드하기 위한 플랫폼 명세서를 정의했다.
[Defines]
PLATFORM_NAME = OverFlowExam
PLATFORM_GUID = 587CE499-6CBE-43CD-94E2-186218569478
PLATFORM_VERSION = 0.1
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/OverFlowExam
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS = RELEASE
SKUID_IDENTIFIER = DEFAULT
[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
StackCheckLib|MdePkg/Library/StackCheckLibNull/StackCheckLibNull.inf
[Components]
OverFlowExam/ExploitSetter/ExploitSetter.inf
OverFlowExam/VulnDriver/VulnDriver.inf
[Defines]: 빌드 환경 설정 플랫폼의 전반적인 환경 변수를 정의한다.
- SUPPORTED_ARCHITECTURES: X64로 지정하여 64비트 환경을 타겟으로 함을 명시
- OUTPUT_DIRECTORY: 빌드 완료 후 생성될 .efi 파일들이 저장될 경로(Build/OverFlowExam)를 지정한다.
- BUILD_TARGETS: RELEASE로 설정하여 최적화된 바이너리를 생성하도록 한다.
[LibraryClasses]: 라이브러리 인스턴스 매핑
- 각 모듈(.inf)에서 필요로 하는 라이브러리 클래스를 실제 동작하는 라이브러리 파일 경로와 1:1로 연결해 주는 섹션
- 빌드해 보고 안될 때마다 하나씩 추가하면서 넣으면 된다.
- 대부분 MdePkg에서 제공하는 표준 라이브러리를 사용하였으나, StackCheckLibNull을 통해 스택 보호 기법 비활성화하는 더미 라이브러리를 연결하였다. 만약 실제 보호 라이브러리가 연결되면 오버플로우 발생 시 펌웨어가 이를 감지하고 차단해 버릴 수 있음
[Components]: 빌드 대상 지정
실제 컴파일할 모듈의 목록이다. 앞서 작성한 ExploitSetter.inf과 VulnDriver.inf를 모두 포함시켜, 한 번의 빌드 명령으로 두 개의 실행 파일이 생성되도록 구성
4. 결과 확인
4.1. 환경 설정 및 빌드
. edksetup.sh
build --platform=OverFlowExam/OverFlowExam.dsc --arch=X64 --buildtarget=RELEASE --tagname=GCC5
먼저 edk2 빌드 환경을 초기화하기 위해 . edksetup.sh 스크립트를 실행한다. 이후 build 명령어를 통해 앞서 작성한 OverFlowExam 패키지를 컴파일한다.
- platform: 빌드할 플랫폼 명세 파일(.dsc)의 경로 (OverFlowExam/OverFlowExam.dsc)
- arch: 대상 아키텍처 (X64)
- buildtarget: 빌드 타겟 설정 (RELEASE 모드)
- tagname: 사용할 툴체인 (GCC5)
4.2. 실행 파일 배포
mkdir ~/UEFI_disk
cp Build/OverFlowExam/RELEASE_GCC5/X64/VulnDriver.efi ~/UEFI_disk/
cp Build/OverFlowExam/RELEASE_GCC5/X64/ExploitSetter.efi ~/UEFI_disk/
빌드가 성공적으로 완료되면, 결과물(.efi)은 .dsc에서 지정한 출력 디렉터리에 생성된다.
QEMU 실행 시 가상 USB 저장소 역할을 수행할 ~/UEFI_disk 디렉터리를 생성한 뒤, 빌드된 VulnDriver.efi와 ExploitSetter.efi 파일을 해당 경로로 복사한다.
- UEFI_disk 폴더는 QEMU 구동 시 가상 머신에 FAT 파일 시스템으로 포맷된 USB 드라이브처럼 마운트되어, 내부의 파일들을 UEFI Shell에서 접근할 수 있게 해준다.
4.3. QEMU를 통한 에뮬레이션
qemu-system-x86_64 \
-drive if=pflash,format=raw,file=Build/OvmfX64/RELEASE_GCC5/FV/OVMF.fd \
-drive format=raw,file=fat:rw:~/UEFI_disk \
-nographic \
-net none
위 명령어를 통해 QEMU 가상 머신을 구동한다. 이때 -drive if=pflash 옵션으로 OVMF 이미지를 로드하여 UEFI 펌웨어 환경을 구축하고, 앞서 만든 UEFI_disk 폴더를 드라이브로 연결한다.

실행 직후에는 부팅 가능한 운영체제가 없기 때문에 BdsDxe: failed to load Boot0002...와 같은 메시지가 출력될 수 있다. 정상적인 상황이므로 가볍게 무시하고 아무 키나 눌러 부팅 매니저 메뉴로 진입한다.

부팅 매니저 화면에서의 각 메뉴의 역할은 다음과 같다.
- EFI Firmware Setup: 바이오스 설정 화면.
- UEFI QEMU HARDDISK...: 연결된 가상 하드디스크.
- EFI Internal Shell: UEFI 명령행 인터페이스
직접 구현한 애플리케이션과 드라이버를 수동으로 실행해야 하므로 EFI Internal Shell을 선택한다.

쉘 진입 후, 가상 USB 드라이브로 이동하기 위해 파일시스템 별칭인 fs0:을 입력한다. ls 명령어로 디렉터리를 조회하면 복사해 둔 두 개의 .efi 파일을 확인할 수 있다.

먼저 ExploitSetter.efi를 실행하여 NVRAM에 공격용 페이로드를 주입한다. 이후 VulnDriver.efi를 로드하면 즉시 시스템 크래시가 발생하며 멈추는 것을 확인할 수 있다.
CPU 레지스터 상태를 확인해 보면, 다음 실행할 명령어 주소를 담는 RIP 레지스터가 앞서 페이로드로 설정한 값인 0x41(A)로 가득 차 있고, 다른 범용 레지스터들도 마찬가지인 것을 확인할 수 있다. 스택 버퍼오버플로우가 성공적으로 실행돼서 제어흐름이 탈취된 것!
UEFI DXE 드라이버는 운영체제 커널과 동일한 권한인 Ring 0에서 동작한다. 이번 실습에서는 일반적인 DXE 드라이버를 공격했지만, 만약 VulnDriver.efi가 SMI 핸들러로 등록된 드라이버였다면, 공격자는 오버플로우를 통해 Ring -2 영역인 SMM 내부로 침투할 수 있게 된다.
이번에 수행한 GetVariable 오버플로우와 SMM 영역의 대표적인 취약점인 CommBuffer 조작, SMM Callout을 비교 분석하면 다음과 같다.
1. GetVariable Overflow(이번 실습)
- 공격자가 NVRAM 변수를 조작하여 DXE 드라이버의 스택을 파괴하고 제어권을 탈취함.
- 비유: 도둑이 거실(DXE, Ring 0)에 침입해 CPU에게 창고(NVRAM)의 거대한 상자를 탁자(Stack)로 옮기게 했다. 상자가 너무 커서 탁자 위 원래대로 복귀하라는 메모(Return Address)를 덮어버렸고, 그 자리엔 도둑의 명령을 따르라는 가짜 메모가 놓이게 됐다. CPU는 메모대로 행동했고 도둑이 거실의 통제권을 얻음
2. CommBuffer 조작
- Ring 0(OS/DXE)에서 Ring -2(SMM)로 데이터를 전달하는 통로인 CommBuffer를 조작함. SMI 핸들러가 입력 데이터의 크기를 검증하지 않고 SMRAM 내부로 복사할 때 오버플로우가 발생함. (1번과 원리는 같으나 목표가 SMM임)
- 비유:거실(Ring 0)에서 금고 방(SMM, Ring -2)으로 물건을 보내는 택배 상자(CommBuffer)가 있다. 금고 방 안의 집사(SMI Handler)가 택배 상자의 크기를 확인하지 않고 방 안으로 들여서 연다. 상자 안의 내용물이 터지며 금고 방(SMM) 내부가 장악당한다.
3. SMM Callout
- 격리된 SMM 코드가 실수로 SMM 외부(Ring 0 메모리)에 있는 함수를 호출하거나 참조하는 취약점. 공격자는 외부 메모리에 악성 코드를 심어 두고, SMM이 이를 실행하게 유도함.
- 비유: 금고 방(SMM)의 집사는 절대로 방 밖으로 나와선 안 된다. 하지만 개발자의 실수로 집사가 작업을 위해 거실(Ring 0)로 통하는 문을 열고 나온다. 거실에 매복해 있던 해커가 집사를 덮쳐 금고 방의 권한을 탈취한다.
참고
https://github.com/Kostr/UEFI-Lessons
GitHub - Kostr/UEFI-Lessons: Lessons to get to know UEFI programming in Linux with the help of EDKII
Lessons to get to know UEFI programming in Linux with the help of EDKII - Kostr/UEFI-Lessons
github.com
UEFI DXE 바이너리 취약점 분석기 프로젝트_2
SMM SMM(System Management Mode)는 x86 및 x86-64 프로세서의 작동 모드로, 해당 모드는 OS가 실행되는 동안 OS의 아래에서 저수준 시스템 관리 작업을 수행하는 데 사용된다. 초기엔 부팅 관련 Phase에서만 사
ludence8.github.io
'CS > Graduation project' 카테고리의 다른 글
| [UEFI] SetVariable() (0) | 2026.02.14 |
|---|---|
| [UEFI] 리눅스(WSL) 환경에서 EDK2 빌드 및 실행 (0) | 2026.02.14 |
| SMM 권한 상승과 Secure Boot 우회 (CVE-2025-7026, 3052) (0) | 2026.01.26 |
| CVE-2023-45230: UEFI DHCPv6 드라이버 힙 오버플로우 (0) | 2026.01.19 |
| [UEFI] UEFI DRIVERS (1) | 2026.01.12 |