hardware firmware security

Hunting Bootkits in the EFI System Partition

Baseline the EFI System Partition and hunt malicious EFI binaries (ESPecter, BlackLotus, Bootkitty, Glupteba) by mounting the ESP, hashing and verifying boot loaders, scanning with YARA, and detecting anomalous non-EFI files.

bootkitefi-system-partitionfirmware-forensicsmeasured-bootsecure-bootthreat-huntinguefiyara
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

The EFI System Partition (ESP) is a small FAT32-formatted partition that the platform firmware reads at power-on to locate and execute the operating system's boot loader. Because it executes before the operating system, kernel, and any EDR agent, the ESP is one of the most coveted persistence locations for advanced adversaries. UEFI bootkits that live on the ESP survive OS reinstallation, disk reformatting of the OS partition, and most endpoint defenses.

This skill follows the detection research published by Eclypsium ("Enhanced Threat Detection: Bootloaders, Bootkits, and Secure Boot", and the Bootkitty and Glupteba analyses) and aligns with the ESP-hunting methodology Rapid7 documented for Velociraptor (Windows.Forensics.UEFI, Windows.Detection.Yara.UEFI). The threat context is concrete and current:

  • ESPecter (ESET, 2021) — a UEFI bootkit that persists on the ESP as a patched Windows Boot Manager (bootmgfw.efi) plus malicious kernel-mode drivers. It marked the move of UEFI threats from SPI flash to the ESP, where they are far easier to deploy.
  • BlackLotus (2023) — the first in-the-wild UEFI bootkit able to bypass Secure Boot on fully patched Windows 11 by exploiting CVE-2022-21894 ("baton drop") and staging a vulnerable signed bootmgfw.efi on the ESP.
  • Bootkitty (2024) — the first UEFI bootkit targeting Linux, dropped onto the ESP.
  • Glupteba — commodity malware whose UEFI variant replaces software on the EFI partition.

The core detection insight from Eclypsium and Rapid7 is that the bootloader normally changes only during a vendor or OS update; an out-of-band change to ESP binaries, an unsigned or untrusted-signed boot loader, or any file in the ESP root that is not under the EFI/ directory is a high-fidelity indicator of compromise. This skill builds that baseline and hunts deviations from it.

When to Use

  • During proactive threat hunts for firmware/bootkit persistence (MITRE ATT&CK T1542.003 — Pre-OS Boot: Bootkit)
  • After an incident where an adversary achieved SYSTEM/root and may have established below-OS persistence
  • When validating Secure Boot posture across a fleet and confirming boot-chain integrity
  • As a periodic integrity check, comparing the current ESP contents against a trusted golden baseline
  • When triaging hosts that show measured-boot (TPM PCR) mismatches

