|
11 | 11 | from typing import List, Optional
|
12 | 12 |
|
13 | 13 | import lief
|
14 |
| -import pixie |
15 |
| - |
16 |
| -def check_ELF_PIE(executable) -> bool: |
17 |
| - ''' |
18 |
| - Check for position independent executable (PIE), allowing for address space randomization. |
19 |
| - ''' |
20 |
| - elf = pixie.load(executable) |
21 |
| - return elf.hdr.e_type == pixie.ET_DYN |
22 |
| - |
23 |
| -def check_ELF_NX(executable) -> bool: |
24 |
| - ''' |
25 |
| - Check that no sections are writable and executable (including the stack) |
26 |
| - ''' |
27 |
| - elf = pixie.load(executable) |
28 |
| - have_wx = False |
29 |
| - have_gnu_stack = False |
30 |
| - for ph in elf.program_headers: |
31 |
| - if ph.p_type == pixie.PT_GNU_STACK: |
32 |
| - have_gnu_stack = True |
33 |
| - if (ph.p_flags & pixie.PF_W) != 0 and (ph.p_flags & pixie.PF_X) != 0: # section is both writable and executable |
34 |
| - have_wx = True |
35 |
| - return have_gnu_stack and not have_wx |
36 | 14 |
|
37 | 15 | def check_ELF_RELRO(executable) -> bool:
|
38 | 16 | '''
|
39 | 17 | Check for read-only relocations.
|
40 | 18 | GNU_RELRO program header must exist
|
41 | 19 | Dynamic section must have BIND_NOW flag
|
42 | 20 | '''
|
43 |
| - elf = pixie.load(executable) |
| 21 | + binary = lief.parse(executable) |
44 | 22 | have_gnu_relro = False
|
45 |
| - for ph in elf.program_headers: |
| 23 | + for segment in binary.segments: |
46 | 24 | # Note: not checking p_flags == PF_R: here as linkers set the permission differently
|
47 | 25 | # This does not affect security: the permission flags of the GNU_RELRO program
|
48 | 26 | # header are ignored, the PT_LOAD header determines the effective permissions.
|
49 | 27 | # However, the dynamic linker need to write to this area so these are RW.
|
50 | 28 | # Glibc itself takes care of mprotecting this area R after relocations are finished.
|
51 | 29 | # See also https://marc.info/?l=binutils&m=1498883354122353
|
52 |
| - if ph.p_type == pixie.PT_GNU_RELRO: |
| 30 | + if segment.type == lief.ELF.SEGMENT_TYPES.GNU_RELRO: |
53 | 31 | have_gnu_relro = True
|
54 | 32 |
|
55 | 33 | have_bindnow = False
|
56 |
| - for flags in elf.query_dyn_tags(pixie.DT_FLAGS): |
57 |
| - assert isinstance(flags, int) |
58 |
| - if flags & pixie.DF_BIND_NOW: |
| 34 | + try: |
| 35 | + flags = binary.get(lief.ELF.DYNAMIC_TAGS.FLAGS) |
| 36 | + if flags.value & lief.ELF.DYNAMIC_FLAGS.BIND_NOW: |
59 | 37 | have_bindnow = True
|
| 38 | + except: |
| 39 | + have_bindnow = False |
60 | 40 |
|
61 | 41 | return have_gnu_relro and have_bindnow
|
62 | 42 |
|
63 | 43 | def check_ELF_Canary(executable) -> bool:
|
64 | 44 | '''
|
65 | 45 | Check for use of stack canary
|
66 | 46 | '''
|
67 |
| - elf = pixie.load(executable) |
68 |
| - ok = False |
69 |
| - for symbol in elf.dyn_symbols: |
70 |
| - if symbol.name == b'__stack_chk_fail': |
71 |
| - ok = True |
72 |
| - return ok |
| 47 | + binary = lief.parse(executable) |
| 48 | + return binary.has_symbol('__stack_chk_fail') |
73 | 49 |
|
74 | 50 | def check_ELF_separate_code(executable):
|
75 | 51 | '''
|
76 | 52 | Check that sections are appropriately separated in virtual memory,
|
77 | 53 | based on their permissions. This checks for missing -Wl,-z,separate-code
|
78 | 54 | and potentially other problems.
|
79 | 55 | '''
|
80 |
| - elf = pixie.load(executable) |
81 |
| - R = pixie.PF_R |
82 |
| - W = pixie.PF_W |
83 |
| - E = pixie.PF_X |
| 56 | + binary = lief.parse(executable) |
| 57 | + R = lief.ELF.SEGMENT_FLAGS.R |
| 58 | + W = lief.ELF.SEGMENT_FLAGS.W |
| 59 | + E = lief.ELF.SEGMENT_FLAGS.X |
84 | 60 | EXPECTED_FLAGS = {
|
85 | 61 | # Read + execute
|
86 |
| - b'.init': R | E, |
87 |
| - b'.plt': R | E, |
88 |
| - b'.plt.got': R | E, |
89 |
| - b'.plt.sec': R | E, |
90 |
| - b'.text': R | E, |
91 |
| - b'.fini': R | E, |
| 62 | + '.init': R | E, |
| 63 | + '.plt': R | E, |
| 64 | + '.plt.got': R | E, |
| 65 | + '.plt.sec': R | E, |
| 66 | + '.text': R | E, |
| 67 | + '.fini': R | E, |
92 | 68 | # Read-only data
|
93 |
| - b'.interp': R, |
94 |
| - b'.note.gnu.property': R, |
95 |
| - b'.note.gnu.build-id': R, |
96 |
| - b'.note.ABI-tag': R, |
97 |
| - b'.gnu.hash': R, |
98 |
| - b'.dynsym': R, |
99 |
| - b'.dynstr': R, |
100 |
| - b'.gnu.version': R, |
101 |
| - b'.gnu.version_r': R, |
102 |
| - b'.rela.dyn': R, |
103 |
| - b'.rela.plt': R, |
104 |
| - b'.rodata': R, |
105 |
| - b'.eh_frame_hdr': R, |
106 |
| - b'.eh_frame': R, |
107 |
| - b'.qtmetadata': R, |
108 |
| - b'.gcc_except_table': R, |
109 |
| - b'.stapsdt.base': R, |
| 69 | + '.interp': R, |
| 70 | + '.note.gnu.property': R, |
| 71 | + '.note.gnu.build-id': R, |
| 72 | + '.note.ABI-tag': R, |
| 73 | + '.gnu.hash': R, |
| 74 | + '.dynsym': R, |
| 75 | + '.dynstr': R, |
| 76 | + '.gnu.version': R, |
| 77 | + '.gnu.version_r': R, |
| 78 | + '.rela.dyn': R, |
| 79 | + '.rela.plt': R, |
| 80 | + '.rodata': R, |
| 81 | + '.eh_frame_hdr': R, |
| 82 | + '.eh_frame': R, |
| 83 | + '.qtmetadata': R, |
| 84 | + '.gcc_except_table': R, |
| 85 | + '.stapsdt.base': R, |
110 | 86 | # Writable data
|
111 |
| - b'.init_array': R | W, |
112 |
| - b'.fini_array': R | W, |
113 |
| - b'.dynamic': R | W, |
114 |
| - b'.got': R | W, |
115 |
| - b'.data': R | W, |
116 |
| - b'.bss': R | W, |
| 87 | + '.init_array': R | W, |
| 88 | + '.fini_array': R | W, |
| 89 | + '.dynamic': R | W, |
| 90 | + '.got': R | W, |
| 91 | + '.data': R | W, |
| 92 | + '.bss': R | W, |
117 | 93 | }
|
118 |
| - if elf.hdr.e_machine == pixie.EM_PPC64: |
| 94 | + if binary.header.machine_type == lief.ELF.ARCH.PPC64: |
119 | 95 | # .plt is RW on ppc64 even with separate-code
|
120 |
| - EXPECTED_FLAGS[b'.plt'] = R | W |
| 96 | + EXPECTED_FLAGS['.plt'] = R | W |
121 | 97 | # For all LOAD program headers get mapping to the list of sections,
|
122 | 98 | # and for each section, remember the flags of the associated program header.
|
123 | 99 | flags_per_section = {}
|
124 |
| - for ph in elf.program_headers: |
125 |
| - if ph.p_type == pixie.PT_LOAD: |
126 |
| - for section in ph.sections: |
| 100 | + for segment in binary.segments: |
| 101 | + if segment.type == lief.ELF.SEGMENT_TYPES.LOAD: |
| 102 | + for section in segment.sections: |
127 | 103 | assert(section.name not in flags_per_section)
|
128 |
| - flags_per_section[section.name] = ph.p_flags |
| 104 | + flags_per_section[section.name] = segment.flags |
129 | 105 | # Spot-check ELF LOAD program header flags per section
|
130 | 106 | # If these sections exist, check them against the expected R/W/E flags
|
131 | 107 | for (section, flags) in flags_per_section.items():
|
132 | 108 | if section in EXPECTED_FLAGS:
|
133 |
| - if EXPECTED_FLAGS[section] != flags: |
| 109 | + if int(EXPECTED_FLAGS[section]) != int(flags): |
134 | 110 | return False
|
135 | 111 | return True
|
136 | 112 |
|
@@ -203,8 +179,8 @@ def check_control_flow(executable) -> bool:
|
203 | 179 |
|
204 | 180 | CHECKS = {
|
205 | 181 | 'ELF': [
|
206 |
| - ('PIE', check_ELF_PIE), |
207 |
| - ('NX', check_ELF_NX), |
| 182 | + ('PIE', check_PIE), |
| 183 | + ('NX', check_NX), |
208 | 184 | ('RELRO', check_ELF_RELRO),
|
209 | 185 | ('Canary', check_ELF_Canary),
|
210 | 186 | ('separate_code', check_ELF_separate_code),
|
|
0 commit comments