draugr.git


draugr

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

checksec.odin (5476B)

      1 package elf
      2 
      3 import "core:strings"
      4 
      5 Relro :: enum { None, Partial, Full }
      6 Nx    :: enum { Unknown, Enabled, Disabled }
      7 Pie   :: enum { None, Pie, Static_Pie, Shared_Library, Rel }
      8 
      9 Checksec :: struct {
     10 	relro       : Relro,
     11 	nx          : Nx,
     12 	pie         : Pie,
     13 	canary      : bool,
     14 	textrel     : bool,
     15 	rpath       : string,
     16 	runpath     : string,
     17 	rwx_segment : bool,
     18 	fortified   : int,
     19 	fortifiable : int,
     20 	partial     : int,
     21 	stripped    : bool,
     22 }
     23 
     24 // Derived from glibc's exported __*_chk symbols.
     25 FORTIFIABLE := [?]string{
     26 	"asprintf", "confstr", "dprintf", "explicit_bzero", "fdelt", "fgets", "fgets_unlocked",
     27 	"fgetws", "fgetws_unlocked", "fprintf", "fread", "fread_unlocked", "fwprintf", "getcwd",
     28 	"getdomainname", "getgroups", "gethostname", "getlogin_r", "gets", "getwd", "inet_ntop",
     29 	"inet_pton", "longjmp", "mbsnrtowcs", "mbsrtowcs", "mbstowcs", "memcpy", "memmove",
     30 	"mempcpy", "memset", "memset_explicit", "obstack_printf", "obstack_vprintf", "poll",
     31 	"ppoll", "pread", "pread64", "printf", "ptsname_r", "read", "readlink", "readlinkat",
     32 	"realpath", "recv", "recvfrom", "snprintf", "sprintf", "stpcpy", "stpncpy", "strcat",
     33 	"strcpy", "strlcat", "strlcpy", "strncat", "strncpy", "swprintf", "syslog", "ttyname_r",
     34 	"vasprintf", "vdprintf", "vfprintf", "vfwprintf", "vprintf", "vsnprintf", "vsprintf",
     35 	"vswprintf", "vsyslog", "vwprintf", "wcpcpy", "wcpncpy", "wcrtomb", "wcscat", "wcscpy",
     36 	"wcslcat", "wcslcpy", "wcsncat", "wcsncpy", "wcsnrtombs", "wcsrtombs", "wcstombs", "wctomb",
     37 	"wmemcpy", "wmemmove", "wmempcpy", "wmemset", "wprintf",
     38 }
     39 
     40 checksec :: proc(info: ^ELF_Info) -> Checksec {
     41 	c: Checksec
     42 
     43 	c.relro = relro_state(info)
     44 	c.nx = nx_state(info)
     45 	c.pie = pie_state(info)
     46 	c.canary = has_symbol(info, "__stack_chk_fail") || has_symbol(info, "__stack_chk_guard")
     47 	c.textrel = has_textrel(info)
     48 	c.rpath = info.rpath
     49 	c.runpath = info.runpath
     50 	c.rwx_segment = has_rwx_segment(info)
     51 	c.stripped = len(info.symbols) == 0
     52 	c.fortified, c.fortifiable, c.partial = fortify_counts(info)
     53 
     54 	return c
     55 }
     56 
     57 relro_state :: proc(info: ^ELF_Info) -> Relro {
     58 	relro := false
     59 	for ph in info.program_hdrs {
     60 		if ph.type == PT_GNU_RELRO {
     61 			relro = true
     62 			break
     63 		}
     64 	}
     65 	if !relro {
     66 		return .None
     67 	}
     68 
     69 	if _, ok := dyn_val(info, DT_BIND_NOW); ok {
     70 		return .Full
     71 	}
     72 	if flags, ok := dyn_val(info, DT_FLAGS); ok && flags & DF_BIND_NOW != 0 {
     73 		return .Full
     74 	}
     75 	if flags1, ok := dyn_val(info, DT_FLAGS_1); ok && flags1 & DF_1_NOW != 0 {
     76 		return .Full
     77 	}
     78 	return .Partial
     79 }
     80 
     81 // An absent PT_GNU_STACK is not the same as NX on: the kernel falls back to a
     82 // build-time default, so the honest answer is unknown.
     83 nx_state :: proc(info: ^ELF_Info) -> Nx {
     84 	for ph in info.program_hdrs {
     85 		if ph.type == PT_GNU_STACK {
     86 			return .Disabled if ph.flags & PF_X != 0 else .Enabled
     87 		}
     88 	}
     89 	return .Unknown
     90 }
     91 
     92 // ET_DYN alone does not mean PIE: shared libraries are ET_DYN too.
     93 pie_state :: proc(info: ^ELF_Info) -> Pie {
     94 	if info.header.type == ET_REL {
     95 		return .Rel
     96 	}
     97 	if info.header.type != ET_DYN {
     98 		return .None
     99 	}
    100 
    101 	has_interp := false
    102 	for ph in info.program_hdrs {
    103 		if ph.type == PT_INTERP {
    104 			has_interp = true
    105 			break
    106 		}
    107 	}
    108 	if has_interp {
    109 		return .Pie
    110 	}
    111 
    112 	flags1, _ := dyn_val(info, DT_FLAGS_1)
    113 	if flags1 & DF_1_PIE != 0 {
    114 		return .Static_Pie
    115 	}
    116 	if _, ok := dyn_val(info, DT_SONAME); ok {
    117 		return .Shared_Library
    118 	}
    119 	return .Shared_Library
    120 }
    121 
    122 has_textrel :: proc(info: ^ELF_Info) -> bool {
    123 	if _, ok := dyn_val(info, DT_TEXTREL); ok {
    124 		return true
    125 	}
    126 	flags, _ := dyn_val(info, DT_FLAGS)
    127 	return flags & DF_TEXTREL != 0
    128 }
    129 
    130 has_rwx_segment :: proc(info: ^ELF_Info) -> bool {
    131 	for ph in info.program_hdrs {
    132 		if ph.type != PT_LOAD {
    133 			continue
    134 		}
    135 		if ph.flags & PF_W != 0 && ph.flags & PF_X != 0 {
    136 			return true
    137 		}
    138 	}
    139 	return false
    140 }
    141 
    142 has_symbol :: proc(info: ^ELF_Info, name: string) -> bool {
    143 	for sym in info.dyn_symbols {
    144 		if sym.name == name {
    145 			return true
    146 		}
    147 	}
    148 	for sym in info.symbols {
    149 		if sym.name == name {
    150 			return true
    151 		}
    152 	}
    153 	return false
    154 }
    155 
    156 // __stack_chk_fail is the canary, not a fortified call, so it is excluded.
    157 fortify_counts :: proc(info: ^ELF_Info) -> (fortified: int, fortifiable: int, partial: int) {
    158 	seen_chk := make(map[string]bool)
    159 	seen_raw := make(map[string]bool)
    160 	defer delete(seen_chk)
    161 	defer delete(seen_raw)
    162 
    163 	for sym in info.dyn_symbols {
    164 		if sym.name == "" || sym.name == "__stack_chk_fail" || sym.name == "__stack_chk_guard" {
    165 			continue
    166 		}
    167 		if strings.has_prefix(sym.name, "__") && strings.has_suffix(sym.name, "_chk") {
    168 			seen_chk[sym.name[2 : len(sym.name) - 4]] = true
    169 		} else {
    170 			seen_raw[sym.name] = true
    171 		}
    172 	}
    173 
    174 	for name in FORTIFIABLE {
    175 		switch {
    176 		case seen_chk[name]:
    177 			fortified += 1
    178 			fortifiable += 1
    179 			if seen_raw[name] {
    180 				partial += 1
    181 			}
    182 		case seen_raw[name]:
    183 			fortifiable += 1
    184 		}
    185 	}
    186 	return
    187 }
    188 
    189 relro_str :: proc(r: Relro) -> string {
    190 	switch r {
    191 	case .Full:    return "Full"
    192 	case .Partial: return "Partial"
    193 	case .None:    return "None"
    194 	}
    195 	return "None"
    196 }
    197 
    198 nx_str :: proc(n: Nx) -> string {
    199 	switch n {
    200 	case .Enabled:  return "Enabled"
    201 	case .Disabled: return "Disabled"
    202 	case .Unknown:  return "Unknown (no PT_GNU_STACK)"
    203 	}
    204 	return "Unknown"
    205 }
    206 
    207 pie_str :: proc(p: Pie) -> string {
    208 	switch p {
    209 	case .Pie:            return "PIE"
    210 	case .Static_Pie:     return "Static PIE"
    211 	case .Shared_Library: return "Shared library (not an executable)"
    212 	case .Rel:            return "Relocatable object"
    213 	case .None:           return "None (ET_EXEC)"
    214 	}
    215 	return "None"
    216 }