draugr.git


draugr

~ single ELF file analysis tool
git clone https://github.com/djnnvx/draugr.git
log | files | refs | archive | README

main.odin (7250B)

      1 package main
      2 
      3 import "core:flags"
      4 import "core:fmt"
      5 import "core:os"
      6 
      7 import "./elf"
      8 import "./analysis"
      9 
     10 // Author: djnn
     11 
     12 Options :: struct {
     13 	elf_path    : string `args:"pos=0,required" usage:"ELF binary to analyse."`,
     14 	hexdump     : bool   `usage:"Hex dump entire file to stdout."`,
     15 	hexdump_out : string `usage:"Hex dump to file."`,
     16 	code        : bool   `usage:"Code region report: executable sections, entry point, function starts."`,
     17 	code_out    : string `usage:"Code region report to file."`,
     18 	entropy     : bool   `usage:"Shannon entropy report."`,
     19 	entropy_out : string `usage:"Entropy report to file."`,
     20 	symbols     : bool   `usage:"Print symbol table (nm-like)."`,
     21 	sections    : bool   `usage:"Print section headers."`,
     22 	segments    : bool   `usage:"Print program headers (objdump -p style)."`,
     23 	show_map    : bool   `args:"name=map" usage:"Visual memory layout map."`,
     24 	bin_diff    : string `args:"name=bin-diff" usage:"Diff this binary's layout against a reference ELF."`,
     25 	call_func   : string `args:"name=call-function" usage:"Load from memory and call this symbol."`,
     26 	call_args   : string `args:"name=call-args" usage:"Comma-separated args for --call-function: integers, or s:string."`,
     27 	patch       : string `args:"name=patch" usage:"Semicolon-separated in-memory patches applied before --call-function. Specs: sym=ret:N, sym=jmp:target, got:sym=target."`,
     28 	checksec    : bool   `usage:"Hardening report: RELRO, NX, canary, PIE, RPATH, FORTIFY."`,
     29 	relocs      : bool   `usage:"Relocation table and PLT/GOT map."`,
     30 	info        : bool   `usage:"Print ELF summary only."`,
     31 	json        : bool   `usage:"Full structured dump as JSON (header, segments, sections, dynamic, symbols, relocations, GOT, checksec)."`,
     32 	verbose     : bool   `usage:"Verbose output."`,
     33 }
     34 
     35 main :: proc() {
     36 	opts: Options
     37 	flags.parse_or_exit(&opts, os.args, .Unix)
     38 
     39 	elf_data, read_err := os.read_entire_file_from_path(opts.elf_path, context.allocator)
     40 	if read_err != nil {
     41 		fmt.fprintf(os.stderr, "Failed to read file: %s\n", opts.elf_path)
     42 		os.exit(1)
     43 	}
     44 
     45 	if !elf.probe(elf_data) {
     46 		fmt.fprintf(os.stderr, "Not a valid ELF file: %s\n", opts.elf_path)
     47 		os.exit(1)
     48 	}
     49 
     50 	info, parse_err := elf.load(elf_data)
     51 	if parse_err != "" {
     52 		fmt.fprintf(os.stderr, "ELF parse error: %s\n", parse_err)
     53 		os.exit(1)
     54 	}
     55 	defer elf.destroy(&info)
     56 
     57 	bits := 64 if info.is_64bit else 32
     58 	elf_type := "PIE" if info.is_pie else "ET_EXEC"
     59 	dyn_type := "dynamic" if info.is_dynamic else "static"
     60 
     61 	fmt.fprintln(os.stderr, "----------------------------------------")
     62 	fmt.fprintf(os.stderr, "arch=%-12s class=ELF%d\t%s\t%s\n",
     63 		elf.machine_to_str(info.header.machine), bits, elf_type, dyn_type)
     64 	fmt.fprintln(os.stderr, "")
     65 
     66 	if opts.patch != "" && opts.call_func == "" {
     67 		fmt.fprintln(os.stderr, "--patch requires --call-function")
     68 		os.exit(1)
     69 	}
     70 
     71 	if opts.json {
     72 		if opts.hexdump || opts.hexdump_out != "" ||
     73 		   opts.entropy || opts.entropy_out != "" ||
     74 		   opts.code    || opts.code_out    != "" ||
     75 		   opts.show_map || opts.bin_diff != "" || opts.call_func != "" {
     76 			fmt.fprintln(os.stderr, "--json is not supported with --hexdump, --entropy, --code, --map, --bin-diff or --call-function")
     77 			os.exit(1)
     78 		}
     79 		if err := elf.print_json(&info); err != "" {
     80 			fmt.fprintln(os.stderr, err)
     81 			os.exit(1)
     82 		}
     83 		return
     84 	}
     85 
     86 	if opts.info {
     87 		elf.print_info(&info, opts.verbose)
     88 		return
     89 	}
     90 
     91 	failed := false
     92 
     93 	if opts.hexdump || opts.hexdump_out != "" {
     94 		section_header("Hexdump", opts.hexdump_out)
     95 		report("Hexdump", analysis.hexdump(elf_data, 0, opts.hexdump_out), &failed)
     96 		fmt.println("")
     97 	}
     98 
     99 	if opts.entropy || opts.entropy_out != "" {
    100 		section_header("Entropy Analysis", opts.entropy_out)
    101 		report("Entropy", analysis.entropy_output(elf_data, opts.entropy_out), &failed)
    102 		fmt.println("")
    103 	}
    104 
    105 	if opts.code || opts.code_out != "" {
    106 		section_header("Code Regions", opts.code_out)
    107 		report("Code", analysis.code_output(elf_data, &info, opts.code_out), &failed)
    108 		fmt.println("")
    109 	}
    110 
    111 	if opts.checksec {
    112 		section_header("Checksec", "")
    113 		elf.print_checksec(&info)
    114 		fmt.println("")
    115 	}
    116 
    117 	if opts.relocs {
    118 		section_header("Relocations", "")
    119 		elf.print_relocs(&info)
    120 		fmt.println("")
    121 	}
    122 
    123 	if opts.symbols {
    124 		section_header("Symbol Table (nm-style)", "")
    125 		print_symbols(&info)
    126 		fmt.println("")
    127 	}
    128 
    129 	if opts.sections {
    130 		section_header("Section Headers", "")
    131 		print_sections(&info)
    132 		fmt.println("")
    133 	}
    134 
    135 	if opts.segments {
    136 		section_header("Program Headers (objdump-style)", "")
    137 		print_segments(&info)
    138 		fmt.println("")
    139 	}
    140 
    141 	if opts.show_map {
    142 		section_header("Memory Map", "")
    143 		report("Map", analysis.map_output(elf_data, &info, ""), &failed)
    144 		fmt.println("")
    145 	}
    146 
    147 	if opts.call_func != "" {
    148 		section_header("Call Function", "")
    149 		report("Call", analysis.call_function(elf_data, &info, opts.elf_path, opts.call_func, opts.call_args, opts.patch), &failed)
    150 		fmt.println("")
    151 	}
    152 
    153 	if opts.bin_diff != "" {
    154 		section_header("Binary Diff", "")
    155 		report("BinDiff", analysis.bin_diff_output(elf_data, &info, opts.bin_diff, ""), &failed)
    156 		fmt.println("")
    157 	}
    158 
    159 	if failed {
    160 		os.exit(1)
    161 	}
    162 }
    163 
    164 report :: proc(label: string, err: string, failed: ^bool) {
    165 	if err == "" {
    166 		return
    167 	}
    168 	fmt.fprintf(os.stderr, "%s error: %s\n", label, err)
    169 	failed^ = true
    170 }
    171 
    172 section_header :: proc(title: string, out_path: string) {
    173 	fmt.fprintln(os.stderr, "----------------------------------------")
    174 	if out_path == "" {
    175 		fmt.fprintf(os.stderr, "%s:\n", title)
    176 	} else {
    177 		fmt.fprintf(os.stderr, "%s -> %s\n", title, out_path)
    178 	}
    179 }
    180 
    181 print_symbols :: proc(info: ^elf.ELF_Info) {
    182 	print_symbol_table(info.symbols[:])
    183 	print_symbol_table(info.dyn_symbols[:])
    184 }
    185 
    186 print_symbol_table :: proc(syms: []elf.Symbol) {
    187 	for sym in syms {
    188 		if sym.name == "" {
    189 			continue
    190 		}
    191 		if sym.shndx == elf.SHN_UNDEF {
    192 			fmt.printf("%18s %c %s\n", "", sym_to_nm_type(sym), sym.name)
    193 		} else {
    194 			fmt.printf("0x%016x %c %s\n", sym.value, sym_to_nm_type(sym), sym.name)
    195 		}
    196 	}
    197 }
    198 
    199 // Odin's fmt zero-fills numeric widths, so decimal columns pad via a string.
    200 print_sections :: proc(info: ^elf.ELF_Info) {
    201 	fmt.printf("%-4s %-20s %-10s %-10s %-10s %s\n",
    202 		"Idx", "Name", "Type", "Addr", "Offset", "Size")
    203 
    204 	for shdr, i in info.section_hdrs {
    205 		name := elf.get_section_name(info, shdr)
    206 		if name == "" {
    207 			name = fmt.tprintf("[%d]", i)
    208 		}
    209 		fmt.printf("%-4s %-20s %-10s 0x%08x 0x%08x 0x%x\n",
    210 			fmt.tprintf("%d", i), name, elf.section_type_str(shdr.type),
    211 			shdr.addr, shdr.offset, shdr.size)
    212 	}
    213 }
    214 
    215 print_segments :: proc(info: ^elf.ELF_Info) {
    216 	fmt.printf("%-13s %-5s %-10s %-10s %-10s %s\n",
    217 		"Type", "Flags", "Offset", "VirtAddr", "FileSize", "MemSize")
    218 
    219 	for ph in info.program_hdrs {
    220 		fmt.printf("%-13s %-5s 0x%08x 0x%08x 0x%08x 0x%08x\n",
    221 			elf.segment_type_str(ph.type),
    222 			elf.flags_to_str(ph.flags),
    223 			ph.offset, ph.vaddr, ph.filesz, ph.memsz)
    224 	}
    225 }
    226 
    227 sym_to_nm_type :: proc(sym: elf.Symbol) -> u8 {
    228 	if sym.shndx == elf.SHN_UNDEF {
    229 		return 'U'
    230 	}
    231 
    232 	letter: u8 = '?'
    233 	switch sym.type {
    234 	case elf.STT_NOTYPE:  letter = ' '
    235 	case elf.STT_OBJECT:  letter = 'O'
    236 	case elf.STT_FUNC:    letter = 'T'
    237 	case elf.STT_SECTION: letter = 'S'
    238 	case elf.STT_FILE:    letter = 'F'
    239 	}
    240 
    241 	if sym.bind == elf.STB_LOCAL && letter >= 'A' && letter <= 'Z' {
    242 		letter += 'a' - 'A'
    243 	}
    244 	return letter
    245 }