draugr~ single ELF file analysis tool |
| git clone https://github.com/djnnvx/draugr.git |
| log | files | refs | archive | README |
dynamic_test.odin (4978B)
1 package elf 2 3 import "core:testing" 4 5 DYN_BASE :: 0x400000 6 DYN_STR :: "\x00libtest.so.1\x00libc.so.6\x00/opt/rpath\x00/opt/runpath\x00" 7 8 DYN_OFF_NEEDED_1 :: 1 9 DYN_OFF_NEEDED_2 :: 14 10 DYN_OFF_RPATH :: 24 11 DYN_OFF_RUNPATH :: 35 12 13 build_dynamic_elf :: proc(is64: bool, little: bool, entries: [][2]u64) -> []u8 { 14 dyn_off :: 0x100 15 str_off :: 0x200 16 total :: 0x300 17 18 ph_off := 64 if is64 else 52 19 ph_size := PHDR_SIZE_64 if is64 else PHDR_SIZE_32 20 ent_size := 16 if is64 else 8 21 22 data := make([]u8, total) 23 data[0] = 0x7f 24 data[1] = 'E' 25 data[2] = 'L' 26 data[3] = 'F' 27 data[4] = ELF_CLASS_64 if is64 else ELF_CLASS_32 28 data[5] = ELF_DATA_LSB if little else ELF_DATA_MSB 29 data[6] = 1 30 31 put_u16(data, 16, ET_DYN, little) 32 put_u16(data, 18, EM_X86_64 if is64 else EM_ARM, little) 33 34 if is64 { 35 put_u64(data, 32, u64(ph_off), little) 36 put_u64(data, 40, 0, little) 37 put_u16(data, 54, u16(ph_size), little) 38 put_u16(data, 56, 2, little) 39 } else { 40 put_u32(data, 28, u32(ph_off), little) 41 put_u32(data, 32, 0, little) 42 put_u16(data, 42, u16(ph_size), little) 43 put_u16(data, 44, 2, little) 44 } 45 46 write_phdr(data, ph_off, is64, little, Program_Header{ 47 type = PT_LOAD, offset = 0, vaddr = DYN_BASE, 48 filesz = total, memsz = total, flags = PF_R | PF_X, align = 0x1000, 49 }) 50 write_phdr(data, ph_off + ph_size, is64, little, Program_Header{ 51 type = PT_DYNAMIC, offset = dyn_off, vaddr = DYN_BASE + dyn_off, 52 filesz = u64(len(entries) * ent_size), memsz = u64(len(entries) * ent_size), 53 flags = PF_R | PF_W, align = 8, 54 }) 55 56 pos := dyn_off 57 for e in entries { 58 if is64 { 59 put_u64(data, pos, e[0], little) 60 put_u64(data, pos + 8, e[1], little) 61 } else { 62 put_u32(data, pos, u32(e[0]), little) 63 put_u32(data, pos + 4, u32(e[1]), little) 64 } 65 pos += ent_size 66 } 67 68 copy(data[str_off:], DYN_STR) 69 return data 70 } 71 72 DEFAULT_DYN_ENTRIES := [?][2]u64{ 73 {DT_NEEDED, DYN_OFF_NEEDED_1}, 74 {DT_NEEDED, DYN_OFF_NEEDED_2}, 75 {DT_SONAME, DYN_OFF_NEEDED_1}, 76 {DT_RPATH, DYN_OFF_RPATH}, 77 {DT_RUNPATH, DYN_OFF_RUNPATH}, 78 {DT_STRTAB, DYN_BASE + 0x200}, 79 {DT_STRSZ, u64(len(DYN_STR))}, 80 {DT_NULL, 0}, 81 } 82 83 check_dynamic :: proc(t: ^testing.T, is64: bool, little: bool) { 84 data := build_dynamic_elf(is64, little, DEFAULT_DYN_ENTRIES[:]) 85 defer delete(data) 86 87 info, err := load(data) 88 defer destroy(&info) 89 90 testing.expect_value(t, err, "") 91 testing.expect_value(t, len(info.dyn_entries), 7) 92 testing.expect_value(t, len(info.needed), 2) 93 testing.expect_value(t, info.needed[0], "libtest.so.1") 94 testing.expect_value(t, info.needed[1], "libc.so.6") 95 testing.expect_value(t, info.soname, "libtest.so.1") 96 testing.expect_value(t, info.rpath, "/opt/rpath") 97 testing.expect_value(t, info.runpath, "/opt/runpath") 98 } 99 100 @(test) 101 test_dynamic_elf64_le :: proc(t: ^testing.T) { check_dynamic(t, true, true) } 102 103 @(test) 104 test_dynamic_elf64_be :: proc(t: ^testing.T) { check_dynamic(t, true, false) } 105 106 @(test) 107 test_dynamic_elf32_le :: proc(t: ^testing.T) { check_dynamic(t, false, true) } 108 109 @(test) 110 test_dynamic_elf32_be :: proc(t: ^testing.T) { check_dynamic(t, false, false) } 111 112 @(test) 113 test_dynamic_stops_at_dt_null :: proc(t: ^testing.T) { 114 entries := [][2]u64{ 115 {DT_NEEDED, DYN_OFF_NEEDED_1}, 116 {DT_STRTAB, DYN_BASE + 0x200}, 117 {DT_STRSZ, u64(len(DYN_STR))}, 118 {DT_NULL, 0}, 119 {DT_NEEDED, DYN_OFF_NEEDED_2}, 120 } 121 data := build_dynamic_elf(true, true, entries[:]) 122 defer delete(data) 123 124 info, _ := load(data) 125 defer destroy(&info) 126 127 testing.expect_value(t, len(info.needed), 1) 128 testing.expect_value(t, info.needed[0], "libtest.so.1") 129 } 130 131 @(test) 132 test_dynamic_strtab_outside_file_is_ignored :: proc(t: ^testing.T) { 133 entries := [][2]u64{ 134 {DT_NEEDED, DYN_OFF_NEEDED_1}, 135 {DT_STRTAB, DYN_BASE + 0xdeadbeef}, 136 {DT_STRSZ, u64(len(DYN_STR))}, 137 {DT_NULL, 0}, 138 } 139 data := build_dynamic_elf(true, true, entries[:]) 140 defer delete(data) 141 142 info, _ := load(data) 143 defer destroy(&info) 144 145 testing.expect_value(t, len(info.dyn_entries), 3) 146 testing.expect_value(t, len(info.needed), 0) 147 } 148 149 @(test) 150 test_vaddr_to_offset_maps_through_pt_load :: proc(t: ^testing.T) { 151 data := build_dynamic_elf(true, true, DEFAULT_DYN_ENTRIES[:]) 152 defer delete(data) 153 154 info, _ := load(data) 155 defer destroy(&info) 156 157 off, ok := vaddr_to_offset(&info, DYN_BASE + 0x200) 158 testing.expect(t, ok, "vaddr inside PT_LOAD must resolve") 159 testing.expect_value(t, off, 0x200) 160 161 _, miss := vaddr_to_offset(&info, DYN_BASE + 0x99999) 162 testing.expect(t, !miss, "vaddr outside every PT_LOAD must fail") 163 } 164 165 @(test) 166 test_no_phantom_sections_when_shoff_zero :: proc(t: ^testing.T) { 167 data := build_dynamic_elf(true, true, DEFAULT_DYN_ENTRIES[:]) 168 defer delete(data) 169 170 info, _ := load(data) 171 defer destroy(&info) 172 173 testing.expect_value(t, len(info.section_hdrs), 0) 174 } 175 176 @(test) 177 test_dyn_tag_str_unknown_falls_back_to_hex :: proc(t: ^testing.T) { 178 testing.expect_value(t, dyn_tag_str(DT_RUNPATH), "RUNPATH") 179 testing.expect_value(t, dyn_tag_str(DT_VERNEEDNUM), "VERNEEDNUM") 180 testing.expect_value(t, dyn_tag_str(0x70000001), "0x70000001") 181 }