The first step is alwaysdetermine the stage and module type:
SEC → PEI → DXE → BDS → OS / Runtime(+ SMM 独立一路)
Within the same firmware, modules from different stages run incompletely different execution environments: different available memory, different callable services (PPI / Protocol / Runtime Services), different lifecycles (DXE_DRIVER and DXE_RUNTIME_DRIVER are not the same thing).
So the order is:first determine the stage and type, then discuss the logic—if the type is determined incorrectly, all subsequent reasoning about "why it is written this way" will be wrong. See [[pi-stages]] for details. </CORE RULE>
Stages and module types (classify first)
| Type | Stage | Service form / lifecycle |
|---|---|---|
| SEC | Earliest | Security and initialization entry point, no general memory environment |
| PEIM | PEI | PPI; one of PEI's main tasks is to establish permanent memory |
| DXE driver | DXE | Protocol; exists only in the boot services environment |
| DXE runtime driver | DXE + Runtime | Cross-lifecycle:ExitBootServices() still exists after,SetVirtualAddressMap() is relocated at |
| UEFI driver / application | After DXE/BDS | Drivers/applications running in the UEFI environment; not the same thing as firmware built-in modules |
| SMM/MM driver | Independent | Executes within SMM; privilege level and lifecycle form their own separate system |
Three high-frequency criteria (for details and the decision tree, see [[pi-stages]]):
- Read a series of structures with no producer xref → first think HOB(one-way handoff from PEI to DXE, read-only on the DXE side)
LocateProtocol(&guid)→ do GUID dictionary parsing, restore toEFI_XXX_PROTOCOL_GUIDand interface vtable;the protocol graph is more meaningful than the direct call graph- Firmware address ≠ OS runtime address → it is not necessarily a hook or a relocation error; first confirm which stage's mapping is being compared
When to use / when not to use
- Prerequisite: you have a firmware image but are not sure whether it is a UEFI/PI structure or an embedded image → the firmware section of [[re-analyze/system-fingerprints]] (FV → FFS → section hierarchy)
- Use: the firmware image contains EFI structures (Firmware Volume / FFS files / DXE drivers)—BIOS update packages (.fd/.rom/.bin), UEFI drivers, EFI applications
- Use: bootkit localization (SMM handlers, timer callbacks, boot path hooks) and verification
- Use: Secure Boot / boot chain related analysis (signatures, certificates, NVRAM variables)—requires legal authorization, see pitfall 4
- Do not use: the firmware is a traditional Linux embedded rootfs (go through [[re-fw-extract]] / [[re-fw-rootfs]]; the [[re-firmware]] gateway determines first)
- Do not use: Legacy BIOS Option ROM / non-EFI legacy firmware (binwalk goes through [[re-fw-extract]])
- Do not use: you only need to unpack the whole thing and look at the contents (first do a preliminary check with [[re-fw-extract]], and once confirmed to be a UEFI structure, enter this skill)
- Note: static analysis comes first (the principle for large firmware); emulating a bootkit = running malicious code, sandbox by default (the highest principle in [[re-analyze/platform-tips]])
Tool preparation
Verify all tools before use. Firmware image parsing/module static analysis may skip the sandbox; OVMF emulation (step 5) is dynamic execution, sandboxed by default + network isolation ([[re-analyze/platform-tips]] highest principle).
UEFITool / UEFIExtract (mainstay for firmware parsing)
- Debian/Ubuntu:
apt install uefitool uefitool-cli(official package: GUI + CLI; CLI includes UEFIExtract/UEFIFind) - Arch: AUR
yay -S uefitool(oruefitool-git; unofficial repository) - Fedora: no official package → Linux x64 binary from GitHub releases (https://github.com/LongSoft/UEFITool/releases)或源码构建(qmake + Qt6)
- macOS: universal macOS binary from GitHub releases (same page)
- Windows: win64 binary from GitHub releases (or
choco install uefitoolif the mirror is available) - Verify:
uefiextract --helpoutputs usage; GUIuefitoolcan open the firmware image
UEFIFind (locate files by pattern string, ships with uefitool-cli)
- The NE version only has positional-argument syntax:
uefifind imagefile {header|body|all} {list|count} pattern, no -g/-t options - To locate by GUID, use UEFIExtract's GUID argument mode instead:
uefiextract fw.bin <GUID>(see below) - Installation same as above (included in Debian/Ubuntu's uefitool-cli)
- Verify:
uefifind --help
ghidra / ida (module decompilation; see [[re-ghidra]] / [[re-ida]] for installation)
- .efi modules are PE/COFF (subsystem EFI_BOOT_SERVICE_DRIVER / EFI_APPLICATION), both can import them directly
- EfiRom plugin (optional): EDK2 tool, used for converting between ROM files and .efi / querying size
- Verify: importing the extracted .efi automatically identifies the subsystem as EFI boot service driver
ovmf —— OVMF (Open Virtual Machine Firmware, UEFI emulation firmware)
- Debian/Ubuntu:
apt install ovmf(/usr/share/OVMF/OVMF_CODE.fd、OVMF_VARS.fd) - Fedora/RHEL:
dnf install edk2-ovmf(path /usr/share/edk2/ovmf/OVMF_CODE.fd—— the old /usr/share/OVMF layout is deprecated) - Arch:
pacman -S edk2-ovmf(install only /usr/share/edk2/x64/OVMF_CODE.4m.fd, 4m format) - macOS:
brew install qemuships with firmware ($(brew --prefix)/share/qemu/edk2-x86_64-code.fd, with the qemu package) - Windows: bundled with the QEMU installer (edk2-x86_64-code.fd under share/qemu/)
- Verify: look up the corresponding path by distribution —— Debian/Ubuntu
ls /usr/share/OVMF/OVMF_CODE.fd;Fedora/RHELls /usr/share/edk2/ovmf/OVMF_CODE.fd;Archls /usr/share/edk2/x64/OVMF_CODE.4m.fd; on macOS use the brew prefix path
qemu-system-x86_64 (OVMF runtime host)
- Debian/Ubuntu:
apt install qemu-system-x86 - Fedora:
dnf install qemu-system-x86-core(full virtualization group isdnf install @virtualization) - Arch:
pacman -S qemu-system-x86 - macOS:
brew install qemu - Windows: qemu.weilnetz.de installer
- Verify:
qemu-system-x86_64 --version
Procedure
Execute in order, record evidence for each step (path + sha256, see [[re-triage]]). Do this first for initial reconnaissance:file fw.bin + entropy to confirm whether it is a UEFI structure ([[re-triage]]).
-
Firmware image parsing (Firmware Volume → File → Section):
file fw.bin uefiextract fw.bin all # 全量递归解包(含嵌套 FV 与压缩节,见坑 1/2) ls fw.bin.dump/ # 树:Volume0/ → File0/ → Section0/ → PE32 等- GUI: open fw.bin in UEFITool, the left tree goes top-down FV → FFS File → Section; first count the number of FVs and their types —— the main DXE FV is usually the largest, containing hundreds or thousands of modules; the PEI FV and SEC Core are at the very front
- First confirm how many layers of FV are in the image (nested/recovery volumes), don't just stare at the first layer (pitfall 1)
-
DXE driver extraction:
ueifind fw.bin all list <GUID> # 按模式串(含 GUID 文本)定位文件——NE 版仅位置参数语法,无 -g/-t # 按 GUID 直接提取指定文件(UEFIExtract 的 GUID 参数模式): # -m 合法值: all|body|header|info|file;-t 是十六进制节类型(PE32=0x10,即 -t 10) uefiextract fw.bin <GUID> -o driver.efi -m body -t 10 file driver.efi # 期望输出: PE32+ executable (EFI boot service driver)- UEFITool GUI: right-click File → Extract body, export the "raw PE" (strips FFS/section headers, see pitfall 3)
- Hash the extracted artifact first with
sha256sumfor evidence ([[re-triage]]), then take it into the decompiler
-
Module analysis (entry / Protocol services):
- Open the .efi with [[re-ghidra]] / [[re-ida]]; the entry = image entry (DriverEntry), which is the starting point for understanding the driver
- Protocol services are UEFI's "API":
gBS->LocateProtocol(&guid,...)(Get Service),gBS->InstallProtocolInterface(...)(Register Service),gBS->CreateEvent(...)(Event/Callback),gST->ConOut(Console Output) - GUID is the "symbol"—recognizing the GUID of LocateProtocol is equivalent to recognizing the API call; use UEFITool's built-in GUID database (the protocol name is shown next to the GUID in the UI) or search the UEFI specification/UEFI GUID table
strings driver.efi | grep -iE 'protocol|runtime|efi_' | head -30- Record: entry function, which Protocols were installed/obtained, whether event callbacks were registered (connect to step 4)
-
bootkit localization (SMM / timer callback / boot path hooking):
- Timer callback:
gBS->CreateEvent(EVT_TIMER, TPL, Callback, ...)+gBS->SetTimer(..., PERIODIC/ONESHOT, ...)→ the callback function is where the malicious logic is executed periodically (the most common hook point) - SMM:
SmiHandlerRegister/ SMST service → SMM driver registers an SMI handler (code executed within SMM, a persistence layer running with higher privilege than kernel ring0, inside SMRAM) - Boot path hooking: tampering at the BDS stage (NVRAM variables BootOrder/BootNext, replacing EFI boot entries, hooking the boot manager/PXE path); S3 resume path (AP wake-up code)
- Runtime Services hook: intercept/replace
gRT->SetVariable/GetVariableand other Runtime Services pointers - Identifying characteristics: the driver does not unload, carries a custom GUID, DXE_DEPEX depends on other launcher modules, paths/keys/URLs appear in strings; after confirming the suspicious module's GUID and dependencies, proceed to step 5
- Timer callback:
-
OVMF emulation verification(running malicious code—inside a sandbox, network
-net none):# 只读代码固件 + 可写变量固件(两片 pflash 标准做法);OVMF_CODE.fd 路径按发行版: # Debian/Ubuntu: /usr/share/OVMF/OVMF_CODE.fd # Fedora/RHEL: /usr/share/edk2/ovmf/OVMF_CODE.fd(旧 /usr/share/OVMF 布局已废弃) # Arch: /usr/share/edk2/x64/OVMF_CODE.4m.fd(4m 格式) qemu-system-x86_64 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \ -drive if=pflash,format=raw,file=OVMF_VARS.fd \ -m 1024 -net none -boot menu=on- Verify that the firmware can boot to the UEFI Shell/system → then hook the extracted driver/firmware modifications (UEFITool insertion or rebuilding the firmware image) into the same OVMF to observe behavioral differences
- When there is no real firmware, OVMF is the baseline environment (no Secure Boot signature constraints, see pitfall 4); for emulation details and user-space alternatives see [[re-fw-emulate]]
- Dynamic behavior confirmation (callback triggered/variable tampering) → sandbox + snapshot ([[re-sandbox]]), behavior analysis goes to [[re-malware]]; preserve evidence for each step's artifacts
Branches (references)
- [[pi-stages]] —— PI stage model: identification of SEC/PEI/DXE/BDS/Runtime/SMM and differences in execution environment;HOB(PHIT at the beginning, PEI one-way production, DXE read-only,
Build*Hobasserts in DXE);GUID dictionary parsing(Protocol/PPI/FFS/HOB/variable namespace/configuration table);FV → FFS → section → PE layers (do not carve directly by PE magic number);DXE_DRIVERvsDXE_RUNTIME_DRIVER(ExitBootServicesafter, all boot services are unavailable,EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGEnotification functions are prohibited from calling any service,ConvertPointer、EFI_RUNTIME_ARCH_PROTOCOLtheVirtualMode/AtRuntimecriterion)
Cross-domain federation
- [[re-firmware]]: this skill is referenced by the re-firmware gateway—UEFI firmware branch; the overall process is still orchestrated by the gateway as extract→rootfs→emulate
- [[re-fw-emulate]]: the OVMF/QEMU full-system emulation in step 5 is this skill's UEFI concretization of [[re-fw-emulate]]
- [[re-binary-core]]: static decompilation of DXE drivers ([[re-ghidra]] / [[re-ida]] / [[re-radare2]]), import table [[re-imports]]
- [[re-triage]]: initial survey (hash / architecture / entropy), preserve evidence for each layer's artifacts
- [[re-malware]]: bootkit dynamic verification goes into the sandbox ([[re-sandbox]]), malicious behavior analysis goes to [[re-malware]]
- Non-EFI firmware (legacy BIOS / embedded images) → [[re-fw-extract]] (binwalk preliminary assessment)
- [[re-analyze/platform-tips]] Related branches: static-first (large firmware samples), dynamic default sandbox (network -net none when emulating bootkits), Linux/Windows platform branches
Common pitfalls and traps
- Firmware images with multiple nested FVs: Symptom—UEFITool shows only a few files/modules when opened, and the driver you are looking for cannot be found; Cause—modern firmware encapsulates FVs in nested form (PEI FV embedding DXE FV, recovery volume/Capsule, FV embedded in ACPI tables), so analyzing only the outer layer naturally misses components; Countermeasure—use
uefiextract fw.bin allto fully recursively expand (automatically handling nesting), or expand sub-FVs layer by layer in the GUI; confirm the image structure before analyzing - Compressed/padding regions obscuring content: Symptom—FFS file sections appear as compressed data or FREE_SPACE/FIXED padding, and PE32 code cannot be seen; Cause—vendors compress the entire DXE volume (EFI/LZMA compressed section), and the empty regions are normal layout; Countermeasure—UEFITool/UEFIExtract will automatically decompress and show the decompressed Section (use it to view, not binwalk's raw bytes); "no code found" in binwalk results does not mean there is none, go back to UEFITool to confirm
- PE header offset within FFS: Symptom—the "file" extracted from firmware fails to open directly with file/IDA or decompiles into gibberish; Cause—the FFS File Header (24/32 bytes) + Section Header + alignment padding come before the PE32 body, so the PE header is not at file offset 0; Countermeasure—use UEFITool right-click Extract body /
uefiextract fw.bin <GUID> -m bodyto export the bare PE, and after exporting usefileto confirm the output contains "PE32+ executable (EFI boot service driver)" before entering the decompiler; manual extraction requires calculating offsets according to the Section layout - Secure Boot signature verification bypass analysis requires legal authorization: Symptom—wanting to "bypass Secure Boot"/"re-sign firmware" for experiments, and problems arise when touching real signed firmware; Cause—signature bypass, firmware key/certificate extraction, and validating bootkits on real devices all involve legal and authorization boundaries; Countermeasure—by default, analyze and validate behavior in OVMF development firmware (without signing constraints); before working on real firmware, confirm the scope of authorization (owned devices, vulnerability research authorization), and do not touch anything unauthorized; state the boundaries clearly in the report (same type of expression as the authorization boundary in [[re-hardware-io]])
- Treating cross-stage data as ordinary global variables whose producer is missing: Symptom—a chain of structures read by a function has no visible construction point, leading to suspicion that it was trimmed or the code is incomplete; Cause—it may be HOB(a one-way handoff from PEI to DXE, read-only on the DXE side, and the construction call also asserts); Countermeasure—first parse according to the HOB list, and establish the correspondence of "which stage produces it, who consumes it" ([[pi-stages]])
- Treating GUIDs as semantically meaningless UUIDs: Symptom—a large number of GUID constants cannot be explained, and analysis stops at "a constant was passed in"; Cause—a GUID may be any one of a Protocol/PPI/FFS file/HOB/variable namespace/configuration table; Countermeasure—build a GUID→symbol dictionary, restore calls like
LocateProtocolinto semantic names and interface vtables, anduse a protocol graph to supplement the call graph - Not distinguishing between
DXE_DRIVERandDXE_RUNTIME_DRIVER: Symptom—comparing addresses in firmware with addresses captured during the OS runtime stage and concluding it is hooked; Cause—runtime drivers still exist afterExitBootServices()and will be relocated according to the mapping ofSetVirtualAddressMap(), so firmware addresses are inherently not equal to runtime addresses; Countermeasure—first determine the module type and lifecycle stage (EFI_RUNTIME_ARCH_PROTOCOL'sVirtualMode/AtRuntimeis an off-the-shelf criterion), then discuss consistency ([[pi-stages]])