Skip to content

Instantly share code, notes, and snippets.

@magnus919
Last active July 31, 2026 22:20
Show Gist options
  • Select an option

  • Save magnus919/ce6bdf9adec4fe67a0086f2019de248d to your computer and use it in GitHub Desktop.

Select an option

Save magnus919/ce6bdf9adec4fe67a0086f2019de248d to your computer and use it in GitHub Desktop.
A live static-analysis skill demo for AI agents

A static-analysis skill an AI agent can actually interrogate

What this is: binary-analysis is an Agent Skill that gives an AI agent a deterministic, read-only workflow for inspecting unfamiliar PE, ELF, and Mach-O files using Ghidra-backed static analysis. It can identify binary structure, enumerate imports and strings, decompile functions, explore references/call paths, run bounded heuristics, and export evidence-backed reports.

Get it from: the magnus919/agent-skills GitHub repository. The skill lives at binary-analysis/.

An agent handling an unfamiliar executable needs more than strings, a one-line verdict, or a polished hallucination. It needs a bounded, reproducible workflow that can preserve evidence, name uncertainty, and leave behind an audit trail.

This demo uses a harmless, transparent Mach-O fixture. Its source is included in this gist. The fixture is not executed. It contains three deliberately inspectable code paths:

  • write a small local configuration file,
  • resolve a documentation-only hostname, and
  • dynamically look up a symbol in the system library.

Those paths are guarded by argc == -1, an impossible normal-process condition. They exist so a static-analysis agent has something concrete to inspect.

What an agent can do with this skill

  1. Create an isolated analysis project and copy an unknown binary into it.
  2. Record its SHA-256 and identify its format, architecture, sections, imports, functions, and strings.
  3. Move from a string or import to a named function, a decompilation, and its callers/callees.
  4. Run rule-derived capability and suspicious-API checks without turning a heuristic into a verdict.
  5. Export a durable Markdown report with the project’s provenance and diagnostics.

Reproduce

The commands in commands.sh are the complete capture sequence. They assume the skill’s binary CLI is on PATH, with Java 21, Ghidra 12.1.2+, and PyGhidra 3.1+ installed.

clang -Wall -Wextra -Werror -O0 -g telemetry_fixture.c -o telemetry_fixture
binary doctor --json
binary project create telemetry-fixture --json
binary import telemetry_fixture --project telemetry-fixture --json
binary analyze --project telemetry-fixture --profile standard --json

A real capture of this fixture reported a 51,232-byte ARM-64 Mach-O with SHA-256:

9f2892a12a3fcddca1cc14944db7cd3abc7b1a49fd3ae77c9a2b88bff57a2eaf

The captured response envelopes identified the backend as Ghidra 12.1.2, with success: true, partial: false, and no diagnostics for every command shown below.

Evidence, not theater

1. Binary identity

{
  "format": "Mach-O",
  "architecture": "ARM-64",
  "endianness": "LITTLE",
  "size_bytes": 51232,
  "entry_point": "0x1000004f8"
}

2. A focused string query

binary strings --project telemetry-fixture --contains telemetry --json
{
  "items": [
    {
      "text": "https://telemetry.example.invalid/v1/event",
      "encoding": "ASCII",
      "address": "0x1000007c1",
      "length": 42
    },
    {
      "text": "telemetry.example.invalid",
      "encoding": "ASCII",
      "address": "0x100000821",
      "length": 25
    }
  ],
  "total": 5
}

That is an observation: the strings occur in the binary. It is not evidence that the executable made a network connection.

3. Functions an agent can target

[
  {"name": "main", "address": "0x1000004f8", "size_bytes": 140, "confidence": "HIGH"},
  {"name": "write_demo_config", "address": "0x100000584", "size_bytes": 164, "confidence": "HIGH"},
  {"name": "resolve_demo_endpoint", "address": "0x100000628", "size_bytes": 116, "confidence": "HIGH"},
  {"name": "resolve_optional_symbol", "address": "0x10000069c", "size_bytes": 108, "confidence": "HIGH"}
]

4. Decompile a focused function

binary decompile --project telemetry-fixture function:write_demo_config --json
int write_demo_config(void)
{
  int iVar1;
  size_t sVar2;
  ssize_t sVar3;

  iVar1 = _open(kConfigPath, 0x601);
  if (iVar1 < 0) {
    return -1;
  }
  sVar2 = _strlen("{\"demo\":true}\n");
  sVar3 = _write(iVar1, "{\"demo\":true}\n", sVar2);
  _close(iVar1);
  return (int)sVar3;
}

The actual decompiler output contained an explicit warning about an unknown calling convention and unresolved local-variable storage. A trustworthy agent carries that caveat forward rather than silently cleaning it away.

5. Follow the call path from main

binary callees --project telemetry-fixture function:main --json

The capture found direct calls to _puts, _fputs, _fputc, write_demo_config, resolve_demo_endpoint, and resolve_optional_symbol.

