Why? ---- It was always my intention to write avbroot in a compiled language. Python was a stop-gap solution since it was possible to use the various tools and parsers from AOSP to make the initial prototyping and implementation easier. However, doing so required a whole lot of hacks since nearly all of the Python modules we use were intended to be used as executables, not libraries, and they were definitely not meant to be used outside of AOSP's code base. Although the dependencies on AOSP code have been reduced over time, working on the Python code is still frustrating. The majority of the modules we use from both the standard library and external dependencies are lacking type annotations. All of the Python language servers and type checker tools I've used choked on them. There have been serveral avbroot bugs in the past that wouldn't have happened with any statically typed language. The catalyst for me working on this recently was dealing with some python-protobuf versions that wouldn't work with AOSP's pregenerated protobuf bindings. When parsing protobuf messages, it would fail with obscure runtime type errors. I need my projects to not feel frustrating or else I'll just get burnt out. Hence, the Rust rewrite. With fewer hacks this time! avbroot no longer has any dependencies on external tools like openssl. I'll be providing precompiled binaries for the three major desktop OS's, built by GitHub Actions. avbroot will also be versioned now, starting at 2.0.0. Whats new? ---------- * A new `avbroot ota verify` subcommand has been added to check that all OTA and AVB related components have been properly hashed and signed. This works for all OTA images, including stock ones. * A couple new `avbroot avb` subcommands have been added for dumping vbmeta header/footer information and verifying AVB signatures. These are roughly equivalent to avbtool's `info_image` and `verify_image` subcommands, though avbroot is about an order of magnitude faster than the latter. * A new set of `avbroot boot` subcommands have been added for packing and unpacking boot images. It supports Android v0-v4 images and vendor v3-v4 images. Repacking is lossless even when using deprecated fields, like the boot image v4 VTS signature. * A new `avbroot ramdisk` subcommand has been added for inspecting the CPIO structure of ramdisks. * A new set of `avbroot key` subcommands have been added for generating signing keys so that it's no longer necessary to install openssl and avbtool (though of course, keys generated by other tools remain fully compatible). * Since avbroot has a ton of CLI options, a new `avbroot completion` subcommand has been added for generating tab-completion configs for various shells (eg. bash, zsh, fish, powershell). What was removed? ----------------- Nothing :) The `patch` and `extract` subcommands have been moved under `avbroot ota` and the `magisk-info` subcommand has been moved under `avbroot boot`, but there are compatibility shims in place to keep all the old commands working. The command-line interface will remain backwards compatible for as long as possible, even with new major releases. The Rust API, however, has no backwards compatibility guarantees. I currently don't intend for avbroot's "library" components to be used anywhere outside of Custota and avbroot itself. Performance ----------- Due to having better access to low-level APIs (especially `pread` and `pwrite`), nearly everything that can be multithreaded in avbroot is now multithreaded. In addition, during the patching operation, everything is done entirely in memory without temp files and the maximum memory usage is still about 100MB lower than with the Python implementation. The new implementation is bottlenecked by how fast a single CPU core can calculate 3 SHA256 hashes of overlapping regions spanning the majority of the OTA file. About 90% of the CPU time is spent calculating SHA256 hashes and another 5% or so performing XZ-compression. Some numbers: * Patching should take roughly 40%-70% of the time it took before. * Extracting with `--all` should take roughly 10%-30% of the time it took before. Folks with x86_64 CPUs supporting SHA-NI extensions (eg. Intel 11th gen and newer) should see even bigger improvements. Reproducibility --------------- The new implementation's output files are bit-for-bit identical when the inputs are the same. However, they do not exactly match what the Python implementation produced. * The zip entries, aside from `metadata` and `metadata.pb`, are written in sorted order. * All zip entries are stored without compression. * All zip entries are stored without additional metadata (eg. modification timestamp). * The OTA certificate, both in the OTA zip and in the recovery ramdisk's `otacerts.zip`, goes through deserialization + serialization before being written. Text in the certificate file before the header and after the footer will be stripped out. * The protobuf structures (payload header and OTA metadata) are serialized differently. Protobuf has more than one way to encode the same messages "on the wire". The Rust quick_protobuf library serializes messages a bit differently than python-protobuf, but the outputs are mutually compatible. * XZ compression of modified partition images in the payload is now done at compression level 0 instead of 6. This reduces the patching time by several seconds at the cost of a couple MiB increase in file size. * Ramdisks are now compressed with standard LZ4 instead of LZ4HC (high compression mode). For our use case, the difference is <100 KiB, but using standard LZ4 allows us to use a pure-Rust LZ4 library and makes the compression step much faster. * Older ramdisks compressed with gzip are slightly different due to a different gzip implementation being used (flate2 vs. zlib). The two implementations structure the gzip frames slightly differently, but the output is identical when decompressed. * Magisk's config file in the ramdisk (`.backup/.magisk`) will have the `SHA1` field set to all zeros. This allows avbroot to keep track of less information during patching for better performance. The field is only used for Magisk's uninstall feature, which can't ever be used in a locked bootloader setup anyway. Misc ---- While working on the new `avbroot ota verify` subcommand, I found that the `ossi` stock image (OnePlus 10 Pro) used in avbroot's tests has an invalid vbmeta hash for the `odm` partition. I thought it was an avbroot bug, but AOSP's avbtool reports the same invalid hash too. If that image actually boots, then I'm not sure AVB can be trusted on those devices... Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
avbroot
avbroot is a program for patching Android A/B-style OTA images for root access while preserving AVB (Android Verified Boot) using custom signing keys. It is compatible with both Magisk and KernelSU.
Having a good understanding of how AVB and A/B OTAs work is recommended prior to using avbroot. At the very least, please make sure the warnings and caveats are well-understood to avoid the risk of hard bricking.
NOTE: avbroot 2.0 has been rewritten in Rust and no longer relies on any AOSP code. The CLI is fully backwards compatible, but the old Python implementation can be found in the python branch if needed.
Patches
avbroot applies two patches to the boot images:
-
Magisk is applied to the
bootorinit_bootimage, depending on device, as if it were done from the Magisk app. -
The
boot,recovery, orvendor_bootimage, depending on device, is patched to replace the OTA signature verification certificates with the custom OTA signing certificate. This allows future patched OTAs to be sideloaded after the bootloader has been locked. It also prevents accidental flashing of the original OTA package while booted into recovery.
Warnings and Caveats
-
The device must use (non-legacy-SAR) A/B partitioning. This is the case on newer Pixel and OnePlus devices. To check if a device uses this partitioning sceme, open the OTA zip file and check that:
payload.binexistsMETA-INF/com/android/metadata.pbexistsMETA-INF/com/android/metadatacontains the line:ota-type=AB
-
The device must support using a custom public key for the bootloader's root of trust. This is normally done via the
fastboot flash avb_custom_keycommand. All Pixel devices with unlockable bootloaders support this, as well as most OnePlus devices. Other devices may support it as well, but there's no easy way to check without just trying it. -
Do not ever disable the
OEM unlockingcheckbox when using a locked bootloader with root. This is critically important. With root access, it is possible to corrupt the running system, for example by zeroing out the boot partition. In this scenario, if the checkbox is turned off, both the OS and recovery mode will be made unbootable andfastboot flashing unlockwill not be allowed. This effectively renders the device hard bricked. -
Any operation that causes an unsigned or differently-signed boot image to be flashed will result in the device being unbootable and unrecoverable without unlocking the bootloader again (and thus, triggering a data wipe). This includes:
-
Performing a regular (unpatched) A/B OTA update. This can be blocked via a Magisk module (see: Blocking A/B OTA Updates).
-
The
Direct installmethod for updating Magisk. Magisk updates must be done by repatching as well.
-
Generating Keys
avbroot signs a few components while patching an OTA zip:
- the root
vbmetaimage - the boot image
vbmetafooters (if the original ones were signed) - the OTA payload
- the OTA zip itself
The boot-related components are signed with an AVB key and OTA-related components are signed with an OTA key. They can be the same RSA keypair, though the following steps show how to generate two separate keys.
-
Generate the AVB and OTA signing keys:
avbroot key generate-key -o avb.key avbroot key generate-key -o ota.key -
Convert the public key portion of the AVB signing key to the AVB public key metadata format. This is the format that the bootloader requires when setting the custom root of trust.
avbroot key extract-avb -k avb.key -o avb_pkmd.bin -
Generate a self-signed certificate for the OTA signing key. This is used by recovery for verifying OTA updates.
avbroot key generate-cert -k ota.key -o ota.crt
The commands above are provided for convenience. avbroot is compatible with any standard PKCS8-encoded 4096-bit RSA private key and X509 certificate (eg. like those generated by openssl).
Usage
-
Make sure the caveats listed above are understood. It is possible to hard brick by doing the wrong thing!
-
Download the latest version from the releases page. To verify the digital signature, see the verifying digital signatures section.
-
Follow the steps to generate signing keys.
-
Patch the full OTA ZIP.
avbroot ota patch \ --input /path/to/ota.zip \ --privkey-avb /path/to/avb.key \ --privkey-ota /path/to/ota.key \ --cert-ota /path/to/ota.crt \ --magisk /path/to/magisk.apkIf
--outputis not specified, then the output file is written to<input>.patched.NOTE: If you are using Magisk version >=25211, you need to know the preinit partition name (
--magisk-preinit-device <name>). For details, see the Magisk preinit device section.If you prefer to use an existing boot image patched by the Magisk app or you want to use KernelSU, see the advanced usage section.
-
[Initial setup only] Unlock the bootloader. This will trigger a data wipe.
-
[Initial setup only] Extract the patched images from the patched OTA.
avbroot ota extract \ --input /path/to/ota.zip.patched \ --directory extracted -
[Initial setup only] Flash the patched images and the AVB public key metadata. This sets up the custom root of trust. Future updates are done by simply sideloading patched OTA zips.
# Flash the boot images that were extracted for image in extracted/*.img; do partition=$(basename "${image}") partition=${partition%.img} fastboot flash "${partition}" "${image}" done # Flash the AVB signing public key fastboot erase avb_custom_key fastboot flash avb_custom_key /path/to/avb_pkmd.bin -
[Initial setup only] Run
dmesg | grep libfs_avbas root to verify that AVB is working properly. A message similar to the following is expected:init: [libfs_avb]Returning avb_handle with status: Success -
[Initial setup only] Lock the bootloader. This will trigger a data wipe again. Do not uncheck
OEM unlocking!WARNING: If you are flashing CalyxOS, the setup wizard will automatically turn off the
OEM unlockingswitch. Make sure to manually reenable it again from Android's developer settings. Consider using avbroot'soemunlockonbootMagisk module to automatically ensure OEM unlocking is enabled on every boot.
Updates
To update Android or Magisk:
-
Follow step 4 in the previous section to patch the new OTA (or an existing OTA with a newer Magisk APK).
-
Reboot to recovery mode. If stuck at a
No commandscreen, press the volume up button once while holding down the power button. -
Sideload the patched OTA.
-
Reboot.
avbroot Magisk modules
avbroot's Magisk modules can be found on the releases page or they can be built locally by running:
python modules/build.py
This requires Java and the Android SDK to be installed. The ANDROID_HOME environment variable should be set to the Android SDK path.
clearotacerts: Blocking A/B OTA Updates
Unpatched OTA updates are already blocked in recovery because the original OTA certificate has been replaced with the custom certificate. To disable automatic OTAs while booted into Android, turn off Automatic system updates in Android's Developer Options.
The clearotacerts module additionally makes A/B OTAs fail while booted into Android to prevent accidental manual updates. The module simply overrides /system/etc/security/otacerts.zip at runtime with an empty zip so that even if an OTA is downloaded, signature verification will fail.
Alternatively, see Custota for a custom OTA updater app that pulls from a self-hosted OTA server.
oemunlockonboot: Enable OEM unlocking on every boot
To help reduce the risk of OEM unlocking being accidentally disabled (or intentionally disabled as part of some OS's initial setup wizard), this module will attempt to enable the OEM unlocking option on every boot.
The logs for this module can be found at /data/local/tmp/avbroot_oem_unlock.log.
Magisk preinit device
Magisk versions 25211 and newer require a writable partition for storing custom SELinux rules that need to be accessed during early boot stages. This can only be determined on a real device, so avbroot requires the partition's block device name to be specified via --magisk-preinit-device <name>. To find the partition name:
-
Extract the boot image from the original/unpatched OTA:
avbroot ota extract \ --input /path/to/ota.zip \ --directory . \ --boot-only -
Patch the boot image via the Magisk app. This MUST be done on the target device! The partition name will be incorrect if patched from Magisk on a different device.
The Magisk app will include a line like the following in the output:
- Pre-init storage partition device ID: <name>Alternatively, avbroot can print out what Magisk detected by running:
avbroot ota magisk-info \ --image magisk_patched-*.imgThe partition name will be shown as
PREINITDEVICE=<name>.Now that the partition name is known, it can be passed to avbroot when patching via
--magisk-preinit-device <name>. The partition name should be saved somewhere for future reference since it's unlikely to change across Magisk updates.
If it's not possible to run the Magisk app on the target device (eg. device is currently unbootable), patch and flash the OTA once using --ignore-magisk-warnings, follow these steps, and then repatch and reflash the OTA with --magisk-preinit-device <name>.
Verifying OTAs
To verify all signatures and hashes related to the OTA installation and AVB boot process, run:
avbroot ota verify \
--input /path/to/ota.zip \
--cert-ota /path/to/ota.crt \
--public-key-avb /path/to/avb_pkmd.bin
If the --cert-ota and --public-key-avb options are omitted, then the signatures are only checked for validity, not that they are trusted.
Tab completion
Since avbroot has tons of command line options, it may be useful to set up tab completions for the shell. These configs can be generated from avbroot itself.
bash
Add to ~/.bashrc:
eval "$(avbroot completion -s bash)"
zsh
Add to ~/.zshrc:
eval "$(avbroot completion -s zsh)"
fish
Add to ~/.config/fish/config.fish:
avbroot completion -s fish | source
PowerShell
Add to PowerShell's profile.ps1 startup script:
Invoke-Expression (& avbroot completion -s powershell)
Advanced Usage
Using a prepatched boot image
avbroot can replace the boot image with a prepatched image instead of applying the Magisk root patch itself. This is useful for using a boot image patched by the Magisk app or for KernelSU. To use a prepatched boot image, pass in --prepatched <boot image> instead of --magisk <apk>. When using --prepatched, avbroot will skip applying the Magisk root patch, but will still apply the OTA certificate patch.
For KernelSU, also pass in --boot-partition @gki_kernel for both the patch and extract commands. avbroot defaults to Magisk's semantics where the boot image containing the GKI ramdisk is needed, whereas KernelSU requires the boot image containing the GKI kernel. This only affects devices launching with Android 13, where the GKI kernel and ramdisk are in different partitions (boot vs. init_boot), but it is safe and recommended to always use this option for KernelSU.
Note that avbroot will validate that the prepatched image is compatible with the original. If, for example, the header fields do not match or a boot image section is missing, then the patching process will abort. The checks are not foolproof, but should help protect against accidental use of the wrong boot image. To bypass a somewhat "safe" subset of the checks, use --ignore-prepatched-compat. To ignore all checks (strongly discouraged!), pass it in twice.
Skipping root patches
avbroot can be used for just resigning an OTA by specifying --rootless instead of --magisk/--prepatched. With this option, the patched OTA will not be rooted. The only modification applied is the replacement of the OTA verification certificate so that the OS can be upgraded with future (patched) OTAs.
Replacing partitions
avbroot supports replacing entire partitions in the OTA, even partitions that are not boot images (eg. vendor_dlkm). A partition can be replaced by passing in --replace <partition name> /path/to/partition.img.
The only behavior this changes is where the partition is read from. When using --replace, instead of reading the partition image from the original OTA's payload.bin, it is read from the specified file. Thus, the replacement partition images must have proper vbmeta footers, like the originals.
This has no impact on what patches are applied. For example, when using Magisk, the root patch is applied to the boot partition, no matter if the partition came from the original payload.bin or from --replace.
Clearing vbmeta flags
Some Android builds may ship with a root vbmeta image with the flags set such that AVB is effectively disabled. When avbroot encounters these images, the patching process will fail with a message like:
ValueError: vbmeta flags disable AVB: 0x3
To forcibly enable AVB (by clearing the flags), pass in --clear-vbmeta-flags.
Non-interactive use
avbroot prompts for the private key passphrases interactively by default. To run avbroot non-interactively, either:
-
Supply the passphrases via files:
avbroot ota patch \ --pass-avb-file /path/to/avb.passphrase \ --pass-ota-file /path/to/ota.passphrase \ <...>On Unix-like systems, the "files" can be pipes. With shells that support process substituion (bash, zsh, etc.), the passphrase can be queried from a command (eg. querying a password manager).
avbroot ota patch \ --pass-avb-file <(command to query AVB passphrase) \ --pass-ota-file <(command to query OTA passphrase) \ <...> -
Supply the passphrases via environment variables. This is less secure since any process running as the same user can see the environment variable values.
export PASSPHRASE_AVB="the AVB passphrase" export PASSPHRASE_OTA="the OTA passphrase" avbroot ota patch \ --pass-avb-env-var PASSPHRASE_AVB \ --pass-ota-env-var PASSPHRASE_OTA \ <...> -
Use unencrypted private keys. This is strongly discouraged.
Extracting the entire OTA
To extract all images contained within the OTA's payload.bin, run:
avbroot ota extract \
--input /path/to/ota.zip \
--directory extracted \
--all
Building from source
Make sure the Rust toolchain is installed. Then run:
cargo build --release
The output binary is written to target/release/avbroot.
Debug builds work too, but they will run significantly slower (in the sha256 computations) due to compiler optimizations being turned off.
By default, the build links to the system's bzip2 and liblzma libraries, which are the only external libraries avbroot depends on. To compile and statically link these two libraries, pass in --features static.
Verifying digital signatures
First, save the public key to a file listing the keys to be trusted.
echo 'avbroot ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDOe6/tBnO7xZhAWXRj3ApUYgn+XZ0wnQiXM8B7tPgv4' > avbroot_trusted_keys
Then, verify the signature of the zip file using the list of trusted keys.
ssh-keygen -Y verify -f avbroot_trusted_keys -I avbroot -n file -s <file>.zip.sig < <file>.zip
If the file is successfully verified, the output will be:
Good "file" signature for avbroot with ED25519 key SHA256:Ct0HoRyrFLrnF9W+A/BKEiJmwx7yWkgaW/JvghKrboA
Contributing
Contributions are welcome! However, I'm unlikely to accept changes for supporting devices that behave significantly differently from Pixel devices.
License
avbroot is licensed under GPLv3. Please see LICENSE for the full license text.