💻
UEFI 프로젝트
  • 🧑‍🏫프로젝트 개요
  • 📖UEFI 개념
    • 1. BIOS의 과거
    • 2. UEFI 개념
    • 3. BIOS vs UEFI
    • 4. UEFI 부팅 단계
  • 🖥️UEFI 개발
    • UEFI 개발 시작하기
      • 0. EDK II 빌드 환경 구성
      • 1. 간단한 EFI application 만들기
      • 2. 간단한 Pkg 만들기
      • 3. Hello World 출력하기
      • 4. 라이브러리와 Hello World
      • 5. Conf를 통한 Build 단순화
    • 핸들 및 프로토콜
      • 6. 핸들/프로토콜 데이터 베이스 구조 - Part 1
      • 7. 핸들/프로토콜 데이터 베이스 구조 - Part 2
      • 8. HandleProtocol API 함수 & ImageHandle 프로토콜을 통한 정보
      • 9. ProtocolsPerHandle API를 통한 ImageHandle 프로토콜 가져오기
      • 10. EFI_STATUS 타입 과 EFI_ERROR 매크로
    • 메모리 맵
      • 11. EFI 메모리 맵 정보 얻기
      • 12. EFI 메모리 맵을 리눅스 커널 스타일로 바꾸기
    • 명령줄 인수를 받는 간단한 앱 만들기
      • 13.ShellAppMain Entry point
      • 14.gRT->GetNextVariableName API를 사용하여 모든 변수 이름 및 GUID 가져오기
    • 부팅 옵션
      • 15. gRT->GetVariable API를 사용하여 부팅 변수 가져오기 및 구문 분석
      • 16. OVMF 이미지 내에 부팅 옵션 추가
      • 17. 부팅 옵션에 WaitForEvent 함수 추가
      • 18. ReadKeyStroke 함수로 사용자 입력 처리
      • 19. bcfg 명령어를 사용한 부팅 옵션 수정
    • PCD
      • 20. PCD 소개
      • 21. PCD 변수에 대한 Overriding
      • 22. Feature Flag PCD와 BOOLEAN FixedAtBuild PCD의 비교
      • 23. PatchableInModule PCD 및 GenPatchPcdTable/PatchPcdValue 유틸리티를 통해 PCD를 변경하는 방법
      • 24. Dynamic/DynamiEx PCDs
      • 25. PCD 더 알아보기
    • 테이블
      • 26. EFI_CONFIGURATION_TABLE에서 참조되는 테이블
      • 27. dmem/EFI_SMBIOS_PROTOCOL/smbiosview를 통해서 SMBIOS 정보 가져오기
      • 28. EFI_SHELL_PROTOCOL을 통하여 ACPI 테이블을 파일에 저장하기
      • 29. EFI_ACPI_SDT_PROTOCOL 및 ShellLib를 사용하여 ACPI BGRT 테이블에서 BMP 이미지 저장하기
    • PCI
      • 30. PCI 루트 브리지 찾은 후 시스템의 모든 PCI 기능 가져오기
      • 31. ShellLib/PrintLib 함수를 사용해 PCI Vendor/Device 정보 가져오기
      • 32. EFI_PCI_IO_PROTOCOL을 사용해 PCI Option ROM 이미지 표시
      • 33. EfiRom 유틸리티를 사용한 PCI Option ROM 이미지 파싱 및 생성
    • 드라이버 및 라이브러리
      • 34. 간단한 UEFI 드라이버 생성
      • 35. 애플리케이션에서 사용할 간단한 라이브러리 생성
      • 36. Library의 constructor와 destructor, NULL Library
      • 37. Shell에 acpiview 명령을 추가하는 방법 조사
      • 38. 사용자 지정 프로토콜을 만들고 사용하기
      • 39. RegisterKeyNotify / UnrigisterKeyNotify 함수를 사용해 단축키 기능을 추가하는 드라이버 만들기
      • 40. Key #### NVRAM 변수
    • 디버그
      • 41. DEBUG 출력문 내부 구조와 DEBUG 문 제어를 위한 PCD 분석, 그리고 OVMF 부트 로그 가져오기
      • 42. GDB를 이용한 Driver/Application 및 OVMF Debug
    • HII
      • 43. HII 데이터베이스 개념 및 출력
      • 44. HII 데이터베이스 내부
      • 45. EFI_HII_DATABASE_PROTOCOL의 NewPackageList를 사용하여 문자열 패키지가 포함된 문자열 목록 게시
      • 46. EFI_HII_DATABASE_PROTOCOL의 NewPackageList를 사용하여 문자열 패키지가 포함된 HII 패키지 목록 게시
      • 47. EFI_HII_DATABASE_PROTOCOL의 NewPackageList를 사용하여 문자열 패키지가 포함된 HII 패키지 목록 게시
      • 48. UNI 파일 및 HiiLib를 사용하여 HII String 패키지 게시 및 작업하기
      • 49.MODULE_UNI_FILE/PACKAGE_UNI_FILE/[UserExtensions.TianoCore."ExtraFiles"]의 도움으로 UNI 파일 선언하기
      • 50.UEFI_HII_RESOURCE_SECTION을 사용하여 문자열 패키지와 함께 HII 패키지 목록 게시하기
      • 51. UEFI APP에 메뉴얼 추가하기(shell의 -?와 help 옵션)
      • 52. Russian 글꼴 추가 - Part 1.
      • 53. Russian 글꼴 추가 - Part 2.
      • 54. EFI_HII_STRING_PROTOCOL의 NewString 및 SetString 함수를 사용하여 다른 언어에 대한 문자열 패키지를 동적으로 추가
      • 55. PlatformLangCodes EFI 변수 수정 및 다른 언어를 동적 추가하기
      • 56. 코드에서 FILE_GUID 및 BASE_NAME을 가져오기
    • VFR
      • 57. VFR을 사용해 간단한 폼 생성 및 EFI_FORM_BROWSER2_PROTOCOL.SendForm()를 통해 화면에 폼 표시하기
      • 58. VFR 요소 : subtitle 및 text
      • 59. 간단한 폼 애플리케이션을 UEFI 드라이버 Form으로 변환하기
      • 60. gRT->SetVariable() 함수를 사용한 UEFI 변수 생성, 변경 및 삭제
      • 61.dmpstore 명령을 사용하여 변수를 파일에 저장/로드하기
      • 62. UEFI Device path의 구조
      • 63. checkbox를 가진 HII 폼 만들기
      • 64. checkbox를 가진 HII폼 만들기
      • 65. VFR 추가 입력 요소 Part 1: number
      • 66. VFR 추가 입력 요소 Part 2: string
      • 67. VFR 추가 입력 요소 Part 3: date & time
      • 68. VFR 추가 입력 요소 Part 3: oneof & orderedlist
      • 69. VFR의 조건부 키워드
      • 70. VFR의 상수 및 연산자가 내장된 기본 조건문
      • 71. 기본 VFR 내장 문자열용 함수
      • 72. label 키워드를 이용하여 HII 양식에 동적 요소 추가하기
      • 73. VFR question 기본값 설정
  • 🔐UEFI 보안
    • 1. 개요
    • 2. 공격 벡터
    • 3. mitigation
    • 4. 정적 분석 방법
    • 5. 동적 분석 방법
Powered by GitBook
On this page
  • efivarstore에서 사용자 정의 구조체를 사용하는 HII 애플리케이션 생성하기
  • 숫자형 VFR 요소
  • 단계 필드
  • IFR 코드 분석
  • 숫자 플래그
  1. UEFI 개발
  2. VFR

65. VFR 추가 입력 요소 Part 1: number

이전 장에서는 checkbox VFR요소를 사용했었다. 사용자 입력에 사용할 수 있는 다른 VFR 요소들을 살펴보자.

efivarstore에서 사용자 정의 구조체를 사용하는 HII 애플리케이션 생성하기

HIIFormCheckbox 드라이버의 코드를 기반으로 하나의 checkbox 요소가 포함된 HIIFormDataElements 드라이버를 만들어보자.

이 레슨의 UEFI 변수는 폼에 있는 다른 VFR 요소의 데이터를 포함하므로 사용자 정의 구조체 유형 내부의 checkbox에 대한 UINT8 스토리지를 배치해야 한다.

typedef를 통해 사용자 정의 구조체를 만들기 위해 헤더 파일 UefiLessonsPkg/HIIFormDataElements/Data.h 를 생성한다. 변수 이름과 GUID 정의(우리의 경우 FORMSET_GUID와 동일하다고 결정함)도 이 헤더파일로 이동시켜준다.

UefiLessonsPkg/HIIFormDataElements/Data.h

#ifndef _DATA_H_
#define _DATA_H_

#define FORMSET_GUID  {0x531bc507, 0x9191, 0x4fa2, {0x94, 0x46, 0xb8, 0x44, 0xe3, 0x5d, 0xd1, 0x2a}}

#define UEFI_VARIABLE_STRUCTURE_NAME L"FormData"

#pragma pack(1)
typedef struct {
  UINT8 CheckboxValue;
} UEFI_VARIABLE_STRUCTURE;
#pragma pack()

#endif

다음은 UefiLessonsPkg/HIIFormDataTemplate/HIIFormDataElements.c에 적용해야 하는 수정 사항이다.

...
#include "Data.h"			<--- 헤더 파일 추가하기

...

EFI_STRING      UEFIVariableName = UEFI_VARIABLE_STRUCTURE_NAME;   <--- UEFI 변수 이름에 대한 상수 추가


EFI_STATUS
EFIAPI
HIIFormDataElementsUnload (
  EFI_HANDLE ImageHandle
  )
{
  ...

  UEFI_VARIABLE_STRUCTURE EfiVarstore;		<--- `UINT8`대신 우리의 커스텀 스토리지 사용
  BufferSize = sizeof(UEFI_VARIABLE_STRUCTURE); <---

  Status = gRT->GetVariable(
                UEFIVariableName,				<--- UEFI 변수 이름으로 변수 사용
                &mHiiVendorDevicePath.VendorDevicePath.Guid,
                NULL,
                &BufferSize,
                &EfiVarstore);
  if (!EFI_ERROR(Status)) {
    Status = gRT->SetVariable(
                  UEFIVariableName,                             <--- UEFI 변수 이름으로 변수 사용
                  &mHiiVendorDevicePath.VendorDevicePath.Guid,
                  0,
                  0,
                  NULL);
  ...
}


EFI_STATUS
EFIAPI
HIIFormDataElementsEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  ...

  UEFI_VARIABLE_STRUCTURE EfiVarstore;        	<--- `UINT8`대신 우리의 커스텀 스토리지 사용
  BufferSize = sizeof(UEFI_VARIABLE_STRUCTURE); <---

  Status = gRT->GetVariable (
                UEFIVariableName,				<--- UEFI 변수 이름으로 변수 사용
                &mHiiVendorDevicePath.VendorDevicePath.Guid,
                NULL,
                &BufferSize,
                &EfiVarstore);
  if (EFI_ERROR(Status)) {
    ZeroMem(&EfiVarstore, sizeof(EfiVarstore));
    Status = gRT->SetVariable(
                  UEFIVariableName,                             <--- UEFI 변수 이름으로 변수 사용
                  &mHiiVendorDevicePath.VendorDevicePath.Guid,
                  EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  sizeof(EfiVarstore),
                  &EfiVarstore);
  ...
}

그리고 UefiLessonsPkg/HIIFormDataElements/Form.vfr의 수정사항

#include <Uefi/UefiMultiPhase.h>
#include "Data.h"                             <--- 헤더 파일 추가하기

formset
  guid     = FORMSET_GUID,
  title    = STRING_TOKEN(FORMSET_TITLE),
  help     = STRING_TOKEN(FORMSET_HELP),

  efivarstore UEFI_VARIABLE_STRUCTURE,                                          <--- 사용자 정의 유형 사용
    attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
    name  = FormData,                                                           <--- 새 UEFI 변수 이름
    guid  = FORMSET_GUID;

  form
    formid = 1,
    title = STRING_TOKEN(FORMID1_TITLE);

    checkbox
      varid = FormData.CheckboxValue,                                           <--- 구조체 요소로 엑세스
      prompt = STRING_TOKEN(CHECKBOX_PROMPT),
      help = STRING_TOKEN(CHECKBOX_HELP),
    endcheckbox;
endformset;

드라이버를 빌드하고 모든 것이 제대로 작동하는지 확인해본다.

숫자형 VFR 요소

새 요소에는 스토리지가 필요하므로 UEFI 변수 구조에 UINT8 NumericValue 필드를 추가한다.

typedef struct {
  UINT8 CheckboxValue;
  UINT8 NumericValue;
} UEFI_VARIABLE_STRUCTURE;

또한 HII폼에 필요한 몇 가지 문자열을 UefiLessonsPkg/HIIFormDataElements/Strings.uni에 추가한다.

#string NUMERIC_PROMPT         #language en-US  "Numeric prompt"
#string NUMERIC_HELP           #language en-US  "Numeric help"

요소에 대한 최소한의 VFR 코드는 다음과 같다.

numeric
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  minimum = 5,
  maximum = 20,
endnumeric;

minimum과 maximum필드는 숫자 요소에 필수이다. 이 예제에서는 값을 5..20 범위로 제한한다.

UEFI 변수의 크기를 변경했으므로 드라이버를 로드하기 전에 제거하는 것이 좋다.(unload 명령어 사용)

FS0:\> dmpstore -guid 531bc507-9191-4fa2-9446-b844e35dd12a -d
FS0:\> load HIIFormDataElements.efi

우리의 폼을 로드하면 다음과 같다.

보다시피 기본적으로 값은 0이다.(ZeroMem(&EfiVarstore, sizeof(EfiVarstore))을 사용하기 때문) 이는 값이 제한 범위(5..20)를 벗어남을 의미한다. 요점은 HII 폼은 초기 값을 제어할 수 없다는 것이다. 최대값 및 최소값은 오직 사용자의입력에 대한 제한이다.

범위를 벗어난 값(예: 4)을 입력하려고 하면 폼에서 허용하지 않는다.

폼 엔진은 허용되지 않는 입력을 입력하는 것까지만 허용한다. 예를 들어 첫 번째 기호로 3을 입력하면 폼 엔진은 다른 숫자 기호를 추가로 입력하는 것을 허용하지 않는다. 값이 사용 가능한 범위를 벗어나기 때문이다.

그러나 허용 범위에 있으면 값을 성공적으로 입력하고 저장(F10)할 수 있다.

dmpstore 명령을 사용하여 값이 성공적으로 설정되었는지 확인할 수 있다. 우리의 경우 값은 저장소의 두 번째 바이트에 있다.(0x0f = 15)

Shell> dmpstore -guid 531bc507-9191-4fa2-9446-b844e35dd12a
Variable NV+BS '531BC507-9191-4FA2-9446-B844E35DD12A:FormData' DataSize = 0x02
  00000000: 00 0F                                            *..*

단계 필드

숫자 VFR 요소는 단계 필드를 포함할 수 있다. 예를 들어

numeric
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  minimum = 5,
  maximum = 20,
  step = 2,
endnumeric;

이 필드가 있는 경우 폼의 왼쪽 하단에 값 조정 도움말 메시지가 표시된다.

이는 +/-를 입력하여 요소 값을 늘리거나 줄일 수 있음을 의미한다. 그리고 +/-를 입력할 때마다 단계 필드에 설정한 값만큼 값이 증가/감소한다. 폼 엔진은 심지어 범위 필드를 어느 정도 존중한다. 예를 들어 값이 19이고 +를 입력하면 값이 20으로 설정된다. 다음 +는 값을 5로 변경하고 다음 +는 7로 변경한다.

IFR 코드 분석

평소처럼 바뀐 IFR 코드를 조사해보자.

Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIIFormDataElements/HIIFormDataElements/DEBUG/Form.lst

    numeric
>0000006C: 07 91 07 00 08 00 02 00 01 00 01 00 00 10 05 14 02
      varid = FormData.NumericValue,
      prompt = STRING_TOKEN(0x0007),
      help = STRING_TOKEN(0x0008),
      minimum = 5,
      maximum = 20,
      step = 2,
    endnumeric;
>0000007D: 29 02

이를 해석하려면 UEFI 스펙을 참조해야한다.

EFI_IFR_NUMERIC

Summary:
Creates a number question.

Prototype:
#define EFI_IFR_NUMERIC_OP 0x07

typedef struct _EFI_IFR_NUMERIC {
 EFI_IFR_OP_HEADER   Header;
 EFI_IFR_QUESTION_HEADER Question;
 UINT8 Flags;

 union {
   struct {
    UINT8 MinValue;
    UINT8 MaxValue;
    UINT8 Step;
   } u8;
   struct {
    UINT16 MinValue;
    UINT16 MaxValue;
    UINT16 Step;
   } u16;
   struct {
    UINT32 MinValue;
    UINT32 MaxValue;
    UINT32 Step;
   } u32;
   struct {
    UINT64 MinValue;
    UINT64 MaxValue;
    UINT64 Step;
   } u64;
 } data;
} EFI_IFR_NUMERIC;

Members:
Header 		The sequence that defines the type of opcode as well as the length of the opcode being defined. Header.OpCode = EFI_IFR_NUMERIC_OP.
Question 	The standard question header.
Flags 		Specifies flags related to the numeric question.
MinValue 	The minimum value to be accepted by the browser for this opcode. The size of the data field may vary from 8 to 64 bits.
MaxValue 	The maximum value to be accepted by the browser for this opcode. The size of the data field may vary from 8 to 64 bits.
Step 		Defines the amount to increment or decrement the value each time a user requests a value change. If the step value is 0, then
		the input mechanism for the numeric value is to be free-form and require the user to type in the actual value. The size of the
		data field may vary from 8 to 64 bits.

checkbox 요소에 대해 이야기할 때 이미 EFI_IFR_OP_HEADER 및 EFI_IFR_QUESTION_HEADER를 조사했었다.

이 데이터를 빼면 10 05 14 02만 남는다.

이것은 우리의 새 필드가 다음과 같이 채워짐을 의미한다.

typedef struct _EFI_IFR_NUMERIC {
 EFI_IFR_OP_HEADER   Header;
 EFI_IFR_QUESTION_HEADER Question;
 UINT8 Flags;                        = 0x10
 struct {
    UINT8 MinValue;                  = 0x05
    UINT8 MaxValue;                  = 0x14
    UINT8 Step;                      = 0x02
 } u8;
} EFI_IFR_NUMERIC;

0x10 값을 갖는 Flags 필드를 제외하고 모든 것이 예상대로 채워진다. 0x10은 무슨 의미일까?

특수 숫자 플래그는 다음과 같다.

#define EFI_IFR_NUMERIC_SIZE           0x03
#define EFI_IFR_NUMERIC_SIZE_1         0x00
#define EFI_IFR_NUMERIC_SIZE_2         0x01
#define EFI_IFR_NUMERIC_SIZE_4         0x02
#define EFI_IFR_NUMERIC_SIZE_8         0x03

#define EFI_IFR_DISPLAY                0x30
#define EFI_IFR_DISPLAY_INT_DEC        0x00
#define EFI_IFR_DISPLAY_UINT_DEC       0x10
#define EFI_IFR_DISPLAY_UINT_HEX       0x20

따라서 우리의 경우 EFI_IFR_DISPLAY_UINT_DEC 및 EFI_IFR_NUMERIC_SIZE_1 플래그가 있는 것이다. 즉, 부호 없는 10진수로 표시되며 크기는 1바이트라는 것을 표시해준다.

숫자 플래그

플래그 값을 변경하려면 VFR에서 이 키워드를 사용해야 한다.

NUMERIC_SIZE_1
NUMERIC_SIZE_2
NUMERIC_SIZE_4
NUMERIC_SIZE_8
DISPLAY_INT_DEC
DISPLAY_UINT_DEC
DISPLAY_UINT_HEX

예를 들어 flags = DISPLAY_UINT_HEX를 사용하면 값이 16진수로 렌더링된다.

numeric
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  flags = DISPLAY_UINT_HEX,
  minimum = 5,
  maximum = 200,
  step = 2,
endnumeric;

음의 정수를 설정할 가능성을 추가하려면 DISPLAY_INT_DEC 플래그를 추가하면 된다.

numeric
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  flags = DISPLAY_INT_DEC,
  minimum = -5,
  maximum = 20,
  step = 2,
endnumeric;

크기 플래그는 기본적으로 저장소의 변수의 크기에 따라 자동으로 설정된다. 예를 들어 숫자 필드의 유형을 UINT32로 변경하는 경우,

typedef struct {
  UINT8 CheckboxValue;
  UINT32 NumericValue;
} UEFI_VARIABLE_STRUCTURE;

VFR 코드:

numeric
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  minimum = 0x11223344,
  maximum = 0xAABBCCDD,
  step = 2,
endnumeric;

빌드 하면 다음 IFR 결과를 생성한다.

    numeric
>0000006C: 07 9A 07 00 08 00 02 00 01 00 01 00 00 12 44 33 22 11 DD CC BB AA 02 00 00 00
      varid = FormData.NumericValue,
      prompt = STRING_TOKEN(0x0007),
      help = STRING_TOKEN(0x0008),
      minimum = 0x11223344,
      maximum = 0xaabbccdd,
      step = 2,
    endnumeric;
>00000086: 29 02

데이터를 구문 분석해보면,

flags   = 0x12       = EFI_IFR_DISPLAY_UINT_DEC | EFI_IFR_NUMERIC_SIZE_4
minimum = 0x11223344
maximum = 0xaabbccdd
step    = 0x00000002

크기 플래그가 EFI_IFR_NUMERIC_SIZE_1에서 EFI_IFR_NUMERIC_SIZE_4로 변경되었다.

빌드 시스템은 빌드 프로세스에서 스토리지 크기를 확인한다는 것을 알 수 있다.

그래서 크기만 UINT8로 되돌리고 minimum/maximum/step에 대해 UINT32 값을 그대로 두면 빌드가 실패한다.

ERROR 12288: Overflow: Value 0x11223344 is too large to store in a UINT8

또한 저장소 크기와 충돌하는 크기 플래그를 입력할 수 없다.

ERROR 12288: Numeric Flag is not same to Numeric VarData type

efivarstore의 경우 크기 플래그는 중복성을 가진다. 이것은 다른 유형의 스토리지를 사용하는 경우에만 중요하다. 하지만 VFR에 있는 저장소의 크기를 명시적으로 표시하려는 경우에 넣을 수 있다.

또한 UEFI 변수 크기를 변경할 때마다 UEFI 변수를 삭제하는 것을 잊지 말자. 꼭 업데이트된 드라이버를 로드하기 전에 수행해야 한다.

FS0:\> dmpstore -guid 531bc507-9191-4fa2-9446-b844e35dd12a -d

| 연산자의 도움으로 여러 플래그를 결합할 수 있음을 기억하자.

flags = NUMERIC_SIZE_8 | DISPLAY_UINT_HEX,

Previous64. checkbox를 가진 HII폼 만들기Next66. VFR 추가 입력 요소 Part 2: string

Last updated 2 years ago

숫자 VFR 요소를 조사해보자. 이를 통해 사용자는 HII 폼에서 숫자 값을 입력할 수 있다.

🖥️
https://edk2-docs.gitbook.io/edk-ii-vfr-specification/2_vfr_description_in_bnf/211_vfr_form_definition#2.11.6.6.1-vfr-numeric-statement-definition