GeographicCone / UefiVarTool

Scriptable tool to read and write UEFI variables from EFI shell. View, save, edit and restore hidden UEFI (BIOS) Setup settings faster than with the OEM menu forms.
GNU General Public License v3.0
46 stars 3 forks source link

UEFI Variable Tool (UVT) Logo

UEFI Variable Tool (UVT)

UEFI Variable Tool (UVT) is a command-line application that runs from the UEFI shell. It can be launched in seconds from any FAT flash drive with no prior machine-specific setup, and lets you view and modify the content of individual UEFI variables at a byte level.

UVT's purpose is to allow changing all the hidden UEFI (BIOS) Setup hardware settings. It is well-suited for situations when custom firmware, such as a modified BIOS that would unhide all the menu forms, cannot be flashed due to restrictive anti-features such as Boot Guard being enabled.

While various utilities have existed for a long time to allow doing just the same, making the functionality as such hardly a novelty, UVT aims to make the process as efficient and unencumbered as possible. To that effect:


_UVT is free software, and full source code is available for anyone to tinker with. It is a quite heavily modified version of setup_var.efi by @datasone, to whom I originally made some improvement suggestions. He followed up on them and even graciously included me as a co-author in his commit, never mind that I had not at that point written a single line of code._

_@datasone's last-posted version seemed at least 90% there but wasn't working for me in a couple of ways. Since he's probably busy with other stuff in his life, I decided it was my turn to contribute something. Hence this fork, which although is completely refactored to the point it might not look very much like the original, would have never been possible without all of @datasone's work, which he generously shared with the world. For that I am eternally grateful to him, and want everyone to know he deserves all the credit as the original author of this utility._

How to Use

The in-application usage information summary is reproduced below for reference:

