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._
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
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.
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 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:
-f
or --force
Force-write values where the current values is equal to the new one. The default behavior is to skip such operations, and annotate such entries with an # Already
comment in the output.-h
or --help
Shows the usage information. If this option is selected, no other operations will be performed.-r
or --restart
Reboots the system upon successful completion. No restart will be performed if any of the operations failed.-s
or --simulate
If set, no changes will be made to UEFI variables. All the other aspects of the application will still be functioning exactly in the same way. This might be useful for checking what an operation would do, or whether the arguments are syntactically correct. If -f
or --force
is specified together with this option, no writing will happen regardless: the simulation takes precedence.Operations define either reading (querying, or getting) or writing (assigning, or setting) a value. The syntax is:
<VarName>[(<VarId>)]:<Offset>[(<Size>)][=<Value>]
Where:
<VarName>
is the UEFI variable name. It is case-sensitive and mandatory: there is no default.<VarId>
is an optional identifier to distinguish between variables in a situation when two or more share the same name. In the unlikely scenario this happens, the application will automatically list all the variables with the matching name, alongside with their respective identifiers.<Offset>
is the position of data within the variable where the value data starts. Remember the count starts from 0, not 1.<Size>
is the optional size of the variable: it defaults to a single byte, i.e. (1)
, which can also be specified, although that's unnecessary. The application can write at most 8 bytes (or 64 bits) at a time.<Value>
is the new value to be written at the given offset. The value must fit within the <Size>
constraint, which is checked. Multi-byte values are little-endian, which means that if you write 0x01
to 4 bytes starting at offset 0x00
, the value of 0x01
will be at the offset of 0x00
and not 0x03
, although if you read these 4 bytes again, the result will also be shown as 0x00000001
. If you are unfamiliar with the concept or do not understand its implications, it's best to write individual bytes, and that's what the vast majority of UEFI Setup settings are anyway. This part, alongside the =
assignment operator, is optional: if absent, the default action is to query and output the current value.For example:
uvt Lang:0x00
reads the byte value at offset 0x00
in the variable Lang
uvt -s Lang:0x00(4)=0x01020304
simulates writing a double-word (four-byte) value to an offset starting at 0x00
in the variable Lang
uvt Lang:0x00(4)
reads again the double word that has just been written with the preceding commandAn 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.
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
.
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).
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.
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:
` space between the filename and the redirection sign is mandatory: neither
uvt <in.txtnor
uvt< in.txt` will work.<a
and >a
despite being specified in the UEFI Shell Manual do not seem to work properly in the latest EFI Shell 2.2 (as of August 2023). Do not use them.|
pipe operator does not seem to work either with built-in commands such as echo
or type
. The standard-input stream received by UVT is empty.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 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.
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
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.
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:
usr/share/edk2-shell/x64/Shell_Full.efi
from inside the package archive edk2-shell-YYYYMMDD-#-any.pkg.tar.zst
UEFI-Shell-2.2-YYH#-RELEASE.iso
which can be opened with any archiving utility, and extract the file efi/boot/bootx64.efi
from itbootx64.efi
and place it on the flash drive in the efi/boot
directory.efi/tools
and put uvt.efi
downloaded from the latest release in it: you're now ready to rollYou 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.
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:
.exe
files, and, if all else fails, visit @platomav's wonderland of BIOSUtilities, remembering to also grab TianoCompress.exe from the official ed2k-BaseTools-win32 repository as a dependency899407D7-99FE-43D8-9A21-79EC328CAC21
in case of an AMI BIOS, or FE3542FE-C1D3-4EF8-657C-8048606FF670
for Insyde; in rare cases, the data might be stored elsewhere, in which case a list of common guids.csv might also come handySection_PE32_Image_Setup_Body.efi
(or similarly named)ifrextractor Section_PE32_Image_Setup_Body.efi verbose
Section_PE32_Image_Setup_Body.efi.0.0.en-US.ifr.txt
already contains all the information you need about all the settings, however it's a bit cryptic. Thus, I recommend running it through the SlimIFR script by yours truly to streamline the formatting and make it more human-readable:
node SlimIFR.js Section_PE32_Image_Setup_Body.efi.0.0.en-US.ifr.txt Setup.txt
AMITSESetup:0x0040 # Boot: Quiet Boot [0x00 / 0x01]
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.
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.
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.
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.
The source files are organized as follows. In the project root directory:
Cargo.toml
contains the project metadata and build settings being used by cargo
and the compiler toolchainCargo.lock
is an automatically-generated file that stores the information about package dependency versions used by the project; if deleted, it will be regenerated but if the information there changes, the project might no longer build, or it might introduce unpredictable errors in UVT's operations due to changes upstream: consider yourself warnedtarget
is the directory where all the objects and information generated during the build process is stored, alongside the resulting executable in target/x86_64-uknown-uefi/{debug,release}/uvt.efi
: all of this can be safely deleted at any timesrc
is where all the source files are located, and the directory is discussed separately belowThe source files (all with the *.rs
extension) are organized as follows:
main.rs
is the main file that provides the entry point and launches all operationsMost of the logic (code) is located in the following three files:
firmware.rs
performs UEFI operations such as querying and setting UEFI variablesparse.rs
processes command-line and stream (standard) input into data structuresstring.rs
provides string manipulation routines, including an extension to CStr16
(UEFI-specific equivalent to str
)The following files contain primarily data, with very little code:
config.rs
stores configurable parameters together for easy adjustmentconfig/locale_en.rs
stores translateable user interface messagesdata.rs
defines data types and structures used throughout the applicationerror.rs
allows for error handling in a single centralized mannerThe 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
.
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.
.efi
extension in lower case was explicitly specified as part of the command line--write_on_demand
to --force
(or -f
in short) and invert the meaning, also change --reboot
to --restart
--simulate
(or -s
in short) simulation mode, where no actual write operations are performed,
(comma) instead of :=
(Algol/Pascal-style assignment operator) for definitions, !
instead of @
for options, and @
instead of $
for references in operations,
comma separator, which provides for better legibility in script formattinguefi
and uefi_services
external package dependencies to their latest versions