UEFI 스터디 7차 - 기드라를 사용한 드라이버 정적분석 스크립트 작성 (1)
Published:
스크립트 작성 주제 관련
UEFI에서만 나타나는 취약점을 찾고 싶었지만 스크립트 작성을 맡은 만큼. 이러다가 취약점만 찾다가 2월이 끝날 것같아, 기드라 사용법을 익히기 위해 현재 까지 찾은 취약점으로 임시로 픽스.
- LOGOFAIL과 관련된 취약점 중
- 복잡한 로직없이 패턴매칭을 이용하여서
- 기드라 스크립트를 통해 취약점 탐지
리버싱된 모듈의 엔트리 이해해보기
이해를 위한 기본 사항들 복습
서비스 -> 일반적이고 많이 사용하는 기능성을 제공.
검증을 많이 거쳐서 문제가 터지는 곳이 아닌 문제가 들어오는 종착지, 입구
프로토콜 -> 기기의 기능에 접근하기 위한 인터페이스
모듈과 드라이버차이는?(GPT)
드라이버 C 모듈.
모듈은 .efi 확장자로 FV에 저장되는 놈들 총칭.(단순 실행과 기능의 단위)
드라이버는 주로 DXE단계에서 로드 되어, 다른 모듈이 하드웨어를 사용할 수 있도록 인터페이스(프로토콜)을 생성
모듈이며 드라이버가 아닌 것으로 UEFI 쉘, OS 로더 등이 있다.
UEFI 스펙에서도 드라이버는 EFI_DRIVER_BINDING_PROTOCOL을 주로 구현한 것으로 구별된다.
EDK2도 모듈 빌드 가이드라인에 모듈 타입을 구분한다고함.
DXE_DRIVER→ 드라이버
DXE_RUNTIME_DRIVER → 위중에서 런타임 남는거
UEFI_APPLICATION→ 얘는 어플리케이션

모든 UEFI 모듈들은 엔트리포인트(UEFIMain)에서 두번째 인자로 EFI_SYSTEM_TABLE의 포인터를 전달 받음.
서비스는 전역변수인
- SystemTable->BootServices
- SystemTable->RuntimeServices
로 접근 가능하다.
이것이 사람들이 UEFI 모듈을 만들때
<Library/UefiBootServicesTableLib.h>나 <Library/UefiBootServicesTableLib.h>를 인클루드 하는 이유다. (그래서 리버싱하면 해당 해더의 전역변수 선언들이 나타나있음)
대표적으로 중요한 서비스
1.메모리 할당 관련
- gBS-> AllocatePool(작은 8바이트 단위 aligned 된 버퍼)
- gBS -> AllocatePage(커다란 4KB aligned된 버퍼)
2.이벤트 관련
- gBS→ createEvent
- gBS → createEventEx(GUID로 구분되는 그룹을 인식가능)
3번째 인자(EFI_EVENT_NOTIFY)함수를 마지막 인수(EFI_EVEN)가 시그널되면 실행한다.
이벤트 타입은 두가지로EVT_NOTIFY_SIGNAL(0x200) ,EVT_NOTIFY_WAIT(0x100)다. 이는 첫번째 인자로 식별 가능하며 이벤트 시그널 날리는 방식이 달라진다.
- EVT_NOTIFY_SIGNAL(0x200): gBS->SignalEvent
- EVT_NOTIFY_WAIT(0x100): gBS->CheckEvent, gBS->WaitForEvent
하지만 DXE에서는 대부분 gBS->RegisterProtocolNotify을 gBS→CreateEvent와 세트로 보게될거다. (첫번째 인자의 GUID로 식별된 프로토콜이 설치되어있으면 두번째 인자에 해당하는 이벤트의 시그널을 보낸다.)
3.프로토콜 관련
gBS→LocateProtocol
모듈간 통신, 타 모듈이 제공하는 기능 쓰는 서비스 사용하기 위한. 즉 DXE driver 분석시 어떤 프로토콜 함수의 사용례가 궁금하다면 LocateProtocol(GUID)가 사용된 드라이버를 찾으면 됨.
gBS→InstallProtocolInterface
로 자신의 GUID에 함수포인터 묶음을 등록 사용할 녀석은 해당 GUID로 프로토콜 등록된거의 주소를 알려달라함(이게 로케이트 프로토콜)
?그럼 드라이버에서 사용되는 프로토콜을 어떻게 찾을까?
-> gBS→ InstallProtocollinterface나 gBS→InstallMultipleProtocollinterfaces를 찾자.
보통 후자가드라이버 엔트리에 있다. 인자로 들어간 것의 GUID 프로토콜을 찾으면된다.
플러그인 스크립트 이용해 entry 예쁘게 디컴파일 하기
이제 이론 상 나는 리버싱한 코드의 엔트리는 해석 가능하다. 기드라에서 임의의 EDK2 드라이버의 엔트리 부분을 리버싱한 결과를 보도록 하자.

