Sign in

Engine

Apple's Wi-Fi daemon still carries the load command that tells a disassembler which bytes inside its code segment are data:

$ otool -l /usr/libexec/airportd | grep -A3 LC_DATA_IN_CODE
      cmd LC_DATA_IN_CODE
  cmdsize 16
  dataoff 1490096
 datasize 0

The command is present. The table is empty. bluetoothd, sharingd, secinitd, and notifyd all ship the same way — command emitted, datasize 0. Every switch in those daemons compiled down to a jump table, and none of them is annotated any more. A tool that reads the hint resolves nothing, and the indirect branch at the heart of an XPC request dispatcher — the function deciding what a root daemon does with a message from an unprivileged process — becomes a dead end in the call graph.

A binary records a handful of roots — entry point, exports, unwind tables — because the loader needs nothing else. Function boundaries, control flow, types, calling conventions, which library a stub really reaches, which string reached a dlopen: none of that is in the file. The engine recovers it.

SectionWhat it answers
The analysis recordwhat one analysis emits, and the two invariants every consumer relies on
Format parsingwhat Mach-O, PE, and ELF each give up directly, and what identity each carries
IL liftinghow instructions become one architecture-independent form, and which conventions type the boundaries
Recovery passeswhat is reconstructed because the file never recorded it — stack strings, call edges, jump tables, syscall numbers, call-site arguments
Curated knowledgethe shipped databases that put names on otherwise anonymous code
Cross-cutting viewscapabilities, code signatures, hardening, behavioral aspects, secrets, and the authorization audit matrix
Outputsthe behavioral fingerprint, the Rust reconstruction, inspection and diff
Limitswhere the engine is blind, by architecture and by design
Execution modelhow one analysis is parallelized and served

The analysis record

One typed record per binary — every fact the analysis produced, in one structure, type-checked in both directions at the boundary so a shape change is loud rather than silent. It carries its own schema version, distinct from the version of the build that produced it; a consumer ingesting a record newer than it knows warns rather than fails. It is the only thing that crosses the serialized wire, and the engine never reads its own output back as an input fact.

Two things travel beside it, in-process only:

Companion inputWhy it is not in the record
The raw bytes of the analyzed architecture slicethe taint detectors re-lift the whole binary from bytes rather than trust a cached IL, and byte-matching detectors need offsets relative to the same slice the record's sections are relative to — hand them the unsliced input and every match on a universal binary lands in the wrong sub-image
A typed carrier for analyzer-produced fields the findings and malware layers consumeas never-serialized fields on the record they were invisible to every fixture and every round-trip check: one was hard-coded to false, silently disabling a Rust-binary suppression and producing roughly 487 false positives on one Linux binary. The carrier has no serialization at all, so it cannot regrow a wire shape by accident

Alongside it, a Rust source project that mirrors the program closely enough to compile.

The path from bytes to that record:

bytes
 ├─ detect          → container format (Mach-O / PE / ELF / …) + an arch string
 ├─ parse           → sections, segments, symbols, relocations, PLT stubs, unwind tables
 ├─ decode          → mnemonic and typed operands per machine instruction
 ├─ lift            → architecture-independent IL
 ├─ cfg             → basic blocks and edges
 ├─ ssa             → each value written once, every use pointing at one producer
 ├─ analysis        → constants, value ranges, types, taint, points-to, guard dominance
 ├─ bridge          → call graph and data cross-references, read back out of the IL
 ├─ recovery        → ObjC / Swift / C++ / Go metadata, demangling
 └─ the analysis record                                   ◄── the output contract

An unknown architecture yields nothing, never the wrong decode. Detection returns an endian-qualified string (mips32-be, not mips32) or the literal unknown; a bare mips32 resolves to unsupported by design. Exactly one place classifies an architecture and exactly one place picks a lifter, both written as exhaustive matches so a new instruction set fails to compile until it is routed. The rule is scar tissue: a 32-bit PE once lifted as arm64 SVE/NEON garbage at 0.92 confidence, and x86-64 Mach-O once shipped an empty call graph because the only edge decoder in the tree was the arm64 one.

Unknown resolves to top, and top never proves safety. Every abstract domain over-approximates. A value absent from the range map is unbounded; arithmetic that could wrap the operand width returns unbounded; loop-carried values widen to unbounded. A consumer can therefore introduce no new false negatives by consulting a domain, only miss. The bias is toward silence everywhere.

Format parsing

Format is recognized from the leading bytes — 0xFEEDFACF for 64-bit Mach-O, MZ then a PE\0\0 header, \x7FELF — and each format parses into one structured object the rest of the pipeline queries. The parsers are in-house (crates/formats).

FormatStructures parsedFunction extents from
Mach-Ochained fixups — the modern LC_DYLD_CHAINED_FIXUPS format Apple uses on arm64e — walked through bind ordinals into concrete imported symbol namesLC_FUNCTION_STARTS, a delta-encoded list that survives strip; extents come from the next start. Compact unwind is deliberately unparsed, since function starts already survive stripping and it would earn little
PEimports, exports, the certificate directory, the application manifest resource, and the CodeView debug record naming the external symbol filethe .pdata runtime-function table, which gives explicit begin/end pairs — PE has the extent problem solved for free
ELFdynamic sections, .rela.plt and .rela.dyn relocations, BTI-aware arm64 PLT trampolines, IBT-aware x86-64 .plt.sec, IRELATIVE entries for IFUNC resolvers, versioning tables, and the GNU build identifiereach .eh_frame unwind entry carries a function start and an exact code extent; the extent is used only to shrink the next-start heuristic window, never to grow it, because an entry spanning past the next start is suspect data

Imports carry a kind tag — function or data — so a consumer can tell a function-pointer slot from a data-pointer entry like __stack_chk_guard or _environ. Same surface across all three formats, on different evidence:

FormatImport-kind evidence
ELFthe relocation type, read directly — and the only format carrying a third kind, ifunc, because only ELF has an indirect-function relocation to classify
Mach-Oa curated libSystem data-symbol allowlist
PEcurated per-DLL C-runtime export allowlists

Non-returning callees

Unwind tables answer a question nothing else can: which callees never return. A function whose window ends exactly at an unwind-table end, whose last instruction is a direct call falling through exactly to that end, calls something that does not return — the compiler ended the code there because there was no return path. That seed feeds a whole-program fixed point which propagates "does not return" through every local wrapper, sets the flag, and drops the bogus fall-through edge after each such call. On a fully stripped static ELF it is the only seed available, because name-based seeding dies with the symbol table. A binary built with unwind tables disabled has an empty seed set, the fixed point is blind, and dead fall-through arms survive into reconstruction.

