resurgo

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 20, 2026 License: MIT Imports: 7 Imported by: 0

README

resurgo

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.

Features

  • 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

Supported architectures

  • x86_64 (AMD64)
  • ARM64 (AArch64)

Supported function metadata

Function prologues

resurgo detects the following function prologue patterns:

x86_64:

  • classic - push rbp; mov rbp, rsp
  • no-frame-pointer - sub rsp, imm
  • push-only - push
  • lea-based - lea rsp, [rsp-imm]

ARM64:

  • stp-frame-pair - stp x29, x30, [sp, #-N]!; mov x29, sp
  • str-lr-preindex - str x30, [sp, #-N]!
  • sub-sp - sub sp, sp, #N
  • stp-only - stp x29, x30, [sp, #-N]!

For detailed explanations of each pattern, see docs/PROLOGUES.md.

Call site analysis

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.

Usage

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)
    }
}
Example output
[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
Call site analysis
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)
    }
}
Example output
[call] 0x401005 -> 0x401100 (pc-relative, high)
[call] 0x40102a -> 0x401200 (pc-relative, high)
[jump] 0x401050 -> 0x401080 (pc-relative, medium)
Combined analysis

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))
        }
    }
}
Example output
0x401000: both (confidence: high)
  Called from: 3 locations
0x401100: prologue-only (confidence: medium)
0x401200: call-target (confidence: medium)
  Called from: 1 locations

API Reference

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.

Implementation

Architecture
┌─────────────────┐
│   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      │
   └───────────────┘

Limitations

  • 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

Dependencies

  • Go 1.21+
  • golang.org/x/arch - x86 and ARM64 disassembler
  • debug/elf (standard library) - ELF parser

References

Documentation

Overview

Package resurgo identifies functions in executable binaries through static analysis using instruction-level disassembly. It provides two complementary approaches:

Prologue Detection

Detects function entry points by recognizing common prologue patterns including classic frame pointer setup (push rbp; mov rbp, rsp), no-frame-pointer functions (sub rsp, imm), push-only prologues, and LEA-based stack allocation.

Use DetectPrologues to analyze raw bytes directly, or DetectProloguesFromELF to extract and analyze the .text section of an ELF binary.

Call Site Analysis

Identifies functions by detecting CALL and JMP instructions and extracting their target addresses. This approach works on heavily optimized code where prologues may be omitted and is compiler-agnostic. It provides confidence scoring based on instruction type (call vs jump) and addressing mode (direct vs indirect).

Use DetectCallSites to analyze raw bytes, or DetectCallSitesFromELF for ELF binaries. Results are filtered to only include targets within the .text section.

Combined Analysis

For highest-confidence function detection, use DetectFunctions which combines both prologue and call site analysis. Functions detected by both methods receive the highest confidence rating. This is particularly effective for recovering functions in stripped binaries or heavily optimized code.

Confidence Scoring

The confidence level indicates the reliability of a detection:

  • High: Direct CALL instructions or prologue + called/jumped to
  • Medium: Unconditional JMP or prologue-only
  • Low: Conditional jumps (usually intra-function branches)
  • None: Register-indirect (cannot be statically resolved)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AddressingMode added in v0.3.0

type AddressingMode string

AddressingMode represents how the target address is specified.

const (
	AddressingModePCRelative       AddressingMode = "pc-relative"
	AddressingModeAbsolute         AddressingMode = "absolute"
	AddressingModeRegisterIndirect AddressingMode = "register-indirect"
)

Recognized addressing modes for call site instructions.

type Arch

type Arch string

Arch represents a CPU architecture.

const (
	ArchAMD64 Arch = "amd64"
	ArchARM64 Arch = "arm64"
)

Supported architectures.

type CallSiteEdge added in v0.3.0

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"`
}

CallSiteEdge represents a detected call site (call or jump to a function).

func DetectCallSites added in v0.3.0

func DetectCallSites(code []byte, baseAddr uint64, arch Arch) ([]CallSiteEdge, error)

DetectCallSites analyzes raw machine code bytes and returns detected call sites (CALL and JMP instructions with their targets). baseAddr is the virtual address corresponding to the start of code. arch selects the architecture-specific detection logic. This function performs no I/O and works with any binary format.

Example
package main

import (
	"fmt"
	"log"

	"github.com/maxgio92/resurgo"
)

func main() {
	// x86-64 machine code: call $+0x20 (E8 1B 00 00 00)
	// At address 0x1000, calls target at 0x1000 + 5 + 0x1B = 0x1020
	code := []byte{0xE8, 0x1B, 0x00, 0x00, 0x00}
	edges, err := resurgo.DetectCallSites(code, 0x1000, resurgo.ArchAMD64)
	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)
	}
}
Output:

[call] 0x1000 -> 0x1020 (pc-relative, high)

func DetectCallSitesFromELF added in v0.3.0

func DetectCallSitesFromELF(r io.ReaderAt) ([]CallSiteEdge, error)

DetectCallSitesFromELF parses an ELF binary from the given reader, extracts the .text section, and returns detected call sites. The architecture is inferred from the ELF header.

Example
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/maxgio92/resurgo"
)

func main() {
	f, err := os.Open("/usr/bin/ls")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	edges, err := resurgo.DetectCallSitesFromELF(f)
	if err != nil {
		log.Fatal(err)
	}

	// Show first 5 high-confidence call edges
	count := 0
	for _, e := range edges {
		if e.Type == resurgo.CallSiteCall && e.Confidence == resurgo.ConfidenceHigh {
			fmt.Printf("[%s] 0x%x -> 0x%x (%s)\n",
				e.Type, e.SourceAddr, e.TargetAddr, e.AddressMode)
			count++
			if count >= 5 {
				break
			}
		}
	}
}

