ResurGo is a Go library for static function recovery from executable binaries.
It works with raw bytes from any binary format as well as parsing ELF files.
- Prologue-Based Detection: Recognizes common function entry patterns by instruction analysis
- Format-Agnostic Core: Works on raw machine code bytes from any binary format
- ELF Convenience Wrapper: Built-in support for parsing ELF executables
- Pattern Classification: Labels detected prologues by type
- x86_64 (AMD64)
- ARM64 (AArch64)
resurgo detects the following function prologue patterns:
x86_64:
classic- push rbp; mov rbp, rspno-frame-pointer- sub rsp, immpush-only- pushlea-based- lea rsp, [rsp-imm]
ARM64:
stp-frame-pair- stp x29, x30, [sp, #-N]!; mov x29, spstr-lr-preindex- str x30, [sp, #-N]!sub-sp- sub sp, sp, #Nstp-only- stp x29, x30, [sp, #-N]!
For detailed explanations of each pattern, see docs/PROLOGUES.md.
resurgo also identifies functions through call site analysis by detecting CALL and JMP instructions and extracting their target addresses. This approach:
- Works on optimized code where prologues may be omitted
- Is compiler-agnostic (all compilers generate call instructions)
- Provides confidence scoring based on instruction type and addressing mode
- Can be combined with prologue detection for higher-confidence function identification
Supported call site types:
call- Function calls (high confidence for direct calls)jump- Jumps (medium confidence for unconditional, low for conditional)
Addressing modes:
pc-relative- PC-relative addressing (can be resolved statically)absolute- Absolute addressing (can be resolved statically)register-indirect- Register-based addressing (cannot be resolved statically)
Confidence levels:
high- Direct CALL instructions (almost always function calls)medium- Unconditional JMP (could be tail calls or internal jumps)low- Conditional jumps (usually intra-function branches)none- Register-indirect (cannot be statically resolved)
For detailed explanations, see docs/CALLSITES.md.
Import resurgo in your Go project:
package main
import (
"fmt"
"log"
"os"
"github.com/maxgio92/resurgo"
)
func main() {
f, err := os.Open("./myapp")
if err != nil {
log.Fatal(err)
}
defer f.Close()
prologues, err := resurgo.DetectProloguesFromELF(f)
if err != nil {
log.Fatal(err)
}
for _, p := range prologues {
fmt.Printf("[%s] 0x%x: %s\n", p.Type, p.Address, p.Instructions)
}
}[classic] 0x401000: push rbp; mov rbp, rsp
[classic] 0x401020: push rbp; mov rbp, rsp
[no-frame-pointer] 0x401040: sub rsp, 0x20
[push-only] 0x401060: push rbx
package main
import (
"fmt"
"log"
"os"
"github.com/maxgio92/resurgo"
)
func main() {
f, err := os.Open("./myapp")
if err != nil {
log.Fatal(err)
}
defer f.Close()
edges, err := resurgo.DetectCallSitesFromELF(f)
if err != nil {
log.Fatal(err)
}
for _, e := range edges {
fmt.Printf("[%s] 0x%x -> 0x%x (%s, %s)\n",
e.Type, e.SourceAddr, e.TargetAddr, e.AddressMode, e.Confidence)
}
}[call] 0x401005 -> 0x401100 (pc-relative, high)
[call] 0x40102a -> 0x401200 (pc-relative, high)
[jump] 0x401050 -> 0x401080 (pc-relative, medium)
Combine prologue and call site detection for higher-confidence function identification:
package main
import (
"fmt"
"log"
"os"
"github.com/maxgio92/resurgo"
)
func main() {
// Read binary
data, err := os.ReadFile("./myapp.bin")
if err != nil {
log.Fatal(err)
}
// Detect functions using both signals
candidates, err := resurgo.DetectFunctions(data, 0x400000, resurgo.ArchAMD64)
if err != nil {
log.Fatal(err)
}
for _, c := range candidates {
fmt.Printf("0x%x: %s (confidence: %s)\n",
c.Address, c.DetectionType, c.Confidence)
if len(c.CalledFrom) > 0 {
fmt.Printf(" Called from: %d locations\n", len(c.CalledFrom))
}
}
}0x401000: both (confidence: high)
Called from: 3 locations
0x401100: prologue-only (confidence: medium)
0x401200: call-target (confidence: medium)
Called from: 1 locations
Functions:
// Prologue detection - works on raw machine code bytes, no I/O.
// arch selects architecture-specific detection logic.
func DetectPrologues(code []byte, baseAddr uint64, arch Arch) ([]Prologue, error)
// Convenience wrapper - parses ELF from the reader, extracts .text, calls DetectPrologues.
// Architecture is inferred from the ELF header.
func DetectProloguesFromELF(r io.ReaderAt) ([]Prologue, error)
// Call site analysis - detects CALL and JMP instructions and extracts target addresses.
func DetectCallSites(code []byte, baseAddr uint64, arch Arch) ([]CallSiteEdge, error)
// Convenience wrapper - parses ELF from the reader, extracts .text, calls DetectCallSites.
// Filters results to only include targets within the .text section.
func DetectCallSitesFromELF(r io.ReaderAt) ([]CallSiteEdge, error)
// Combined analysis - merges prologue and call site detection for higher confidence.
// Functions detected by both methods receive the highest confidence rating.
func DetectFunctions(code []byte, baseAddr uint64, arch Arch) ([]FunctionCandidate, error)
// Convenience wrapper - parses ELF from the reader, extracts .text, calls DetectFunctions.
func DetectFunctionsFromELF(r io.ReaderAt) ([]FunctionCandidate, error)Types:
type Arch string
const (
ArchAMD64 Arch = "amd64"
ArchARM64 Arch = "arm64"
)
type PrologueType string
// x86_64 prologue types
const (
PrologueClassic PrologueType = "classic"
PrologueNoFramePointer PrologueType = "no-frame-pointer"
ProloguePushOnly PrologueType = "push-only"
PrologueLEABased PrologueType = "lea-based"
)
// ARM64 prologue types
const (
PrologueSTPFramePair PrologueType = "stp-frame-pair"
PrologueSTRLRPreIndex PrologueType = "str-lr-preindex"
PrologueSubSP PrologueType = "sub-sp"
PrologueSTPOnly PrologueType = "stp-only"
)
type Prologue struct {
Address uint64 `json:"address"`
Type PrologueType `json:"type"`
Instructions string `json:"instructions"`
}
// Call site types
type CallSiteType string
const (
CallSiteCall CallSiteType = "call"
CallSiteJump CallSiteType = "jump"
)
type AddressingMode string
const (
AddressingModePCRelative AddressingMode = "pc-relative"
AddressingModeAbsolute AddressingMode = "absolute"
AddressingModeRegisterIndirect AddressingMode = "register-indirect"
)
type Confidence string
const (
ConfidenceHigh Confidence = "high"
ConfidenceMedium Confidence = "medium"
ConfidenceLow Confidence = "low"
ConfidenceNone Confidence = "none"
)
type CallSiteEdge struct {
SourceAddr uint64 `json:"source_addr"`
TargetAddr uint64 `json:"target_addr"`
Type CallSiteType `json:"type"`
AddressMode AddressingMode `json:"address_mode"`
Confidence Confidence `json:"confidence"`
}
// Combined analysis types
type DetectionType string
const (
DetectionPrologueOnly DetectionType = "prologue-only"
DetectionCallTarget DetectionType = "call-target"
DetectionJumpTarget DetectionType = "jump-target"
DetectionBoth DetectionType = "both" // Prologue + called/jumped to
)
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"`
}DetectPrologues accepts raw bytes, a base virtual address, and a target architecture, making it format-agnostic (works with ELF, PE, Mach-O, raw dumps).
DetectProloguesFromELF accepts an io.ReaderAt (e.g. *os.File), infers the architecture from the ELF header, and handles parsing internally.
┌─────────────────┐
│ ELF Binary │
└────────┬────────┘
│
▼
┌─────────────────┐
│ ELF Parser │ ← debug/elf package
│ (.text section)│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Disassembler │ ← golang.org/x/arch (x86asm / arm64asm)
│ (ASM decode) │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌────────┐ ┌────────────┐
│Prologue│ │ Call Site │
│Matcher │ │ Analyzer │
│(seq) │ │(CALL/JMP) │
└───┬────┘ └─────┬──────┘
│ │
▼ ▼
┌────────┐ ┌────────────┐
│[]Prolog│ │[]CallSite │
│ ue │ │ Edge │
└───┬────┘ └─────┬──────┘
│ │
└──────┬──────┘
▼
┌───────────────┐
│DetectFunctions│ ← Merge + score
└───────┬───────┘
▼
┌───────────────┐
│[]FunctionCand │
│ idate │
└───────────────┘
- No Symbol Information: Works on stripped binaries but reports addresses only
- Heuristic-Based: May have false positives in data sections or inline data
- Linear Disassembly: Doesn't handle indirect jumps or computed addresses
- Go 1.21+
golang.org/x/arch- x86 and ARM64 disassemblerdebug/elf(standard library) - ELF parser