Build identity

Each format spells its unique identifier differently, and the surface is made symmetric.

FormatUnique identifierAlso carried
Mach-OLC_UUIDminimum-OS, SDK, and source versions; the bundle identifier
ELFthe GNU build IDDT_SONAME, the interpreter path
PEthe file hash, de factothe PDB filename out of the CodeView record — but not the GUID, which is parsed and not yet surfaced

Four hash families are computed on every analysis on the same byte slice — SHA-256, MD5, SHA-1, and an ssdeep fuzzy hash — with both a whole-file and a slice-only SHA-256 for universal binaries, so a binary joins cleanly into VirusTotal, MISP, and any other corpus keyed on a particular algorithm.

Containers

Container formats are unwrapped into the images they hold, and each image is analyzed in its own right.

A wider recursive layer sits in front covering filesystems, archives, compression, firmware carriers, disk images, the Apple OTA chain, and UPX; the images it can name but not open are btrfs, LogFS, eCos and Wind River kernel images. Mechanism, budgets, and the packed-to-payload verdict handoff are in Unpacking.

IL lifting

Decoded instructions become IL — one representation independent of the CPU that produced it. Operations and expressions are a single set, and every pass refines that set in place rather than lowering between altitudes.

Flags split cleanly from data flow: ADDS lifts as an assignment plus a separate flag-set, so a pass tracking values never has to model condition codes. Operand width is stamped from the explicit operand, and W-forms sign-extend — a 32-bit operation modelled at 64 bits makes range analysis reason at 2^64 and silently manufactures false negatives.

An operation the lifter recognizes but has no primitive for becomes an explicit unmodelled-operation marker — a measured gap, and deliberately distinct from a decode failure, which means the byte stream at that address was never instructions in the first place. What is never done is lifting one operation as a different one:

OperationWhereHow it lifts
MODcBPFremainder; it once lifted as unsigned divide — a real bug
ROTL / ROTRH8/300rotate; it once lifted as a bitwise or with zero — a real bug
8-bit rotates8051, 6800, PICreal rotates, carry bit included
Count-leading-zerosarm, arm64a real unary operation
BCD decimal-adjust (DA A, DAA)8051, 6800the accumulator re-defined as itself — the one identity over-approximation left

The decimal-adjust case is deliberate: the corrected value depends on the carry and half-carry flags and the nibble values, and is not expressible without conditionals. Identity keeps the def-use chain intact for taint at the cost of a value-range gap on those two architectures.

Control-flow graph and SSA

SSA sits on top of the control-flow graph: each value is written exactly once and every later use points back at a single producer, so a pass asking where did this come from walks one edge instead of searching the function. Dominators via Lengauer-Tarjan; SSA construction via the Cytron pruned-phi algorithm.

CFG construction is shared for most of the tree, not all of it. Twenty-two architectures — the embedded families, M68k, Z80, s390x, LoongArch, TriCore, MSP430, 8051, 6800, PA-RISC and the rest — implement one small trait and get block splitting and edge recovery from a common builder. arm64, x86-64, arm and mips each carry a hand-written builder, so a new architecture in that class does not get a CFG for free.

Instruction coverage

The spec registry carries 104 targets, of which 72 are architectures — the other 32 describe executable formats, platform surfaces, and cross-cutting concerns rather than an instruction set. The 72 split into three tiers whose union is asserted equal to the registry, so a target that cannot be lifted is never made to look as though it can and a new architecture cannot land unaccounted-for. The per-target breakdown is in Architectures.

TierTargetsDecodesLifts to ILNote
Native architectures39yesyes, with deep probes
Bytecode VMs9yesyes, through a non-native seamwasm, Dalvik, JVM, CIL fully; EBC, Lua, Python, BPF and cBPF partial
Decode-only targets24yesnothis is the coverage gap, named rather than hidden: alpha, mcore, rx, rl78, tms320, xcore and the rest get no taint, no CWE detection, no value range, no call graph

The arm64 and x86-64 decoders in detail:

Dozens more instruction sets sit under the same dispatch.

Calling conventions

Function boundaries carry typed parameters and typed returns from the first pass. The convention is selected from the architecture-and-format pair. Forty-eight ship; the ones a reader meets most:

ConventionWhereDistinguishing feature
AAPCS64-ApplemacOS, iOS arm64X18 platform-reserved; ObjC variadic calls bypass registers
AAPCS64-SysVLinux/BSD arm64X18 caller-saved; no ObjC promotion path
SysVAmd64macOS, Linux x86-64Standard System V
MS_X64Windows x86-64Microsoft x64 ABI
GoRegabiGo binaries, any architectureGo's register-based sequence
Swift and Swift-methodany Apple architectureseparate error and context registers; the method form reserves the receiver slot
MSVC x8632-bit Windowsfour distinct forms — stdcall, cdecl, fastcall, thiscall — that differ on who cleans the stack

The remaining forty are per-architecture: MIPS o32 and n64, Darwin PowerPC and SysV PowerPC 32/64, AAPCS with and without the VFP float variant, the BPF helper and seccomp-cBPF sequences, and one each for roughly thirty embedded targets.

Recovery passes

Stack strings, call edges, jump tables, syscall numbers and call-site arguments are all reconstructed rather than read. The loader never needed any of them, so no format records them.

Stack strings

A string that never appears in the file is recovered from the stores that spell it out. Obfuscated loaders keep their strings out of the string section by writing them onto the stack a chunk at a time, so strings returns nothing; the pass groups consecutive constant stores to adjacent stack offsets at any width up to 8 bytes, splits each immediate into its in-memory bytes using the target's byte order, resolves overlaps last-write-wins, and reports printable runs of 4 characters or more:

mov qword ptr [rsp+0],  0x6c6c642e6c64746e
mov word  ptr [rsp+8],  0x3233
mov byte  ptr [rsp+10], 0x21
                                    →  "ntdl.dll32!"

Three stores of three different widths compose into one eleven-character string rather than three fragments. Byte order is load-bearing and directional: the same 32-bit immediate 0x6c64746e yields ntdl under a little-endian convention and ldtn under a big-endian one, so getting it wrong reverses every string recovered on PowerPC, SPARC, s390x, and m68k.

