---
title: 'Zorya: Go Binary Concolic Execution Framework'
url: https://www.emergentmind.com/topics/zorya
type: topic
---

# Zorya: Go Binary Concolic Execution Framework

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 [2505.20183][2512.10799][2605.03492]. 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 [2505.20183][2512.10799][2605.03492].

## 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 [2505.20183]. 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 [2505.20183].

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 [2505.20183][2512.10799]. 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 [2505.20183]. Source-level Go tools are correspondingly limited by type coverage, runtime support, or inability to analyze binaries [2505.20183][2512.10799]. 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 [2505.20183][2512.10799]. 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 [2505.20183].

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 [2505.20183]. 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 [2505.20183]. Logging is instruction-level and trace-level, with `execution_log.txt` and `execution_trace.txt` used for reporting [2505.20183].

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 [2512.10799]. 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 [2605.03492].

| Paper | Main scope | Distinguishing additions |
|---|---|---|
| [2505.20183] | TinyGo-compiled, single-threaded binaries | P-Code pipeline, S1/S2/S3, generic and custom invariants |
| [2512.10799] | Single-threaded Go/TinyGo binaries | panic-gated concolic execution, bug detection in concretely not taken paths, function-mode |
| [2605.03492] | 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 [2505.20183]. 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 [2505.20183]. 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 [2505.20183][2512.10799]. A representative transition form used in the descriptions is
\[
(\text{state}_{c}, \text{state}_{s}, \phi) \xrightarrow{\text{instr}} (\text{state}_{c}', \text{state}_{s}', \phi'),
\]
where $\text{state}_{c}$ is the concrete state, $\text{state}_{s}$ the symbolic state, and $\phi$ the path constraint [2505.20183].

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 [2505.20183]. **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” [2505.20183]. **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 [2505.20183].

The generic panic invariant can be written in the state-oriented form
\[
\forall t.\ \text{PC}(t) \notin P,
\]
where $P$ is the set of known panic call sites [2505.20183]. For C binaries, built-in checks include null dereference, misaligned memory, and use of uninitialized variable. The misalignment condition is given explicitly as
\[
addr \bmod s \neq 0,
\]
for address $addr$ and access size $s$ [2505.20183]. The framework also supports custom invariants of the form $\Box\, \varphi$, encoded through instruction handlers or solver constraints [2505.20183].

The later panic-gated system refines path exploration by focusing only on negated branches that are relevant to panic reachability [2512.10799]. Given a concrete path predicate $\Pi$ and a branch guard $\phi$, alternative execution is explored through satisfiability of $\Pi \cup \{\neg \phi\}$ rather than indiscriminate forking at every branch [2512.10799]. 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 [2512.10799]. 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 [2505.20183]. Its Go-specific handling centers on TinyGo’s panic runtime, especially `runtime/panic.go`, and on cross-reference analysis for panic functions [2505.20183]. 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 [2512.10799]. This bypasses runtime initialization, scheduler setup, CLI parsing, and related path explosion sources. In the same system, slices are modeled as triples $(ptr, len, cap)$ 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 [2512.10799].

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 [2605.03492]. 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 [2605.03492]. The evaluation uses main-only scheduling, freezing non-target threads while the target function runs on the main thread [2605.03492].

The principal new exploration mechanism in the gc paper is overlay concolic execution with copy-on-write semantics [2605.03492]. 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 [2605.03492]. The default bound is 15 blocks [2605.03492]. This mechanism is explicitly aimed at detecting silent vulnerabilities on untaken branches, including unsigned integer overflows that do not trigger a panic in Go [2605.03492].

## 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 [2505.20183]. 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 [2505.20183][2512.10799].

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 [2512.10799]. The panic-reachability condition is stated as
\[
(pc \in \mathcal{R}) \;\lor\; (t \in \mathcal{R} \cup \mathcal{P}),
\]
where $\mathcal{R}$ is the backward-reachable set from panic sites and $\mathcal{P}$ is the set of panic sites [2512.10799].

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` [2605.03492]. 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 [2605.03492]. 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 [2505.20183][2512.10799]. 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 [2605.03492]. 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 [2505.20183][2512.10799], and the gc paper’s multi-thread support is primarily used to stabilize the runtime environment and reach target functions under main-only scheduling [2605.03492].

## 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 [2505.20183]. 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 [2505.20183]. 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 [2505.20183].

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 [2512.10799]. 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 [2512.10799]. The same evaluation states that Zorya detects all panics while existing tools detect at most two [2512.10799].

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 [2605.03492]. 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 [2605.03492]. 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 [2605.03492]. 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` [2605.03492].

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 [2505.20183]. 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 [2512.10799]. 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` [2605.03492]. 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 [2505.20183][2512.10799][2605.03492].

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 [2512.10799]. 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 [2505.20183][2512.10799][2605.03492].

Source: https://www.emergentmind.com/topics/zorya