|
| 1 | +# prologo |
| 2 | + |
| 3 | +A Go library for detecting function prologues in ELF binaries by disassembling and analyzing instruction patterns. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +**prologo** is a Go library that disassembles the `.text` section of ELF executables and identifies common function prologue patterns used by compilers. |
| 8 | + |
| 9 | +## Features |
| 10 | + |
| 11 | +- **ELF Binary Support**: Parses and analyzes ELF format executables |
| 12 | +- **Multiple Prologue Detection**: Recognizes various function entry patterns |
| 13 | +- **Pattern Classification**: Labels detected prologues by type |
| 14 | +- **Fast Analysis**: Efficient Go-based disassembly |
| 15 | + |
| 16 | +## Supported Architectures |
| 17 | + |
| 18 | +- **x86_64** (AMD64) - Full support |
| 19 | + |
| 20 | +## Supported Function Prologues |
| 21 | + |
| 22 | +prologo detects the following x86_64 function prologue patterns: |
| 23 | + |
| 24 | +### 1. Classic Frame Pointer Setup |
| 25 | +```asm |
| 26 | +push rbp ; Save caller's frame pointer |
| 27 | +mov rbp, rsp ; Set up new frame pointer |
| 28 | +``` |
| 29 | + |
| 30 | +**Detection Label**: `classic` |
| 31 | + |
| 32 | +**Description**: Traditional prologue that establishes a complete stack frame with base pointer. |
| 33 | + |
| 34 | +- Used by: Non-optimized builds (`-O0`), code with `-fno-omit-frame-pointer` |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +### 2. No-Frame-Pointer Function |
| 39 | +```asm |
| 40 | +sub rsp, 0x20 ; Allocate stack space directly |
| 41 | +``` |
| 42 | + |
| 43 | +**Detection Label**: `no-frame-pointer` |
| 44 | + |
| 45 | +**Description**: Optimized prologue that skips frame pointer setup entirely. |
| 46 | + |
| 47 | +- Used by: Optimized builds (`-O2`, `-O3`), `-fomit-frame-pointer` flag |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +### 3. Push-Only Prologue |
| 52 | +```asm |
| 53 | +push rbp ; Save frame pointer only |
| 54 | +``` |
| 55 | + |
| 56 | +**Detection Label**: `push-only` |
| 57 | + |
| 58 | +**Description**: Minimal prologue that saves RBP but doesn't establish a frame chain. |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +### 4. LEA-Based Stack Allocation |
| 63 | +```asm |
| 64 | +lea rsp, [rsp-0x20] ; Allocate using LEA instead of SUB |
| 65 | +``` |
| 66 | + |
| 67 | +**Detection Label**: `lea-based` |
| 68 | + |
| 69 | +**Description**: Alternative stack allocation using LEA instruction. LEA doesn't modify CPU flags (unlike SUB). |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## Usage |
| 74 | + |
| 75 | +Import prologo in your Go project: |
| 76 | + |
| 77 | +```go |
| 78 | +package main |
| 79 | + |
| 80 | +import ( |
| 81 | + "fmt" |
| 82 | + "log" |
| 83 | + "os" |
| 84 | + |
| 85 | + "github.com/maxgio92/prologo" |
| 86 | +) |
| 87 | + |
| 88 | +func main() { |
| 89 | + f, err := os.Open("./myapp") |
| 90 | + if err != nil { |
| 91 | + log.Fatal(err) |
| 92 | + } |
| 93 | + defer f.Close() |
| 94 | + |
| 95 | + prologues, err := prologo.DetectProloguesFromELF(f) |
| 96 | + if err != nil { |
| 97 | + log.Fatal(err) |
| 98 | + } |
| 99 | + |
| 100 | + counts := make(map[prologo.PrologueType]int) |
| 101 | + for _, p := range prologues { |
| 102 | + fmt.Printf("[%s] 0x%x: %s\n", p.Type, p.Address, p.Instructions) |
| 103 | + counts[p.Type]++ |
| 104 | + } |
| 105 | + |
| 106 | + fmt.Println() |
| 107 | + for typ, n := range counts { |
| 108 | + fmt.Printf(" %s: %d\n", typ, n) |
| 109 | + } |
| 110 | + fmt.Printf("Total: %d\n", len(prologues)) |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +#### Example output |
| 115 | + |
| 116 | +``` |
| 117 | +[classic] 0x401000: push rbp; mov rbp, rsp |
| 118 | +[classic] 0x401020: push rbp; mov rbp, rsp |
| 119 | +[no-frame-pointer] 0x401040: sub rsp, 0x20 |
| 120 | +[push-only] 0x401060: push rbp |
| 121 | +... |
| 122 | +
|
| 123 | + classic: 1547 |
| 124 | + no-frame-pointer: 1 |
| 125 | + push-only: 2 |
| 126 | +Total: 1550 |
| 127 | +``` |
| 128 | + |
| 129 | +#### API Reference |
| 130 | + |
| 131 | +**Functions:** |
| 132 | + |
| 133 | +```go |
| 134 | +// Core detection — works on raw machine code bytes, no I/O. |
| 135 | +func DetectPrologues(code []byte, baseAddr uint64) []Prologue |
| 136 | + |
| 137 | +// Convenience wrapper — parses ELF from the reader, extracts .text, calls DetectPrologues. |
| 138 | +func DetectProloguesFromELF(r io.ReaderAt) ([]Prologue, error) |
| 139 | +``` |
| 140 | + |
| 141 | +**Types:** |
| 142 | + |
| 143 | +```go |
| 144 | +type PrologueType string |
| 145 | + |
| 146 | +const ( |
| 147 | + PrologueClassic PrologueType = "classic" |
| 148 | + PrologueNoFramePointer PrologueType = "no-frame-pointer" |
| 149 | + ProloguePushOnly PrologueType = "push-only" |
| 150 | + PrologueLEABased PrologueType = "lea-based" |
| 151 | +) |
| 152 | + |
| 153 | +type Prologue struct { |
| 154 | + Address uint64 `json:"address"` |
| 155 | + Type PrologueType `json:"type"` |
| 156 | + Instructions string `json:"instructions"` |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +`DetectPrologues` accepts raw bytes and a base virtual address, making it format-agnostic (works with ELF, PE, Mach-O, raw dumps). |
| 161 | + |
| 162 | +`DetectProloguesFromELF` accepts an `io.ReaderAt` (e.g. `*os.File`) and handles ELF parsing internally. |
| 163 | + |
| 164 | +## Implementation Details |
| 165 | + |
| 166 | +### Architecture |
| 167 | + |
| 168 | +``` |
| 169 | +┌─────────────────┐ |
| 170 | +│ ELF Binary │ |
| 171 | +└────────┬────────┘ |
| 172 | + │ |
| 173 | + ▼ |
| 174 | +┌─────────────────┐ |
| 175 | +│ ELF Parser │ ← debug/elf package |
| 176 | +│ (.text section)│ |
| 177 | +└────────┬────────┘ |
| 178 | + │ |
| 179 | + ▼ |
| 180 | +┌─────────────────┐ |
| 181 | +│ Disassembler │ ← golang.org/x/arch/x86/x86asm |
| 182 | +│ (x86_64 decode)│ |
| 183 | +└────────┬────────┘ |
| 184 | + │ |
| 185 | + ▼ |
| 186 | +┌─────────────────┐ |
| 187 | +│ Pattern Matcher │ ← Prologue detection logic |
| 188 | +│ (1-2 inst seq) │ |
| 189 | +└────────┬────────┘ |
| 190 | + │ |
| 191 | + ▼ |
| 192 | +┌─────────────────┐ |
| 193 | +│ []Prologue │ |
| 194 | +│ (addr + type) │ |
| 195 | +└─────────────────┘ |
| 196 | +``` |
| 197 | + |
| 198 | +### Limitations |
| 199 | + |
| 200 | +- **No Symbol Information**: Works on stripped binaries but reports addresses only |
| 201 | +- **Heuristic-Based**: May have false positives in data sections or inline data |
| 202 | +- **Linear Disassembly**: Doesn't handle indirect jumps or computed addresses |
| 203 | +- **No CET Support**: ENDBR64 detection not yet implemented |
| 204 | + |
| 205 | +## Dependencies |
| 206 | + |
| 207 | +- **Go 1.21+** |
| 208 | +- [`golang.org/x/arch/x86/x86asm`](https://pkg.go.dev/golang.org/x/arch/x86/x86asm) - x86 disassembler |
| 209 | +- `debug/elf` (standard library) - ELF parser |
| 210 | + |
| 211 | +## References |
| 212 | + |
| 213 | +- [System V AMD64 ABI](https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf) |
| 214 | +- [Intel 64 and IA-32 Architectures Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) |
| 215 | +- [Go x86 Assembler](https://pkg.go.dev/golang.org/x/arch/x86/x86asm) |
| 216 | +- [ELF Format Specification](https://refspecs.linuxfoundation.org/elf/elf.pdf) |
| 217 | + |
| 218 | +--- |
| 219 | + |
| 220 | +**Author**: Massimiliano Giovagnoli ([@maxgio92](https://github.com/maxgio92)) |
0 commit comments