The measurement that forced the design: an earlier pass handled only the byte-at-a-time idiom — the arm64 and -O0 shape — and recovered exactly one stack string across 679 corpus samples, 128 malware families, and 100,639 functions, because byte-at-a-time is not the idiom the x86-64 and Mach-O corpus uses. Widening to any-width turned that into runs like PasivRobber assembling 44 consumed strings inside a single function (RemoteUninstall, cp -r "%s" "%s", use_ansi_encoding) and a batch-command builder assembling nine.

The 8-byte ceiling is a real blind spot: a 16-byte vector store cannot carry its value in the IL's 64-bit immediate, so it is skipped rather than truncated into a wrong four-character fragment.

It is genuinely dual-use. Benign jq builds hello and goodbye this way; benign curl builds AUTHENTICATE and ssh-ed25519. The doubt is carried downstream by a flag recording whether the slot's address was taken, plus a measured count gate — never by refusing to recover the fact.

Dataflow passes

Passes over the SSA form fall into three groups — value tracking, runtime-noise removal, and source structure:

GroupPassWhat it does
Value trackingConstant propagationmoves literals forward through arithmetic
Value trackingCopy propagationcollapses register-to-register chains
Value trackingCommon-subexpression eliminationremoves redundant computation
Value trackingType propagationinfers shapes from compare instructions, mask widths, signed-versus-unsigned operations, and pointer arithmetic
Value trackingStruct recoverygroups field accesses through a common base pointer into named layouts, unified across call boundaries so a struct discovered in one function keeps its identity in every caller and callee
Value trackingNon-zero bit-mask propagationtracks which bits of a value can be set at all, narrowing a 64-bit type down to 8, 16, or 32 bits on AND-mask evidence
Value trackingInterval domainruns beside all of it, narrowing each value to a low-high pair from constants, arithmetic, and branch guards, tracking intervals through stack slots, and feeding the bounds-checking detectors
Runtime noiseARC loweringrecognizes Apple's automatic-reference-counting calls — objc_retain, objc_release, objc_storeStrong — and lowers them to semantic operations; those calls account for 30 to 50 percent of call-graph edges in typical Objective-C
Runtime noiseDead code eliminationremoves unreachable and unused work
Runtime noisePeephole rewriting35 rules under fixpoint iteration capped at 20 rounds — 12 arm64 idiom rewrites (csel, ccmp, ubfx, extr) and 23 architecture-independent ones: division-by-constant recovery, boolean identities and annihilators, shift and arithmetic identities, pointer-arithmetic and redundant-load collapsing. The set runs twice, before and after constant, copy and subexpression propagation — exactly one extra pass, not a global restart loop — because folding constants and canonicalizing operands exposes rewrites the first pass could not match
Source structureStack-slot recoveryrebuilds the local variable layout
Source structureConstant-sequence recoveryreassembles literals — printable strings, byte arrays, GUIDs, short structs — that the compiler split across several byte stores
Source structureVariable mergingunifies values sharing a storage origin using interval-based merging rather than union-find, so cross-origin merges cannot leak into each other

The interval domain's known cost: a bounded loop induction variable reads as unbounded — precision traded for soundness deliberately — and Asr, Sdiv, and Srem are modelled only for provably non-negative operands.

Naming. Up to three sources of human-meaningful names — argument-taint analysis, Objective-C ivar layouts, and DWARF when present — are applied at print time without touching the IL, so the raw stage dumps stay raw.

Guards. A structural pass answers "is this index checked?" and can miss a real bug but never manufactures a false one. It runs without value range or path sensitivity, and is conservative toward suppression: an index masked with & C or % C is bounded without any compare, and treating it as unguarded would false-positive on every hash table and ring buffer in existence. A compare in a dominating block counts as a guard even if the closure that found it was over-broad.

Memory. Points-to facts key on allocation-site address, not on the SSA value, so a use-after-free survives an SSA reload and an escape through a global. free(p) is a strong update to freed only when p resolves to exactly one object; empty, multiple, or non-unique targets weaken to maybe-freed, which never fires. At a branch join, freed and alive meet at maybe-freed.

One class of fact crosses a call: a callee proven by its bottom-up summary to free a parameter on every path applies that free to the caller's argument object, which is what catches a use-after-free through a one-level wrapper. A callee that only dereferences a parameter needs no transfer — the state already at the call site answers that. Nothing else crosses — an indirect target, an unlifted callee, a recursion-cap hit, or no summary for that callee leaves the object as it was. Only total leaks are reported: an error-path leak where some paths free and some do not is maybe-freed and stays silent.

Interprocedural summaries

Intraprocedural passes stop at a call; a family of bottom-up function summaries crosses it. The call graph is condensed into strongly-connected components with Tarjan, the condensation is walked bottom-up, and each function gets a summary of what it allocates, dereferences, frees, and flows to which sink — with a fixpoint only on recursion, so no callee body is re-analyzed at each call site. Those summaries feed points-to and use-after-free reasoning, interprocedural taint through helper functions, and type and range propagation across calls.

Two summary consumers ship and fire: the cross-call free transfer behind use-after-free through a wrapper, and cross-function taint from a source through a helper to a sink, which makes a local wrapper transparent to the whole sink catalog.

A third does not. An argument-access oracle would reflect a callee's dereference and free pattern back into the caller's taint state and fire three callee-aware rules on shapes an intraprocedural lattice cannot see. The rules and the oracle exist; nothing in the shipping tree calls either them or the interprocedural argument-access propagation, so those three rules cannot fire on any input, with or without the gate meant to control them. The yield measured when the wiring was present is the size of that gap, not current behavior:

Fixturenull-deref.callee-dereferencesuse-after-free.callee-usescallee-dereferencing taint flow
curl485
jq1634
airportd001
ripgrep000

The taint substrate itself carries no notion of vulnerability — it answers reachability only, and saturates: once a value is marked as coming from any source, no later union can lose that, so every sink keeps firing and only source attribution degrades. Which flows are a CWE is a separate layer (Findings).

Bounded emulation

A single-path, integer-only interpreter over the IL runs wherever propagation cannot pin a value — when it depends on a memory load, a non-trivial loop, or arithmetic the compiler folded into a non-obvious form.

Budgets are tiered by driver and span four orders of magnitude:

BudgetValueOn exhaustion
Per probe, default4,096 lifted IL operations — one step is one IL operation, not one machine instructionthe run's result is discarded, never truncated into a partial answer
Per seeded extraction run262,144 steps, because a real compression-class packer stub was measured to exhaust anything smaller while still writing outputsame — a partial decrypt is dropped, not surfaced
Per entry-point unpack run64,000,000 steps, plus an 8-second wall-clock deadline and a 4 MiB copy-on-write overlay capone such run is 16 times the whole per-binary ledger, so it is a deliberate, separately-driven exception rather than something a scan falls into
Per binary4,000,000 steps across every proberemaining sites return empty rather than running more emulator work

The overlay cap is a memory bound, not a step bound: an overlay byte costs a measured 21.5 bytes of resident memory regardless of write pattern, so 4 MiB written is roughly 86 MB of peak resident memory for one run.

Every run ends in one of four states:

End stateMeaning
resolvedthe value folded to a concrete number
budget-exhaustedeither budget ran out
unknown-loadan unmodelled address was read
divergeda syscall, floating point, a condition whose flag comparison is not live, a branch or call target that does not fold, or a store whose address or value does not fold to a concrete number

Divergence is the honest edge of the model. Copy-on-write memory lets an in-place decryptor read back the plaintext it wrote, bounded by an overlay cap. An uninitialized read follows an explicit policy and never returns a silent zero: the default faults, and the alternative returns zero but flags the run so the output gate can reject a plaintext derived from uninitialized memory.

Nothing auto-runs unbounded during a scan. Five consumers probe the interpreter on constant-propagation misses:

ConsumerProbes for
Jump-table lifterthe table base
Syscall passsvc immediates
Dynamic-loader passstring arguments
Behavioral signaturescall-site arguments and format-string prototypes
Indirect-call fallbacktargets the call-graph builder could not pin

Seeded extraction runs that recover decrypted strings and malware configs, and the hash-recurrence oracle that reverses API-hash resolvers, sit on the same core and are described in Emulator — including the sweep of five real UPX-packed samples that returned 0 of 5, every run diverging at operation 31 on a branch whose truth depends on a stack-relative load. Seeding now binds the stack-pointer and frame-pointer registers to a synthetic base and continues past an unknown read instead of aborting, but still never binds the stack contents, since no kernel-supplied argument frame exists at that address; neither sweep has been re-measured since.

Call resolution

Every bl, blr, and call maps to a real target, with runtime indirection followed through.

PlatformIndirectionHow it resolves
Appleobjc_msgSend stub trampolines; arm64e authentication stubsstubs are decoded so selectors appear directly as objc_msgSend$initWithMetricName:options:; authentication stubs resolve through GOT entries via chained-fixup bind ordinals
Windowsposition-independent call qword ptr [rip+disp32]resolves through the import address table to DLL function names, with no lifter changes
LinuxPLT/GOT trampolines, including the BTI-aware arm64 and IBT-aware x86-64 variantsjump-slot relocations bind each stub to its import name; the versioning table resolves the library ordinal

On ELF, C++ exception landing pads recovered from .gcc_except_table are seeded as discovered functions and rooted, so a catch handler the unwinder branches to reads as reachable rather than dead code — the same behaviour Ghidra's GccExceptionAnalyzer locks in.

On Apple binaries roughly 98 percent of call sites — bl and blr instructions — end up traced to a named target rather than left dangling. Around that:

One symbol table underlies all of it, merging the static symbol table, imports, Objective-C stub trampolines, authentication-stub targets, recognized library functions, and the synthetic addresses assigned to resolved syscalls into a single space the rest of the pipeline queries.

The hard limit is architectural: edges decode for arm64 and for x86/x86-64, and nowhere else. Everything outside those families still gets a symbol table, a function list, and lifted IL, but an edge-free call graph with a surfaced "no edge decoder" error — an embedded MIPS or PowerPC firmware image is honestly empty on edges rather than quietly wrong. Indirect targets that devirtualization cannot resolve stay edge-less on every architecture.

Jump tables

Jump tables are resolved from the instruction idiom itself rather than from the LC_DATA_IN_CODE hint, because Apple ships its daemons with that table emptied and a hint-reading resolver therefore resolves nothing on precisely the binaries most worth auditing. Compilers turn a switch over dense integer keys into a base address plus an array of offsets dispatched by an indirect branch, and until the table resolves, the call graph stops there, cross-references stop, and reachability walks stop.

The fallback detects the canonical idiom directly: cmp, then adrp+add, then ldrsw with a shift, then adr+add+br. Multi-tier range checks up to three nested cmp/b.gt pairs — the shape macOS daemon XPC dispatchers use — collapse into a single switch. The dispatcher's predecessor compare chain carries the bounds; the load-dispatch sequence carries the entry size; on arm64 the emulator supplies the table base when constant propagation cannot. x86-64 has no emulator by design, so every x86-64 shape is recovered by pattern-matching the lifted IL plus direct reads of the image bytes. Resolved handler addresses become first-class call-graph edges, so a cross-reference on a handler address finds its dispatcher, reachability walks pass through, and reconstruction emits the dispatcher as a match with one arm per handler.

Cross-references

Every reference between code and data is indexed forward and reverse. ADRP+ADD and ADRP+LDR pairs on arm64 resolve to target addresses and land in two indexes: which data each function references, and which functions reference each address. String constants resolve through those indexes and inline as comments, so a load of 0x1000d2480 reads as ; "com.apple.wifi.set_channel" beside the instruction.

The same index answers query-by-needle: an imported symbol, a virtual address, a string literal, or an Objective-C class name returns every referencing site, each tagged by kind — call, address load, class reference, string reference, jump table.

System calls

Syscalls are resolved whether they go through the standard library or straight to the instruction, with a separate path per OS because the ABIs diverge. Most go through the standard library; going straight to the instruction is a common way to bypass library-level instrumentation.

OSMechanism
Applesvc #0x80, number in x16
Linux x86-64syscall, number in rax
Linux arm64svc #0, number in x8
Linux ARM32svc 0, number in r7
Linux MIPS o32syscall, number in $v0
Linux PowerPCsc
Windowsusually Win32 imports; a raw syscall is itself the tell

One number table is curated per OS and per architecture, nine of them for Linux alone, straight from each kernel's own. The spread between them is the point — a number valid on one is a different call on another, which is why there is no single Linux table:

TableEntriesNote
Apple457 BSD entries plus 62 Mach trapsfrom XNU's syscalls.master
Linux arm64328started from the trimmed asm-generic list
Linux MIPS o32445carries decades of legacy numbering
Linux x86-64385
Linux x86 (32-bit)423
Linux ARM (32-bit)424
Linux PowerPC425
Linux SPARC434
Linux s390x381
Linux MIPS n64374
FreeBSD499the BSDs diverge harder than the Linux architectures do
NetBSD377
DragonFly316
OpenBSD231a direct readout of that project's minimalism
Solaris/illumos246

Windows renumbers its service table on every release, where Linux numbers have been stable for decades and the BSDs move only on major versions, so a Windows syscall number means nothing without a version to read it against:

Windows tableEntriesCovers
Primary Nt*142 malware-relevant entriestheir Windows 11 23H2 and Windows 10 22H2 numbers
Per-build57 entriesa separate number for each of Windows 10 1809, 10 22H2, 11 21H2, 11 22H2, 11 23H2, and 11 24H2, so a hardcoded index can be read back as which build the sample was written for
win32k.sys graphics21 entriesthe graphics service table
Windows 7 RTM fallback401 NT entries and over 800 graphics entries per bitnessthe embedded and industrial systems frozen there, and the 2009–2015 samples that hardcoded those indices

A number outside every table is reported as an unresolved direct syscall, not guessed at, and that non-answer is itself a strong indicator of syscall-based evasion.

Resolved syscalls are placed in a synthetic address range so they appear as first-class call-graph edges and cross-reference targets alongside imports. A separate counter tracks direct-syscall sites where the number could not be pinned — a spike in that counter is itself the obfuscation signal.

When the binary under analysis is libsystem_kernel.dylib, the Mach-trap number table is statically carved out of the SSA IL and keyed to that library's own build identifier, so trap numbers join to names without running a kernel; the carve also diffs itself against the curated table and reports which entries are new and which disappeared. The artifact is written only when explicitly asked for, and the shared-cache identifier the record has a slot for is never filled in — only the library's own identifier is real.

Dynamic loading

Each dynamic-loading site is an indirect call whose target is a string, and until the string is recovered the call graph at that site is empty: dlopen("/usr/lib/libobjc.A.dylib"), dlsym(h, "objc_msgSend"), NSClassFromString(@"NSXPCConnection"), objc_getClass, sel_registerName. Sites split three ways:

Site stateMeaning
Resolvedpinned to a literal; the site is annotated with the exact path, symbol, or class
Computedbuilt from a computation traceable but not constant-foldable — a concatenation, a sprintf with a constant format and a variable
Unresolvedneither

A high computed count is almost always string obfuscation, and the count is a first-class field on the fingerprint.

Behavioral signatures

A behavioral signature is the resolved arguments at a specific call site — one step past "this binary calls open". Opens a path under /Users/*/Library/Keychains/login.keychain-db. Execs /usr/bin/osascript. Connects to a Unix socket under /var/run/.

Sixty call primitives carry per-argument schemas:

FamilyPrimitives
Filesystemopen, openat, stat, access, readlink, getattrlist, chmod, mkdir and their at variants
Process spawn and identityexecve, posix_spawn, the execl and setuid families
Dynamic loadingthe dlopen/dlsym family
Kernel-state queriessysctlbyname
BSD networkingsocket, connect, bind, listen, accept, getaddrinfo, gethostbyname, res_query
CFNetworkthe URL/HTTP/Host/Stream surface
Mach-XPCthe XPC surface, including the initWithMachServiceName: message stubs

Two transforms make a signature stable across hosts:

Resolution decides what is emitted:

Required arguments resolvedEmitted as
Alla full-resolution signature
At least onea partial signature
Nonenothing at all

That last rule is what keeps the surface searchable: asking a corpus for every binary that opens a path under /Users/*/Library/Keychains/ returns answers, not noise.

Curated knowledge

Names, layouts, and reference distributions the binary does not carry come from shipped databases, from the metadata a runtime requires of itself, and from debug information when it can be proven to match.

Apple framework layouts

A curated database of 3,162 classes and 18,633 fields — Foundation, AppKit, CoreData, CoreFoundation, Security, QuartzCore, CoreLocation, CoreText — turns Apple framework field accesses back into names. Most of what an Apple binary does is load fields out of those types, and without the layouts every one of them reads as an anonymous offset. NSXPCConnection, for one:

offset  8  →  _connection        (an OS_xpc_remote_connection)
offset 16  →  _repliesExpected
offset 24  →  _userQueue         (a dispatch queue)
offset 32  →  _state

Known struct types (CGRect, CGPoint, NSRange) resolve offsets to named members; known enums resolve integer constants to named variants. Loaded on first access, so a binary that never touches Apple frameworks pays nothing.

Library recognition

Four independent corpora identify linked-in library code and name it, consulted in descending confidence. A real binary is mostly not the application's own code — it is OpenSSL, libcurl, sqlite, and several hundred other linked-in projects — and without recognizing them, analysis spends its time reading library internals and reconstruction emits them instead of imports.

CorpusScaleKeyed on
Hand-built signatures67the strings and algorithmic constants a function references plus its size range — zlib, sqlite, OpenSSL, BoringSSL, libcurl, lz4, zstd, libpng, mbedTLS, xxHash. The constants are drawn from FIPS, RFC, and ISO specifications: permanent mathematical facts, not version-specific bytes
Prologue hashes87,828 hashes from 219 open-source librariesfunction prologues, shipped as a 6.5 MB binary index
Apple kernel fingerprints3,797 distinct kernel functions across 11 Apple Silicon variants, including the virtualization kernelbyte-identical signatures mined from the arm64e kernels on disk. Each signature carries the macOS version it was mined from, so a refresh after an OS upgrade is an append, not a replacement. They put real names like Switch_context and cluster_pagein on calls that were otherwise anonymous
FLIRT pattern corporaa vendored Mandiant FLARE pack of 1,236,310 parsed entries (Apache-2.0), plus a generated corpus of 1,015 blobs covering 99 statically-linked libraries across all three formats — 667 ELF blobs spanning x86-64, arm64, arm, riscv64, ppc64, s390x, mips64 and both musl and glibc variants, 295 PE, and 53 Mach-OFLIRT patterns. The FLARE pack covers MSVC runtime libraries for PE x86 and x86-64 and is lazy-loaded so it never fires on a non-PE input; the generated corpus covers libcrypto, libsodium, libonig, libpcre2, libnghttp2, libzstd, libopus, libwebp, libevent, libcares and the rest, each blob decompressing on first use and parsing once per process

