draugr.git


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 }