type CallSiteType added in v0.3.0

type CallSiteType string

CallSiteType represents the type of call site instruction.

const (
	CallSiteCall CallSiteType = "call"
	CallSiteJump CallSiteType = "jump"
)

Recognized call site instruction types.

type Confidence added in v0.3.0

type Confidence string

Confidence represents the reliability of a call site detection.

const (
	ConfidenceHigh   Confidence = "high"
	ConfidenceMedium Confidence = "medium"
	ConfidenceLow    Confidence = "low"
	ConfidenceNone   Confidence = "none"
)

Confidence levels for call site detection.

type DetectionType added in v0.3.0

type DetectionType string

DetectionType represents how a function was detected.

const (
	DetectionPrologueOnly DetectionType = "prologue-only"
	DetectionCallTarget   DetectionType = "call-target"
	DetectionJumpTarget   DetectionType = "jump-target"
	DetectionBoth         DetectionType = "both" // Prologue + called/jumped to
)

Recognized detection types.

type FunctionCandidate added in v0.3.0

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"`
}

FunctionCandidate represents a potential function detected through one or more signals (prologue detection, call site analysis, or both).

func DetectFunctions added in v0.3.0

func DetectFunctions(code []byte, baseAddr uint64, arch Arch) ([]FunctionCandidate, error)

DetectFunctions combines prologue detection and call site analysis to identify function entry points with higher confidence. Functions detected by both methods receive the highest confidence rating.

Example
package main

import (
	"fmt"
	"log"

	"github.com/maxgio92/resurgo"
)

func main() {
	// x86-64 code with prologue and call:
	// 0x1000: push rbp; mov rbp, rsp
	// 0x1004: call 0x1020
	// ...
	// 0x1020: push rbp; mov rbp, rsp (called function)
	code := make([]byte, 0x30)
	code[0x00] = 0x55 // push rbp
	code[0x01] = 0x48 // mov rbp, rsp
	code[0x02] = 0x89
	code[0x03] = 0xe5
	code[0x04] = 0xE8 // call
	code[0x05] = 0x17 // rel32 = 0x17
	code[0x06] = 0x00
	code[0x07] = 0x00
	code[0x08] = 0x00
	code[0x09] = 0xC3 // ret (establish function boundary)
	code[0x20] = 0x55 // push rbp at target
	code[0x21] = 0x48 // mov rbp, rsp
	code[0x22] = 0x89
	code[0x23] = 0xe5

	candidates, err := resurgo.DetectFunctions(code, 0x1000, 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)
	}
}
Output:

0x1000: prologue-only (confidence: medium)
0x1020: both (confidence: high)

func DetectFunctionsFromELF added in v0.3.0

func DetectFunctionsFromELF(r io.ReaderAt) ([]FunctionCandidate, error)

DetectFunctionsFromELF parses an ELF binary from the given reader, extracts the .text section, and returns detected function candidates using combined prologue detection and call site analysis. The architecture is inferred from the ELF header.

type Prologue

type Prologue struct {
	Address      uint64       `json:"address"`
	Type         PrologueType `json:"type"`
	Instructions string       `json:"instructions"`
}

Prologue represents a detected function prologue.

func DetectPrologues

func DetectPrologues(code []byte, baseAddr uint64, arch Arch) ([]Prologue, error)

DetectPrologues analyzes raw machine code bytes and returns detected function prologues. baseAddr is the virtual address corresponding to the start of code. arch selects the architecture-specific detection logic. This function performs no I/O and works with any binary format.

Example
package main

import (
	"fmt"
	"log"

	"github.com/maxgio92/resurgo"
)

func main() {
	// x86-64 machine code: nop; push rbp; mov rbp, rsp
	code := []byte{0x90, 0x55, 0x48, 0x89, 0xe5}
	prologues, err := resurgo.DetectPrologues(code, 0x1000, resurgo.ArchAMD64)
	if err != nil {
		log.Fatal(err)
	}
	for _, p := range prologues {
		fmt.Printf("[%s] 0x%x: %s\n", p.Type, p.Address, p.Instructions)
	}
}
Output:

[classic] 0x1001: push rbp; mov rbp, rsp

func DetectProloguesFromELF

func DetectProloguesFromELF(r io.ReaderAt) ([]Prologue, error)

DetectProloguesFromELF parses an ELF binary from the given reader, extracts the .text section, and returns detected function prologues. The architecture is inferred from the ELF header.

Example
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/maxgio92/resurgo"
)

func main() {
	f, err := os.Open("/usr/bin/ls")
	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)
	}
}

type PrologueType

type PrologueType string

PrologueType represents the type of function prologue.

const (
	PrologueClassic        PrologueType = "classic"
	PrologueNoFramePointer PrologueType = "no-frame-pointer"
	ProloguePushOnly       PrologueType = "push-only"
	PrologueLEABased       PrologueType = "lea-based"
)

Recognized x86_64 function prologue patterns.

const (
	PrologueSTPFramePair  PrologueType = "stp-frame-pair"
	PrologueSTRLRPreIndex PrologueType = "str-lr-preindex"
	PrologueSubSP         PrologueType = "sub-sp"
	PrologueSTPOnly       PrologueType = "stp-only"
)

Recognized ARM64 function prologue patterns.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL