방망이
[프로젝트]UEFI DXE 바이너리 취약점 분석기 16주차 - 스크립트 업데이트를 위하여.. 본문
왜 업그레이드가 필요한가
기존에 작성한 DepexHunter 스크립트는 UEFI 펌웨어 이미지를 UEFIExtract로 풀어놓은 덤프 폴더를 순회하면서, 각 DXE 드라이버의 DEPEX(Dependency Expression) 섹션을 파싱해 "이 드라이버가 요구하는 프로토콜이 이미지 안의 어떤 드라이버로도 제공되지 않는" 경우를 찾아내는 스크립트다. 이 스크립트는 GhidraScript를 상속하고 있음에도 정작 Ghidra의 핵심 기능인 디스어셈블/디컴파일/P-code 분석은 전혀 쓰지 않는다. 실제 동작은 info.txt와 body.bin을 텍스트·바이너리 레벨에서 직접 파싱하는 파일 시스템 순회에 가깝다.
프로토콜을 "누가 제공하는가"를 판단하는 로직도 마찬가지로 코드 분석이 아니라 이름 매칭에 의존한다. 1단계로 약 200개 프로토콜을 하드코딩해둔 switch-case 테이블(knownProtocolProvider)을 먼저 확인하고, 여기서 못 찾으면 2단계로 프로토콜 이름에서 "Protocol", "Guid", "Efi" 같은 접두·접미사를 제거한 뒤 그 문자열이 드라이버 이름에 부분 문자열로 포함되는지를 본다. 이 방식은 두 방향으로 오류가 난다. 이름이 우연히 겹치면 실제로는 관련 없는 드라이버를 provider로 오인하고, 이름이 표준 명명 규칙을 따르지 않는 벤더 커스텀 드라이버라면 실제로 프로토콜을 제공하고 있어도 놓친다. 하드코딩 테이블도 EDK2 표준 프로토콜에만 대응하기 때문에 새로운 펌웨어나 벤더 특화 프로토콜이 나올 때마다 사람이 계속 추가해줘야 하는 구조다.
이걸 근본적으로 고치려면, "이름이 비슷한가"가 아니라 "그 드라이버의 코드가 실제로 해당 GUID로 프로토콜을 install하는가"를 코드 레벨에서 직접 확인해야 한다. (이게 P-code를 공부하는 이유)
필요한 개념 1: 간접 호출 해석 (EFI_BOOT_SERVICES 구조체)
UEFI 드라이버 코드에서 프로토콜을 등록하는 호출은 대부분
gBS->InstallProtocolInterface(...)
gBS->InstallMultipleProtocolInterfaces(...)
위 두 형태로, EFI_BOOT_SERVICES 구조체 포인터를 통한 간접 호출로 나타난다. 어셈블리 레벨에서는 이게 CALLIND(간접 호출) PcodeOp으로 표현되고, 타겟 주소는 특정 함수 심볼이 아니라 어떤 레지스터나 메모리 위치에서 LOAD된 값이다.
이걸 식별하려면 CALLIND의 타겟 Varnode를 역추적해서, 그것이 gBS 전역 포인터에서 구조체 필드 오프셋만큼 떨어진 위치를 LOAD한 결과인지 확인해야 한다. EFI_BOOT_SERVICES 구조체에서 InstallProtocolInterface는 정해진 오프셋에 위치하므로, Ghidra에 이 구조체 타입을 데이터 타입으로 씌워두면 오프셋 기반 필드 매칭이 훨씬 수월해진다. 이 부분을 중점으로 앞으로 공부하지 않을까 싶다.
필요한 개념 2: CALL 인자 추적 (Use-Def 체인)
호출 대상이 InstallProtocolInterface라는 것을 확인했다면, 다음은 그 두 번째 인자인 GUID 포인터를 추적할 차례다. 이 인자로 쓰인 Varnode에서 getDef()를 통해 정의 지점을 거슬러 올라가면, 결국 .data 섹션의 어떤 고정 주소(보통 gEfiXxxProtocolGuid 심볼)를 가리키는 CONST Varnode에 도달하게 된다 "이 인자가 어떤 고정된 상수인가"를 확인하는 것이므로 Use-Def체인을 사용한다.
필요한 개념 3: GUID 상수 추출
CONST Varnode로 가리켜지는 주소를 찾았다면, 그 주소에서 16바이트를 읽어 실제 GUID 값을 얻어야 한다. 이 값을 기존에 갖고 있는 GUID 데이터베이스(guids.csv)와 대조하면, 이름 문자열이 아니라 실제 바이트 값으로 어떤 프로토콜인지 확정할 수 있다. 이렇게 하면 기존에 똥멍청이스크립트에서 사용하던 하드코딩된 switch-case 테이블도, 이름 유사도 기반 substring 매칭도 모두 필요 없어진다. 드라이버 이름이 표준 명명 규칙을 따르든 따르지 않든, 코드가 실제로 그 GUID를 install하기만 하면 잡아낼 수 있다.
학습 순서
우선 EFI_BOOT_SERVICES 구조체를 Ghidra 데이터 타입으로 적용하고 CALLIND의 타겟을 오프셋 기반으로 식별하는 연습부터 시작하는 게 좋을 것 같다. 이게 되면 InstallProtocolInterface 호출 지점을 코드에서 자동으로 찾아낼 수 있는 상태가 된다고 한다(클로드가). 그다음은 CALL 인자 추적으로 GUID 포인터의 Use-Def 체인을 따라가 실제 상수 주소를 특정하고, 마지막으로 그 주소에서 GUID 바이트를 읽어 기존 GUID DB와 매칭하는 순서로 진행하면 좋을 것 같다.
이 세 단계가 완성되면 provider 추정 로직 전체를 이름 기반 휴리스틱에서 코드 기반 검증으로 교체할 수 있고, 이후 알려진 CVE가 있는 실제 펌웨어 바이너리에 적용해보면서 하드코딩 테이블 없이도 정확히 동작하는지 검증하는 단계로 넘어갈 수 있다.
'2026_졸업프로젝트' 카테고리의 다른 글
| [프로젝트]UEFI DXE 바이너리 취약점 분석기 15주차 - P-code Phase3 (0) | 2026.07.02 |
|---|---|
| [프로젝트]UEFI DXE 바이너리 취약점 분석기 14주차 - P-code Phase2 (0) | 2026.05.31 |
| [프로젝트]UEFI DXE 바이너리 취약점 분석기 13주차 - P-code Phase1 (0) | 2026.05.12 |
| [프로젝트]UEFI DXE 바이너리 취약점 분석기 12주차 - P-code 이론 (0) | 2026.05.04 |
| [프로젝트]UEFI DXE 바이너리 취약점 분석기 11주차 - Ghidra Script 수정 및 검증 (0) | 2026.04.02 |