This supports an agent assessment such as: the binary contains code paths for local configuration writing, hostname resolution, and dynamic-symbol lookup. It does not establish that those paths execute. In this fixture, the supplied source proves they are intentionally unreachable in normal use.

6. Triage and rule-derived results

binary triage --project telemetry-fixture --json
binary suspicious-apis --project telemetry-fixture --json
binary capability-map --project telemetry-fixture --json

Triage recorded deterministic observations including Mach-O format, ARM-64 architecture, SHA-256, standard analysis profile, and 12 mapped sections. The suspicious-API rule set applied ten rules and returned zero matches.

The capability map emitted LOW-confidence candidates, including networking from the URL string and file-system from the configuration path. It also produced clearly over-broad LOW-confidence candidates from generic Mach-O strings. That is useful behavior to expose publicly: a good agent reports a heuristic as a heuristic, checks its evidence, and does not call this fixture privileged or malicious.

7. Produce a durable handoff

binary export-report --project telemetry-fixture --type triage --format markdown --json
binary project status telemetry-fixture --json

The report export succeeded. The project finished in READY state with one binary and no project lock. The full local capture includes the report, all JSON envelopes, and the project audit trail.

Why this matters inside an agent

An agent can turn a vague request such as “what is this executable doing?” into a disciplined chain:

unknown file
  → identity and hash
  → structural survey
  → focused strings/imports/functions
  → decompilation and call relationships
  → evidence / heuristic / unknown separation
  → report with provenance

That is different from simply asking a model to speculate about a binary. The CLI owns deterministic operations. The agent chooses the next question and explains the limits of the answer.

Prototype status

This is a live capture from a work-in-progress real Ghidra/PyGhidra adapter patch, not a claim about every currently distributed binary-analysis installation. The capture is real, but the adapter implementation still requires release hardening before this exact workflow should be presented as a stable, generally available interface. In particular, the project’s existing fixture-oriented test suite needs completion work and Ghidra timeout enforcement needs a process boundary rather than a helper thread.

Capture integrity and privacy

All command outputs above came from a live local run against the SHA-256 shown here. The public excerpts were deliberately field-selected: timestamps, UUIDs, host-local file paths, temporary compiler paths, and report locations were omitted. The source and exact commands are included so anyone can reproduce the analysis in their own environment.

This is static analysis only. The target was never executed, loaded as a library, uploaded, or exposed through a network listener.

#!/usr/bin/env bash
set -euo pipefail
# `binary` is the static-analysis CLI bundled with the binary-analysis skill.
# It must be on PATH, and Java 21, Ghidra 12.1.2+, and PyGhidra 3.1+ must be
# installed. The target is never executed.
clang -Wall -Wextra -Werror -O0 -g telemetry_fixture.c -o telemetry_fixture
binary doctor --json
binary project create telemetry-fixture --json
binary import telemetry_fixture --project telemetry-fixture --json
binary analyze --project telemetry-fixture --profile standard --json
binary metadata --project telemetry-fixture --json
binary sections --project telemetry-fixture --json
binary imports --project telemetry-fixture --json
binary strings --project telemetry-fixture --contains telemetry --json
binary functions --project telemetry-fixture --json
binary decompile --project telemetry-fixture function:write_demo_config --json
binary callees --project telemetry-fixture function:main --json
binary callgraph --project telemetry-fixture function:main --depth 3 --json
binary triage --project telemetry-fixture --json
binary suspicious-apis --project telemetry-fixture --json
binary capability-map --project telemetry-fixture --json
binary export-report --project telemetry-fixture --type triage --format markdown --json
binary project status telemetry-fixture --json
/*
* A transparent, harmless fixture for static-analysis demonstrations.
* The capability paths deliberately require an impossible argc value and are
* never executed in this demo. The analyzer inspects this file statically.
*/
#include <dlfcn.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static const char *kEndpoint = "https://telemetry.example.invalid/v1/event";
static const char *kConfigPath = "/tmp/binary-analysis-demo/config.json";
static int write_demo_config(void) {
int fd = open(kConfigPath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) return -1;
const char *body = "{\"demo\":true}\n";
int result = (int)write(fd, body, strlen(body));
close(fd);
return result;
}
static int resolve_demo_endpoint(void) {
struct addrinfo hints = {0};
struct addrinfo *result = NULL;
hints.ai_socktype = SOCK_STREAM;
int status = getaddrinfo("telemetry.example.invalid", "443", &hints, &result);
if (result) freeaddrinfo(result);
return status;
}
static void *resolve_optional_symbol(void) {
void *handle = dlopen("/usr/lib/libSystem.B.dylib", RTLD_LAZY);
if (!handle) return NULL;
void *symbol = dlsym(handle, "getpid");
dlclose(handle);
return symbol;
}
int main(int argc, char **argv) {
puts("binary-analysis demo fixture");
if (argc == -1 && argv != NULL) {
fputs(kEndpoint, stderr);
fputc('\n', stderr);
write_demo_config();
resolve_demo_endpoint();
resolve_optional_symbol();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment