draugr.git


draugr

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

code.odin (6302B)

      1 package analysis
      2 
      3 import "core:fmt"
      4 import "core:os"
      5 
      6 import "../elf"
      7 
      8 code_output :: proc(data: []u8, info: ^elf.ELF_Info, out_path: string) -> string {
      9 	f, err := open_out(out_path)
     10 	if err != "" {
     11 		return err
     12 	}
     13 	defer close_out(f, out_path)
     14 
     15 	fmt.fprintf(f, "Code Regions\n")
     16 	fmt.fprintf(f, "============\n\n")
     17 
     18 	print_exec_regions(f, data, info)
     19 	print_entry_analysis(f, info)
     20 	print_function_starts(f, data, info)
     21 
     22 	return ""
     23 }
     24 
     25 print_exec_regions :: proc(f: ^os.File, data: []u8, info: ^elf.ELF_Info) {
     26 	fmt.fprintf(f, "Executable Sections:\n")
     27 	fmt.fprintf(f, "%-20s %-18s %-12s %-12s %s\n", "Name", "VAddr", "Offset", "Size", "Entropy")
     28 
     29 	found := false
     30 	for shdr in info.section_hdrs {
     31 		if shdr.flags & elf.SHF_EXECINSTR == 0 || shdr.type == elf.SHT_NOBITS {
     32 			continue
     33 		}
     34 		found = true
     35 		fmt.fprintf(f, "%-20s 0x%016x 0x%-10x 0x%-10x %.2f\n",
     36 			elf.get_section_name(info, shdr), shdr.addr, shdr.offset, shdr.size,
     37 			region_entropy(data, shdr.offset, shdr.size))
     38 	}
     39 
     40 	if !found {
     41 		fmt.fprintf(f, "  (no section headers, falling back to executable segments)\n")
     42 		for ph in info.program_hdrs {
     43 			if ph.type != elf.PT_LOAD || ph.flags & elf.PF_X == 0 {
     44 				continue
     45 			}
     46 			fmt.fprintf(f, "%-20s 0x%016x 0x%-10x 0x%-10x %.2f\n",
     47 				"PT_LOAD", ph.vaddr, ph.offset, ph.filesz,
     48 				region_entropy(data, ph.offset, ph.filesz))
     49 		}
     50 	}
     51 	fmt.fprintf(f, "\n")
     52 }
     53 
     54 region_entropy :: proc(data: []u8, offset: u64, size: u64) -> f64 {
     55 	if !elf.in_bounds(data, offset, size) || size == 0 {
     56 		return 0.0
     57 	}
     58 	return calculate_entropy(data[offset : offset + size])
     59 }
     60 
     61 print_entry_analysis :: proc(f: ^os.File, info: ^elf.ELF_Info) {
     62 	entry := info.header.entry
     63 	fmt.fprintf(f, "Entry Point: 0x%016x\n", entry)
     64 
     65 	for shdr in info.section_hdrs {
     66 		if shdr.addr != 0 && entry >= shdr.addr && entry - shdr.addr < shdr.size {
     67 			fmt.fprintf(f, "  in section: %s\n", elf.get_section_name(info, shdr))
     68 			break
     69 		}
     70 	}
     71 
     72 	executable := false
     73 	mapped := false
     74 	for ph in info.program_hdrs {
     75 		if ph.type != elf.PT_LOAD || ph.memsz == 0 {
     76 			continue
     77 		}
     78 		if entry >= ph.vaddr && entry - ph.vaddr < ph.memsz {
     79 			mapped = true
     80 			if ph.flags & elf.PF_X != 0 {
     81 				executable = true
     82 			}
     83 		}
     84 	}
     85 
     86 	switch {
     87 	case !mapped:
     88 		fmt.fprintf(f, "  WARNING: entry point is outside every PT_LOAD\n")
     89 	case !executable:
     90 		fmt.fprintf(f, "  WARNING: entry point is in a non-executable segment\n")
     91 	case:
     92 		fmt.fprintf(f, "  in an executable segment\n")
     93 	}
     94 	fmt.fprintf(f, "\n")
     95 }
     96 
     97 DW_EH_PE_OMIT :: 0xff
     98 
     99 DW_EH_PE_ABSPTR :: 0x00
    100 DW_EH_PE_UDATA2 :: 0x02
    101 DW_EH_PE_UDATA4 :: 0x03
    102 DW_EH_PE_UDATA8 :: 0x04
    103 DW_EH_PE_SDATA2 :: 0x0a
    104 DW_EH_PE_SDATA4 :: 0x0b
    105 DW_EH_PE_SDATA8 :: 0x0c
    106 
    107 DW_EH_PE_PCREL   :: 0x10
    108 DW_EH_PE_DATAREL :: 0x30
    109 
    110 // .eh_frame_hdr holds a sorted table of function start addresses, and it
    111 // survives stripping.
    112 print_function_starts :: proc(f: ^os.File, data: []u8, info: ^elf.ELF_Info) {
    113 	hdr_vaddr, hdr_off, hdr_size, ok := find_eh_frame_hdr(info)
    114 	if !ok || !elf.in_bounds(data, hdr_off, hdr_size) {
    115 		fmt.fprintf(f, "Function Starts: no PT_GNU_EH_FRAME\n\n")
    116 		return
    117 	}
    118 
    119 	starts, err := eh_frame_starts(data, info, hdr_vaddr, hdr_off, hdr_size)
    120 	defer delete(starts)
    121 
    122 	if err != "" {
    123 		fmt.fprintf(f, "Function Starts: %s\n\n", err)
    124 		return
    125 	}
    126 
    127 	fmt.fprintf(f, "Function Starts: %d (from .eh_frame_hdr)\n", len(starts))
    128 	shown := min(len(starts), 10)
    129 	for i := 0; i < shown; i += 1 {
    130 		fmt.fprintf(f, "  0x%016x\n", starts[i])
    131 	}
    132 	if len(starts) > shown {
    133 		fmt.fprintf(f, "  ... %d more\n", len(starts) - shown)
    134 	}
    135 	fmt.fprintf(f, "\n")
    136 }
    137 
    138 find_eh_frame_hdr :: proc(info: ^elf.ELF_Info) -> (vaddr: u64, offset: u64, size: u64, ok: bool) {
    139 	for ph in info.program_hdrs {
    140 		if ph.type == elf.PT_GNU_EH_FRAME && ph.filesz > 0 {
    141 			return ph.vaddr, ph.offset, ph.filesz, true
    142 		}
    143 	}
    144 	return 0, 0, 0, false
    145 }
    146 
    147 eh_frame_starts :: proc(data: []u8, info: ^elf.ELF_Info, hdr_vaddr, hdr_off, hdr_size: u64) -> ([dynamic]u64, string) {
    148 	starts := make([dynamic]u64)
    149 	order := elf.byte_order(info.header)
    150 
    151 	buf := data[hdr_off : hdr_off + hdr_size]
    152 	if len(buf) < 4 {
    153 		return starts, "header too small"
    154 	}
    155 	if buf[0] != 1 {
    156 		return starts, fmt.tprintf("unsupported version %d", buf[0])
    157 	}
    158 
    159 	frame_enc := buf[1]
    160 	count_enc := buf[2]
    161 	table_enc := buf[3]
    162 	pos := 4
    163 
    164 	if count_enc == DW_EH_PE_OMIT || table_enc == DW_EH_PE_OMIT {
    165 		return starts, "no binary search table"
    166 	}
    167 	if _, e := read_encoded(buf, &pos, frame_enc, hdr_vaddr, hdr_vaddr, order, info.is_64bit); e != "" {
    168 		return starts, e
    169 	}
    170 	count, cerr := read_encoded(buf, &pos, count_enc, hdr_vaddr, hdr_vaddr, order, info.is_64bit)
    171 	if cerr != "" {
    172 		return starts, cerr
    173 	}
    174 
    175 	for i := u64(0); i < count; i += 1 {
    176 		loc, lerr := read_encoded(buf, &pos, table_enc, hdr_vaddr, hdr_vaddr, order, info.is_64bit)
    177 		if lerr != "" {
    178 			return starts, lerr
    179 		}
    180 		if _, aerr := read_encoded(buf, &pos, table_enc, hdr_vaddr, hdr_vaddr, order, info.is_64bit); aerr != "" {
    181 			return starts, aerr
    182 		}
    183 		append(&starts, loc)
    184 	}
    185 	return starts, ""
    186 }
    187 
    188 read_encoded :: proc(buf: []u8, pos: ^int, enc: u8, pc_vaddr, data_vaddr: u64, order: elf.Byte_Order, is_64bit: bool) -> (u64, string) {
    189 	base := u64(0)
    190 	switch enc & 0x70 {
    191 	case 0:                 base = 0
    192 	case DW_EH_PE_PCREL:    base = pc_vaddr + u64(pos^)
    193 	case DW_EH_PE_DATAREL:  base = data_vaddr
    194 	case:                   return 0, fmt.tprintf("unsupported pointer application 0x%x", enc & 0x70)
    195 	}
    196 
    197 	format := enc & 0x0f
    198 	if format == DW_EH_PE_ABSPTR {
    199 		format = DW_EH_PE_UDATA8 if is_64bit else DW_EH_PE_UDATA4
    200 	}
    201 
    202 	value := u64(0)
    203 	size := 0
    204 	switch format {
    205 	case DW_EH_PE_UDATA2: value = u64(elf.read_u16(buf, pos^, order));               size = 2
    206 	case DW_EH_PE_UDATA4: value = u64(elf.read_u32(buf, pos^, order));               size = 4
    207 	case DW_EH_PE_UDATA8: value = elf.read_u64(buf, pos^, order);                    size = 8
    208 	case DW_EH_PE_SDATA2: value = u64(i64(i16(elf.read_u16(buf, pos^, order))));     size = 2
    209 	case DW_EH_PE_SDATA4: value = u64(i64(i32(elf.read_u32(buf, pos^, order))));     size = 4
    210 	case DW_EH_PE_SDATA8: value = elf.read_u64(buf, pos^, order);                    size = 8
    211 	case:                 return 0, fmt.tprintf("unsupported pointer format 0x%x", format)
    212 	}
    213 
    214 	if pos^ + size > len(buf) {
    215 		return 0, "truncated .eh_frame_hdr"
    216 	}
    217 	pos^ += size
    218 	return base + value, ""
    219 }