Prerequisites

  • Administrative/root access to the target host (mounting and reading the ESP requires elevation)
  • Linux analysis tooling — install on Debian/Ubuntu:
    sudo apt-get update
    sudo apt-get install -y sbsigntool pesign efitools efibootmgr binwalk yara
    pip install pefile
  • UEFITool for inspecting firmware/binaries (download from https://github.com/LongSoft/UEFITool/releases)
  • A trusted golden baseline of EFI binary hashes for the OS/vendor versions in scope (build it once on a known-clean, freshly imaged host)
  • For Windows targets: WinPE or a forensic boot environment, or Velociraptor with the Windows.Forensics.UEFI artifact

Objectives

  • Mount and enumerate the ESP read-only without altering evidence
  • Inventory every EFI binary and compute cryptographic hashes
  • Verify Secure Boot signatures on each boot loader against trusted keys
  • Compare contents against a golden baseline to surface out-of-band changes
  • YARA-scan ESP binaries for known bootkit signatures
  • Flag anomalies: files outside EFI/, unsigned loaders, modified bootmgfw.efi/grubx64.efi, and tampered boot entries
  • Confirm measured-boot (TPM PCR 0/2/4) integrity where TPM is present

MITRE ATT&CK Mapping

ID Name Relevance
T1542.003 Pre-OS Boot: Bootkit Adversaries place malicious boot loaders on the ESP to execute before the OS and EDR, achieving stealthy, resilient persistence. This skill hunts exactly that artifact.

Workflow

Step 1: Identify and mount the ESP read-only

The ESP is a FAT32 partition, usually flagged EF00 (GPT) or esp,boot. Locate it and mount read-only to preserve evidence.

# Identify the ESP (type code EF00 / "EFI System")
sudo lsblk -o NAME,FSTYPE,PARTTYPENAME,MOUNTPOINT
sudo fdisk -l | grep -i "EFI System"
 
# Mount the ESP read-only (replace /dev/sda1 with the identified partition)
sudo mkdir -p /mnt/esp
sudo mount -o ro,umask=077 /dev/sda1 /mnt/esp
 
# Confirm the canonical layout: EFI/ should be the only top-level dir
ls -la /mnt/esp

Step 2: Detect anomalous top-level entries (high-fidelity hunt)

Per Rapid7/Velociraptor guidance, the ESP root should contain only the EFI/ directory (and possibly a vendor System Volume Information). Anything else is suspicious.

# Any path in the ESP root NOT under EFI/ is a red flag
find /mnt/esp -maxdepth 1 -mindepth 1 ! -name 'EFI' ! -iname 'System Volume Information'
 
# Hunt for boot binaries dropped outside expected vendor folders
find /mnt/esp -type f \( -iname '*.efi' -o -iname '*.sys' -o -iname '*.dll' \) -printf '%p\t%s bytes\t%TY-%Tm-%Td\n'

Step 3: Inventory and hash every EFI binary

Compute SHA-256 of all boot binaries for baseline comparison and threat-intel lookup.

# Recursively hash all EFI/PE binaries on the ESP
find /mnt/esp -type f \( -iname '*.efi' -o -iname '*.sys' \) -print0 \
  | xargs -0 sha256sum | tee /tmp/esp_hashes.txt
 
# Quick triage on the primary loaders
sha256sum /mnt/esp/EFI/Microsoft/Boot/bootmgfw.efi 2>/dev/null
sha256sum /mnt/esp/EFI/Boot/bootx64.efi 2>/dev/null
sha256sum /mnt/esp/EFI/*/grubx64.efi /mnt/esp/EFI/*/shimx64.efi 2>/dev/null

Submit unknown hashes to VirusTotal / the LOLDrivers and Binarly catalogs to identify known-vulnerable or malicious loaders (e.g., the BlackLotus-abused bootmgfw.efi builds).

Step 4: Verify Secure Boot signatures on each loader

A legitimate loader is signed by Microsoft UEFI CA (Windows/shim) or the distro vendor. Use sbverify to list/verify signatures and pesign for the certificate chain.

# List embedded signatures on a loader
sbverify --list /mnt/esp/EFI/Microsoft/Boot/bootmgfw.efi
 
# Verify against the platform's db certificate (export it first)
sudo efi-readvar -v db -o /tmp/db.esl   # dump Secure Boot db
sbverify --cert /path/to/MicrosoftUEFICA.pem /mnt/esp/EFI/Boot/bootx64.efi
 
# Inspect the PE certificate chain
pesign -S -i /mnt/esp/EFI/Microsoft/Boot/bootmgfw.efi

An unsigned loader, a loader signed by an unexpected/self-signed certificate, or a known-vulnerable signed binary staged by an attacker (BlackLotus technique) is a confirmed finding.

Step 5: Compare against the golden baseline

Diff the live ESP hash inventory against a trusted baseline captured from a clean image of the same OS/vendor build.

# Sort both inventories on the hash column and diff
awk '{print $1}' /tmp/esp_hashes.txt | sort > /tmp/live.sha256
sort baseline_esp_hashes.sha256 > /tmp/base.sha256
 
# Hashes present live but absent from the baseline = unexpected/new binaries
comm -23 /tmp/live.sha256 /tmp/base.sha256

Because the bootloader changes only during legitimate updates, any new hash that does not correspond to a known patch is an actionable lead.

Step 6: YARA-scan ESP binaries for bootkit signatures

Run YARA rules for known bootkits (ESPecter, BlackLotus, Bootkitty, CosmicStrand) across the ESP — the same approach as Velociraptor's Windows.Detection.Yara.UEFI.

# Scan all ESP files recursively with a bootkit ruleset
yara -r -w bootkit_rules.yar /mnt/esp/
 
# Example: scan only the binaries collected in Step 3
yara -r bootkit_rules.yar /mnt/esp/EFI/

Source rules from the YARA-Rules project, Eclypsium/Binarly publications, and ESET's bootkit reports.

Step 7: Inspect and validate UEFI boot entries

ESPecter-class bootkits manipulate the boot order/entries. Enumerate NVRAM boot variables and confirm each points to an expected, signed loader.

# List boot entries and the current order (Bootkitty/ESPecter tamper here)
sudo efibootmgr -v
 
# Confirm Secure Boot is enabled and look for revoked hashes in dbx
mokutil --sb-state
sudo efi-readvar -v dbx -o /tmp/dbx.esl

A boot entry referencing a file outside \EFI\ or a loader whose hash appears in dbx (revoked) but is still present indicates tampering.

Step 8: Validate measured boot against TPM PCRs (where available)

Measured boot records the boot-chain components into TPM PCRs (notably PCR 0, 2, 4). A bootkit that alters loaders changes these measurements.

# Read PCRs that cover firmware and the boot loader
sudo tpm2_pcrread sha256:0,2,4,7
 
# Parse the TCG event log to see what was measured into each PCR
sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements

Compare measured values against the host's known-good attestation baseline; an unexplained PCR 4 change correlates with a modified boot loader.

Step 9: Document findings and preserve evidence

# Make a forensic image of the ESP for offline analysis before remediation
sudo dd if=/dev/sda1 of=/evidence/esp_$(hostname)_$(date +%F).img bs=4M conv=noerror,sync
sha256sum /evidence/esp_*.img > /evidence/esp_image.sha256
sudo umount /mnt/esp

Record every anomalous binary (path, hash, signature status, baseline diff result, YARA match) in the case file before any cleanup.

Tools and Resources

Tool Purpose Source
sbsigntool (sbverify) List/verify Secure Boot signatures on EFI binaries https://git.kernel.org/pub/scm/linux/kernel/git/jejb/sbsigntools.git
pesign Inspect PE/COFF signatures and certificate chains https://github.com/rhboot/pesign
efitools (efi-readvar) Dump Secure Boot variables (db/dbx/KEK/PK) https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git
efibootmgr / mokutil Enumerate boot entries and Secure Boot state https://github.com/rhboot/efibootmgr
UEFITool Parse and inspect UEFI binaries/firmware https://github.com/LongSoft/UEFITool
YARA Signature-scan ESP binaries for bootkits https://github.com/VirusTotal/yara
tpm2-tools Read PCRs and TCG event log for measured boot https://github.com/tpm2-software/tpm2-tools
Velociraptor Windows.Forensics.UEFI Scale ESP hunting across a fleet https://docs.velociraptor.app/
Eclypsium bootkit research Threat context and detection methodology https://eclypsium.com/blog/threat-detection-bootloaders-bootkits-secureboot/
Rapid7 UEFI hunting blog ESP hunting with Velociraptor artifacts https://www.rapid7.com/blog/post/2024/02/29/how-to-hunt-for-uefi-malware-using-velociraptor/

Known Bootkit Indicators

Bootkit ESP Artifact / Behavior Reference
ESPecter Patched bootmgfw.efi + kernel drivers on ESP ESET WeLiveSecurity 2021
BlackLotus Vulnerable signed bootmgfw.efi staged to bypass Secure Boot (CVE-2022-21894) ESET 2023
Bootkitty Malicious EFI loader on ESP targeting Linux ESET / Eclypsium 2024
Glupteba (UEFI) Replaces software on the EFI partition Eclypsium
CosmicStrand UEFI firmware implant hooking the boot chain Eclypsium / Kaspersky

Validation Criteria

  • ESP located and mounted read-only without modifying evidence
  • Top-level ESP contents enumerated; any non-EFI/ entries flagged
  • All EFI binaries inventoried with SHA-256 hashes
  • Secure Boot signatures verified on every boot loader
  • Live inventory diffed against a trusted golden baseline
  • YARA bootkit ruleset run across the ESP with no unexplained matches
  • Boot entries (efibootmgr) validated; no references outside \EFI\
  • Secure Boot confirmed enabled; no present binary matches a dbx-revoked hash
  • TPM PCR 0/2/4 measurements compared to baseline (where TPM present)
  • Forensic ESP image preserved and all findings documented
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.7 KB

Command Reference — ESP Bootkit Hunting

ESP discovery and mounting

Command Purpose
lsblk -o NAME,FSTYPE,PARTTYPENAME,MOUNTPOINT List partitions with type names to find the ESP
fdisk -l | grep -i "EFI System" Identify the ESP partition device
mount -o ro,umask=077 /dev/sdXN /mnt/esp Mount the ESP read-only (evidence-safe)
umount /mnt/esp Unmount after analysis

sbverify (sbsigntool) — Secure Boot signatures

Command Purpose
sbverify --list <binary.efi> List all embedded signatures on an EFI binary
sbverify --cert <ca.pem> <binary.efi> Verify a binary against a known-good signing certificate
sbverify --detached <sig> <binary.efi> Verify a detached signature

pesign — PE/COFF signature inspection

Command Purpose
pesign -S -i <binary.efi> Show signatures / certificate chain on a PE binary
pesign -h -i <binary.efi> Print the PE authenticode hash

efitools / efibootmgr / mokutil — Secure Boot state

Command Purpose
efi-readvar -v db -o db.esl Dump the Secure Boot allow-list (db)
efi-readvar -v dbx -o dbx.esl Dump the Secure Boot revocation list (dbx)
efibootmgr -v List boot entries and boot order with loader paths
mokutil --sb-state Report whether Secure Boot is enabled
mokutil --list-enrolled List enrolled Machine Owner Keys

YARA — bootkit scanning

Command Purpose
yara -r -w <rules.yar> /mnt/esp/ Recursively scan the ESP, suppress warnings
yara -r -s <rules.yar> /mnt/esp/EFI/ Scan with matched-string output

tpm2-tools — measured boot

Command Purpose
tpm2_pcrread sha256:0,2,4,7 Read firmware/boot-loader/Secure-Boot PCRs
tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements Parse the TCG measured-boot event log

Hashing and baseline diff

Command Purpose
find /mnt/esp -type f \( -iname '*.efi' -o -iname '*.sys' \) -print0 | xargs -0 sha256sum Inventory and hash all boot binaries
comm -23 live.sha256 base.sha256 Show hashes present live but absent from baseline
dd if=/dev/sdXN of=esp.img bs=4M conv=noerror,sync Forensically image the ESP

Velociraptor artifacts (fleet hunting)

Artifact Purpose
Windows.Forensics.UEFI Parse the ESP partition table and FAT to enumerate files
Windows.Detection.Yara.UEFI YARA-scan ESP contents at scale
Generic.System.EfiSignatures Collect EFI signature data
Windows.Forensics.UEFI.BootApplication Parse Measured Boot TCG logs
standards.md1.4 KB

Standards Mapping — Hunting Bootkits in the EFI System Partition

MITRE ATT&CK

ID Name Rationale
T1542.003 Pre-OS Boot: Bootkit Adversaries install malicious boot loaders on the ESP to execute before the OS and security tooling; this skill hunts and validates those ESP boot artifacts.

NIST Cybersecurity Framework (CSF 2.0)

ID Name Rationale
DE.CM-01 Networks and network services are monitored to find potentially adverse events (extended here to host boot-chain integrity monitoring) Continuous baselining and integrity monitoring of ESP boot binaries detects out-of-band bootkit persistence.

Supporting References

Scripts 1

agent.py6.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
esp_bootkit_hunter.py — Baseline and hunt malicious EFI binaries on the EFI System Partition.

Mounts (or reads an already-mounted) ESP, inventories EFI/PE boot binaries, computes
SHA-256 hashes, verifies Secure Boot signatures with sbverify, flags files outside the
canonical EFI/ directory, diffs against a golden baseline, and optionally YARA-scans.

Real tooling used: sbverify (sbsigntool), yara, sha256 hashing. No placeholders.

Usage:
  sudo python3 esp_bootkit_hunter.py --esp /mnt/esp --baseline baseline.sha256
  sudo python3 esp_bootkit_hunter.py --device /dev/sda1 --mount --yara bootkit.yar
"""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile

EFI_EXTENSIONS = (".efi", ".sys", ".dll")


def run(cmd):
    """Run a command, return (returncode, stdout, stderr)."""
    try:
        p = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        return p.returncode, p.stdout, p.stderr
    except FileNotFoundError:
        return 127, "", f"binary not found: {cmd[0]}"
    except subprocess.TimeoutExpired:
        return 124, "", "timeout"


def mount_esp(device, mountpoint):
    os.makedirs(mountpoint, exist_ok=True)
    rc, _, err = run(["mount", "-o", "ro,umask=077", device, mountpoint])
    if rc != 0:
        sys.exit(f"[!] Failed to mount {device}: {err.strip()}")
    print(f"[+] Mounted {device} read-only at {mountpoint}")


def sha256_file(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()


def find_efi_binaries(esp_root):
    found = []
    for dirpath, _, files in os.walk(esp_root):
        for name in files:
            if name.lower().endswith(EFI_EXTENSIONS):
                found.append(os.path.join(dirpath, name))
    return found


def detect_anomalous_root(esp_root):
    """Per Velociraptor/Rapid7: EFI/ should be the only top-level dir on the ESP."""
    allowed = {"efi", "system volume information"}
    anomalies = []
    for entry in os.listdir(esp_root):
        if entry.lower() not in allowed:
            anomalies.append(os.path.join(esp_root, entry))
    return anomalies


def verify_signature(path):
    """Use sbverify --list to confirm the binary carries a Secure Boot signature."""
    if not shutil.which("sbverify"):
        return "sbverify-missing"
    rc, out, _ = run(["sbverify", "--list", path])
    if rc != 0:
        return "UNSIGNED-or-invalid"
    # sbverify --list prints 'signature N' lines for each embedded signature
    return "signed" if "signature" in out.lower() else "UNSIGNED"


def load_baseline(path):
    baseline = set()
    if not path or not os.path.exists(path):
        return baseline
    with open(path) as f:
        for line in f:
            tok = line.strip().split()
            if tok:
                baseline.add(tok[0].lower())
    return baseline


def yara_scan(rules, esp_root):
    if not shutil.which("yara"):
        return "yara-missing"
    rc, out, err = run(["yara", "-r", "-w", rules, esp_root])
    return out.strip() or ("(no matches)" if rc == 0 else err.strip())


def main():
    ap = argparse.ArgumentParser(description="Hunt bootkits on the EFI System Partition.")
    ap.add_argument("--esp", default="/mnt/esp", help="ESP mountpoint")
    ap.add_argument("--device", help="ESP block device to mount (e.g. /dev/sda1)")
    ap.add_argument("--mount", action="store_true", help="Mount --device read-only first")
    ap.add_argument("--baseline", help="Golden baseline sha256 file to diff against")
    ap.add_argument("--yara", help="YARA ruleset to scan the ESP with")
    ap.add_argument("--json", help="Write findings to this JSON path")
    args = ap.parse_args()

    if os.geteuid() != 0:
        print("[!] Warning: ESP access typically requires root.", file=sys.stderr)

    if args.mount:
        if not args.device:
            sys.exit("--mount requires --device")
        mount_esp(args.device, args.esp)

    if not os.path.isdir(args.esp):
        sys.exit(f"[!] ESP path not found: {args.esp}")

    report = {"esp": args.esp, "binaries": [], "root_anomalies": [],
              "baseline_new": [], "yara": None}

    # Step 2: anomalous root entries
    report["root_anomalies"] = detect_anomalous_root(args.esp)
    for a in report["root_anomalies"]:
        print(f"[!] ANOMALY: non-EFI entry in ESP root -> {a}")

    # Steps 3-4: inventory, hash, verify signatures
    baseline = load_baseline(args.baseline)
    for path in find_efi_binaries(args.esp):
        try:
            digest = sha256_file(path)
        except OSError as e:
            print(f"[!] Cannot read {path}: {e}", file=sys.stderr)
            continue
        sig = verify_signature(path)
        new = bool(baseline) and digest.lower() not in baseline
        entry = {"path": path, "sha256": digest, "signature": sig, "new_vs_baseline": new}
        report["binaries"].append(entry)
        flag = ""
        if sig.startswith("UNSIGNED"):
            flag += " [UNSIGNED]"
        if new:
            flag += " [NOT-IN-BASELINE]"
            report["baseline_new"].append(digest)
        print(f"[+] {digest}  {sig:18s} {path}{flag}")

    # Step 6: YARA
    if args.yara:
        report["yara"] = yara_scan(args.yara, args.esp)
        print(f"[+] YARA results:\n{report['yara']}")

    # Summary
    n_unsigned = sum(1 for b in report["binaries"] if b["signature"].startswith("UNSIGNED"))
    print(f"\n[=] {len(report['binaries'])} EFI binaries | "
          f"{n_unsigned} unsigned | {len(report['baseline_new'])} new vs baseline | "
          f"{len(report['root_anomalies'])} root anomalies")

    if args.json:
        with open(args.json, "w") as f:
            json.dump(report, f, indent=2)
        print(f"[+] Wrote findings to {args.json}")

    # Non-zero exit if anything suspicious was found (CI/hunt automation friendly)
    if n_unsigned or report["baseline_new"] or report["root_anomalies"]:
        sys.exit(2)


if __name__ == "__main__":
    main()
Keep exploring