BINARY NINJA
Typed data, decoded structures, symbols, cross-references and graphs where supported. Work inside the analysis UI you already know.
CPU microcode isn't supposed to be readable. We read it anyway.
A home for AMD low-level reversing, binary analysis, and the things hiding below the ISA. Powered by Zenella — a Binary Ninja plugin for taking AMD microcode updates apart, byte by byte.
THE TOOL / ZENELLA
Zenella reverses AMD microcode updates inside Binary Ninja. Open a microcode .bin, run one command, and the update container is typed and labelled. K8/K10 patches (families 0x0F–0x12) have their obfuscated triads decoded and disassembled. Zen 1/2 payloads are disassembled and lifted to LLIL and HLIL. Zen 5 updates get register geometry and decoded 36-byte op-quads in Linear view.
Zen updates share a signed container: header, RSA material, options, match registers, then microcode. Earlier K8/K10 patches use a smaller 0x40-byte header-and-match-register container followed by obfuscated triads. Zenella identifies the profile from the header and applies it to the bytes in front of you.
Nothing is executed, decrypted or signed by the plugin. It defines types, data variables, symbols, and comments in your analysis database. Apply operations are one undoable transaction; a bad hypothesis costs a Ctrl + Z, not a corrupted analysis.
Typed data, decoded structures, symbols, cross-references and graphs where supported. Work inside the analysis UI you already know.
zenella_core.py and prezen_core.py work without Binary Ninja. The same core drives command-line inspection and JSON output.
Data-only analysis and reversible changes. Verification runs before applying a Zen 5 structural transaction.
Zen 5 field names and sequence flow come from published research and experiments. Undocumented semantics stay explicitly labelled.
GENERATIONS / BINARY FORMATS
From scrambled K8 triads to Zen 5c op-quads. One toolkit, multiple generations of AMD update formats.
Pre-Zen patches are 0x40 + 28·N bytes. Each scrambled triad holds three 64-bit micro-ops and a sequence word. Zenella de-obfuscates the triads, mirrors them into an executable patch RAM section at uc_0c00, and follows match-register handlers and branches.
Each update has 64 instruction packages. The packages become executable code in the amd_zen1_ucode or amd_zen2_ucode architecture. Micro-ops are decoded in the ZenUtils style and lifted to LLIL, from which Binary Ninja derives MLIL and HLIL.
Loader 0x8015 uses 31 match and 31 mask words. From offset 0x420, 36-byte op-quads contain four 64-bit micro-ops plus a sequence word. The Linear view exposes opcode names and operands; an experimental report reconstructs sequence order on decrypted samples.
! Zen 5 micro-op encodings and operand projections are empirical inferences, not a vendor-confirmed ISA. Its sequence-ordered report is read-only and experimental.
HEX IS A MAP
Three layouts, exact offsets and structures from Zenella. Select a generation to inspect the decoded file map.
| Offset | Symbol / type | Bytes |
|---|---|---|
| 0x0000 | amd_mc_header | 0x20 |
| 0x0020 | amd_mc_signature | 0x100 |
| 0x0120 | amd_mc_modulus | 0x100 |
| 0x0220 | amd_mc_check | 0x100 |
| 0x0320 | amd_mc_options | 0x04 |
| 0x0324 | amd_mc_rev | 0x04 |
| 0x0328 | amd_mc_match_regs | 0x7C |
| 0x03A4 | amd_mc_mask_regs | 0x7C |
| 0x0420 | amd_ucode_body | 0x2A30 |
| 0x2E50 | amd_mc_zero_padding | 0x9D0 |
0x0B10104E and 0x0B101054). The count follows the actual zero padding. Loader 0x8010 uses a searched equal match/mask split; 0x8004 / 0x8005 use zentool's fixed tables.| Offset | Symbol / type | Bytes |
|---|---|---|
| 0x0000 | amd_mc_header | 0x20 |
| 0x0020 | signature / modulus / check | 0x300 |
| 0x0320 | AMD_Zen12_UcodeOptions | 0x04 |
| 0x0324 | amd_mc_revision_copy | 0x04 |
| 0x0328 | amd_zen12_match_entries | 0x58 |
| 0x0380 | amd_zen1_ucode / amd_zen2_ucode | 0x900 |
| Offset | Symbol / type | Bytes |
|---|---|---|
| 0x0000 | amd_prezen_header | 0x20 |
| 0x0020 | amd_prezen_match_regs | 0x20 |
| 0x0040 | amd_prezen_triads_raw | 28 × N |
| +0x15000 | uc_0c00 → executable mirror | 28 × N |
ONE MENU. MANY WAYS IN.
All apply commands support file start for standalone updates and at cursor for firmware-embedded updates.
Reads the header, picks K8/K10, Zen 1, Zen 2 or Zen 5, and applies the matching structure. Start here; the log explains the chosen profile.
Plugins → AMD MicrocodeTypes the container and obfuscated triads; mirrors decoded triads under amd_prezen_ucode at uc_0c00. Handler functions, conditional jumps and sequence-word branches become edges.
K8 / K10Read-only report: header, match registers, every decoded triad, raw words and sequence word, using the same text as GhidraAmdMicrocode's K8/K10 specification.
Pre-Zen → ListingMaps 64 executable packages into the matching Binary Ninja architecture. Cross-references, graph view and lifting work like any other supported target.
Zen1 / Zen2 → ApplyA plain-text report of Zen 1/2 match registers and packages, rendered in a ZenUtils-compatible listing format.
Zen1-Zen2 → ListingApplies loader 0x8015 geometry: 31 match + 31 mask registers at 0x328, op-quads from 0x420. Adds types, data variables, symbols, opcode names and annotations; checks the result before committing.
Zen5 → ApplyFour alternative Zen 5 research layouts, plus two read-only sequence-ordered reports. The flow view follows observed patch targets and match-register hooks; it does not change the database.
Zen5 → ExperimentalThe experimental Zen 5 listing begins at the observed patch RAM base 0x1f00. A target inside 0x1f00 … 0x1f00 + quads continues in-patch; a match-register target re-enters at that hook's quad. ROM targets terminate a run. Expect many short runs: each update contains multiple hooks, and the CPU's ROM microcode is not present in the file.
NO GUI REQUIRED
zenella_inspect.py prints the same regions, op-quads, sequence words and alignment diagnostics as text or JSON. Run prezen_core.py directly for K8/K10 decoded triads. The decoders have no Binary Ninja dependency.
Sequence order uses inferred rules and is intended for research, not as a proven execution model.
# Structural regions, op-quads & sequence words
python3 zenella_inspect.py update.bin
# Export machine-readable JSON
python3 zenella_inspect.py update.bin --json
# Force layout / inspect an embedded update
python3 zenella_inspect.py update.bin --layout exact
python3 zenella_inspect.py image.bin --base 0x1000
# Experimental sequence-ordered micro-ops
python3 zenella_inspect.py update_dec.bin --flow
python3 zenella_inspect.py update_dec.bin --flow-detail
python3 zenella_inspect.py update_dec.bin --flow --patch-base none
# Pre-Zen: header, match registers, triads
python3 prezen_core.py k10_patch.binSTRUCTURE IS SIGNAL
A compact reference to the packed types and bitfields Zenella applies. Expand any structure to inspect its fields.
Shared across Zen generations. The proc_sig CPUID is expanded and annotated using cpuid_descriptions.json.
| Type | Field |
|---|---|
| u16 | year |
| u8 | day, month |
| u32 | update_revision |
| AMD_MC_LoaderIdTag | loader_id |
| u16 | size_of_patch |
| u32 | minimum_patch_level |
| u16 | nb_ven, nb_dev, sb_ven, sb_dev |
| AMD_MC_CpuId | proc_sig |
| u8 | bios_revision, flags, reserved, reserved2 |
Empirical 64-bit encoding. Bit zero is least significant. Names derive from published ZenUtils / zentool research.
| 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 |
| Type | Field |
|---|---|
| AMD_Zen5_MicroOp64 × 4 | uop0 … uop3 |
| u32 | sequence_word |
Linear view displays opcode name, class and inferred operands. The sequence word is annotated, not lifted.
| Type | Field |
|---|---|
| AMD_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 (reference updates) |
| u8[2512] | zero_padding (reference updates) |
Also registered as AMD_Zen5_Patch; body length and padding follow the actual update, and can differ.
AMD_MC_UcodeOptions (4 bytes): u8 autorun, u8 encrypted, u16 loaderid.
AMD_MC_LoaderIdTag is a u16 enum:
| Value | Identifier |
|---|---|
| 0x8004 | 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 = u32 match_reg[31]. AMD_MC_MaskRegisterBlock = u32 mask_reg[31]. Each is 124 bytes for loader 0x8015; other loaders have scoped widths.
AMD_Zen12_InstructionPackage (36 bytes): u64 uop0…uop3 + u32 sequence_word. AMD_Zen12_MatchEntry (4 bytes): u32 raw.
| Type | AMD_Zen12_Patch field |
|---|---|
| AMD_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 |
| Type | Field |
|---|---|
| u32 | date (BCD: 0xMMDDYYYY) |
| u32 | patch_level |
| AMD_PreZen_LoaderIdTag | loader_id |
| u8 | op_triad_count |
| u8 | init_flag |
| u32 | op_triad_checksum |
| u32 | nb_dev_id |
| u8[4] | unknown0 |
| u16 | cpuid (equivalence ID) |
| u8[6] | unknown1 |
Loader enum: 0x8000 PREZEN_K8_K10_0x8000, 0x8003 PREZEN_FAMILY12_0x8003.
AMD_PreZen_MatchRegister: u32 raw, low 16 bits identify the ROM triad (0xFFFF means unused).
AMD_PreZen_TriadObfuscated: u32 obfuscated_word[7].
AMD_PreZen_Triad: three u64 ops (op0…op2) plus a u32 sequence_word. Decoding happens per instruction using its original address, and the sequence controls branches or completion.
| Bits | Field |
|---|---|
| 54–62 | operation (9 bits) |
| 59–62 | hi4 = 5 marks jcc |
| 54–58 | cc (32 condition codes) |
| 52 | 3op |
| 46–51 | dst (64 register names) |
| 30–31 | size (.b .w .d .q) |
| 24–29 | src1 |
| 23 | regmode (1=register, 0=imm16) |
| 9–14 | src2reg |
| 0–15 | imm |
| 0–11 | braddr (jcc target triad) |
Sequence actions: 0 → sw_next, 1 → sw_complete, 2 → sw_branch (target in bits 0–11), others → sw_raw.
Recognized operations include add, or, adc, sbb, and, sub, xor, cmp, test, rll, rrl, sll, srl, writepc, mov, mul, imul, muleh, mulel, div1, div2, ld, st, lea, bswap, not, crash. Unknown operations display as unk_op. Register names follow the GhidraAmdMicrocode specification.
Names come from published ZenUtils research. Values above 0xFF are synthetic; load/store class does not follow just the opcode bits.
| Value | Opcode |
|---|---|
| 0x00 | UOP_LD_ST_00 |
| 0x100 | LD (synthetic) |
| 0x101 | ST (synthetic) |
| 0x05 | BR_JMP |
| 0xFF | SPEC_NOP |
| 0xDE | TYPE5_READ |
| 0x19 | REG_NSUB |
| 0x30 | REG_AND |
| 0x40 | REG_SHL |
| 0x41 | REG_BLL |
| 0x42 | REG_ROL |
| 0x44 | REG_RLC |
| 0x46 | REG_RRD |
| 0x47 | REG_SRC |
| 0x48 | REG_SHR |
| 0x4A | REG_ROR |
| 0x4C | REG_RRC |
| 0x4F | REG_SRD |
| 0x50 | REG_SUB |
| 0x52 | REG_SBB |
| Value | Opcode |
|---|---|
| 0x55 | REG_NADD |
| 0x5C | REG_ADD2 |
| 0x5D | REG_ADC |
| 0x5E | REG_ADD3 |
| 0x5F | REG_ADD |
| 0x6F | REG_VZEROUPPER_64B |
| 0x70 | REG_POPCNT |
| 0x72 | REG_SBIT |
| 0x7F | REG_VZEROUPPER_32B |
| 0x93 | REG_MOV2 |
| 0xA0 | REG_MOV_SREG |
| 0xA9 | REG_BSWAP |
| 0xB5 | REG_XOR |
| 0xBE | REG_OR |
Displayed register-operation names omit the common AMD_ZEN_ prefix for readability.
Packed structures preserve older names for existing databases and scripts. New identifiers are generation-prefixed, and K8/K10 types use AMD_PreZen_.
GET HANDS-ON
Zenella is a plugin folder, not a single Python file. It runs on the Python runtime shipped with Binary Ninja. The standalone inspectors need only Python 3.
In Binary Ninja, select Plugins → Open Plugin Folder… or navigate to your platform's path.
~/Library/Application Support/Binary Ninja/plugins/%APPDATA%\Binary Ninja\plugins\~/.binaryninja/plugins/Place the entire zenella directory in the plugins folder. Remove any older standalone amd_zen_ucode.py copy to avoid duplicate menus.
Check Plugins → AMD Microcode. The log reports the loaded Zenella version and module path.
Load a .bin as raw bytes, run Auto-detect and apply at the beginning of the file, or move the cursor to the header of an embedded update.
git clone https://github.com/ercihan/zenella.gitBinary Ninja Desktop with Python scripting enabled. No third-party Python packages. CLI inspectors use Python 3 and the standard library.
HOW TO ACTUALLY USE IT
Use each architecture's strongest supported view. Do not treat inferred Zen 5 control flow as proven silicon behavior.
Open the raw update (or place the cursor on its embedded header) and run auto-detect. The log reports which profile was chosen and why.
Find amd_mc_header in Linear view. Read the revision, loader ID, CPUID and processor-name comment.
K8/K10: uc_0c00 and handler labels. Zen 1/2: graph or HLIL view on mapped packages. Zen 5: amd_ucode_body, with NOP and finalization sections labelled.
Use the standalone JSON and --flow reports to compare updates. On decrypted Zen 5 patches, the experimental listing can follow sequence-word chains rather than file order.
Zen 5 semantics remain undocumented: opcode labels and operand projection are inferred, and Zen 5 code is not claimed to be executed or lifted to IL. K8/K10 triads are disassembled with control-flow edges but not data-flow lifted. That's the point of a research tool: expose the evidence without inventing certainty.
Read the bytes. Share the findings. Keep reversing.