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 }