edk2 + OVMF boot and runtime hook toolkit
- C 79.3%
- Shell 19.1%
- Makefile 1.6%
| edk2/NineLivesPkg | ||
| runtime/linux | ||
| scripts | ||
| tools | ||
| .gitignore | ||
| LICENSE | ||
| Makefile | ||
| README.txt | ||
_ _ _ _ _ _
| \ | (_)_ __ (_) ___| (_)_ _____ ___
| \| | | '_ \| |/ _ \ | \ \ / / _ \/ __|
| |\ | | | | | | __/ | |\ V / __/\__ \
|_| \_|_|_| |_|_|\___|_|_| \_/ \___||___/
boot and runtime control flow interception
for edk2 firmware
ninelives is a small research framework for establishing and measuring
control flow interception primitives inside EDK II firmware. it starts at
DXE, survives ExitBootServices and SetVirtualAddressMap and keeps a piece
of itself alive into the running OS without writing to disk
the point is not the payload. the point is measuring which interception
primitive survives which phase and how many independent indicators each
one leaves behind. every primitive is selectable, the hooks can be removed,
and the whole thing is built against edk2-stable201908
<================================================================================>
what it does
five primitives, one bit each:
P0 table replace the LoadImage pointer in the boot services table
P1 inline patch the LoadImage prologue with a relocated trampoline
P2 window hook StartImage and ExitBootServices
P3 runtime hook GetVariable and SetVariable, survive into the OS
P4 persist keep the selected mask in NVRAM across power cycles
the default mask is 0x1d, which is table + window + runtime + persist.
inline is opt in because it is the only primitive that touches code bytes
P0 and P1 both target LoadImage. if both bits are set P1 is used and the
loader falls back to P0 when the prologue cannot be relocated
<================================================================================>
detection
nlscan reads a physical memory image, finds the EFI system table signature,
prints the boot and runtime service entry points, and diffs them against a
golden capture:
scripts/golden.sh
tools/nlscan out/matrix/p0_table/dump.bin --base 0x70000000 \
--golden out/golden/dump.bin
a clean run reports indicator_count 0. a run with the default mask reports
five moved entry points:
load_image, start_image, exit_boot_services, get_variable, set_variable
the inline primitive moves none of those and is only visible by comparing the
prologue bytes of the original function, which nlscan does as a separate
indicator
<================================================================================>
runtime observer
the state variable is readable from Linux through efivarfs:
tools/nlcfg get
tools/nlcfg set 0x09
kernel module:
make -C runtime/linux
insmod runtime/linux/ninelives_rt.ko
cat /proc/ninelives
<================================================================================>
references
[1] UEFI specification 2.7 and 2.8, runtime services and SetVirtualAddressMap
[2] PI specification 1.6, DXE dispatch
[3] edk2-stable201908, MdeModulePkg/Core/Dxe/Image/Image.c
[4] edk2-stable201908, MdeModulePkg/Core/RuntimeDxe
[5] CHIPSEC, platform security assessment framework
[6] LoJax, ESET, 2018
[7] Beyond BIOS, second edition
<================================================================================>
credits
built on edk2 and OVMF. thanks to the people who publish bootchain research
and keep the tools open :)