Precedence on hash collision is explicit and three-layered: an operator-supplied path beats a user cache, which beats the bundled corpus. The user cache is extensible from three miners — one refreshing the kernel fingerprints after a macOS upgrade, one extracting every named function from any dyld cache (a vendor SDK, an iOS firmware cache, a different macOS version), and one doing the same for a standalone dylib, daemon, or app.

The match set rolls up into build provenance — a consensus compiler (Apple clang, MinGW, MSVC, GCC), a consensus optimization level, and an agreement ratio — plus a statically-linked-library inventory with per-library name, version, and matched-function count. That inventory is what the CVE layer matches against (CVEs & SBOM).

Runtime metadata

Every binary publishes some metadata that its own runtime needs, and that metadata survives stripping because the program stops working without it.

LanguageRead fromWhat comes out
Objective-C__objc_classlistClasses, instance and class methods, ivars, protocol conformances. Type encodings (v24@0:8@16) decoded into readable signatures. Both the arm64e relative method-list layout and the legacy absolute-pointer layout. Shared-cache images additionally read Apple's libobjc optimization tables for selectors and class names the per-image path cannot reach
Swift__swift5_types, __swift5_fieldmd, __swift5_protoType descriptors through relative 32-bit pointer resolution. Field names, types, mutability, indirect enum cases. Protocol descriptors and conformance records with retroactive and synthesized flags. Pure-Rust demangling. Module names inferred from prefixes
C++vtables in __const, RTTI in __data_constItanium ABI vtable parsing. Typeinfo classified leaf, single-inheritance, or multiple-inheritance. Full class hierarchy. Pure-virtual slot detection. Vtable function pointers merge into the global symbol table
Gobuild info, pclntab, moduledata, type descriptors, interface tablesGo version, module path, build settings. Function names, boundaries, and source-line mappings even when stripped, across ELF, Mach-O, and PE. moduledata anchors the walk when section headers lie. Function-chain analysis attributes each function to its defining module. Toolchain provenance splits stdlib evidence into compiler-mandated versus author-elected imports, so a go build hello-world stops looking like it chose to link networking. Interface-table parsing resolves dispatch sites to concrete type-method pairs

Each binary is classified Objective-C, Swift, C++, Rust, Go, Mixed, or Unknown from which metadata is present and which symbol patterns appear, and the classification is a first-class field on both the analysis and the fingerprint, so a similarity search can scope to one runtime.

DWARF

Debug information is used only when it can be proven to belong to the binary, and refused silently when it cannot — a mismatched dSYM or a CRC-mismatched sidecar means the analysis proceeds without it rather than with the wrong names.

What comes back: function signatures with named parameters and typed returns, struct field names and sizes, enum variant names, source-file paths from the line table. DWARF locals are one of three rename sources; precedence runs argument-taint, then ivar layout, then DWARF, then mechanical, so DWARF supplements rather than overwrites.

Function rarity

Each function is scored by Mahalanobis distance against a reference distribution of centroids, computed per binary type (daemon, app, extension, kernel) so scores are comparable within a class. Once library code is filtered out, what remains is the application's own — and most of that is unsurprising scaffolding. The reference distribution is a 65 KB blob compiled into the executable and parsed on first use: no setup, no cluster fit, no model serving. The output is an ordering, most unusual first.

This is the engine's only built-in machine learning. Heavier classifiers live on the consumer side, fed by the fingerprint and by a per-function feature vector of 41 dimensions:

Cross-cutting views

Each of these views is computed from the one analysis record, read from a different angle.

Capabilities

What a binary can do to the system, mapped into platform-specific domains by combining entitlement evidence, framework evidence, and resolved syscall evidence.

PlatformDomainsEvidence
AppleLocation, Keychain, Network, Storage, Hardware, IPC, Process, Analytics, Security, SystemEntitlements from the code-signature plist (specific keys plus prefix patterns), linked frameworks, syscalls and libSystem wrappers through the aspect taxonomy
LinuxNetwork, Process, Filesystem, Memory, IPC, Crypto, DynamicCode, Time, SystemInfo, ThreadingA curated table of 297 libc symbols, nine per-architecture syscall tables, TLS-base and fs-segment intrinsic detection
WindowsProcessControl, Network, Crypto, HostInteraction, AntiAnalysisImported DLL classification (kernel32, ws2_32, bcrypt, crypt32, ole32), TLS-callback presence, control-flow-guard and exception-continuation directory entries

An entitlement with no mapping is reported as unmapped, never dropped. Every hit carries its raw evidence — the entitlement string, the framework name, or the syscall — and severity is left to whoever reads it.

Code signatures

One symmetric identifier surface across the three formats, so a consumer can join on it:

FormatIdentity digestOther recovered fields
Mach-OCDHash, a digest of the signed structure, from the CodeDirectory blobteam identifier, platform identifier, and signing flags decoded into names — hardened runtime, library validation, restrict. Each binary classifies as Apple-signed, developer-signed, ad-hoc, or unsigned
PEa SHA-256 over the full Authenticode PKCS#7 signature blob, the PE counterpart to the CDHashthe first signer's common name, the digest algorithm from the SignerInfo OID, and the signing time from the PKCS#9 attribute. A malformed-but-present signature still surfaces whatever fields could be recovered; an absent certificate directory returns unsigned
ELFthe GNU build ID — typically 20 bytes for SHA-1, 16 for MD5written into a note section and its corresponding note segment

The PE application manifest — the Win32 equivalent of an entitlements plist — surfaces the requested execution level (asInvoker, highestAvailable, requireAdministrator), the UI-access flag, and the DPI declaration.

Build hardening

Hardening is emitted on every analysis rather than behind a separate command, so two builds of one source with a single mitigation flipped differ in the fingerprint and in the diff automatically. Twenty-two Mach-O finding kinds, ten PE, eleven ELF. The per-flag meaning of each is in Binary anatomy.

Six of the Mach-O kinds read entitlements rather than build flags, because an entitlement can hand back a mitigation the compiler switched on:

The Linux classifier reads architecture-specific control-flow-integrity bits out of the PT_GNU_PROPERTY segment — and when a toolchain emits that property record only as a section, with no corresponding program header, it falls back to a section walk. That is the exact shape checksec and pwntools miss and report as unprotected.

And there is no global hardening score. Findings carry per-finding severity inheriting industry-tool conventions, but aggregating across formats would be dishonest and policy belongs to the consumer. A hardening hash and an on/off summary feed the fingerprint; the diff carries regression and improvement sections so a mitigation flip across versions surfaces as a fact.

Surface consistency

