ResurGo is a Go library for static function recovery from stripped executable binaries.
- Disassembly-based detection: function entry recovery via three complementary signals - prologue pattern matching, call-site analysis, and alignment boundary analysis
- DWARF CFI-based detection: high-confidence function entries extracted from
.eh_frameFDE records - compiler-written, survivesstrip --strip-all - False positive filtering: discards intra-function jump targets and linker-generated PLT stubs from the candidate set
- Format-agnostic core: works on raw machine code bytes from any binary format
- ELF convenience wrapper: built-in support for parsing ELF executables and inferring architecture
- x86_64 (AMD64)
- ARM64 (AArch64)
Resurgo disassembles the .text section and runs three independent signals in parallel, then merges the results:
- Prologue matching - recognizes architecture-specific function entry instruction sequences. See docs/PROLOGUES.md.
- Call-site analysis - extracts
CALLandJMPtargets; functions called or jumped to from many sites carry higher confidence. See docs/CALLSITES.md. - Alignment boundary analysis - recovers pure-leaf and never-called functions by detecting the alignment gap compilers emit between adjacent functions. See docs/BOUNDARY.md.
Candidates from all three signals are merged and scored. ELF-specific false-positive filters (PLT ranges, intra-function jump anchor check) are applied before the final result is returned.
When the binary contains an .eh_frame section, resurgo parses its FDE (Frame Description Entry) records and uses their initial_location fields as a high-confidence function entry set. These addresses were written by the compiler - not inferred by heuristics - and are typically present in stripped ELF binaries where .symtab and .debug_* are long gone.
The EhFrameDetector emits these addresses as candidates. The EhFrameFilter then retains only candidates confirmed by an FDE, dropping disassembly noise. See docs/CFI.md.
package main
import (
"debug/elf"
"fmt"
"log"
"github.com/maxgio92/resurgo"
)
func main() {
f, err := elf.Open("./myapp")
if err != nil {
log.Fatal(err)
}
defer f.Close()
candidates, err := resurgo.DetectFunctionsFromELF(f)
if err != nil {
log.Fatal(err)
}
for _, c := range candidates {
fmt.Printf("0x%x: %s (confidence: %s)\n",
c.Address, c.DetectionType, c.Confidence)
}
}0x401000: both (confidence: high)
0x401100: prologue-only (confidence: medium)
0x401200: call-target (confidence: medium)
0x401300: aligned-entry (confidence: low)
0x401400: cfi (confidence: high)
For non-ELF binaries or raw memory dumps, use the lower-level primitives directly:
prologues, err := resurgo.DetectPrologues(data, 0x400000, resurgo.ArchAMD64)
edges, err := resurgo.DetectCallSites(data, 0x400000, resurgo.ArchAMD64)// DetectFunctionsFromELF runs all detectors then all filters against f and
// returns a deduplicated, sorted slice of function candidates.
// Architecture is inferred from the ELF header.
// opts may include WithDetectors or WithFilters to replace either pipeline.
func DetectFunctionsFromELF(f *elf.File, opts ...Option) ([]FunctionCandidate, error)
// WithDetectors replaces the default detector pipeline.
// Detectors run in order; results are merged before filtering.
func WithDetectors(detectors ...CandidateDetector) Option
// WithFilters replaces the default filter pipeline.
// Filters run in order. Pass no arguments to disable all filters.
func WithFilters(filters ...CandidateFilter) Option
// Built-in detectors, enabled by default in the order listed:
var DisasmDetector CandidateDetector // prologue, call-site, and alignment-boundary detection
var EhFrameDetector CandidateDetector // emits candidates from .eh_frame FDE records
// Built-in filters, enabled by default in the order listed:
var CETFilter CandidateFilter // drops non-ENDBR64 aligned entries on CET AMD64 binaries
var EhFrameFilter CandidateFilter // retains only FDE-confirmed candidates
var PLTFilter CandidateFilter // removes PLT-section candidates (always last)
// DetectPrologues scans raw machine code bytes for architecture-specific
// function prologue patterns. Works on any binary format.
func DetectPrologues(code []byte, baseAddr uint64, arch Arch) ([]Prologue, error)
// DetectCallSites scans raw machine code bytes for CALL and JMP instructions
// and returns their resolved target addresses. Works on any binary format.
func DetectCallSites(code []byte, baseAddr uint64, arch Arch) ([]CallSiteEdge, error)Key types:
type DetectionType string
const (
DetectionPrologueOnly DetectionType = "prologue-only"
DetectionCallTarget DetectionType = "call-target"
DetectionJumpTarget DetectionType = "jump-target"
DetectionPrologueCallSite DetectionType = "prologue-callsite"
DetectionAlignedEntry DetectionType = "aligned-entry"
DetectionCFI DetectionType = "cfi"
)
type FunctionCandidate struct {
Address uint64 `json:"address"`
DetectionType DetectionType `json:"detection_type"`
PrologueType PrologueType `json:"prologue_type,omitempty"`
CalledFrom []uint64 `json:"called_from,omitempty"`
JumpedFrom []uint64 `json:"jumped_from,omitempty"`
Confidence Confidence `json:"confidence"`
}+------------------+
| *elf.File |
+------------------+
|
+-------------------------------+
| |
v v
+------------------+ +------------------+
| DisasmDetector | | EhFrameDetector |
| (.text bytes) | | (.eh_frame) |
+---+---------+----+ +--------+---------+
| | | |
v v v v
+------+ +------+ +--------+ +------------------+
|Prolog| |Call | |Boundary| | FDE entry VAs |
|ues | |Sites | |Analysis| | (DetectionCFI) |
+--+---+ +--+---+ +---+----+ +--------+---------+
| | | |
+--------+---------+----------------+
v
+------------------+
| mergeCandidates |
| (dedup by addr) |
+--------+---------+
|
v
+------------------+
| CETFilter | drops non-ENDBR64 aligned entries (CET binaries)
+--------+---------+
|
v
+------------------+
| EhFrameFilter | retains only FDE-confirmed candidates
+--------+---------+
|
v
+------------------+
| PLTFilter | removes PLT-section candidates
+--------+---------+
|
v
+------------------+
|[]FunctionCandidate|
+------------------+
- Reports addresses only - no symbol names on stripped binaries
- Disassembly signals are heuristic; CRT scaffolding on aligned addresses can still produce false positives when
.eh_frameis absent - Linear disassembly - indirect jumps and computed addresses are not resolved
- Go 1.25.7+
golang.org/x/arch- x86 and ARM64 disassemblerdebug/elf(standard library) - ELF parser
