Sign in

Architectures and platforms

A vendor firmware image for a Zyxel VMG8825-T50K VDSL2 gateway is a 26 MiB zip. Inside it is a proprietary "2RDH" container, inside that a compressed kernel and a SquashFS root filesystem, and unpacking the lot yields 1,683 files. 538 of them are ELF executables, and every one looks like this:

$ xxd -l 32 bin/busybox
00000000: 7f45 4c46 0102 0100 0000 0000 0000 0000  .ELF............
00000010: 0002 0008 0000 0001 0040 3250 0000 0034  .........@2P...4
          ^^^^ ^^^^           ^^^^^^^^^
          |    e_machine 8    e_entry = 0x00403250
          e_type = 2 (EXEC)   (EM_MIPS)

Byte 5 is 02: big-endian. Byte 4 is 01: 32-bit. file adds "no section header" — the link view was stripped. Nothing in this file is x86, nothing is little-endian, and nothing about it resembles the desktop binaries most tooling is written for. 537 of the 538 are in the fixture corpus, and all 537 decode clean: 9,963,208 instructions across the image, zero unknown opcodes.

The 538th, bin/cfg, is a 53,804-byte MIPS32 big-endian executable that never reaches a decoder. The corpus harvester filters by file extension before it looks at the magic, and takes the last dot-separated component of a name as that extension — for a file with no dot, the whole name. cfg is on its non-binary list, alongside sh, conf, key, out, py, js, lua, ini and md, so bin/cfg and bin/sh — the canonical extensionless binary in any firmware rootfs — are discarded as config files. That absence is a measurement bug, not a decoder limit.

That is the job. A binary announces a format and a machine type, and every analysis downstream — disassembly, control flow, taint, syscall naming, calling convention, argument recovery — is built on getting those two fields right and then having a decoder that actually covers the instruction set behind them.

QuestionSection
What happens when the format or machine type is resolved wrongly?Format and architecture misrouting
Which file formats are recognized, and what does reading one yield?Executable formats
Which instruction sets decode, and which of them reach IL?Architectures
Which decoders have been run against real binaries, and how clean?Decode results on real binaries
What validates a decoder that has no fixture binary?Decoder validation
How are arguments and syscall numbers resolved per target?Calling conventions · Platforms and syscall tables
Which containers open, and which are identified only?Containers and unpackers
What is claimed but unproven, or known wrong?Limits

Format and architecture misrouting

Format and architecture resolution fails quietly: a wrong answer does not throw, it produces plausible garbage.

Routing on bitness. PE routing was once decided on bitness, so every 64-bit PE went through the x86_64 lifter — including ARM64 PEs, which produced a full, confident, entirely fictional call graph. The fix routes on the COFF machine field and returns nothing for machines the pipeline does not fully support, so an unsupported PE yields an honest empty call graph instead of x86-misdecoded output. Honest-empty versus plausible-wrong is the distinction every per-architecture gate exists to enforce.

ARM64X hybrid images. A hybrid ARM64X PE carries ARM64 code and x64 code in one image, described by an IMAGE_ARM64EC_METADATA code map, so there is no single answer to "what architecture is this file" — the same problem made structural. The lifter builds a per-range plan, unions the ARM64 exception directory with the x64 ExtraRFETable, and selects a decoder per address. This is proven against a hand-built hybrid PE only — no real ARM64X binary is in the corpus.

Compile-time format sets. Format and architecture support is a compile-time set, not a runtime one, so a format omitted from a build turns into a refusal at analysis time rather than a link error — the one failure mode that does not even involve a binary. A fleet worker built without the PE option compiled cleanly, passed every lint, and then answered every PE with "the pe Cargo feature is disabled" and every ELF with the equivalent — analysing Mach-O and nothing else. Nothing observable went wrong until something was fed to it. The guard that now exists cannot be a compile-time assertion for the same reason: it executes the built binary once per format, because asking whether a build requested a feature is a different question from whether the thing it runs can read the file.

What a stock build actually contains. Every layer below is a compile-time set except identification, which is never gated.

LayerOn in a shipped command-line or worker buildOff unless the build names it
Loadable formats (58)4 — Mach-O unconditionally, plus PE, ELF and UEFI Terse ExecutableDEX, WASM, COFF, standalone MZ, NE, PEF, BFLT, QNX LMF, Qualcomm MBN, Apple SEP, the Android boot image, the Switch homebrew pair, luac, pyc, and the console, retro and legacy-desktop families
Lifted architectures (39)32 — arm64, x86_64, x86, ARM32, MIPS, PowerPC, RISC-V, eBPF and C166 come from the decoder library alone; the shipped builds add 21 more — SuperH, SPARC, m68k, s390x, Xtensa, AVR, MSP430, Z80, H8/300, V850, TriCore, Hexagon, NDS32, LoongArch, 8051, 6502, 8085, 8048, 6800, PIC and HC087 — SH-4, V810, LM32, OpenRISC 1000, CR16, SM83, PA-RISC
Bytecode ISAs (9)CILthe other 8, including the DEX loader
Format identities (170)all 170, by magic

A binary for an omitted architecture gets a refusal, not a misdecode, and a stock build can name a .dex it cannot open.

Executable formats

58 formats are recognized as loadable images. Six carry a manifest deep enough to describe the structure rather than sample it; 20 more carry a smaller one, several of them bundled so that a single manifest covers a whole console or retro family; the rest are recognized by magic and header validation with no item list behind them. Three have been walked over real binaries — ELF across 584, Mach-O across 9, WASM across 1; the other three are checked against their published specification only. Deprecated items stay in the counts.

FormatManifest itemsWhat reading it yields
ELF895 across 22 dimensions, 42 deprecatedprogram/section headers, .dynamic, GOT/PLT, build-id, RELRO and PIE posture, PT_INTERP libc flavour
Mach-O632 across 51 dimensions, 32 deprecatedload commands, chained fixups, code-signature SuperBlob and entitlements, compact unwind, ObjC/Swift metadata, fat-slice split
PE32+249 across 16 dimensions, 5 deprecateddata directories, IAT, .rsrc tree, load config (CFG, CHPE), Authenticode certificate table, .pdata function ranges
DEX31 across 3 dimensions23 header fields, 3 validation constants, 5 format versions — the table offsets that a separate const-pool walker turns into the naming layer for Dalvik bytecode
WASM246 across 4 dimensionssection table, 5 malformed-module error classes, and 224 opcodes — the container and its instruction set in one manifest
SEP6429 across 4 dimensionsApple Secure Enclave firmware split into boot / kernel / app slices

Having a manifest is not the same as matching it. Four format targets fall short:

Format targetItems dispatchedRate
Mac OS Classic PEF16 of 1984.2%
Mach-O439 of 63269.5%
COFF9 of 1656.2%
UEFI TE14 of 2556.0%
ELF, PE, DEX, WASM, SEP64all100%

Mach-O is the one to distrust. Lower rates exist among the architecture targets — mcs96, spc700 and w65c816 sit at 0% — but its 193 unmatched items are the largest count anywhere, a quarter of the 789 unmatched across all 104 targets. They cluster:

Item classUnmatchedOf
nlist symbol types3741
Header flags2730
arm64 and x86_64 relocation types2222
Section types2023
CPU types and arm64/x86_64 subtypes2026
Bind and rebase opcodes1923
Chained-fixup pointer formats1414
Load commands1358
Section attributes910
Export-trie flags88
File types414

The figure is a name-match, so it cannot separate an unwired arm from a name spelled differently on the two sides; entitlements and the code-signature SuperBlob read 100% and are demonstrably parsed, while the relocation tables a linked Mach-O never carries read zero. The export trie has no such excuse: it is how a modern Mach-O enumerates what it exports, and none of its 8 flags matches.

Beyond those six, the router recognizes:

DomainFormats recognized
Android and JavaAndroid boot images, ART/OAT, Android binary XML (AndroidManifest.xml), Java class files
Windows and DOS lineageDOS MZ, 16-bit Windows NE, DOS LE/LX, Xbox XBE, UEFI Terse Executable, Windows user-mode minidumps, DMP64 kernel pagedumps
Unix and other desktopstandalone COFF objects, uClinux BFLT, Mac OS Classic PEF, ECOFF, Plan 9 a.out, Amiga HUNK, MenuetOS, QNX 4 LMF, Apple .symbols, DARPA CGC
Bootloaders and firmwareQualcomm MBN bootloaders and MDT, PC BIOS and option ROMs, Apple GNS1 baseband, ARM Linux self-decompressing zImage wrapper, Atmel AVR firmware as checksum-validated Intel HEX text, Infineon C166, Intel OMF and OMF166, Pebble
Consoles and retroNintendo GameCube/Wii DOL, Switch NRO/NSO, NES, Mega Drive, Master System, SNES ROMs, SNES SPC700 audio dumps, PSX-EXE, N64, Game Boy, GBA, Nintendo DS, 3DS FIRM, C64 PRG, Atari 2600, VICE snapshots

Three cases sit across the boundary between a format and an instruction set:

Architectures

72 instruction sets are claimed, in exactly three tiers: 39 lifted, 9 bytecode VMs that also reach IL, and 24 decode-only. A test reads the specification registry and asserts every architecture in it appears in exactly one tier, failing on either an architecture with no tier or a tier naming an architecture with no specification — so a new architecture cannot land unmeasured and the gap list cannot go stale.

Lifted architectures (39) — full IL analysis

A lifted architecture reaches intermediate language, so taint, CWE detectors, the interval domain, call-graph construction and argument recovery all run on it. Manifest items are decoder breadth — for most architectures that is opcodes; for m68k it also counts addressing modes.

ArchitectureItemsArchitectureItemsArchitectureItems
x86_641186tricore250mc6800118
arm641008riscv198v850111
x86756xtensa197ebpf94
s390x490hexagon196hcs0892
powerpc390m68k174c16689
arm310sh4127nds3288
sparc297avr119808580
mips32281msp43079pic76
loongarch267superh74mips6472
z8071v81065650264
lm3264cr1661804856
arm64e45sm8344805144
h830042pa-risc37or1k36

Two further properties — that arithmetic lifts at the right operand width, and that the syscall instruction reaches the intrinsic the resolver reads — are asserted per-architecture for only eight of the 39:

GroupArchitecturesWidth and syscall probes
Probedx86_64, x86, arm64, arm, powerpc, mips32, mips64, riscvboth asserted
Rides another rowarm64enot probed separately; covered by the arm64 row
Hosted Linux, deferredsparc, m68k, s390x, superh, loongarch, nds32deliberately deferred to real-corpus validation rather than asserted on a synthetic sample
Bare-metal microcontrollersmost of the remaining 30no syscall ABI at all, so the property does not apply
Unmeasuredebpfneither the width probe nor the constant-load interval probe is wired; the test records both as unset, but the rendered matrix prints them as not-applicable — the glyph a bare-metal chip with no syscall ABI gets

Every lifted architecture has a textual disassembler. x86, x86_64, arm64 and arm64e use an edge decoder (iced for the x86 family, bad64 for arm64); the other 35 use a hand-rolled renderer.

Bytecode VMs (9) — decode plus IL

All nine emit real IL, so the standard analyses consume them like native code.

ISAItemsWhere it lives
wasm246.wasm modules; the only bytecode VM with any corpus fixture at all — one module, 4,182 instructions
dalvik195DEX inside every APK
jvm165Java class files
cil66.NET assemblies inside a PE
ebc56UEFI option ROMs and add-in card drivers
cbpf28seccomp filters
bpf27kernel eBPF programs
pyc19Python bytecode caches
luac12compiled Lua, including TP-Link's 5.1 variant

The DEX loader is one of the eight that a stock build leaves out, so a stock analyzer never opens a .dex at all — the Dalvik lifter and its bridge are present, but nothing routes a file to them.

The Dalvik path shows why the container matters as much as the ISA: the register-VM lifter has no DEX context, so an invoke lifts to the opaque token dalvik.method#N and a const-string to dalvik.const_string#N. Every name-keyed detector matches on resolved symbols, so those tokens are invisible to all of them. A separate bridge parses the DEX, walks each class to its method bodies, and rewrites resolvable tokens to their smali names — Ljava/lang/Runtime;->exec(...) — which is what makes the command-execution sink fire. An index that does not resolve, because it is out of range or the app is multidex, keeps its raw token rather than being guessed at.

Decode-only architectures (24) — no IL

These 24 are recognized and disassembled, and nothing else. No taint, no CWE detection, no value-range reasoning, no call graph. Promoting one means writing a lifter.

ArchitectureItemsArchitectureItemsArchitectureItems
hcs12171cp160055rx33
rsp119alpha51amd29k32
mcs96101i400445xcore32
w65c81692rl7844chip826
h850084m8c41mcore25
m16c81dcpu1636xap16
spc70066tms32036brainfuck8
propeller64lh580135malbolge8

Five of those numbers are aspiration rather than decoder breadth. For these five, "disassembled" describes an intent, not an output:

ArchitectureManifest itemsItems with a dispatch arm
mcs961010
w65c816920
h8500847
spc700660
rsp (N64)11951

Decode results on real binaries

14 of the 104 coverage targets are exercised against fixture binaries; the other 90 have never had a real file fed through them. Fixture counts are not comparable across rows either: the eBPF row is five programs totalling 67 instructions, which is enough to prove the decoder runs and nothing more, and six further rows rest on three fixtures or fewer.

TargetFixturesInstructions decodedUnknown
ELF (format walk)58418,866,4771,845
mips3254110,198,9380
x86_64156,468,1780
arm64144,381,3640
Mach-O (format walk)93,418,8980
arm64e2335,2290
arm4287,9141,755
riscv5253,30090
powerpc3173,6520
mips642102,6870
sparc145,6740
s390x138,3720
wasm14,1820
ebpf5670

Two architectures are not clean, and the ELF row's 1,845 unknowns are exactly those two summed — 1,755 ARM32 plus 90 RISC-V. Every other architecture in the 584-binary ELF walk decodes completely.

ARM32 is the larger: a glibc-dynamic ARM32 fixture reports 903 unknown slots in 129,878 instructions (0.695%), and the report names the offenders — 764 unnamed, plus 102 b, 31 ldr pc, 6 bx. Branches and PC-relative loads appearing in that list is the ARM/Thumb interworking frontier: the ARM decoder targets the integer core that firmware is built from, and NEON, VFP, coprocessor and Thumb-IT encodings fall back to a raw word rather than a mnemonic.

RISC-V's 90 unknowns across 5 fixtures are a different shape, and a less legible one: they come back as bare riscv.unknown with no mnemonic attached, so unlike ARM32 the report cannot name what failed.

RISC-V fixtureInstructionsUnknownRate
rv32 musl-static99,166220.022%
rv64 glibc-dynamic81,709290.035%
rv6436,643190.052%
rv6435,711190.053%
fifth fixture7111.41%

The last row clears the 1%-or-50-unknowns threshold that flags a fixture for investigation, and is a denominator artefact rather than a decoder signal. The manifest is no help in locating any of them either: it is complete against the dispatch source, yet 25 bit-manipulation instructions (rev8, bexti, orc.b, the sh1add.uw family, rori and friends) are dispatched without appearing in it at all, so specification and code have drifted in opposite directions on the same architecture.

537 of the 541 MIPS fixtures are the Zyxel router binaries from the opening; the four remaining are cross-compiled fixtures covering both endiannesses. That one firmware image contributes 9,963,208 of the row's 10,198,938 instructions — more than any other architecture target's whole fixture set, and second overall only to the 584-binary ELF format walk, which contains those same 537 files.

Decoder validation

Decoders are checked three ways: replayed golden vectors from rizin and radare2, a capability matrix driven through the production lift seam, and name inventories cross-referenced against rizin and Ghidra. Across all 104 targets, 12,977 of 13,766 manifest items have a dispatch arm — 94.3%.

Golden vectors

Twelve architectures — sparc, xtensa, avr, h8300, h8500, msp430, superh, v850, z80, tricore, nds32, m68k — replay rizin's assembly corpus, and loongarch replays radare2's ESIL corpus. Each vector is an assembly string and its encoding:

d "movea 0xff, r0, r20"  20a6ff00
d "mov 0xffff, r21"      3506ffff0000
d "mov 0x200000, sp"     230600002000

Two things are asserted per vector: the instruction decodes, and the summed per-instruction lengths equal the hex payload length. Length is the paramount axis — a decoder that gets a mnemonic wrong produces one bad row, but a decoder that gets a length wrong desynchronises the stream and corrupts every basic block after it.

OutcomeResult
Length mismatchtest failure
Decode failuretest failure
Placeholder mnemonic where rizin has a real nametest failure
Both sides emit real names but disagree — z80's register-fused ld a,b, sparc's synthetic clr for or %g0,%g0,%rdcounted and reported, does not fail: cannot be separated from harness normalisation without per-architecture equivalence tables
Rizin row marked asm="invalid"passes either way — it asserts that something sane happens on undecodable bytes, not a specific mnemonic
Corpus file missing or holding no parseable vectorsprinted as a skip, run stays green — corpus availability is treated as upstream-vendor state, not a decoder property

Coverage of the corpus is uneven against the manifest in both directions: h8500 replays golden vectors while only 7 of its 84 manifest items have a dispatch arm, so a green h8500 run says the implemented tenth is right, not that the architecture is covered.

The capability matrix

One table-driven test drives the real production lift seam per architecture and asserts seven properties per chipset. Rows left unset are recorded as unmeasured, never as passing.

ProbeWhat it assertsWhere it runs
CFGa minimal function lifts to a non-empty CFG with a terminatorasserted per chipset
Asmthe architecture's claimed edge decoder and textual backend match what is actually wiredasserted per chipset
Widtha width-sensitive arithmetic instruction produces IL at the right operand widthhosted set only
Syscallthe architecture's syscall instruction reaches the uniform intrinsic the resolver consumeshosted set only
Intervala const-load folds to a bounded interval8 hosted architectures plus 23 embedded ones, down to lda #5 on a 6502
Conventionthe calling convention selected for each (architecture, format) pair is the right one20 (architecture, format) pairs
Argsthe selected convention declares integer parameter registers, or is a declared stack ABI35 (architecture, format) pairs

A set row is not the same as an executed one: the 25 embedded-ISA lift probes and 23 embedded interval probes compile out unless the build names that architecture, and none of those features is in the lifter crate's own default set, so testing that crate alone runs none of them.

