/analyzing-go-binaries
Reverse engineer Go binaries by recovering function names and types from pclntab and moduledata using GoReSym, redress, and IDA/Ghidra Go plugins, and by reading Go's non-standard calling convention, interface dispatch, and string layout. Use when a binary contains Go runtime
$ npx -y skills add trilwu/secskills --skill analyzing-go-binaries --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/analyzing-go-binaries
Context preview
The summary Claude sees to decide when to auto-load this skill.
Reverse engineer Go binaries by recovering function names and types from pclntab and moduledata using GoReSym, redress, and IDA/Ghidra Go plugins, and by reading Go's non-standard calling convention, interface dispatch, and string layout. Use when a binary contains Go runtime
SKILL.md
analyzing-go-binaries.SKILL.mdname: analyzing-go-binaries
description: Reverse engineer Go binaries by recovering function names and types from pclntab and moduledata using GoReSym, redress, and IDA/Ghidra Go plugins, and by reading Go's non-standard calling convention, interface dispatch, and string layout. Use when a binary contains Go runtime strings, when strings show runtime.main or go:buildid, when a stripped binary is unexpectedly large, or when analyzing Go malware or a Go-based service.
verified: 2026-07-27
Analyzing Go Binaries
Go binaries look hostile — statically linked, tens of megabytes, no imports you recognize, and "stripped" in a way that makes tools show thousands of `sub_` functions. They are not. Go ships its own symbol table for runtime reflection and panic traces, and `strip` does not remove it. Recover it and the binary becomes one of the easiest targets there is.
When to Use
- `strings` shows `runtime.main`, `go:buildid`, `go.buildinfo`, or
`runtime.gopanic`
- A "stripped" binary is 5–50 MB with almost no dynamic imports
- The disassembler shows thousands of unnamed functions and unreadable strings
- Analyzing Go malware, a Go CLI tool, or a compiled Go service
When NOT to Use
- **Rust binaries** — use `analyzing-rust-binaries`; the symbol recovery is
entirely different
- **.NET assemblies** — use `analyzing-dotnet-assemblies`
- **Suspected malware, before containment** — use `analyzing-malware` for the
environment, then come back here
- **General native RE** — use `analyzing-binaries`
Confirm It Is Go, and Which Version
strings -n 6 target | rg -m5 'go1\.[0-9]+|go:buildid|runtime\.main|GOROOT'
go version target # works on unstripped and many stripped builds
go version -m target # module list and build settings — free SBOM
`go version -m` is the highest-value first command. It prints the module dependency graph with versions, which gives you the third-party libraries in use before you disassemble anything — often answering the question outright (which HTTP library, which crypto, which C2 framework).
The Go version matters because `pclntab` layout changed at 1.2, 1.16, 1.18, and 1.20. Tooling that fails is usually version mismatch, not a hardened binary.
Recover Symbols
# GoReSym — extracts pclntab, moduledata, types, and build info
GoReSym -t -d -p target > syms.json
# -t user type metadata
# -d include standard library
# -p paths
# redress — Go-aware analysis, works well when GoReSym struggles
redress info target
redress symbols target
redress types target
# Load into the disassembler
# IDA: AlphaGolang, golang_loader_assist, or the GoReSym IDA script
# Ghidra: GolangAnalyzerExtension, or gotools
# Binja: the Golang loader plugin
After applying symbols, functions carry their real names — `main.processRequest`, `crypto/tls.(*Conn).Handshake`, `github.com/vendor/pkg.Function`. **Filter to `main.*` and to third-party module paths.** Everything under `runtime.`, `internal/`, and the standard library is stock and is 90%+ of the function count.
jq -r '.UserFunctions[].FunctionName' syms.json | rg -v '^(runtime|internal|reflect|sync)\.' | head -40
Reading Go Code in a Disassembler
Four things make Go listings confusing until you know them:
**Strings have no terminator.** Go strings are a pointer plus a length, so `strings` output runs adjacent literals together and the disassembler shows a pointer load followed by a length constant. Look for the pair — the constant next to the pointer is the length, and that is how you slice the correct substring out of the blob.
**Calling convention.** Before Go 1.17 all arguments and return values went on the stack, not in registers. From 1.17 a register ABI applies on amd64/arm64. A decompiler configured for the C convention will show wrong arguments; Go-aware plugins fix this, and it is the main reason decompiler output looks nonsensical.
**Interface dispatch.** Calls through an interface go via an `itab` — a table holding the concrete type and its method pointers. To resolve a call target, find the `itab` being loaded, then read the concrete type. Type recovery tools name these, which turns an indirect call into a readable one.
**Goroutines and defers.** `go f()` compiles to `runtime.newproc` with `f` as an argument, so concurrent logic does not appear as a direct call. `defer` becomes `runtime.deferproc`/`deferreturn`, which scatters cleanup code away from where it was written. When following control flow, check `newproc` call sites for work you would otherwise miss entirely.
Type Recovery
Go embeds full type descriptors for reflection. That means struct field names and layouts are recoverable — including the JSON tags that map straight to a wire protocol.
redress types target | rg -A10 'type main\.'
# Struct tags like `json:"api_key"` recover the exact protocol field names
This is the fastest route to a Go service's API surface or a Go implant's C2 message format: recover the request and response structs, and you have the protocol without reading a single instruction.
Go-Specific Security Review
If the job is finding bugs rather than understanding behaviour, the Go-specific classes worth targeting:
- **Ignored errors.** `_ =` on a function returning an error, especially
around auth, crypto, and file operations.
- **`math/rand` for security values.** Token, session ID, or nonce generation
using the non-crypto RNG.
- **`InsecureSkipVerify: true`** in a `tls.Config`.
- **`fmt.Sprintf` building SQL, shell commands, or URLs.**
- **Data races** on shared maps and structs — often the source of
authorization bugs under load.
- **`os/exec` with a shell**, or with an argument built from input.
With source available, use `auditing-code-for-vulnerabilities` and `govulncheck`; the above is for when you only have the binary.
Go Malware Notes
Go is common in cross-platform implants, and it leave
Read more
name: analyzing-go-binaries description: Reverse engineer Go binaries by recovering function names and types from pclntab and moduledata using GoReSym, redress, and IDA/Ghidra Go plugins, and by reading Go's non-standard calling convention, interface dispatch, and string layout. Use when a binary contains Go runtime strings, when strings show runtime.main or go:buildid, when a stripped binary is unexpectedly large, or when analyzing Go malware or a Go-based service. verified: 2026-07-27
Analyzing Go Binaries
Go binaries look hostile — statically linked, tens of megabytes, no imports you recognize, and "stripped" in a way that makes tools show thousands of `sub_` functions. They are not. Go ships its own symbol table for runtime reflection and panic traces, and `strip` does not remove it. Recover it and the binary becomes one of the easiest targets there is.
When to Use
- `strings` shows `runtime.main`, `go:buildid`, `go.buildinfo`, or
`runtime.gopanic`
- A "stripped" binary is 5–50 MB with almost no dynamic imports
- The disassembler shows thousands of unnamed functions and unreadable strings
- Analyzing Go malware, a Go CLI tool, or a compiled Go service
When NOT to Use
- **Rust binaries** — use `analyzing-rust-binaries`; the symbol recovery is
entirely different
- **.NET assemblies** — use `analyzing-dotnet-assemblies`
- **Suspected malware, before containment** — use `analyzing-malware` for the
environment, then come back here
- **General native RE** — use `analyzing-binaries`
Confirm It Is Go, and Which Version
strings -n 6 target | rg -m5 'go1\.[0-9]+|go:buildid|runtime\.main|GOROOT' go version target # works on unstripped and many stripped builds go version -m target # module list and build settings — free SBOM
`go version -m` is the highest-value first command. It prints the module dependency graph with versions, which gives you the third-party libraries in use before you disassemble anything — often answering the question outright (which HTTP library, which crypto, which C2 framework).
The Go version matters because `pclntab` layout changed at 1.2, 1.16, 1.18, and 1.20. Tooling that fails is usually version mismatch, not a hardened binary.
Recover Symbols
# GoReSym — extracts pclntab, moduledata, types, and build info GoReSym -t -d -p target > syms.json # -t user type metadata # -d include standard library # -p paths # redress — Go-aware analysis, works well when GoReSym struggles redress info target redress symbols target redress types target # Load into the disassembler # IDA: AlphaGolang, golang_loader_assist, or the GoReSym IDA script # Ghidra: GolangAnalyzerExtension, or gotools # Binja: the Golang loader plugin
After applying symbols, functions carry their real names — `main.processRequest`, `crypto/tls.(*Conn).Handshake`, `github.com/vendor/pkg.Function`. **Filter to `main.*` and to third-party module paths.** Everything under `runtime.`, `internal/`, and the standard library is stock and is 90%+ of the function count.
jq -r '.UserFunctions[].FunctionName' syms.json | rg -v '^(runtime|internal|reflect|sync)\.' | head -40
Reading Go Code in a Disassembler
Four things make Go listings confusing until you know them:
**Strings have no terminator.** Go strings are a pointer plus a length, so `strings` output runs adjacent literals together and the disassembler shows a pointer load followed by a length constant. Look for the pair — the constant next to the pointer is the length, and that is how you slice the correct substring out of the blob.
**Calling convention.** Before Go 1.17 all arguments and return values went on the stack, not in registers. From 1.17 a register ABI applies on amd64/arm64. A decompiler configured for the C convention will show wrong arguments; Go-aware plugins fix this, and it is the main reason decompiler output looks nonsensical.
**Interface dispatch.** Calls through an interface go via an `itab` — a table holding the concrete type and its method pointers. To resolve a call target, find the `itab` being loaded, then read the concrete type. Type recovery tools name these, which turns an indirect call into a readable one.
**Goroutines and defers.** `go f()` compiles to `runtime.newproc` with `f` as an argument, so concurrent logic does not appear as a direct call. `defer` becomes `runtime.deferproc`/`deferreturn`, which scatters cleanup code away from where it was written. When following control flow, check `newproc` call sites for work you would otherwise miss entirely.
Type Recovery
Go embeds full type descriptors for reflection. That means struct field names and layouts are recoverable — including the JSON tags that map straight to a wire protocol.
redress types target | rg -A10 'type main\.' # Struct tags like `json:"api_key"` recover the exact protocol field names
This is the fastest route to a Go service's API surface or a Go implant's C2 message format: recover the request and response structs, and you have the protocol without reading a single instruction.
Go-Specific Security Review
If the job is finding bugs rather than understanding behaviour, the Go-specific classes worth targeting:
- **Ignored errors.** `_ =` on a function returning an error, especially
around auth, crypto, and file operations.
- **`math/rand` for security values.** Token, session ID, or nonce generation
using the non-crypto RNG.
- **`InsecureSkipVerify: true`** in a `tls.Config`.
- **`fmt.Sprintf` building SQL, shell commands, or URLs.**
- **Data races** on shared maps and structs — often the source of
authorization bugs under load.
- **`os/exec` with a shell**, or with an argument built from input.
With source available, use `auditing-code-for-vulnerabilities` and `govulncheck`; the above is for when you only have the binary.
Go Malware Notes
Go is common in cross-platform implants, and it leave
**Security skills for Claude Code that encode the judgment professionals bring, not another wrapper around a scanner.** Offense, defense, and the reverse-engineering and code-audit core both sides share. Every skill is fact-checked against primary sources.

