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 }