The rendered matrix is a hand-maintained copy of these tables rather than a projection of them, and twelve of its ticks have no asserting row behind them: interval for MSP430, Hexagon, s390x, LoongArch, C166 and V810, and argument recovery for arm64e, eBPF, C166, V810, LM32 and OpenRISC 1000.

Each column exists because it caught something:

Cross-tool inventories

Filenames under vendors/rizin/librz/{arch,bin}/p/ and vendors/ghidra/Ghidra/Processors/ are cross-referenced against the manifest names, and one entry in 157 is unshared.

InventorySharedOf
rizin architectures5757
rizin formats6363
Ghidra processors3637

The single Ghidra-only entry is its 68000 tree — m68k is covered, under that name, with a 162-item opcode dimension at full dispatch, but the parity map is keyed on directory name and does not alias the two. The real m68k gap is elsewhere: 2 of 12 addressing modes, IdxAn and IdxPc, are in the manifest with no dispatch arm, so index-register and PC-indexed operands are the frontier.

Calling conventions

48 conventions, selected automatically from the pair (architecture, image format), not from architecture alone. The same x86_64 instruction stream is SysV on ELF and Mach-O and MS_X64 on PE: different argument registers, and 32 bytes of caller-reserved shadow space on Windows against zero on the others. Getting this wrong recovers the wrong arguments for every call in the binary rather than failing outright.

The format half of the pair only bites where more than one convention is reachable. 32-bit x86 resolves to MsX86Stdcall on every format, ELF and Mach-O included, so the other three MS x86 conventions below are never the automatic answer — they are names an analyst or an annotation can select, not routing outcomes.

TargetConventions
x86_64SysVAmd64, MS_X64
x86 (32-bit)MsX86Stdcall, MsX86Cdecl, MsX86Fastcall, MsX86Thiscall
arm64AAPCS64-Apple, AAPCS64-Objc, AAPCS64-SysV
ARM32AAPCS, AAPCS-VFP
MIPSMIPS_O32, MIPS_N64
PowerPCSysV_PPC32, SysV_PPC64, Darwin_PPC
Other general-purposeRISC-V, SPARC v8, s390x (Linux ELF), LoongArch LP64D, Hppa (PA-RISC SysV), m68k (GCC), Nds32 (Andes ELF), OpenRISC 1000, LM32 (LatticeMico32)
Microcontroller and DSPXtensa CALL0, Hexagon QDSP6, AVR (avr-gcc), MSP430 EABI, SuperH (SH-1/2), TriCore EABI, H8/300 (GCC), V850 EABI, Z80 (SDCC), MCS-51 (8051, SDCC), MOS 6502 (cc65), 8080/8085 (SDCC), MCS-48 (8048), 6800 (Motorola), PIC18 (Microchip), HC08/HCS08 (NXP), CR16 (CR16C stdcall), C166 (Infineon/Keil)
Language and VM ABIsSwiftCC, SwiftMethod, GoRegabi, BPF_Helpers, cBPF_Seccomp

Every register-argument convention the gate names must declare its integer parameter registers, so an architecture that would silently recover zero arguments fails per-chipset. Stack-argument ABIs — 32-bit x86, m68k — are exempt by construction. The gate is 35 (architecture, format) rows and does not reach the whole table: the six architectures missing an argument-recovery row are named under the capability matrix, and the Swift, Go, BPF-helper and seccomp ABIs are asserted for no architecture at all.

Platforms and syscall tables

A syscall is a number in a register, and the number means nothing without knowing which table to read. Five platform families carry tables here — Linux, Apple, Windows, BSD and Solaris/illumos. Number 59:

x86_64   59 = execve          arm64    59 = pipe2
sparc    59 = execve          ppc      59 = oldolduname
x86_32   59 = olduname        arm32    59 = unassigned

And execve itself is 59 on x86_64 and SPARC, 11 on x86_32, ARM32, PowerPC and s390x, 221 on arm64, 4011 on MIPS o32, and 5057 on MIPS n64. Resolving a direct syscall requires the architecture before it requires the operating system.

Behavioural output uses one 12-term vocabulary across all three platforms — FileIo, Network, ProcessControl, Ipc, Memory, Crypto, Time, Signals, Threading, DynamicCode, SystemInfo, Hardware — so an ELF profile and a PE profile can be compared directly rather than each speaking its own dialect.

Linux

9 tables, 3,619 entries.

TableEntriesNotes
x86_64385
x86_32423
arm64328also resolves RISC-V rv32 and rv64 and LoongArch64 — the same asm-generic numbers from include/uapi/asm-generic/unistd.h, not a separate table
ARM32 EABI424
MIPS32 o32445predates asm-generic and cannot share it
MIPS64374predates asm-generic and cannot share it
PowerPC425
SPARC434predates asm-generic and cannot share it
s390x381

Above the syscall layer sit 10 capability domains — Network, Process, Filesystem, Memory, IPC, Crypto, DynamicCode, Time, SystemInfo, Threading — and 57 library families, which is how a statically linked binary with no imports still gets a behavioural reading:

Family groupMembers
libcglibc, musl, uClibc, bionic
CryptoOpenSSL, BoringSSL, mbedTLS, wolfSSL, libsodium, gcrypt, nettle
Compressionzlib, bzip2, LZMA, zstd, lz4, brotli
DatabasesSQLite, LMDB, Berkeley DB, Postgres, MariaDB
Networkcurl, libsoup, nghttp2, libssh and libssh2, c-ares, libevent, libuv
Parsersexpat, libxml2, libyaml, jansson, json-c, cJSON
Medialibpng, libjpeg, libtiff, and the rest

Apple

457 BSD syscalls and 62 Mach traps. Above them sit 1,053 framework and runtime symbols across 52 dimensions — the coverage report's 1,209-across-54 figure counts the same two syscall surfaces again as dimensions of its own, at 101 and 55 entries.

GroupFrameworks and runtimes
Language and object runtimesFoundation, GCD, the ObjC runtime, ARC barriers, the Swift runtime, Combine
Security and authenticationCryptoKit, SecurityFramework, AuthenticationServices, LocalAuthentication, PassKit, StoreKit
NetworkingNetworkExtension, Network, CFNetwork
System, process and deviceIOKit, EndpointSecurity, DriverKit, XPC, os_log, SystemConfiguration, SystemExtensions, ServiceManagement, ContainerizationKit, Virtualization, MetricKit
Capture and mediaPhotos, PhotosUI, ScreenCaptureKit, AVFoundation
Sensors and accessoriesCoreLocation, CoreBluetooth, AccessorySetupKit
Intelligence and MLApple Intelligence and its runtime, NaturalLanguage, Speech, Vision, CoreML
User data and app integrationAppIntents, AlarmKit, EventKit, UserNotifications, ExtensionKit, CoreData, CloudKit, CoreSpotlight, Contacts, GameSave, OSAKit
InterfaceSwiftUI, WebKit

1,105 of the 1,209 are dispatched, and all 104 shortfalls are in three of the framework and runtime dimensions:

DimensionDispatchedOf
Swift runtime251
ObjC runtime3372
Foundation4864

Apple hardening posture is thinner than the framework inventory suggests: 3 of 14 keys have a reader, against 9 of 9 each for Linux and Windows. Nothing dispatches PIE, non-executable heap, RWX segments, stack canaries, FORTIFY, pointer authentication, __DATA_CONST, ad-hoc signing, hardened runtime, library validation or FairPlay encryption. This is the same name-match measurement that reads Mach-O relocations as zero, so some may be read under another name — but it is the weakest measured surface on the platform.

Windows

142 NT SSDT entries and 21 win32k.sys entries in the modern table, plus separate Win7-era tables, because the Vista/7/Server 2008 numbering is a different map, not an older prefix of the same one.

TableNT SSDTwin32k
Modern14221
Win7-era, x86401818
Win7-era, x64401827

On top of those, 57 syscalls carry per-release numbers across six builds. Most are stable, but the ones that move are exactly the ones worth watching:

                          win10   win10   win11   win11   win11   win11
                          1809    22H2    21H2    22H2    23H2    24H2
NtCreateThreadEx          0x00BC  0x00C2  0x00C6  0x00C7  0x00C7  0x00C9
NtCreateUserProcess       0x00C3  0x00C9  0x00CE  0x00CF  0x00CF  0x00D1
NtSuspendProcess          0x01B4  0x01BD  0x01C7  0x01CB  0x01CB  0x01CE
NtSystemDebugControl      0x01B6  0x01BF  0x01C9  0x01CD  0x01CD  0x01D0
NtWriteVirtualMemory      0x003A  0x003A  0x003A  0x003A  0x003A  0x003A

A direct-syscall stub that loads 0xC9 into eax is calling NtCreateThreadEx on Windows 11 24H2 and NtCreateUserProcess on Windows 10 22H2. Low numbers like NtWriteVirtualMemory at 0x3A have not moved across any of the six, which is why the shifting high numbers are where the ambiguity lives.

The stub walk that feeds those tables runs on x86_64 PEs only: an ARM64 or 32-bit PE returns before it starts, so a direct-syscall stub there resolves to nothing rather than to a wrong name.