The gap between what a binary declares — entitlements, manifests, signatures — and what it actually does — resolve imports, call syscalls, follow dynamic loads, expose XPC services — is where the finding lives:

DivergenceMeaning
Declared, unusedA capability is declared but never exercised
Used, undeclaredA capability is exercised without being declared
Via dynamic loadA capability is reached only through dlopen or dlsym, invisible to anything that reads static linkage
Private entitlement, non-AppleAn Apple-private entitlement is held by a binary that is not Apple-signed
Ad-hoc privilegedAn ad-hoc-signed binary holds privileged entitlements
Bundle-ID impersonationThe bundle identifier is constructed to impersonate a trusted identity

Each carries MITRE ATT&CK technique IDs to pivot on, and each is a record, not a verdict. The last two consult a curated Apple team-ID allowlist holding three classes of entry: legacy display strings (Apple Inc., Software Signing — the identity forms codesign -dv printed before team IDs existed), public ten-character team IDs observed on shipped Apple binaries with each row citing the binary it was observed on, and the short prefix sentinels Apple uses internally.

Behavioral aspects

Twelve axes partition what a binary does: file_io, network, process_control, ipc, memory, crypto, time, signals, threading, dynamic_code, system_info, hardware. Three evidence streams feed each — direct syscalls, libc or libSystem wrapper calls, and framework or Win32 API calls off the call graph — so an aspect's evidence count reflects the whole surface rather than raw syscalls alone.

The operation-to-aspect mapping is a curated table per platform. On Apple:

AspectOperations mapped in
file_ioopen, read, write, stat, unlink
networksocket, connect, bind, sendto, plus NSURLSession and CFNetwork
process_controlposix_spawn, execve, fork, kill
ipcmach_msg, XPC bootstrap, Unix-domain sockets
memorymmap, mprotect, madvise, and the allocators

Linux and Windows map into the same twelve.

The summary carries a taxonomy version — a cache-invalidation key for consumers when a new axis ships — a dominant aspect, and a hardening ratio: the share of behavioral evidence that came from direct svc sites rather than wrappers. A high ratio marks a binary talking to the kernel directly.

Network endpoints

Every URL, IPv4 and IPv6 address, and hostname is extracted from strings with false-positive rejection, and each networking call is attributed to the application method that makes it — so the answer is not just which networking APIs are used but which methods use them.

Strings

Printable runs of four or more characters come out across the whole file, plus structured CFString constants with chained-fixup pointer resolution, categorized into telemetry endpoints, URLs, file paths, bundle identifiers, and IOKit constants — so what does this collect and what does this access are answerable without walking the call graph. On airportd the telemetry surface comes out as 120 strings against 23 submission methods.

Entropy and embedded formats

Per-section Shannon entropy is reported across all three formats. A heuristic fires when imports are sparse and code entropy is high — the canonical compressed-or-encrypted shape — and overlay detection reports bytes past the end of the image. When the packer is one that can be statically unpacked, the story does not end at high entropy: the payload is recovered and judged on its own (Unpacking).

The overlay and high-entropy sections are also scanned through the full format-signature table with two confidence tiers, magic-only and header-validated, covering compression, containers, filesystems, firmware carriers, and crypto markers (AES tables, hash-init constants, PEM armour, LUKS and DPAPI headers). Each match carries section, offset, and tier.

Secret material

Twenty-three kinds of credential and key material are scanned for: PEM private keys, public keys and certificates, OpenSSH and PuTTY private keys, X.509 DER, PKCS#8, PKCS#12, PKCS#7 SignedData, raw-DER RSA and EC private keys, PGP private keys, OpenPGP RSA session keys, JWTs, AWS access keys, GitHub personal access tokens, Slack tokens, GCP service-account markers, crypt(3) password hashes, LDAP SHA hashes, expanded AES and SM4 key schedules in data sections, and leaked vendor firmware signing keys.

Scan sites are format-dependent, and the ELF and PE breadth is what makes stripped firmware with keys in read-only data come back non-empty instead of reporting nothing:

FormatScanned
Mach-Ofour sites: the overlay, high-entropy sections, the C-string section, and the Objective-C method-name section
ELFthe overlay plus every non-empty section except loader metadata — .plt, .dynsym, .dynstr, the hash, version and relocation tables
PEthe overlay plus every non-empty section except .idata, .edata, .reloc, .pdata, .tls and .debug*

Findings carry ATT&CK tags (T1552.001, T1552.004, T1552.005).

IOKit attack surface

For Apple kernel collections, IOUserClient subclasses are extracted from the C++ RTTI hierarchy — those are the kernel objects a user-space process can open and call. Entitlement string references inside driver code identify what gates each one. The result is a map both ways: which drivers an entitlement gates, and which entitlements a driver requires. That is the first thing a kernel security review walks.

The authorization audit matrix

A privileged operation reachable from an untrusted entry point with no authorization check on every path is the canonical authorization-gap bug, and the matrix looks for exactly that shape. It walks every entry point — XPC especially — forward through the call graph including jump-table edges, and records two things per reachable function:

Every detector that asks "reachable from X" asks the same deterministic, bitset-backed index rather than walking the graph itself, so the answer cannot differ between the audit matrix and the finding that quotes it; the policy about which nodes are roots and where a walk stops stays with each consumer.

A row is flagged when a privileged operation is reachable and no verifier dominates every path to it. The dominance check is a stated under-approximation of dynamic coverage in both directions: a false negative when a verifier is conditionally skipped inside an intermediate function, a false positive when a privileged call is guarded by a runtime predicate static analysis cannot see. Verifier coverage is never claimed as a safety guarantee. Both tables are curated, auditable in version control, and extensible per macOS release. There is no severity scoring.

Security read-outs

Findings, indicators, malware verdict, and CVE matches are four parallel views of the same analysis, each with its own taxonomy and confidence model:

Security is the orientation map across the four.

Outputs

The behavioral fingerprint

A compact, comparable representation of behavior — so two binaries compare at a glance, a fleet searches by similarity, and drift across versions is detectable without re-reading the code.

Twenty-one structural hashes cover different facets, each content-addressed so identical content produces identical output regardless of analysis host or ordering. They split into two groups that must not be confused:

GroupMembersProperty
In-corpus clustering keys (11)the code section only (so a re-signed binary with identical code matches its previous build), the sorted call-graph symbol set, the nlist function names, frameworks, Objective-C class names, the C++ hierarchy, entitlements, resolved syscalls, the twelve-axis aspect profile, the indicator-rule hit set, the on/off hardening postureprivate SHA-256 digests
Cross-corpus identifiers (10)imphash, telfhash, symhash, import_md5, dylib_hash, export_hash, import_hash, entitlement_hash, TLSH, the Rich-header hashinteroperability strings. Eight are byte-exact with the yara-x function of the same name; TLSH is a raw Trend locality-sensitive hash and the Rich-header hash follows VirusTotal's definition, neither of which yara-x exposes

The two groups overlap in subject and not in value — entitlements are hashed twice, once as a private SHA-256 over the keys and once as the MD5 an external corpus can join on. The call-graph symbol hash is deliberately not imphash, and TLSH is deliberately not folded into similarity scoring — TLSH is what you paste into VirusTotal or a public threat report, not what ranks a corpus.

Beside the hashes, two ingestible bodies of content:

Similarity is a weighted average of per-set Jaccard scores, and the numeric vector is not part of it. Cosine over the vector is a retrieval step only: it pulls 500 candidates out of the index, which are then reranked and scored on Jaccard alone. Cosine and Jaccard orderings genuinely diverge — the pool is 500 rather than 100 precisely so a true Jaccard match is not lost to a cosine ordering that ranked it low.

Weights are calibrated against measured signal, not intuition, on the principle that behavioral coincidence is stronger evidence than structural coincidence:

SetWeight
Indicator-rule hits, entitlements6.0
ATT&CK coverage, consistency flags5.0
Objective-C and Swift type names0.5

Two weight tables exist and are not identical: corpus search adds six behavioral-signature sets at the top weight and omits the raw syscall sets the aspect partition supersedes, so a compare score and a search score for the same pair need not agree. On the compare table, nc returns iperf3 at 60.6%, traceroute at 57.8%, and rarpd at 56.3% — three independent networking tools clustering on behavior alone, none of which shares code with the others.

Every numeric and categorical field is directly ingestible by a downstream classifier or similarity index. Rendered for a human, the same data is a one-page bar chart.

Reconstruction to Rust

The control-flow graph is structured into a region tree — if/else, while, switch with one arm per resolved jump-table handler, break and continue in place of goto — by a "no more gotos" walk that turns the graph into a tree an emitter can render linearly.

Project assembly is one command producing a buildable Cargo project: one file per Objective-C, Swift, or C++ class, grouped C functions, framework stubs typed against the Apple SDK so the project compiles without an SDK dependency, a manifest whose dependencies are inferred from recognized statically-linked libraries, and a library root. A single class or method can be reconstructed instead of the whole binary.

The output preserves real source structure rather than flattening it to functions and gotos:

Binary constructReconstructed as
Objective-C class-reference loadclass!(NSData)
BOOL property paircode that compiles
Multi-way dispatcher, including the XPC request dispatchers in system daemonsmatch with one arm per resolved handler
XPC protocol interfacea trait definition — a typed API contract pulled straight out of the binary

Types are layered, drawn from four sources:

Type sourceSupplies
Objective-C type encoding in the class listsignatures at the function boundary
Swift type sectionSwift signatures
DWARFnames, when present
Argument-taint analysisparameter names, when nothing else does

Parameter direction is not among them. A pointer parameter emits as a raw pointer — *const T or *mut T — from the type lattice, and nothing promotes it to a shared or mutable Rust reference on access evidence. The analysis is written — a walk of every load and store against the parameter slot, propagated bottom-up over the call-graph condensation, with an Objective-C carve-out so self is never promoted to a mutable reference by access inference alone — but neither level has a production caller, and the emitter has no slot for its output. The 520 parameter signatures it would change across four fixtures are the size of the missing improvement, not a description of what ships:

FixtureSignatures it would change
ripgrep396
airportd103
jq20
curl1

Every emitted method carries a confidence score from 0.0 to 1.0 and a count of residual unrecovered operations. A manifest records per-method confidence, that count, resolved-versus-unknown class-reference counts, and a free-text reason when the project did not compile end to end — most often "verify step not run", because the pipeline does not measure compilation itself. Irreducible control flow is not in the manifest — it is a flag on the emitter's output, visible in the per-method stage dump, and costs a fixed 0.10 of the confidence score. On airportd — 37 classes, 1,586 methods — the full-binary path reaches a 99.7% method compile rate. That figure is for the whole binary; a single-class reconstruction is a different measurement and is not it.

A verify step runs cargo check against the output, computes class and method coverage relative to the original binary, and counts residual unknown markers, so reconstruction quality is a number that moves over time rather than an impression.

Method and class inspection

Any single class or method can be interrogated without re-running the analysis.

Fuzzy matching handles partial names; dot notation scopes a method search to one class; a single query searches Objective-C, Swift, and C++ types at once.

Any internal stage of a matched method can be dumped: annotated assembly, pre-SSA IL, the edge list, the full SSA form, and the structured region tree with real condition expressions in its if, while, and switch headers. Stages combine and serialize as JSON. This is what makes a low-confidence reconstruction diagnosable — when the manifest shows a method below threshold, the stage dumps show which stage lost the information.

Diff and compare

Two cross-binary outputs answer version drift and family attribution respectively.

Diff answers what a platform update added in telemetry, what changed in encryption behavior, and what new XPC endpoints appeared. It pairs functions across two versions in three phases:

PhasePopulationPaired on
1named functionssymbol name
2unnamed functionsinstruction hash
3the remainderstructural Jaccard over their call-target sets

Each pair classifies as size-only, body-changed, calls-changed, or fully-changed; new, removed, and changed methods come out as lists; the hardening section reports mitigation regressions and improvements.

Compare runs the same weighted-Jaccard similarity between two binaries directly, for attributing families and tracking drift.

Corpus-level aggregate reports walk a directory of binaries and emit population counts and pairwise similarity statistics. They double as engineering gates: a regression in aspect signal, literal recovery, or override coverage fails the build before merge.

Limits

The engine produces facts about a binary. It does not present them, rank them, alert on them, or decide what they mean. Anything dependent on judgment lives in a surface; anything dependent on measurement lives here.

Execution model

Analysis inside one binary parallelizes over Rayon — per-function passes, and ELF and PE section walks proven byte-equivalent to the serial path — and a pool of pre-warmed engine subprocesses keeps corpora and signature indexes loaded, so a request pays no cold start. Platform is the surface that indexes these facts, searches them, ranks them, and adds the policy layer that turns them into something to act on.