Zorya: Go Binary Concolic Execution Framework
- Zorya is a binary-level concolic execution framework for Go binaries that employs Ghidra’s P-Code IR and Z3 solver to uncover vulnerabilities.
- It utilizes panic-gated and overlay concolic execution strategies to detect runtime panics, nil pointer dereferences, index errors, and silent integer overflows.
- Its evolution from a TinyGo prototype to a multi-threaded system demonstrates significant speedups and robust detection in complex binary environments.
Zorya is a binary-level concolic execution framework for Go binaries that lifts compiled binaries to Ghidra’s P-Code intermediate representation and uses Z3 to reason over both concrete and symbolic values. Across its published iterations, Zorya is presented as a method for systematically discovering inputs that trigger runtime panics and related vulnerabilities in Go/TinyGo binaries, while also being extensible to other languages such as C whose binaries Ghidra can lift (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025, Gorna et al., 5 May 2026). Its development proceeds from an initial prototype for TinyGo-compiled, single-threaded programs, through panic-gated concolic execution and function-mode analysis, to support for multi-threaded binaries produced by Go’s standard gc compiler and overlay concolic execution for untaken branches (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025, Gorna et al., 5 May 2026).
1. Problem domain and motivation
Zorya is motivated by the combination of Go’s widespread use in infrastructure backends and blockchain projects and the difficulty of applying existing symbolic execution tooling to Go binaries. The initial formulation emphasizes that established techniques such as unit testing, static analysis, and program fuzzing provide foundational protection mechanisms, but that opportunities remain to address the complexities of Go’s runtime and concurrency model (Gorna et al., 26 May 2025). The same line of work notes that more than 66% of Go modules contain vulnerabilities, and identifies a complex runtime, rich pointer and memory usage, error handling and panics, and the concurrency model as major sources of analysis difficulty (Gorna et al., 26 May 2025).
The framework is designed around the observation that Go’s structured failure mechanism makes panic reachability a practical analysis target. In Go and TinyGo, violations such as nil pointer dereference, index out of bounds, nil map assignment, too large channel creation, and negative shift manifest as runtime panics, and these panics are typically input-dependent and deterministic (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025). Zorya therefore treats panic sites as bug sinks or target states and directs symbolic reasoning toward them instead of attempting exhaustive whole-program symbolic exploration.
This positioning also explains Zorya’s relation to prior tools. Existing symbolic or concolic engines are described as largely tuned for C/C++ binaries or LLVM-oriented ecosystems, with weak support for Go’s runtime and panic model, system calls, static linking style, or multithreading and goroutines (Gorna et al., 26 May 2025). Source-level Go tools are correspondingly limited by type coverage, runtime support, or inability to analyze binaries (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025). Zorya fills this gap by operating directly on binaries and specializing the analysis around Go runtime safety checks.
2. Binary-level architecture and intermediate representation
A defining architectural choice is the use of Ghidra’s P-Code as the analysis substrate. Zorya works directly on binaries, not source code, and uses low-level or raw P-Code rather than a higher-level decompiler representation in order to preserve micro-level semantics relevant to panics, bounds checks, and optimized binaries (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025). P-Code is treated as language-agnostic, fine-grained, and tightly integrated with Ghidra’s lifting infrastructure, including Go support and x86-64 Sleigh specifications (Gorna et al., 26 May 2025).
The early pipeline consists of Ghidra lifting, a custom pcode-generator to export low-level P-Code outside Ghidra, a Rust pcode-parser to reconstruct instructions and control flow, and a Rust execution engine that maintains CPU state, a memory model, and a virtual file system (Gorna et al., 26 May 2025). The engine interprets each P-Code instruction both concretely and symbolically, with instruction handlers such as handle_int_add and handle_load coordinating concrete updates and symbolic updates (Gorna et al., 26 May 2025). Logging is instruction-level and trace-level, with execution_log.txt and execution_trace.txt used for reporting (Gorna et al., 26 May 2025).
Later versions add further metadata recovery. The panic-gated system parses DWARF debug information to recover function signatures, argument types and locations, and type layouts, then builds a control-flow graph and computes backward panic reachability (Gorna et al., 11 Dec 2025). The gc-oriented extension reconstructs initial state from a gdb dump, including memory contents, per-thread register sets, thread-local storage bases, and thread backtraces, then uses these dumps to bootstrap the initial concrete state for multi-threaded binaries (Gorna et al., 5 May 2026).
| Paper | Main scope | Distinguishing additions |
|---|---|---|
| (Gorna et al., 26 May 2025) | TinyGo-compiled, single-threaded binaries | P-Code pipeline, S1/S2/S3, generic and custom invariants |
| (Gorna et al., 11 Dec 2025) | Single-threaded Go/TinyGo binaries | panic-gated concolic execution, bug detection in concretely not taken paths, function-mode |
| (Gorna et al., 5 May 2026) | gc-compiled, multi-threaded Go binaries | gdb thread-state reconstruction, preemption neutralization, overlay concolic execution |
Because P-Code is ISA-based rather than language-based, Zorya is also described as extensible beyond Go. The initial paper reports successful application to C binaries compiled with gcc, including detection of null pointer dereference, misaligned memory access, and uninitialized variable use (Gorna et al., 26 May 2025). This suggests that the framework’s core execution substrate is not intrinsically Go-specific, even though much of its vulnerability targeting is.
3. Concolic execution model and invariant-based reasoning
Zorya’s execution model is concolic in the standard sense: it couples a concrete machine state with a symbolic state and uses concrete execution to drive symbolic execution (Gorna et al., 26 May 2025). Inputs are assigned concrete values for the observed run, but are simultaneously represented as symbolic variables; derived values become symbolic expressions over those inputs; and each conditional contributes a logical predicate to the current path constraint (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025). A representative transition form used in the descriptions is
where is the concrete state, the symbolic state, and the path constraint (Gorna et al., 26 May 2025).
In the initial design, Zorya uses three analysis strategies. S1 is concrete execution plus a panic flag: if the program counter reaches an address known to be a cross-reference to a panic function, Zorya raises a vulnerability flag (Gorna et al., 26 May 2025). S2 is concolic execution plus a generic invariant: “the program counter must never point to an address that is a cross-reference to a panic function” (Gorna et al., 26 May 2025). S3 is targeted concolic analysis of specific functions: execution begins at a chosen function, selected arguments are symbolic, concrete values are randomized, and user-defined invariants are checked for satisfiability (Gorna et al., 26 May 2025).
The generic panic invariant can be written in the state-oriented form
where is the set of known panic call sites (Gorna et al., 26 May 2025). For C binaries, built-in checks include null dereference, misaligned memory, and use of uninitialized variable. The misalignment condition is given explicitly as
for address and access size (Gorna et al., 26 May 2025). The framework also supports custom invariants of the form , encoded through instruction handlers or solver constraints (Gorna et al., 26 May 2025).
The later panic-gated system refines path exploration by focusing only on negated branches that are relevant to panic reachability (Gorna et al., 11 Dec 2025). Given a concrete path predicate 0 and a branch guard 1, alternative execution is explored through satisfiability of 2 rather than indiscriminate forking at every branch (Gorna et al., 11 Dec 2025). This version introduces a multi-layer filtering mechanism comprising precomputed panic reachability, internal target detection for Ghidra artifact branches, constraint-context filtering, and a bounded AST pre-check before any SMT query is issued (Gorna et al., 11 Dec 2025). The stated effect is to concentrate symbolic reasoning on panic-relevant paths and reduce solver traffic.
4. Go-specific modeling and framework evolution
The earliest Zorya prototype is explicitly limited to TinyGo v0.33.0 on x86-64 and assumes single-threaded execution (Gorna et al., 26 May 2025). Its Go-specific handling centers on TinyGo’s panic runtime, especially runtime/panic.go, and on cross-reference analysis for panic functions (Gorna et al., 26 May 2025). Panics are therefore modeled not as generic crashes but as semantically meaningful safety failures that can be identified at the binary level.
The panic-gated iteration extends this with function-mode analysis. Zorya can start from main.main in main-mode or directly at a user function in function-mode, using DWARF information to recover argument types and locations and then instantiate symbolic arguments accordingly (Gorna et al., 11 Dec 2025). This bypasses runtime initialization, scheduler setup, CLI parsing, and related path explosion sources. In the same system, slices are modeled as triples 3 with concrete backing memory, strings are modeled similarly with a hard size limit, and address-intensive operations rely on lazy concretization so that symbolic indices remain symbolic even when a concrete address is needed for the current run (Gorna et al., 11 Dec 2025).
The gc extension alters the scope substantially. Rather than interpreting program startup from scratch, Zorya executes the binary once under gdb, dumps memory and thread state, classifies threads by backtrace, and reconstructs the initialized runtime state from that snapshot (Gorna et al., 5 May 2026). It then neutralizes runtime preemption before entering the target function by modifying runtime-internal fields so that Go prologues do not branch into scheduler logic (Gorna et al., 5 May 2026). The evaluation uses main-only scheduling, freezing non-target threads while the target function runs on the main thread (Gorna et al., 5 May 2026).
The principal new exploration mechanism in the gc paper is overlay concolic execution with copy-on-write semantics (Gorna et al., 5 May 2026). Instead of fully forking the executor state at each symbolic branch, Zorya creates a lightweight overlay for the untaken branch, stores only written registers and memory in that overlay, explores the branch for a bounded number of P-Code instruction blocks, and then discards the overlay and restores the base state (Gorna et al., 5 May 2026). The default bound is 15 blocks (Gorna et al., 5 May 2026). This mechanism is explicitly aimed at detecting silent vulnerabilities on untaken branches, including unsigned integer overflows that do not trigger a panic in Go (Gorna et al., 5 May 2026).
5. Detection mechanisms and vulnerability classes
Zorya’s primary target class is panic-inducing behavior in Go. The early benchmark suite comprises five TinyGo runtime panics: nil pointer dereference, index out of range, nil map assignment, too large channel creation, and negative shift (Gorna et al., 26 May 2025). The framework detects these by recognizing panic call sites, driving execution to them through concolic branch negation, or starting from a target function whose arguments are symbolic (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025).
The system’s branch-handling logic is increasingly target-directed. The panic-gated algorithm invokes SMT only when the current location or branch target is panic-reachable, the target is not an internal P-Code address, the guard is relevant to symbolic arguments or an existing symbolic context, and a bounded forward exploration of the alternate path reaches a panic (Gorna et al., 11 Dec 2025). The panic-reachability condition is stated as
4
where 5 is the backward-reachable set from panic sites and 6 is the set of panic sites (Gorna et al., 11 Dec 2025).
The gc extension broadens the vulnerability set beyond explicit panics. Nil pointer dereferences are checked around LOAD and STORE; index out-of-bounds is associated with panic sites such as panicIndex; and integer overflow is inspected around arithmetic operations such as INT_MULT (Gorna et al., 5 May 2026). For overflow, the paper gives the specific Go-Ethereum memoryGasCost case, in which a multiplication of two 64-bit quantities is zero-extended to 128 bits and queried for non-zero upper bits under the current path condition (Gorna et al., 5 May 2026). This is significant because unsigned arithmetic overflow in Go is silent by specification and does not itself cause a panic.
A common misconception is that Zorya is merely a fuzzer with symbolic input mutation. The published systems instead follow a different design. The early versions solve flipped branch predicates to synthesize new concrete inputs and re-run selected paths (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025). The gc version, by contrast, is centered on a single concrete run supplied through the gdb snapshot and augments that run with bounded overlay exploration of untaken branches rather than systematic re-execution under solver-generated inputs (Gorna et al., 5 May 2026). Another misconception is that Zorya is a general whole-program verifier for Go concurrency. The earlier versions explicitly do not model goroutines, channels, select, or the scheduler (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025), and the gc paper’s multi-thread support is primarily used to stabilize the runtime environment and reach target functions under main-only scheduling (Gorna et al., 5 May 2026).
6. Evaluation, limits, and significance
The initial evaluation establishes three claims. First, P-Code can be generated and used outside Ghidra with high accuracy: the reported dataset gives 10 true positives and 0 false positives for Go binaries, and 9 true positives and 1 false positive for C binaries (Gorna et al., 26 May 2025). Second, on five TinyGo runtime panics, DuckEEGO, Radius2, and MIASM all yield ND, whereas Zorya yields D for every case; the same paper states that Zorya, using strategy S1 alone, detected all bugs quickly, with each analysis under a minute and no false positives (Gorna et al., 26 May 2025). Third, using invariants for C pointer and memory safety, Zorya successfully detected null pointer dereference, misaligned memory access, and use of uninitialized variable, without false positives (Gorna et al., 26 May 2025).
The panic-gated paper strengthens the scalability argument. On five Go vulnerabilities, panic-reachability gating achieves 1.8–3.9x speedups when filtering 33–70% of branches (Gorna et al., 11 Dec 2025). Function-mode analysis is reported as essential for complex programs: in the Omni Network case, main-mode did not complete within 2 hours, whereas function-mode starting at GetMultiProof terminated with panic-discovering inputs in approximately 83 seconds, described as better than 87× speedup (Gorna et al., 11 Dec 2025). The same evaluation states that Zorya detects all panics while existing tools detect at most two (Gorna et al., 11 Dec 2025).
The gc paper moves from synthetic or TinyGo-oriented settings to 11 real-world vulnerabilities from production Go projects such as Kubernetes, Go-Ethereum, and CoreDNS (Gorna et al., 5 May 2026). Its evaluation reports that Zorya detects seven bugs at the binary level, including four nil pointer dereferences, one integer overflow, and two index out-of-bounds panics (Gorna et al., 5 May 2026). The standout case is the silent integer overflow in Go-Ethereum’s memoryGasCost, which Zorya detects via its INT_MULT checker while no other evaluated tool finds it without a manually written oracle (Gorna et al., 5 May 2026). The average detection time reported for Zorya over the eight vulnerabilities detected by at least one tool is 16.5 minutes, compared with approximately 2 seconds for nilaway, 0.18 seconds for go test -fuzz, and about 7 seconds for GoLibAFL (Gorna et al., 5 May 2026).
These results are accompanied by explicit limitations. The early framework is single-threaded only, has shallow symbolic exploration, and focuses on TinyGo’s panic behavior rather than the full Go runtime (Gorna et al., 26 May 2025). The panic-gated system depends on DWARF, supports only non-interactive binaries whose inputs are available at startup, and is not fully sound or complete because of bounded slice and string sizes and lazy concretization (Gorna et al., 11 Dec 2025). The gc extension still does not evaluate concurrency bug classes, uses main-only scheduling in all 11 runs, relies on correct Ghidra disassembly, and can stall in heavy runtime helpers such as runtime.memmove (Gorna et al., 5 May 2026). Future work across the papers includes goroutine and channel modeling, richer invariants and property templates, better symbolic search strategies, log classification and pattern mining, symbolic summaries for runtime helpers, hybrid fuzzing, and broader language coverage (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025, Gorna et al., 5 May 2026).
In aggregate, Zorya’s significance lies in showing that specialized concolic execution can be made practical for a language ecosystem with runtime safety checks by exploiting binary lifting, language-aware targets, and carefully bounded symbolic exploration (Gorna et al., 11 Dec 2025). The framework’s trajectory—from TinyGo panic detection, through panic-gated path selection, to gc binaries and overlay concolic execution—suggests a broader methodological pattern: binary-level IR normalization via P-Code, domain-specific target selection, and bounded completeness accepted in exchange for tractable bug-finding on real software (Gorna et al., 26 May 2025, Gorna et al., 11 Dec 2025, Gorna et al., 5 May 2026).