Usage: uvt[.efi] [<Options>] <Op1> [<Op2> [... [<OpN>]]
- or - uvt[.efi] < <InputFile>
Where:
<Options>: Optional global-scope application settings
  -f --force     Force-write values even if already set as requested
  -h --help      Show usage information (precludes other operations)
  -r --restart   Upon successful completion, perform a system restart
  -s --simulate  Do not write, only simulate actions (will still read)
<Op#>: Operation(s) to perform, can be multiple, each in the format:
  <VarName>[(<VarId>)]:<Offset>[(<Size>)][=<Value>]
Arg Overview:
  <VarName>      UEFI variable name to read or write to, case-sensitive
  <VarId>        If two variables share a name, will prompt to use this
  <Offset>       Data starting position within the given UEFI variable
  <Size>         Optional, a byte (1) by default if omitted; little-endian
  <Value>        Value to write, 8 bytes (64 bits) maximum; read if absent
  <InputFile>    Script to run, same base format as arguments + see below
File Overview:
  #                                   Comment, ignored until end of line
  !<force|restart|simulate>           Set options, same as above arguments
  <Def>,<VarName>:<Offset>[(<Size>)]  Define a variable to reference later
  @<Def>[=<Value>]                    Assign to a referenced variable
Example Command Line:
  uvt -s Lang:0x00 Lang:0x00(4)=0x01020304 Lang:0x00(4)
  Read byte at offset 0, simulate-set the dword (4 bytes), then read again
Example Input File:
  !simulate              # Simulate only, do not perform actual writes
  Language,Lang:0x00(4)  # Define a reference under the alias "Language"
  @Language=0x01020304   # Write to the target referred to by "Language"

<Offset>, <Size> and <Value> can be decimal or hexadecimal: use prefix "0x"
File should be a UTF-16 LE text, UEFI firmware and shell version-dependent
Output saved to a file can be re-used as input again: format is the same

Prerequisites

You need to boot into UEFI shell on the machine where you want to use the utility. This typically involves setting up a FAT flash drive and placing an EFI shell binary under the path efi/boot/bootx64.efi. You can then place the UVT binary under efi/tools/uvt.efi and run it as uvt regardless of what the current directory is. More on this in the Background section.

On a broader note, you need to know the variable layout, which is specific to your particular hardware, and possibly even the firmware version. How to obtain this information is also addressed in the Background section.

Separately, it is possible to run UVT in an emulator. As this would be mostly of interest to a developer, it is described in the Building section.

Command Line

There are two ways to use UVT. The first one is to run it with command-line arguments.

UVT accepts two kinds of command-line arguments: operations and options. Arguments are separated with spaces. There is no limit on the number of arguments.

Options

Options start with a - (minus) sign and are used to define global-scope settings. Each option has a short and a long form, taking a single - and a letter or a double -- and a keyword respectively. The options are:

Operations

Operations define either reading (querying, or getting) or writing (assigning, or setting) a value. The syntax is:

<VarName>[(<VarId>)]:<Offset>[(<Size>)][=<Value>]

Where:

For example:

An arbitrary number of command-line operations can be specified. They will be executed in the order entered. An error interrupts the processing of any further operations and arguments, terminating the application.

Numerical Values

Any number can be specified as either decimal (base 10) or hexadecimal (base 16). Hexadecimal values should be preceded by 0x or 0X, otherwise they will be parsed as decimal. Only digits 0-9 are allowed in decimal values. The additional digits a-f and A-F in hexadecimal values are case-insensitive.

Offsets and values are output in hexadecimal, while sizes are shown in decimal. When printed, hexadecimal values for offsets will be zero-padded to 2 bytes. Values will be zero-padded to their size. The padding does not have to be preserved in input, i.e. you can type 0x1 for a word-sized (two-byte) value, instead of writing 0x0001.

Output

UVT's output follows the same syntax as the input it accepts. This way, nearly everything it spits out can be fed back to it, for example to restore some previously-saved settings.

The application prints out a header as the first thing it does after it launches, which provides some useful information. It might look like that:

# UEFI Variable Tool (uvt) Version 0.0.0 @ American Megatrends 5.24 UEFI 2.8

This prompt starting with # is also a valid comment, which means it will not interfere if you decided to feed the same file back to UVT. The three items following the @ sign are: the firmware vendor, firmware version (major.minor) and the UEFI revision (specification version compatibility).

Input Stream

UVT's other mode of operation is to take an arbitrarily-long list of commands from the standard input (stdin). To use the application in this mode, make sure not to provide any command-line arguments, other than the redirection operator, which is however handled by the shell.

Redirection

UVT does not read or write files directly: it depends on the UEFI shell redirection. This comes with some quirks, which might also be implementation-dependent.

To feed data to the application's standard input, use the following shell command:

uvt < in.txt

The file in.txt should be properly formatted, as discussed in the next section. You can also save the application's output:

uvt > out.txt

To combine both operations:

uvt < in.txt > out.txt

The quirks mentioned are as follows:

File Format

As the UEFI shell operates internally with UCS-2 encoding, the accepted standard input file format is Unicode UTF-16 Little-Endian (LE): standard ASCII text files will not work. This is a minor inconvenience, although even the Notepad application bundled with Windows can save text in this format, as long as it is specified explicitly.

Any output files produced by a redirection will also be in the same format.

The Byte Order Mark (BOM), mandated by the UTF-16 specification, is optional as far as the application is concerned. In fact, any BOM instances will be filtered out at an early parsing stage.

The input file is split into individual entries, which are rows separated by the Line Feed character LF or \n. Each line can contain at most a single operation and is self-contained, i.e. no operation can span multiple lines. The Carriage Return character CR or \r may be present and will be discarded if that's the case.

The format to define operations is just the same as for the command-line arguments. Options can also be defined, however with a different syntax (read on). Beyond that, there are also definitions, which can be referenced by operations, and comments.

Comments

Comments are marked with the pound sign #. Anything to the right of that sign is discarded. Comments do not have to be separate lines, they can appear on the same line as an operation, an option or a definition:

# This is an example comment on a separate line
!simulate # Simulate only, do not write
Lang:0x00 # Retrieve the byte at offset 0 in "Lang"

When the input is parsed, after filtering out the comments, an entry is trimmed of any leading and trailing whitespace characters. Entries that end up blank are at this point entirely discarded.

Definitions & References

A target for an operation, consisting of a variable name, offset and, optionally, size, can be defined to be referenced elsewhere in the file. The syntax is:

<Def>,<VarName>:<Offset>[(<Size>)]

Where <VarName>, <Offset> and <Size> have the same interpretation as discussed in the command-line arguments section, and <Def> is an identifier that can be reused later to identify the target. For example:

Language,Lang:0x00(4)

Note that any amount of whitespace can appear on either side of the , comma separator, which allows for better legibility in script formatting. For example:

Language9 ,  Lang:0x09
Language10,  Lang:0x0a

The syntax for operations in the input stream is extended to include the following:

@<Def>[=<Value>]

The following example illustrates accessing a value by reference for the purposes of reading and writing respectively:

@Language
@Language=0x01020304

Options

Some of the options (excluding usage information) can be defined in the input stream as well but the syntax for that is different. Namely, it's the ! bang (exclamation mark) followed by the option keyword:

!<Option>

The available options are !force, !restart and !simulate, and their interpretation is the same as discussed in the command-line arguments section.

Background

Setup

To run UVT you need to boot into UEFI shell. The most straightforward way of setting it up is to use an empty flash drive you can boot from:

Automation

You can use a startup script named startup.nsh placed in the efi/boot directory. An example script would look as follows:

@echo -off
fs0:
alias -v so "shellopt.efi"
alias -v v "uvt.efi"
so -s -delay 0
v --help

You can now refer to uvt as v, which saves having to type the extra two letters every time. Furthermore, you can place any commands you want to run automatically on startup below in the file.

One remaining annoyance is that the UEFI shell will have you wait five seconds or press a key before it processes the startup script. This behavior can be changed by passing a command-line argument to the shell: however, it's a chicken-and-egg problem since arguments cannot be passed to the shell directly, only by means of an environment variable.

This problem is discussed in detail in @fpmurphy's blog post from a long while ago: Problems with UEFI Shell Options. He also came up with a solution to it, and that is the shellopt.efi script that appears in the example startup.nsh above. A more recent, updated build has been made available by BIOS developer ChiChen in his post: Passing Parameters to BootX64.efi. If you use this functionality a lot, these five-second delays add up, and you might want to consider this workaround, as cumbersome as it sounds.

Variable Information

While UVT gives you all the means to access and modify the UEFI Setup settings, no matter if they are hidden from the menu, you still have to know what you're looking at, and what can be done with it. This information depends on your specific hardware, and might possibly also change between different firmware (i.e. UEFI BIOS) versions, and has to be figured out separately. Here is a quick summary of the process:

AMITSESetup:0x0040               # Boot: Quiet Boot [0x00 / 0x01]
Or if you skipped the last step The untransformed end result would look like this: ```` CheckBox Prompt: "Quiet Boot", Help: "Enables or disables Quiet Boot option", QuestionFlags: 0x0, QuestionId: 0x106E, VarStoreId: 0xF013, VarOffset: 0x40, Flags: 0x0, Default: Disabled, MfgDefault: Disabled Default DefaultId: 0x0 Value: 1 Default DefaultId: 0x1 Value: 1 End ````

Either way, with all this information at hand, you're now ready to change any hidden settings. Be careful though, changing some of these may brick (or, more likely, soft-brick) your hardware. The usual disclaimers apply: if things go south, you're on your own, so make sure to plan for that contingency.

Building

Once you have the environment set up (if not, read on), building should be fairly straightforward by running the following command in the source directory:

CARGO_BUILD_TARGET=x86_64-unknown-uefi cargo build --release

Remove the --release flag if you want a debug build which is also much faster to produce.

Environment

UVT is written in Rust. To build it, you will need rustc (compiler), rustup (toolchain installer), and cargo (package manager and build automation tool). Make sure these are all installed and in the PATH. On Windows, you can use MSys2.

You will need to install the appropriate build target first by running: rustup target add x86_64-unknown-uefi. And you will also need an Internet connection, since UVT has some external dependencies that have to be resolved at build time: the uefi and uefi-services crates, as well as everything they depend on.

Firmware Emulator

An optional but recommended step is having an emulator set up as well, so that you can immediately run the application as you build it. This is possible with QEMU and Open Virtual Machine Firmware, OVMF, for which the official repository (again) does not offer binary releases: these are helpfully provided by Gerd Hoffmann and can be downloaded from his website.

Once you have downloaded QEMU and extracted the OVMF archives, you can run the emulator as follows:

@echo off
start /min "" "%TOOLS%\MSys64\mingw64\bin\qemu-system-x86_64w.exe" ^
-device isa-debug-exit,iobase=0xf4,iosize=0x04 ^
-device virtio-rng-pci ^
-drive format=raw,file=fat:rw:Filesystem ^
-drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd ^
-drive if=pflash,format=raw,file=OVMF_VARS.fd ^
-m 256M ^
-machine q35,accel=kvm:tcg ^
-net none ^
-nodefaults ^
-smp 4 ^
-vga std

The above example is a Windows batch file but the UN*X sh(1) syntax is similar enough. Adjust the paths as necessary. Before running the emulator, you also need to create the Filesystem directory and set it up with the files as described in the Setup section above. Note that the shellopt workaround to bypass the five-second wait on boot before executing the startup.nsh script does not work in the emulator.

On Windows, you generally want to use the qemu-system-x86_64w.exe binary with the trailing w which does not keep the console window open as it is running. Alternatively, it is also possible to make QEMU attach itself to a console window it is being executed from with the -nographic switch. While running QEMU in this mode, it is good to know that you can press Ctrl-A, X to terminate it at any time.

In the standard, windowed mode it's helpful to be aware that Ctrl-Alt-F toggles full-screen mode, and you can press Ctrl-Alt-G anytime to stop the mouse events from being captured by the client: use this if your cursor has suddenly disappeared.

Project Layout

The source files are organized as follows. In the project root directory:

Source Files

The source files (all with the *.rs extension) are organized as follows:

Most of the logic (code) is located in the following three files:

The following files contain primarily data, with very little code:

The application can easily be translated to other languages by making a copy of locale_en.rs as locale_XX.rs, where XX is a two-letter ISO 639-1 language code. The entry for locale_en can then be replaced with locale_XX in config.rs.

License

UVT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. The full text of the license is available as LICENSE.md in this repository.

UVT is a fork of setup_var.efi based on the last-posted version at the time of the initial release. The original author of this software is @datasone who has generously made his work available under the terms of either the Apache 2.0 or MIT) license.

I am eternally grateful to @datasone for all his work, and implementing the ideas I previously suggested to him. I mostly stepped in to fix the issues that prevented me from using the newest version in his absence. Even if the original did not work for me, once the errors were addressed, I believe it contained about 90% of the current functionality of UVT at the time of the initial release. This is why it is extremely important for me to give credit where credit is due. UVT as it is would have never been possible without all of @datasone's work, which he generously shared with the world. For that I am eternally grateful to him, and want everyone to know he deserves all the credit as the original author of this utility.

That being said, the source has been completely refactored. As a result, all errors and issues within are mine to own. Please do not bother the original author @datasone about any issues you encounter when running UVT.

The original license terms for the portions authored by @datasone are reproduced below:

MIT License

Copyright (c) 2022 datasone

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Note that this only applies to the code from setup_var.efi in its original repository. UVT in its entirety as a fork of it is solely available under the terms of the GNU General Public License Version 3, as indicated in the LICENSE.md file in this repository.

Please also note that the files in the extra directory are not covered by the repository-wide license. In particular, the UVT logo is made available under the terms of CC BY-NC-ND 4.0.

Version History

1.0.0 Initial Public Release