5. 동적 분석 방법

이번에는 UEFI를 동적으로 분석하는 방법을 알아보겠다. 먼저 알아두어야 할 것은 Flash Chip에서 덤프한 UEFI를 전체적으로 한번에 실행시키는 것은 불가능하다는 것이다. 펌웨어 이미지 내부에 존재하는 모듈들은 실제 존재하는 하드웨어 장비와의 상호작용이 필요하기 때문에 의존성 문제가 존재하기 때문이다. 고로 우리는 각각의 모듈에 대해 동적 분석하는 방법을 찾아보았고 다음과 같은 동적 분석 방법들을 알게 되었다.

qiling은 Unicorn Engine기반으로 동작하는 바이너리 에뮬레이션 프레임워크로, 다양한 운영체제 뿐만 아니라 UEFI 에뮬레이팅까지 지원한다. 또한 Unicorn 기반이기 때문에 각종 레지스터 변경이나 함수 후킹등의 작업을 수행할 수 있는 장점이 있다. 또한 디버깅까지 할 수 있다.

다음은 간단하게 efi모듈을 실행시키는 qiling코드이다.

import pickle
from qiling import *

with open("nvram.pickle", 'rb') as f:
    env = pickle.load(f)

ql = Qiling(["0D24_body.efi"], ".", env=env)
ql.run()
[=]     Running from 0x00101000 of 0D24_body.efi
[=]     LocateProtocol(Protocol = 57b153c5-5b40-46bd-a9e2-492b5857b994, Registration = NULL, Interface = 0x0507ffa0) = 0x800000000000000e
[=]     GetVariable(VariableName = L"DptfOptions", VendorGuid = fb3b9ece-4aba-4933-b49d-b4d67d892351, Attributes = 0x0507ffa8, DataSize = 0x0507ffb0, Data = 0x0507ff58) = 0x8000000000000005
[=]     GetVariable(VariableName = L"DptfOptions", VendorGuid = fb3b9ece-4aba-4933-b49d-b4d67d892351, Attributes = 0x0507ffa8, DataSize = 0x0507ffb0, Data = 0x0507ff58) = 0x0
[=]     LocateProtocol(Protocol = EfiGlobalNvsAreaProtocolGuid, Registration = NULL, Interface = 0x0507ff28) = 0x800000000000000e

코드를 실행한다면 이렇게 모듈이 에뮬레이팅이 되는 것을 알 수 있다.

ql.debugger = True

위 코드만 추가해주면 디버깅도 가능하다.

[=]     Running from 0x00101000 of 0D24_body.efi
[=]     gdb> stopped at entry point: 0x101000
[=]     gdb> listening on 127.0.0.1:9999

자동으로 Entry Point에 멈추고 기본 디버깅 포트를 열어 디버깅이 가능하게 해준다.

디버깅은 따로 설명하지 않겠다.

더 많은 것을 알아보고 싶으면 qiling의 github에 방문해보는 것을 추천한다.

https://github.com/qilingframework/qiling

qemu + gdb

qemu에 OVMF라는 가상머신 전용 UEFI를 실행시켜서 efi를 실행시킬 수 있는 efi-shell 환경을 구축할 수 있다. 하지만 모든 모듈이 실행되는 것은 아니다. 의존성이 보장되지 않으면 실행되지 않는 모듈도 존재하기 때문이다.

qemu-system-x86_64 -s -pflash OVMF.fd -hda fat:rw:hda-contents\\
   -net none -debugcon file:debug.log -global isa-debugcon.iobase=0x403

위는 qemu를 실행시키는 스크립트이며, -s옵션을 통해 기본 디버깅 포트(1234)로 디버거를 attach가능하다.

efi-fuzz는 brick을 만든 Sentinel-One에서 만든 efi 전용 NVRAM 퍼저이다. 이 퍼저는 위의 qiling을 기반으로 동작하고 unicornafl을 통한 퍼징을 수행한다.

usage: efi_fuzz.py [-h] [-c COVERAGE_FILE] [-f {crash,stop,ignore,break}] [-e END] [-t TIMEOUT]
                   [-o {trace,disasm,debug,dump,off}]
                   [-s {memory,smm_callout,uninitialized,smm} [{memory,smm_callout,uninitialized,smm} ...]]
                   [-j JSON_CONF] [-v NVRAM_FILE] [-r ROM_FILE]
                   [-x EXTRA_MODULES [EXTRA_MODULES ...]]
                   {run,fuzz} target {nvram} ...

positional arguments:
  {run,fuzz}            What should I do?
  target                Path to the target binary to fuzz
  {nvram}               Fuzzing modes
    nvram               Fuzz contents of NVRAM variables

optional arguments:
  -h, --help            show this help message and exit
  -c COVERAGE_FILE, --coverage-file COVERAGE_FILE
                        Path to code coverage file
  -f {crash,stop,ignore,break}, --fault-handler {crash,stop,ignore,break}
                        What to do when encountering a fault?
  -e END, --end END     End address for emulation
  -t TIMEOUT, --timeout TIMEOUT
                        Emulation timeout in ms
  -o {trace,disasm,debug,dump,off}, --output {trace,disasm,debug,dump,off}
                        Trace execution for debugging purposes
  -s {memory,smm_callout,uninitialized,smm} [{memory,smm_callout,uninitialized,smm} ...], --sanitize {memory,smm_callout,uninitialized,smm} [{memory,smm_callout,uninitialized,smm} ...]
                        Enable memory sanitizer
  -j JSON_CONF, --json-conf JSON_CONF
                        Specify a JSON file to further customize the environment
  -v NVRAM_FILE, --nvram-file NVRAM_FILE
                        Pickled dictionary containing the NVRAM environment variables
  -r ROM_FILE, --rom-file ROM_FILE
                        Path to the UEFI ROM file
  -x EXTRA_MODULES [EXTRA_MODULES ...], --extra-modules EXTRA_MODULES [EXTRA_MODULES ...]
                        Extra modules to load

대표적으로 탐색할 수 있는 것은 GetVariable을 통한 overflow취약점이다. NVRAM변수값을 퍼징하기 때문에 GetVariable을 통해 퍼징된 NVRAM변수값이 들어와서 프로그램이 오동작하면 crash로 판별한다.

위 사진은 프로젝트를 진행하며 진행한 1-Day 취약점 재현에 대한 Crash 발생 사진이다.

afl-fuzz -m none -i afl_inputs/CoolControlData/ -o afl_outputs/ -U -- python3 efi_fuzz.py fuzz _0D24_body.efi nvram CoolControlData @@ 2> log

chipsec은 Intel사에서 쓰이던 내부 툴이었지만 오픈소스로 릴리즈되면서 누구나 다운로드하여 모든 UEFI기반 시스템에서 실행 가능하다. chipsec은 하드웨어, 시스템 펌웨어(BIOS/UEFI) 등 PC 플랫폼의 보안을 분석하기 위한 프레임워크이다.

여기에는 보안 테스트 도구, 저수준 인터페이스에 엑세스하기 위한 도구들이 포함되어 있어 자신이 분석하고 싶은 하드웨어를 실제로 가지고 있다면 매우 유용한 분석 도구이다.

설치 과정이 복잡하고 내장된 기능이 많아서 일일이 설명할 수 없다.

더 많은 정보를 원하면https://chipsec.github.io/에 들어가보길 바란다.

Last updated