💻
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
  1. UEFI 개발
  2. 핸들 및 프로토콜

7. 핸들/프로토콜 데이터 베이스 구조 - Part 2

애플리케이션 프로토콜에 대한 기본적인 사용

Previous6. 핸들/프로토콜 데이터 베이스 구조 - Part 1Next8. HandleProtocol API 함수 & ImageHandle 프로토콜을 통한 정보

Last updated 2 years ago

이번 장에서는 IMAGE_HANDLE에 존재하는 프로토콜의 GUID를 출력하는 것이 목표다. 시작하기 앞서서 EDKII 코드베이스에서 내부적으로 EFI_GUID가 무엇을 뜻하는지 이해해야 한다. ()

///
/// 128-bit buffer containing a unique identifier value.
///
typedef GUID                      EFI_GUID;

GUID 구조는 아래의 링크에 정의되어있다. ()

///
/// 128 bit buffer containing a unique identifier value.
/// Unless otherwise specified, aligned on a 64 bit boundary.
///
typedef struct {
  UINT32  Data1;
  UINT16  Data2;
  UINT16  Data3;
  UINT8   Data4[8];
} GUID;

GUID는 위와 같이 4바이트-2바이트-2바이트-8바이트로 구성되어 있다. 다행히도 위와 같은 구조체의 필드 하나씩 직접 뽑아줄 필요가 없다는 것이다. Print 함수의 %g 옵션을 사용하면 GUIDs가 출력되기 때문에 간편하게 출력할 수 있다. Print 옵션에 대해서는 아래 링크에 자세히 설명되어 있다. ()

/// Printing Guid Using %g Option
Print("GUID=%g\n", myGUID);

우리는 PROTOCOL_INTERFACE 구조에서 참조되는 PROTOCOL_ENTRY의 EFI_GUID 필드를 출력할 것이다. 따라서 이전 챕터에서 언급한 것과 동일한 이유로 구조를 재정의 해야한다.

typedef struct {
  UINTN               Signature;
  /// Link Entry inserted to mProtocolDatabase
  LIST_ENTRY          AllEntries;
  /// ID of the protocol
  EFI_GUID            ProtocolID;
  /// All protocol interfaces
  LIST_ENTRY          Protocols;
  /// Registerd notification handlers
  LIST_ENTRY          Notify;
} PROTOCOL_ENTRY;

typedef struct {
  UINTN                       Signature;
  /// Link on IHANDLE.Protocols
  LIST_ENTRY                  Link;
  /// Back pointer
  IHANDLE                     *Handle;
  /// Link on PROTOCOL_ENTRY.Protocols
  LIST_ENTRY                  ByProtocol;
  /// The protocol ID
  PROTOCOL_ENTRY              *Protocol;
  /// The interface value
  VOID                        *Interface;
  /// OPEN_PROTOCOL_DATA list
  LIST_ENTRY                  OpenList;
  UINTN                       OpenListCount;

} PROTOCOL_INTERFACE;

이전 챕터에서 설명한 것과 동일하게 LINK는 다른 PROTOCOL_INTERFACE에 존재하는 LIST_ENTRY구조를 가리킨다(double-linked list). 아래는 이를 간략하게 표헌한 모습이다.

typedef struct {                                         typedef struct {
  UINTN                       Signature;                   UINTN                       Signature;
  struct LIST_ENTRY {                          |---------> struct LIST_ENTRY {
    LIST_ENTRY  *ForwardLink; -----------------|             LIST_ENTRY  *ForwardLink;
    LIST_ENTRY  *BackLink;                                   LIST_ENTRY  *BackLink;
  } Link;                                                  } Link;
  IHANDLE                     *Handle;                     IHANDLE                     *Handle;
  LIST_ENTRY                  ByProtocol;                  LIST_ENTRY                  ByProtocol;
  PROTOCOL_ENTRY              *Protocol;                   PROTOCOL_ENTRY              *Protocol;
  VOID                        *Interface;                  VOID                        *Interface;
  LIST_ENTRY                  OpenList;                    LIST_ENTRY                  OpenList;
  UINTN                       OpenListCount;               UINTN                       OpenListCount;
} PROTOCOL_INTERFACE;   
#define offsetof(a,b) ((INTN)(&(((a*)(0))->b)))
c
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

간략하게 설명을 하면 container_of 매크로는 포인터(ptr)가 가리키는 구조체 멤버(member)를포함하는 구조체(type)의 주소를 반환한다고 생각하면 된다.

이 모든 점들을 가지고 UefiMain 함수의 최종코드를 작성한다.

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  IHANDLE* MyHandle = ImageHandle;
  Print(L"Signature: %c %c %c %c\n", (MyHandle->Signature >>  0) & 0xff,
                                     (MyHandle->Signature >>  8) & 0xff,
                                     (MyHandle->Signature >> 16) & 0xff,
                                     (MyHandle->Signature >> 24) & 0xff);

  Print(L"Back Protocol Interface Link: %p\n", MyHandle->Protocols.BackLink);
  Print(L"Forward Protocol Interface Link: %p\n", MyHandle->Protocols.ForwardLink);

  LIST_ENTRY *FirstLink = MyHandle->Protocols.ForwardLink;
  LIST_ENTRY *CurrentLink = FirstLink;
  do {
        PROTOCOL_INTERFACE* MyProtocolInterface = container_of(CurrentLink, PROTOCOL_INTERFACE, Link);

        Print(L"\n");
        Print(L"Current Link: %p\n", CurrentLink);
        Print(L"Signature: %c %c %c %c\n", (MyProtocolInterface->Signature >>  0) & 0xff,
                                           (MyProtocolInterface->Signature >>  8) & 0xff,
                                           (MyProtocolInterface->Signature >> 16) & 0xff,
                                           (MyProtocolInterface->Signature >> 24) & 0xff);

        Print(L"Back Link: %p\n", MyProtocolInterface->Link.BackLink);
        Print(L"Forward Link: %p\n", MyProtocolInterface->Link.ForwardLink);
        Print(L"GUID=%g\n", MyProtocolInterface->Protocol->ProtocolID);
        CurrentLink = MyProtocolInterface->Link.ForwardLink;
  } while (CurrentLink != FirstLink);

  return EFI_SUCCESS;
}

해당 코드를 컴파일 이후 실행시 아래와 같은 결과가 나타난다.

FS0:\> ImageHandle.efi
h n d l
Back Protocol Interface Link: 68D4320
Forward Protocol Interface Link: 6891520

Current Link: 6891520
p i f c
Back Link: 6891430
Forward Link: 6891B20
GUID=752F3136-4E16-4FDC-A22A-E5F46812F4CA

Current Link: 6891B20
p i f c
Back Link: 6891520
Forward Link: 68D4320
GUID=BC62157E-3E33-4FEC-9920-2D3B36D750DF

Current Link: 68D4320
p i f c
Back Link: 6891B20
Forward Link: 6891430
GUID=5B1B31A1-9562-11D2-8E3F-00A0C969723B

Current Link: 6891430
  ? ? ?
Back Link: 68D4320
Forward Link: 6891520
GUID=00000000-0000-0000-0000-000000000000

결과로 나온 GUID 값을 EDKII 소스코드에서 grep명령어를통해 찾아보자.

$ grep -i 752F3136 -r ./ --exclude-dir=Build
./MdePkg/Include/Protocol/ShellParameters.h:  0x752f3136, 0x4e16, 0x4fdc, { 0xa2, 0x2a, 0xe5, 0xf4, 0x68, 0x12, 0xf4, 0xca } \
./MdePkg/MdePkg.dec:  gEfiShellParametersProtocolGuid      = { 0x752f3136, 0x4e16, 0x4fdc, {0xa2, 0x2a, 0xe5, 0xf4, 0x68, 0x12, 0xf4, 0xca }}

다음으로 남은 두개의 GUID는 UEFI 스펙(specification)에서 확인할 수 있다.

  1. EFI_LOADE_IMAGE_DEVICE_PATH_PROTOCOL - EFI_LOADE_IMAGE_DEVICE_PATH_PROTOCOL이 설치된 경우 Loaded Image Device Path Protocol은 PE/COFF 이미지가 EFI 부팅서비스인 LoadImage()를 통해 로드될 때 사용된 장치 경로를 지정해준다.

    #define EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID \
    {0xbc62157e,0x3e33,0x4fec,\
     {0x99,0x20,0x2d,0x3b,0x36,0xd7,0x50,0xdf}}
  2. EFI_LOAD_IMAGE_PROTOCOL - 로드된 이미지에 대한 정보를 얻기 위해 모든 이미지 핸들에서 사용할 수 있다.

    #define EFI_LOADED_IMAGE_PROTOCOL_GUID\
     {0x5B1B31A1,0x9562,0x11d2,\
     {0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B}}

마지막으로 출력된 PROTOCOL_INTERFACE 구조는 유효한 " p i f c" 서명이 없으므로 해당 GUID를 유심히 볼 필요는 없다.

하지만 우리는 Link 필드의 ForwardLink나 BackLink가 필요한 것이 아닌 PROTOCOL_INTERFACE 구조 자체에 대한 포인터가 필요하다. 따라서 아래와 같이 몇가지 매크로에 대한 정의가 필요하다. 해당 매크로는 리눅스 커널 프로그래밍을 공부했다면 친숙하게 느껴질 수도 있다. 혹시라도 모를 사람을 위해서 자세히 설명된 링크를 아래에 첨부하겠다. ()

두곳에서 사용되는 것을 볼 수 있다. 사용되는 곳에서의 GUID에 대한 정의는 아래의 링크들에 들어가면 볼 수 있다. () ()

🖥️
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiBaseType.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Base.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PrintLib.h
https://www.bhral.com/post/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90%EC%9D%98-container_of-%EB%A7%A4%ED%81%AC%EB%A1%9C
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/ShellParameters.h