Reverse AMD Zen microcode updates inside Binary Ninja.
Open a microcode .bin, run one command, and the update container is typed
and labelled. Zen 1 and Zen 2 payloads are disassembled and lifted to LLIL and HLIL.
Zen 5 updates get their real register geometry and every 36-byte op-quad decoded in the
Linear view.
AMD ships CPU microcode as small signed blobs. The container format (header, RSA material, options, match registers, then the microcode itself) is the same across generations, but the payload is not. Zenella reads the header, picks the right profile and applies it to the bytes in front of you.
Nothing is executed, decrypted or signed. The plugin only defines types, data variables, symbols and comments in the current database, and every apply runs in one undoable transaction, so a wrong guess costs you a Ctrl‑Z.
The decoder lives in zenella_core.py and has no Binary Ninja dependency.
The same code drives zenella_inspect.py, a command-line tool that prints the
structural report or JSON for a file without opening the GUI.
0xC80-byte updates. The 64 instruction packages become a Binary Ninja architecture: disassembly in the ZenUtils style, LLIL lifting, and MLIL/HLIL from there. Cross references and graph view work as for any other target.
0x3820-byte updates. The loader ID selects the geometry. For loader 0x8015 the register area holds 31 match and 31 mask words, so the op-quads start at 0x420 and run to the zero padding. Each op-quad is four 64-bit micro-ops plus a sequence word, rendered with the opcode enum names.
zenella_inspect.py prints the same regions, op-quads, sequence words
and alignment diagnostics as text or JSON. Useful for diffing updates or checking a
layout before touching a database.
Every Zen 5 update is 0x3820 bytes. The map below is the layout applied for loader 0x8015 (Zen 5c). Segment heights follow the byte sizes, with a minimum so the small ones stay readable.
| Symbol | Offset | Size | Type | Contents |
|---|---|---|---|---|
amd_mc_header |
0x0000 |
0x20 | AMD_MC_Header |
Date, revision, loader ID, patch size, CPUID, flags |
amd_mc_signature |
0x0020 |
0x100 | u8[256] |
RSA signature |
amd_mc_modulus |
0x0120 |
0x100 | u8[256] |
Public key modulus |
amd_mc_check |
0x0220 |
0x100 | u8[256] |
Check block, likely a Montgomery constant |
amd_mc_options |
0x0320 |
0x04 | AMD_MC_UcodeOptions |
autorun, encrypted flag, loader ID. This copy of the loader ID selects the layout |
amd_mc_rev |
0x0324 |
0x04 | u32 |
Second copy of the update revision |
amd_mc_match_regs |
0x0328 |
0x7C | AMD_MC_MatchRegisterBlock |
31 match registers |
amd_mc_mask_regs |
0x03A4 |
0x7C | AMD_MC_MaskRegisterBlock |
31 mask registers |
amd_ucode_body |
0x0420 |
0x2A30 | AMD_Zen5_OpQuad[300] |
Op-quads up to the zero padding. The NOP run and the finalization sequence are carved out as
amd_mc_nop_section and amd_mc_finalization_section |
amd_mc_zero_padding |
0x2E50 |
0x9D0 | u8[2512] |
Trailing zeros |
The op-quad count follows the padding, so it can differ between updates. 300 is what the two reference updates (revisions 0x0B10104E and 0x0B101054) contain. Loader 0x8010 keeps a searched equal match/mask split; 0x8004 and 0x8005 use zentool's fixed tables.
| Symbol | Offset | Size | Type | Contents |
|---|---|---|---|---|
amd_mc_header | 0x0000 | 0x20 | AMD_MC_Header | Same header as Zen 5 |
| signature, modulus, check | 0x0020 | 0x300 | u8[256] × 3 | Same crypto blocks as Zen 5 |
amd_mc_options | 0x0320 | 0x04 | AMD_Zen12_UcodeOptions | autorun, encrypted, two unknown bytes |
amd_mc_revision_copy | 0x0324 | 0x04 | u32 | Second copy of the revision |
amd_zen12_match_entries | 0x0328 | 0x58 | AMD_Zen12_MatchEntry[22] | Two 13-bit match addresses per word plus enable bits |
amd_zen1_ucode / amd_zen2_ucode | 0x0380 | 0x900 | AMD_Zen12_InstructionPackage[64] | 64 packages of four 64-bit instructions and a sequence word; mapped as code and lifted |
Everything sits under Plugins → AMD Microcode. Each apply command comes in two forms: at file start for a standalone update and at cursor for an update embedded in a larger image, where the cursor marks the header.
Reads the header, decides between Zen 1, Zen 2 and Zen 5, and runs the matching apply. This is the one to start with.
Types the container, maps the 64 packages as executable code under the Zen 1 or Zen 2 architecture and lets Binary Ninja lift it. Pick the generation yourself if auto-detect has no opinion.
Prints a plain-text listing of the match registers and all packages in the format ZenUtils users know, as a report tab.
Applies the confirmed loader 0x8015 layout: 31 match and 31 mask registers at 0x328, op-quads from 0x420 to the zero padding. Defines the types, creates the data variables and symbols from the table above, and verifies the result against the bytes before the transaction commits. Other loader IDs get their own geometry.
Four alternative Zen 5 geometries for research. None of them is the default and none carries documented evidence; they exist so a hypothesis can be applied and compared quickly.
The report the plugin builds is also available from a shell. Only the standard library is used.
python3 zenella_inspect.py update.bin # regions, op-quads, sequence words python3 zenella_inspect.py update.bin --json # everything as JSON python3 zenella_inspect.py update.bin --layout exact # force an alternative geometry python3 zenella_inspect.py image.bin --base 0x1000 # embedded update
All structures are packed. Names from Zenella 1.2 are kept, so scripts and existing databases keep working; new names are prefixed with the generation.
AMD_MC_Header 0x20 bytes, shared by all generationsu16 | year |
u8 | day |
u8 | month |
u32 | update_revision |
AMD_MC_LoaderIdTag | loader_id |
u16 | size_of_patch |
u32 | minimum_patch_level |
u16 | nb_ven |
u16 | nb_dev |
u16 | sb_ven |
u16 | sb_dev |
AMD_MC_CpuId | proc_sig |
u8 | bios_revision |
u8 | flags |
u8 | reserved |
u8 | reserved2 |
The CPUID in proc_sig is expanded and commented with the matching
processor description from cpuid_descriptions.json.
AMD_Zen5_MicroOp64 8 bytes, bitfields| Bits | Field |
|---|---|
0–15 | imm16 |
16–20 | imm_flags |
21–25 | rt |
26–30 | rs |
31–35 | rd |
36–41 | flags |
42–44 | size |
45 | load |
46 | store |
47–54 | opcode (AMD_Zen_Opcode) |
55–58 | mid |
59–61 | exec_unit (spec, br, ld, stn, st, regx, reg) |
62–63 | hi |
AMD_Zen5_OpQuad 36 bytesAMD_Zen5_MicroOp64 | uop0 … uop3 |
u32 | sequence_word |
The Linear view shows each micro-op with its opcode name, class and a zentool-style operand projection. The sequence word is annotated, not lifted.
AMD_MC_Patch 0x3820 bytes, Zen 5 containerAMD_MC_Header | header |
u8[256] | signature |
u8[256] | modulus |
u8[256] | check |
AMD_MC_UcodeOptions | options |
u32 | rev |
AMD_MC_MatchRegisterBlock | match_regs |
AMD_MC_MaskRegisterBlock | mask_regs |
AMD_Zen5_OpQuad[300] | body |
u8[2512] | zero_padding |
Also registered under the generation-specific name
AMD_Zen5_Patch. The array length and padding size follow the update.
AMD_MC_UcodeOptions 4 bytesu8 | autorun |
u8 | encrypted |
u16 | loaderid |
AMD_MC_LoaderIdTag u16 enum0x8004 | AMD_MC_LOADER_8004 |
0x8005 | AMD_MC_LOADER_8005 |
0x8010 | AMD_MC_LOADER_8010 |
0x8015 | AMD_MC_LOADER_8015 |
0x8016 | AMD_MC_LOADER_8016 |
AMD_MC_MatchRegisterBlock 124 bytesu32[31] | match_reg |
AMD_MC_MaskRegisterBlock 124 bytesu32[31] | mask_reg |
Sizes shown are for loader 0x8015. Other loaders get scoped block types of their own width.
AMD_Zen12_InstructionPackage 36 bytesu64 | uop0 … uop3 |
u32 | sequence_word |
AMD_Zen12_MatchEntry 4 bytesu32 | raw |
AMD_Zen12_Patch 0xC80 bytesAMD_MC_Header | header |
u8[256] × 3 | signature, modulus, check |
AMD_Zen12_UcodeOptions | options |
u32 | revision_copy |
AMD_Zen12_MatchEntry[22] | match_entries |
AMD_Zen12_ExecutablePayload | payload |
AMD_Zen_Opcode u16 enum, bits 47–54 of a micro-opNames come from the published ZenUtils research. Values above 0xFF are synthetic: a load/store op is identified by its class, not by the opcode bits.
| Class dependent | |
|---|---|
0x00 | AMD_ZEN_UOP_LD_ST_00 |
0x100 | AMD_ZEN_LD |
0x101 | AMD_ZEN_ST |
0x05 | AMD_ZEN_BR_JMP |
| Special | |
0xFF | AMD_ZEN_SPEC_NOP |
0xDE | AMD_ZEN_TYPE5_READ |
| Register ops | |
|---|---|
0x19 | AMD_ZEN_REG_NSUB |
0x30 | AMD_ZEN_REG_AND |
0x40 | AMD_ZEN_REG_SHL |
0x41 | AMD_ZEN_REG_BLL |
0x42 | AMD_ZEN_REG_ROL |
0x44 | AMD_ZEN_REG_RLC |
0x46 | AMD_ZEN_REG_RRD |
0x47 | AMD_ZEN_REG_SRC |
0x48 | AMD_ZEN_REG_SHR |
0x4A | AMD_ZEN_REG_ROR |
0x4C | AMD_ZEN_REG_RRC |
0x4F | AMD_ZEN_REG_SRD |
0x50 | AMD_ZEN_REG_SUB |
0x52 | AMD_ZEN_REG_SBB |
| Register ops, continued | |
|---|---|
0x55 | AMD_ZEN_REG_NADD |
0x5C | AMD_ZEN_REG_ADD2 |
0x5D | AMD_ZEN_REG_ADC |
0x5E | AMD_ZEN_REG_ADD3 |
0x5F | AMD_ZEN_REG_ADD |
0x6F | AMD_ZEN_REG_VZEROUPPER_64B |
0x70 | AMD_ZEN_REG_POPCNT |
0x72 | AMD_ZEN_REG_SBIT |
0x7F | AMD_ZEN_REG_VZEROUPPER_32B |
0x93 | AMD_ZEN_REG_MOV2 |
0xA0 | AMD_ZEN_REG_MOV_SREG |
0xA9 | AMD_ZEN_REG_BSWAP |
0xB5 | AMD_ZEN_REG_XOR |
0xBE | AMD_ZEN_REG_OR |
Zenella is a plugin folder, not a single file. It needs nothing beyond the Python that ships with Binary Ninja.
In Binary Ninja choose Plugins → Open Plugin Folder…, or go straight there:
~/Library/Application Support/Binary Ninja/plugins/ # macOS %APPDATA%\Binary Ninja\plugins\ # Windows ~/.binaryninja/plugins/ # Linux
Clone or download ercihan/zenella
and place the whole directory in the plugin folder. Remove any older single-file copy of
amd_zen_ucode.py first; two copies would both register the menu.
cd ~/Library/Application\ Support/Binary\ Ninja/plugins/ git clone https://github.com/ercihan/zenella.git
The AMD Microcode submenu appears under Plugins. The log shows the loaded version and the path it was loaded from, which is handy if a stale copy is still around.
Load the .bin as a raw file. For an update inside a firmware image, open the
image and place the cursor on the update header.
Plugins → AMD Microcode → Auto-detect and apply at file start, or the cursor variant. The log tells you which profile was chosen and why.
In Linear view amd_mc_header shows date, revision, loader ID and the expanded
CPUID with the processor name as a comment.
Zen 1 and Zen 2: jump into the lifted packages and use graph or HLIL view. Zen 5:
scroll amd_ucode_body; every op-quad is decoded, and the NOP run and the
finalization sequence are labelled so you can skip them.
Zen 5 micro-op semantics are undocumented. The plugin names opcodes and fields from the published research and shows sequence words as stored; it does not claim to execute or lift Zen 5 code. The experimental menu exists precisely because some of the geometry is still being worked out.