Files
Frinzell, Aaron 2e9977bdc1 Update qemu_efi.py for code port
Signed-off-by: Frinzell, Aaron <aaron.frinzell@intel.com>
2023-05-08 09:27:49 -07:00

184 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
# CHIPSEC: Platform Security Assessment Framework
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; Version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Contact information:
# chipsec@intel.com
#
"""Launch a QEMU virtual machine with a UEFI Shell and chipsec
This scripts uses the UEFI Shell provided by OVMF firmware. Therefore it
requires both QEMU (for x86_64 system emulation) and OVMF to be installed.
It also requires qemu-img, to create a temporary boot disk.
On Debian and Ubuntu, installing these requirements can be done with:
sudo apt install ovmf qemu-system-x86 qemu-utils
Usage examples:
# Run chipsec_main.py in the default QEMU virtual machine
qemu_efi.py -m
# Enumerate PCI devices of the virtual machine (like command "pci" of UEFI shell)
qemu_efi.py -u pci enumerate
# Launch a Python interpreter where it is possible to import chipsec and edk2
qemu_efi.py -p
"""
import argparse
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
import tempfile
import zipfile
CHIPSEC_BASE = Path(__file__).parent / ".."
PYTHON_INSTALL_ZIP = CHIPSEC_BASE / "__install__" / "UEFI" / "chipsec_py368_uefi_x64.zip"
PYTHON_EFI_COMMAND = "python.efi"
OVMF_CODE_LOCATIONS = (
# Arch Linux https://archlinux.org/packages/extra/any/edk2-ovmf/
"/usr/share/edk2-ovmf/x64/OVMF_CODE.fd",
# Debian https://packages.debian.org/fr/sid/all/ovmf/filelist
"/usr/share/OVMF/OVMF_CODE.fd",
# Fedora https://fedora.pkgs.org/35/fedora-updates-x86_64/edk2-ovmf-20211126gitbb1bba3d7767-1.fc35.noarch.rpm.html
"/usr/share/edk2/ovmf/OVMF_CODE.fd",
)
def create_efi_disk(filename, startup_nsh=None):
"""Create a disk bootable through UEFI with chipsec
This function copies all needed files to a temporary directory and uses
qemu-img to convert this directory into a FAT partition.
"""
with tempfile.TemporaryDirectory(prefix="efidisk_") as tmpdir:
# Unzip Python into the temporary directory
print("Extracting {} to {} ...".format(PYTHON_INSTALL_ZIP.name, tmpdir))
with zipfile.ZipFile(PYTHON_INSTALL_ZIP) as python_zip:
python_zip.extractall(tmpdir)
# Copy Chipsec
print("Copying chipsec to {} ...".format(tmpdir))
for extension in (".py", ".xml", ".xsd"):
for filepath in CHIPSEC_BASE.glob("**/*" + extension):
destination = Path(tmpdir) / "chipsec" / filepath.relative_to(CHIPSEC_BASE)
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(filepath, destination)
# Create startup.nsh
if startup_nsh:
with (Path(tmpdir) / "startup.nsh").open("w") as f:
f.write(startup_nsh)
# Convert to QCow image
print("Converting to QCow2 format in {} ...".format(filename))
subprocess.run(
("qemu-img", "convert", "-c", "-f", "vvfat", "-O", "qcow2", "fat:" + tmpdir, filename),
check=True
)
def unescape_arguments(args):
"""Replace " -" with "-" in the arguments"""
return [arg[1:] if arg.startswith(" -") else arg for arg in args]
def main():
parser = argparse.ArgumentParser(description="Launch a QEMU virtual machine with a UEFI Shell and chipsec")
group = parser.add_mutually_exclusive_group()
group.add_argument("-m", "--main", nargs="*", type=str,
help="start chipsec_main.py, optionally with some arguments")
group.add_argument("-p", "--python", nargs="*", type=str,
help="start a Python-UEFI shell, optionally with some arguments")
group.add_argument("-u", "--util", nargs="+", type=str,
help="start chipsec_util.py, with some arguments (use ' -' to escape the dash prefix)")
parser.add_argument("--ovmf", type=str,
help="path to the OVMF firmware code")
parser.add_argument("--memory", type=str, default="512M",
help="set the RAM size of the virtual machine (QEMU option -m)")
parser.add_argument("-g", "--gui", action="store_true",
help="use QEMU graphics window")
parser.add_argument("-H", "--host-cpu", action="store_true",
help="use host CPU")
parser.add_argument("-M", "--machine", type=str,
help="specify QEMU machine ('pc', 'q35,smm=on'...)")
parser.add_argument("-Q", "--qemu-arg", nargs="+", type=str,
help="specify additional QEMU arguments (use ' -' to escape the dash prefix)")
args = parser.parse_args()
ovmf_path = args.ovmf
if not ovmf_path:
for test_ovmf_path in OVMF_CODE_LOCATIONS:
if Path(test_ovmf_path).exists():
ovmf_path = test_ovmf_path
break
if not ovmf_path:
print("No OVMF file found. You can specify one using option --ovmf.", file=sys.stderr)
sys.exit(1)
# Create startup.nsh
start_cmd = None
if args.main is not None:
start_cmd = [PYTHON_EFI_COMMAND, "chipsec_main.py"] + unescape_arguments(args.main)
elif args.python is not None:
start_cmd = [PYTHON_EFI_COMMAND] + unescape_arguments(args.python)
elif args.util is not None:
start_cmd = [PYTHON_EFI_COMMAND, "chipsec_util.py"] + unescape_arguments(args.util)
if start_cmd:
startup_nsh = "fs0:\ncd chipsec\n" + " ".join(shlex.quote(arg) for arg in start_cmd) + "\nreset\n"
else:
startup_nsh = "@echo Type 'reset' to exit"
qemu_cmd = [
"qemu-system-x86_64",
# Make OVMF boot directly in UEFI shell instead of trying network boot (PXE)
"-nic", "none",
# Enable exiting by entering "reset" in UEFI shell
"-no-reboot",
# Start OVMF
"-drive", "if=pflash,format=raw,readonly=on,file=" + ovmf_path,
]
if not args.gui:
qemu_cmd += ["-nographic"]
if args.memory:
qemu_cmd += ["-m", args.memory]
if args.host_cpu:
qemu_cmd += ["-cpu", "host", "-enable-kvm"]
if args.machine:
qemu_cmd += ["-machine", args.machine]
with tempfile.NamedTemporaryFile(prefix="efidisk_") as tmpdisk:
create_efi_disk(tmpdisk.name, startup_nsh)
qemu_cmd += ["-drive", "format=qcow2,file=" + tmpdisk.name]
if args.qemu_arg:
qemu_cmd += [arg[1:] if arg.startswith(" ") else arg for arg in args.qemu_arg]
print("Launching {}".format(" ".join(qemu_cmd)))
subprocess.run(qemu_cmd, check=True)
if __name__ == "__main__":
main()