Which of the six a sample targets is the part a PE cannot answer. Both available signals collapse: any MajorOperatingSystemVersion of 6 or higher resolves to a single baseline, because the MSVC linker default is sticky and 2024-built binaries still declare (10, 0); and the manifest supportedOS GUID {8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a} covers Windows 10 and 11 together, since the schema never split them. So 0xC9 resolves to one name, not two candidates, and the version field is a floor rather than a target.

The rest of the Windows surface:

SurfaceContents
COM45 classes
API sets137 api-ms-win-* api-set names mapped to the host module they forward to, so an import of api-ms-win-core-com reads as combase.dll
TEB/PEB4 field classes
Curated importskernel32, ws2_32, bcrypt, crypt32, ole32
DLL-to-domain mapfive behavioural domains — process control, network, crypto, host interaction, anti-analysis — plus a catch-all

BSD family

4 tables, 1,423 entries.

TableEntries
FreeBSD499
OpenBSD231
NetBSD377
DragonFly316

Family-specific surfaces matter more than the totals: OpenBSD's pledge and unveil declare a process's own restrictions, and FreeBSD's Capsicum cap_* and jail-control calls do the same.

Solaris and illumos

246 entries, bridged one-to-one into the Linux capability taxonomy. What justifies a separate table are the illumos primitives with no Linux equivalent — door, port, zone, pset, modctl, processor_bind and processor_info. Their userspace wrappers (door_call, port_create, zone_create, pset_bind and siblings) are not in the syscall table; they resolve through the library-symbol catalog, which is what a dynamically linked Solaris binary actually references.

libc flavour

libc flavour is read from the ELF PT_INTERP path, falling back to the DT_NEEDED soname for shared objects, which have no PT_INTERP.

EvidenceReads as
PT_INTERP /lib/ld-musl-*musl
PT_INTERP /lib64/ld-linux-*, /lib/ld-linux*glibc
PT_INTERP /system/bin/linker*Android bionic
DT_NEEDED libc.so.6glibc
DT_NEEDED libc.musl-*musl
Statically linked — no interpreter, no sonameunknown, which is the honest answer
PT_INTERP /lib/ld-uClibc.so.0unknown — matches no prefix in the classifier

The uClibc row is the weak one: the Zyxel binaries from the opening name /lib/ld-uClibc.so.0 as their interpreter and return unknown, even though the library-identification layer recognizes uClibc by soname and by the __uClibc_main symbol. Embedded Linux is the case this classifier is weakest on, and it supplies 537 of the 584 binaries in the ELF corpus walk.

Containers and unpackers

The identify authority carries 170 format identities, 161 of them backed by a signature-validated magic, and 110 route to a working extractor.

CountWhat it countsWhy it differs from the row above
170format identities in the identify authority
161identities backed by a signature-validated magic9 are identified without a validated magic
110identities that route to a working extractorthe rest are leaves, constants, or executables handed to the analysis engine rather than the unpacker

Most of the 60 that do not extract are categories with nothing to descend into.

CategoryContentsWhy nothing is extracted
Crypto markers12: the AES forward, reverse and acceleration tables, the AES S-box and round constants, CRC32, MD5 and SHA-256 constants, PKCS DER hash prefixes, RSA structure, LUKS and DPAPI headersconstants rather than containers
Media and documentsleaf formatsno nested payload
ExecutablesELF, PE, UEFI TErouted to the analysis engine instead of the unpacker

The four crypto rows that do carve — PEM certificates, private keys, public keys, and OpenSSL blobs — extract because there is an encoded payload to pull out.

Six containers are identified and not extracted. In every case the reason is the same: no extractor has been written, not that the container defeats extraction.

FormatKindState
btrfsfilesystemidentified, not extracted
logfsfilesystemidentified, not extracted
Seamacarrieridentified, not extracted
eCoscarrieridentified, not extracted
Wind River kernelimageidentified, not extracted
Qualcomm MBNbootloaderidentified, with the code, signature and certificate sub-regions left unsplit

Four compression formats are deliberately excluded from automatic routing — compress (.Z), raw zlib, raw LZMA1 and raw xz streams. Their magics are short enough that scanning for them across a firmware image produces more false positives than payloads: raw LZMA1's "magic" is its one-byte properties value, which matches roughly 24% of random bytes. All four extract by name only, from carrier-aware paths that already know what they are looking at. A stripped-OOB YAFFS2 image takes the same posture for the opposite reason — it cannot reach header-valid confidence, so it is never auto-promoted.

The extractor catalog:

CategoryFormats
Filesystemscramfs, EROFS, ext2, FAT, ISO 9660, JFFS2, NTFS, qcow, romfs, squashfs, UBI, UBIFS, UFS, YAFFS2, APFS
Archives7-zip, ar, ARJ, CAB, cpio, CRX, Dahua, RPM, tar, xar, zip, GPG-signed containers
Compressionbzip2, gzip, lz4, lzfse, LZMA, lzop, xz, zstd
Firmware carriersU-Boot, uImage, TRX, Android boot and sparse images, device tree (DTB), FIT, UEFI FFS and capsules, Zyxel 2RDH and sig, CFE, CHK, packimg, RTK, TP-Link and TP-Link RTOS, JBOOT arm / sch2 / stag, Arcadyan, csman, dkbs, D-Link TLV, DLKE, DMS, shrs, QNX IFS, WinCE, pchrom, Autel, DLOB, Matter OTA, SEP64, NSIS, EncFw, encrpted_img, MH01, Motorola S-record, VxWorks symtab, UF2, Apple ftab, XALZ (Xamarin)
Kernel and disk imagesARM32 zImage, ARM64 boot images, x86 boot images, raw Linux kernels validated by banner string, MBR, EFI-GPT, DMG
Appledyld shared cache, pbzx, pbze, AppleArchive, AEA (key-gated), Img4, Trustcache, BOM, BuildManifest, APFS
Other containersOCI and docker save images, OLE (MSI), RAR, .NET single-file apphost bundles, UPX

DMG, the dyld cache, MH_FILESET kernel collections and Mach-O universal binaries bypass magic scanning entirely and go to dedicated loaders. UPX is a format-aware static unpacker that reconstructs the original Mach-O, ELF or PE image rather than stripping a header. The recursion mechanics, budgets, and the packed-payload verdict handoff are in Unpacking.

Measured against vendored binwalk (111 formats identified, 72 extracted), every extraction difference runs the same way:

RelationCountFormats
Extracted here, no binwalk entry at all23ar, xar, AppleArchive, raw LZMA1 and xz streams, Zyxel 2RDH and sig, AEA, BOM, BuildManifest, .NET bundles, the dyld cache, FIT, Img4, NSIS, OCI, OLE, pbze, pbzx, RPM, SEP64, Trustcache, UFS
Extracted here, identified only by binwalk15qcow, U-Boot, CFE, CHK, DLOB, JBOOT arm and stag, the three Linux boot-image formats, packimg, RTK, TP-Link and TP-Link RTOS, Android boot
Extracted by binwalk, identified only here0
Extracted by binwalk, missing here entirely0
Identified by binwalk, no entry here2Debian .deb, binhdr

Limits

90 of the 104 coverage targets have no fixture binary at all. For those, the only claim on record is that the manifest and the dispatch source agree — no real file has ever been fed through them. That agreement is not uniform either: 12,977 of 13,766 items overall, but v850 sits at 86 of 111 with its whole load/store group unmatched, and Mach-O — a target that does have fixtures — at 439 of 632.

The measurement is a name-match between specification and dispatch source, so it drifts in both directions and neither direction is proof. RISC-V dispatches 25 bit-manipulation instructions that appear in no manifest. PA-RISC is the clearest case of the drift running the other way: it reports its integer ALU, logical, address-formation and load/store entries as undispatched — only 8 of its 37 items match — while the real Debian-hppa echo and true from coreutils 9.10-1 decode 5,796 and 5,300 four-byte text slots with 419 and 417 unknown (7.2% and 7.9%), both producing a terminated CFG with a call edge. The remaining unknowns there are floating-point, coprocessor, system and privileged operations, which are out of scope by design; the ceiling that would fail the run is 15%, and deleting the integer decode arms pushes the rate to about 19%.

LimitConsequence
None of the 24 decode-only architectures appears in the calling-convention tablebeyond having no IL they also have no argument recovery and no register naming; a binary for one of them is readable, not analysable
ARM32's interworking tail is unresolved0.525–0.695% of instructions across all four ARM32 fixtures, glibc-dynamic and musl-static alike
ARM64X dual-ISA routing is proven only against a synthetic hybrid PEno real ARM64X binary has been run through it
uClibc binaries report an unknown libc flavour/lib/ld-uClibc.so.0 matches no prefix in the classifier
Windows syscall numbers outside the 57-entry versioned table resolve against the modern NT mapwrong for any build that renumbered them
Direct-syscall stub resolution runs on x86_64 PEs onlyan ARM64 or 32-bit PE that calls the kernel directly yields no syscall names
The golden-vector harness skips a missing or unparseable corpus file without failinga vendored corpus that disappears is indistinguishable from a decoder that passes every vector
Embedded-ISA lift and interval probes compile out unless the build names the architecture25 lift probes and 23 interval probes are green by absence in a build that did not ask for them
The corpus harvester drops extensionless files whose name matches a non-binary extensionbin/cfg, bin/sh and their kind never reach a decoder, so a coverage figure can be complete over an incomplete corpus

Reference