
A PoC of the CVE-2024-56426 vulnerability.
Unified CVE-2024-56426 tooling for the Exynos 990 Galaxy S20, S20 FE, and Note20 families. The exploit accepts all ten model names and maps them onto six verified stock bootloader families.
[!CAUTION] The tracked key bundle and generated images are fusing-capable. Fusing is irreversible. A phone fused to a key can only boot images compatible with that key. A wrong model, rollback revision, patch set, or key bundle can leave the device in a fused boot loop. Use development keys and the UFS payload while iterating. Add
--no-fuseto every preparation/signing command unless custom-key fusing is explicitly intended.
The selected model controls both the BL1 model ID and the exact-model LK patch TSV. Runtime artifact controls which
stock firmware and encrypted split images are used by preflight. The four non-5G flags that use paired 5G runtime
artifacts also patch LK's model-ID check and model-ID programming path.
| Model flag | Runtime artifact | Runtime firmware | Model ID | EVT | Rollback | Tested |
|---|
G780F | G780F | G780FXXSOFYJ1 | 0x154 | 11 | 24 | ❌ |
G980F | G981B | G981BXXSNHYB1 | 0x143 | 11 | 23 | ✅ |
G981B | G981B | G981BXXSNHYB1 | 0x13D | 11 | 23 | ❌ |
G985F | G986B | G986BXXSNHYB1 | 0x142 | 11 | 23 | ✅ |
G986B | G986B | G986BXXSNHYB1 | 0x13C | 11 | 23 | ✅ |
G988B | G988B | G988BXXSNHYB1 | 0x13E | 11 | 23 | ❌ |
N980F | N981B | N981BXXSIHYH3 | 0x153 | 11 | 18 | ❌ |
N981B | N981B | N981BXXSIHYH3 | 0x14E | 11 | 18 | ❌ |
N985F | N986B | N986BXXSIHYH3 | 0x152 | 11 | 18 | ❌ |
N986B | N986B | N986BXXSIHYH3 | 0x14D | 11 | 18 | ❌ |
All ten supported Galaxy S20, S20 FE, and Note20 model flags have an opt-in
CLI-only KVM boot profile. Build a branch
of the Exynos 990 kernel
whose name contains kvm, and add --kvm to the exact-model command, for example:
python3 exploit/exploit.py --build-sboot --model G985F --no-fuse --kvm
This profile removes the LK H-Arx/UH path, asks EL3 to enter the kernel at EL2, and applies the matching decrypted/re-encrypted EL3 monitor patch table. It remains unavailable for stock/tampered bootloader flash modes. The web control center intentionally has no KVM control. With the matching kernel and WindowsInQemu, Windows can run in QEMU on the phone with full speed via KVM.
Do not treat every mode as one numbered installation sequence. Choose a goal:
| Goal | Path |
|---|---|
| Install a signed custom ROM | Exact model/setup → EUB → temporary --signed --no-fuse chain → flash the ROM's complete signed output → UFS first boot |
| Test the exploit | Optional --prepare --no-fuse → EUB → --signed --no-fuse → stop |
| Develop the boot chain (CLI only) | Temporary no-fuse test → build → flash generated SBoot/TZSW/LDFW → UFS |
| Dump / recovery | Use its separate workflow and fuse-state checks |
--prepare is a recommended dry-run, not a required predecessor: --signed
repeats preflight. The generated three-part Heimdall command is a boot-chain development tool; it is not a custom-ROM
flash.
Read USER_GUIDE.md and choose its matching workflow before touching a device. It includes the complete-ROM handoff plus the unfused, fused, and uncertain-state recovery rules.
The browser UI uses only Python's standard library and calls the existing
exploit/exploit.py CLI. Boot-chain development and its generated three-part Heimdall command remain terminal-only
tools.
Start it from the repository root:
python3 exynos990_control_center.py
The launcher binds to 127.0.0.1, generates a new access token, prints the full local URL, and opens it in the default
browser. Use --no-browser when a browser should not be opened automatically:
python3 exynos990_control_center.py --no-browser
The UI provides:
It also shows a CLI-only Exynos 990 KVM notice, but deliberately does not expose a KVM option or forward --kvm to any
web action.
USB access follows the permissions of the process that launched the control center. Configure the supplied udev/driver permissions before starting it. The UI does not request, retain, or forward privilege credentials. Keep the printed token URL private and stop the server immediately after use.
Terminal users can ignore exynos990_control_center.py; every CLI command documented below remains unchanged and fully
supported.
Python 3.10 or newer is required.
Windows 10/11 (native PowerShell):
.\windows\setup.ps1
. .\windows\activate.ps1
python .\exploit\exploit.py --prepare --model G985F --no-fuse
The setup installs a pinned native AArch64 toolchain, LZ4, Heimdall, and a repository virtual environment, then builds all payloads. The BootROM WinUSB driver is an explicit Administrator opt-in because its upstream self-signed certificate changes the machine trust stores. See WINDOWS.md for the complete setup, driver install, Download Mode distinction, verification, and troubleshooting process.
After Windows activation, use python wherever the remaining cross-platform examples show python3.
Linux:
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu lz4
macOS:
brew tap messense/macos-cross-toolchains
brew install aarch64-unknown-linux-gnu lz4
| Path | Purpose |
|---|---|
bootLoaderFiles/originalBl/<model>/ | Clean exact-model BL_<firmware>.tar packages for all ten models. |
bootLoaderFiles/sbootSplitParts_original/<model>/ | Untouched exact-model encrypted SBoot splits, ldfw.img, tzsw.img, manifest, and tail. |
bootLoaderFiles/exynos9830Decrypted/<model>/ | Exact-model decrypted EPBL, EL3, TZSW, and LDFW analysis files. |
bootLoaderFiles/tamperedLoader/<model>/ | Exact-model EUB-triggering BL packages. |
bootLoaderFiles/MODEL_COMPARISON.md | Exact-versus-coupled firmware comparison and patch compatibility notes. |
bootLoaderFiles/exynos990Bootrom/ | Shared Exynos 990 BootROM dump. |
bootromNotes/ | Shared BootROM flow and USB context notes. |
drivers/windows/winusb/ | Pinned Houston WinUSB package for BootROM/EUB 04e8:1234. |
windows/ | Native Windows setup, environment activation, and hash-checking driver installer. |
exploit/extra/images/<model>/ | Disposable model-specific preflight output. |
external/ghidra/ | Exact-model LK and KVM EL3 TSVs plus the Ghidra patch script. |
external/decompiled_G985F/ | G985F-only decompiled reference files. |
exynos990reverseEng_G985F/ | G985F-only Ghidra project. |
external/keys/exynos9830_crecker/ | Shared custom-key bundle. |
exploit/exploit.py | Stable CLI entry point and workflow coordinator. |
exploit/build_payloads.py | Cross-platform native payload builder used by preflight on Windows, Linux, and macOS. |
exploit/preflight.py | Working-image preparation, LK patching, signing, and merge verification. |
exploit/usb_transport.py |
Preparation, signing, and payload modes run the same model-aware preflight:
exploit/extra/images/<model>/ with a clean copy of the untouched encrypted split images.--no-fuse, generate an effective TSV copy with the
five fusing rows disabled first. With --kvm, also enable the LK rows marked kvm, decrypt and patch the matching
EL3 monitor TSV, and re-encrypt its protected region.mem.bin, loader.bin, and Exynos990_boot_custom_key.bin.The process stops on the first firmware, patch, metadata, or signature mismatch. It never patches the immutable source directories in place.
All commands require --model.
| Mode | Purpose |
|---|---|
--prepare | Run preflight without opening USB. |
--build-sboot | Run preflight and build a verified signed sboot.bin in the model image directory. |
--signed | Send the custom-key payload and signed boot chain from EUB. |
--ufs | Start the UFS boot path with loader.bin. |
--dump | Run mem.bin and dump 0x20000 bytes of BootROM. |
--flash-tampered | Validate exact-model UH and flash it to BOOTLOADER to force EUB. |
--flash-stock | Extract and flash exact-model stock SBoot, TZSW, and LDFW from the original BL tar. |
--no-fuse is a modifier, not a standalone mode. It disables five identified custom-key OTP rows while rebuilding the
working LK. Use it for every command that prepares, sends, or builds an unfused development chain. It does not undo an
existing fuse.
The CLI accepts the modifier with UFS and dump modes because those commands also run preflight, but their USB operations
do not transmit the rebuilt LK. The LK already flashed on the phone determines UFS fuse behavior. Consequently, the UI
deliberately offers no no-fuse control for UFS or BootROM dump mode. Both bootloader flash modes reject --no-fuse
because they perform no LK patching or signing.
--kvm is also a modifier. It is accepted with every exact-model workflow that runs preflight. KVM rows in the TSVs are
ignored unless this flag is present, and the browser UI never supplies it.
Example:
python3 exploit/exploit.py --signed --model N986B --no-fuse
Generate the corresponding signed bootloader without opening USB:
python3 exploit/exploit.py --build-sboot --model N986B --no-fuse
The command rebuilds the model image directory from clean stock inputs, applies the LK patch, signs and verifies every
component, merges sboot.bin, checks the embedded components and tail, and prints its size, SHA-256, and a Heimdall
command that sends sboot.bin, signed tzsw.img, and signed ldfw.img.
Only on a device known to be unfused, restore the exact stock boot chain from the selected model's original BL tar:
python3 exploit/exploit.py --flash-stock --model N986B --wait
The command extracts only sboot.bin.lz4, tzsw.img.lz4, and
ldfw.img.lz4, decompresses them in a temporary directory, verifies all three outputs are present and non-empty, and
invokes one Heimdall flash operation. The temporary files are removed afterward. --no-reboot and --verbose are also
supported. The phone must already be in a Heimdall-compatible download mode, and the selected model must exactly match
the physical device.
This does not restore Android, AP, modem, CSC, userdata, or a complete stock ROM. Never run it on a custom-key-fused device. Such a phone requires stock-based software re-signed with the exact fused key; the custom trust root remains permanent. If fuse state is unknown, stop.
[!CAUTION] This procedure is only for a device that you personally own and are authorized to service. Using it on another person's device is strictly prohibited. A wrong partition path can cause permanent data loss or leave the device unable to boot. Back up the target partition and verify its resolved block-device path and size before writing anything.
This repository does not automatically remove Factory Reset Protection (FRP). On devices that use Android's
PersistentDataBlockService, FRP state is stored in the partition commonly named PERSISTENT. See the
AOSP implementation.
After the exploit chain has booted a custom recovery that provides adb and
dd, identify and back up the partition. Do not substitute a guessed numeric block-device path:
adb shell ls -l /dev/block/by-name/PERSISTENT
adb shell dd if=/dev/block/by-name/PERSISTENT of=/tmp/PERSISTENT.backup.img bs=4096
adb pull /tmp/PERSISTENT.backup.img
Only after the backup has been pulled, zero the partition and let Android initialize a fresh persistent-data-block structure:
adb shell
dd if=/dev/zero of=/dev/block/by-name/persistent
reboot
This method is tested and works, FRP is removed and the device is unlocked.
Patch selection follows the artifact mapping:
G780F -> lk_g780f_selected_patches.tsv
G980F -> lk_g980f_selected_patches.tsv (applied to G981B LK)
G981B -> lk_g981b_selected_patches.tsv
G985F -> lk_g985f_selected_patches.tsv (applied to G986B LK)
G986B -> lk_g986b_selected_patches.tsv
G988B -> lk_g988b_selected_patches.tsv
N980F -> lk_n980f_selected_patches.tsv (applied to N981B LK)
N981B -> lk_n981b_selected_patches.tsv
N985F -> lk_n985f_selected_patches.tsv (applied to N986B LK)
N986B -> lk_n986b_selected_patches.tsv
Validate a TSV against stock LK without changing it:
python3 external/tools/apply_lk_patches.py \
bootLoaderFiles/sbootSplitParts_original/G986B/lk.bin \
external/ghidra/lk_g986b_selected_patches.tsv \
--check
external/ghidra/ApplyLkPatches.java accepts the same six-column TSV format and now fails on old-byte mismatches
instead of applying a patch blindly. Rows whose first column is kvm require an additional --kvm script argument.
The legacy check_signature and check_ext4_signature return-zero rows use
profile 0: they document the old bypass locations but are deliberately not
applied, so built images must satisfy LK's real Samsung signature checks.
external/tools/sign_sboot_images.py requires a model and derives the model ID, EVT, and rollback revision from
model_data.py:
python3 external/tools/sign_sboot_images.py \
--images-dir exploit/extra/images/G986B \
--keys-dir external/keys/exynos9830_crecker \
--model G986B
| Image | Custom signing key |
|---|---|
fwbl1.img | BL1 private key plus Stage2 TEE/REE public blobs |
epbl.img, el3_mon.img | Stage2 TEE |
bl2.img, lk.bin | Stage2 REE |
ldfw.img, tzsw.img | Stage2 TEE, inner and outer Stage2 footers |
The Stage2 signatures are verified after signing. The tool does not regenerate Samsung AVB metadata for ldfw.img or
tzsw.img; changing secure-boot bytes inside those wrappers still requires the separate AVB policy used by the target
boot flow. A complete signed ROM must use the exact AVB model and the same key bundle, then be flashed with its complete
generated package. See the
CreckerROM repository
and the installation workflow in USER_GUIDE.md.
The included tampered packages preserve every stock BL member except
sboot.bin.lz4. That member is removed and the package's decompressed
uh.bin is stored as sboot.bin, matching the EUB-triggering layout.
The UI can perform the corresponding Heimdall flow directly. It selects the exact physical model's tampered archive,
verifies that sboot.bin is byte-identical to decompressed uh.bin.lz4, and flashes the validated UH payload to the
BOOTLOADER slot:
python3 exploit/exploit.py --flash-tampered --model G986B --wait
This intentionally prevents normal boot and forces the next boot into EUB. It does not flash the remaining members of the BL tar.
Regenerate one package with:
python3 external/tools/build_tampered_loader.py \
bootLoaderFiles/originalBl/G986B/BL_G986BXXSNHYB1.tar \
bootLoaderFiles/tamperedLoader/G986B/BL_G986BXXSNHYB1_tampered.tar
Use the exact physical model's tampered loader when its directory is present. The coupled runtime mapping applies to exploit preflight and signing, not to the archived stock/tampered BL package selection.
Keep in mind that if you fused the device, you will need to flash a signed uh.bin to your BOOTLOADER slot, since the stock uh is currently signed with the wrong key.
Split and merge:
python3 exploit/split.py sboot.bin -o /tmp/G986B-splits
python3 exploit/merge.py /tmp/G986B-splits
The standalone merger requires tzsw.img and ldfw.img in the parts directory and prints the corresponding three-part
Heimdall command. Only use that command when those two images have already been signed for the selected model;
--build-sboot performs and verifies that signing automatically.
Extract individual LDFW records:
python3 external/tools/extract_ldfw.py ldfw.img -o LDFWs
The supplied split layout reconstructs every canonical stock sboot.bin
byte-for-byte. EPBL and EL3 monitor decrypt/re-encrypt also round-trip byte-for-byte for all firmware families when the
EPBL header is left unchanged.
All supported phones share the same Exynos 990 BootROM. The payloads use common BootROM entry points and IRAM addresses rather than model-specific LK offsets. The generated binaries resolve to these entry points:
| Payload | Exploit jump | Linked entry |
|---|---|---|
mem.bin | 0x02022010 | 0x02022010 |
loader.bin | 0x02022010 | 0x02022010 |
Exynos990_boot_custom_key.bin | 0x02022000 | position-independent stage 1 |
Model-specific behavior is confined to the LK TSV, FWBL1 model ID, and stock rollback revision.
| Part | Start | End |
|---|---|---|
fwbl1.img | 0x000000 | 0x003000 |
epbl.img | 0x003000 | 0x016000 |
bl2.img | 0x016000 | 0x082000 |
lk.bin | 0x0DB000 | 0x35B000 |
el3_mon.img | 0x35B000 | 0x39B000 |
| Stage | Load address |
|---|---|
| BL1 | 0x02022000 |
| EPBL | 0x02026000 |
| BL2 | 0x15600000 |
| LK | 0xE8000000 |
| EL3 monitor | 0xBFE80000 |
halal-beef), through
halal-beef/hubble: the backend code used by exploit/exploit.py; the SoC
layout used by exploit/split.py and
exploit/merge.py; and run_exploit(), which implements the address/overwrite operation.VDavid003/exynos-usbdl: the payload skeleton from which the Exynos990
custom-key payload was derived.| PyUSB framing, device discovery, overwrite, and dump transport. |
exploit/tampered_loader.py | Exact-model UH extraction, tampered-loader validation, and Heimdall EUB flash. |
exploit/stock_restore.py | Exact-model stock archive extraction and Heimdall command construction. |
control_center/ | Browser backend actions, dependency checks, jobs, and HTTP API. |
external/tools/*_crypto.py | Shared EPBL/EL3 AES and ECDSA encoding/signature primitives. |