읭?
왜 이런 걸까?
위쪽의 글을 참고하면 entry의 두번째 인자는 long long 타입이 아닌 프로토콜 구조체 테이블의 주소다. 근데 이걸 주소가아닌 8바이트 정수라고 해석해서 이렇게 된거다.
이 문제를 해결할 GUID 변경, 함수 이름 변경, 타입 추측을 깔끔하게 해 줄 플러그인은 없는걸까?
첫 시도 -> efiseek
UEFI 관련으로 기드라 플러그인 중 가장 많은 스타를 받은 플러그인이었고, 위의 게시글에서도 사용했으므로 이 녀석을 먼저 시도했다. 직접 빌드해서 사용하는 방식이었는데 빌드가 되지 않았다.
나는 당연히 기드라 25.0.2 최신버전을 사용했다.
헤당 빌드는 빌드도구 8.0이상을 지원하지 않음 <-> 해당 기드라버전은 8.5버전 이상의 빌드도구와 호환됨
이 빌드도구가 뱉는 오류 사이에서 씨름을 했다.
뒤늦게 해당 플러그인이 2022년 이후 커밋이 없는 것을 보고 이래서 안됐구나하고 확신했다.
두번째 시도 -> ghidra-firmware-utils
유일하게 UEFI 관련 지속적인 커밋이 있는 플러그인이어서 혹시 이런 기능은 있지 않을까하는 기대로 찾아보았다.
해당 플러그인에 UEFIHelper.java라는 스크립트가 있었다. 이를 이용하면 함수이름, GUID등을 자동으로 변환해 준다고 한다.
해당 익스텐션의 설치 방법은 따로 게시글에 설명해서 미리 공유를 하였다.

엔트리 이름도 바뀌었고, 매개변수의 이름도 바뀌었다. 그러나 아직 예시와 많이 다르다.
임의의 이름을 가진 전역변수, 여전히 long long으로 표기된 SystemTable.
이정도는 사용자가 하라는 거니 싶어서 시스템 테이블의 타입도 변경해주고, 전역변수 이름을 고치니 이렇게 우리가 이해하기 쉽게 되었다.

테스트할 드라이버 구하기
스크립트를 작성할때, 기드라에서 실제 드라이버를 p코드등을 활용하여 분석을할텐데, 실제 테스트할 드라이버가 없다면 아무 의미 없을 것.
실제 문제가 있었던 드라이버를 받아서, 직접 기드라에서 까보자.
이전에 봤던 로고페일 취약점의 사례 보고서에 나온 모델 ‘Yoga 7 14lAL7’의 GUID 1353de63-b74a-4bef-80fd-2c5cfa83040b의 드라이버를 찾아서 추출하면 된다.
레노버에서 현재 해당 모델의 바이오스이 설치용 exe를 매우 쉽게 구할 수 있었다.(공식에서 제공한다.)
이제 exe를 어떻게 쪼개냐 인데,
이전에 서웅이가 Dell의 바이오스 설치용 exe에서 UEFI 이미지를 추출했던 것을 바탕으로 그곳에나온 BIOS Assembler라는 키워드를 바탕으로 레노버 BIOS Assembler라고 검색을하니 이미 레노버에서도 UEFI를 추출한 사람의 블로그를 찾을 수 있었따. 해당 글
추출한 이미지를 UEFItool 에 넣고 ctrl f로 위의 GUID를 검색하니 
잘 나온다. 심지어 툴자체에서 GUID를 프로토콜 이름으로 자동 변환까지 시켰다. 표준 프로토콜은 아닌 것으로 아는데 이툴, 생각보다 많은 것을 알고 있다…
이제 기드라에서 설명하는 해당 함수가 실제 있는 곳을 발견한다면, 이를 어셈블리, p코드로도 분석할 수 있을 것이다.
디컴파일하며 함수의 이름은 알수 없으므로 임의의 함수이름이 지정되지만, 나는 매개변수의 개수에 주목해서 똑같이 7개의 매개변수를 가진 함수를 조사해보았다.
상단 메뉴바 windows -> functions를 클릭해서 나오는 창에서 회색 열제목을 우클릭, Add/Remove Colums에서 Parameter Count를 키면..
이 네놈중에 한놈이 범인이다.
실제로 FUN_00001780이 내가 찾던 보고서속 함수와 똑같이 생겼다.
사례의 코드가 취약점이 발견되어 수정이 되기 전인 것을 감안하면 굉장히 디컴파일이 잘된 모습을 볼 수 있다. (꼬아놓지 않아서 다행이다…)
