OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 # Usage: make_heap_non_executable.py <executable_path> | 7 # Usage: change_mach_o_flags.py [--executable-heap] [--no-pie] <executable_path> |
8 # | 8 # |
9 # Arranges for the executable at |executable_path| to have its data (heap) | 9 # Arranges for the executable at |executable_path| to have its data (heap) |
10 # pages protected to prevent execution on Mac OS X 10.7 ("Lion"). | 10 # pages protected to prevent execution on Mac OS X 10.7 ("Lion"), and to have |
| 11 # the PIE (position independent executable) bit set to enable ASLR (address |
| 12 # space layout randomization). With --executable-heap or --no-pie, the |
| 13 # respective bits are cleared instead of set, making the heap executable or |
| 14 # disabling PIE/ASLR. |
| 15 # |
| 16 # This script is able to operate on thin (single-architecture) Mach-O files |
| 17 # and fat (universal, multi-architecture) files. When operating on fat files, |
| 18 # it will set or clear the bits for each architecture contained therein. |
| 19 # |
| 20 # NON-EXECUTABLE HEAP |
11 # | 21 # |
12 # Traditionally in Mac OS X, 32-bit processes did not have data pages set to | 22 # Traditionally in Mac OS X, 32-bit processes did not have data pages set to |
13 # prohibit execution. Although user programs could call mprotect and | 23 # prohibit execution. Although user programs could call mprotect and |
14 # mach_vm_protect to deny execution of code in data pages, the kernel would | 24 # mach_vm_protect to deny execution of code in data pages, the kernel would |
15 # silently ignore such requests without updating the page tables, and the | 25 # silently ignore such requests without updating the page tables, and the |
16 # hardware would happily execute code on such pages. 64-bit processes were | 26 # hardware would happily execute code on such pages. 64-bit processes were |
17 # always given proper hardware protection of data pages. This behavior was | 27 # always given proper hardware protection of data pages. This behavior was |
18 # controllable on a system-wide level via the vm.allow_data_exec sysctl, which | 28 # controllable on a system-wide level via the vm.allow_data_exec sysctl, which |
19 # is set by default to 1. The bit with value 1 (set by default) allows code | 29 # is set by default to 1. The bit with value 1 (set by default) allows code |
20 # execution on data pages for 32-bit processes, and the bit with value 2 | 30 # execution on data pages for 32-bit processes, and the bit with value 2 |
(...skipping 19 matching lines...) Expand all Loading... |
40 # modifications to set this bit itself. It is also useful for setting this bit | 50 # modifications to set this bit itself. It is also useful for setting this bit |
41 # for non-i386 executables, including x86_64 executables. Apple's linker only | 51 # for non-i386 executables, including x86_64 executables. Apple's linker only |
42 # sets it for 32-bit i386 executables, presumably under the assumption that | 52 # sets it for 32-bit i386 executables, presumably under the assumption that |
43 # the value of vm.allow_data_exec is set in stone. However, if someone were to | 53 # the value of vm.allow_data_exec is set in stone. However, if someone were to |
44 # change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run | 54 # change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run |
45 # without hardware protection against code execution on data pages. This | 55 # without hardware protection against code execution on data pages. This |
46 # script can set the bit for x86_64 executables, guaranteeing that they run | 56 # script can set the bit for x86_64 executables, guaranteeing that they run |
47 # with appropriate protection even when vm.allow_data_exec has been tampered | 57 # with appropriate protection even when vm.allow_data_exec has been tampered |
48 # with. | 58 # with. |
49 # | 59 # |
50 # This script is able to operate on thin (single-architecture) Mach-O files | 60 # POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION |
51 # and fat (universal, multi-architecture) files. When operating on fat files, | 61 # |
52 # it will set the MH_NO_HEAP_EXECUTION bit for each architecture contained | 62 # This script sets or clears the MH_PIE bit in an executable's Mach-O header, |
53 # therein. | 63 # enabling or disabling position independence on Mac OS X 10.5 and later. |
| 64 # Processes running position-independent executables have varying levels of |
| 65 # ASLR protection depending on the OS release. The main executable's load |
| 66 # address, shared library load addresess, and the heap and stack base |
| 67 # addresses may be randomized. Position-independent executables are produced |
| 68 # by supplying the -pie flag to the linker (or defeated by supplying -no_pie). |
| 69 # Executables linked with a deployment target of 10.7 or higher have PIE on |
| 70 # by default. |
| 71 # |
| 72 # This script is never strictly needed during the build to enable PIE, as all |
| 73 # linkers used are recent enough to support -pie. However, it's used to |
| 74 # disable the PIE bit as needed on already-linked executables. |
54 | 75 |
55 | 76 |
| 77 import optparse |
56 import os | 78 import os |
57 import struct | 79 import struct |
58 import sys | 80 import sys |
59 | 81 |
60 | 82 |
61 # <mach-o/fat.h> | 83 # <mach-o/fat.h> |
62 FAT_MAGIC = 0xcafebabe | 84 FAT_MAGIC = 0xcafebabe |
63 FAT_CIGAM = 0xbebafeca | 85 FAT_CIGAM = 0xbebafeca |
64 | 86 |
65 # <mach-o/loader.h> | 87 # <mach-o/loader.h> |
66 MH_MAGIC = 0xfeedface | 88 MH_MAGIC = 0xfeedface |
67 MH_CIGAM = 0xcefaedfe | 89 MH_CIGAM = 0xcefaedfe |
68 MH_MAGIC_64 = 0xfeedfacf | 90 MH_MAGIC_64 = 0xfeedfacf |
69 MH_CIGAM_64 = 0xcffaedfe | 91 MH_CIGAM_64 = 0xcffaedfe |
70 MH_EXECUTE = 0x2 | 92 MH_EXECUTE = 0x2 |
71 MH_NO_HEAP_EXECUTION = 0x1000000 | 93 MH_PIE = 0x00200000 |
| 94 MH_NO_HEAP_EXECUTION = 0x01000000 |
72 | 95 |
73 | 96 |
74 class MachOError(Exception): | 97 class MachOError(Exception): |
75 """A class for exceptions thrown by this module.""" | 98 """A class for exceptions thrown by this module.""" |
76 | 99 |
77 pass | 100 pass |
78 | 101 |
79 | 102 |
80 def CheckedSeek(file, offset): | 103 def CheckedSeek(file, offset): |
81 """Seeks the file-like object at |file| to offset |offset| and raises a | 104 """Seeks the file-like object at |file| to offset |offset| and raises a |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
143 """Writes |uint32| as an unsinged 32-bit integer to the file-like |file| | 166 """Writes |uint32| as an unsinged 32-bit integer to the file-like |file| |
144 object, treating it as having endianness specified by |endian| (per the | 167 object, treating it as having endianness specified by |endian| (per the |
145 |struct| module).""" | 168 |struct| module).""" |
146 | 169 |
147 bytes = struct.pack(endian + 'I', uint32) | 170 bytes = struct.pack(endian + 'I', uint32) |
148 assert len(bytes) == 4 | 171 assert len(bytes) == 4 |
149 | 172 |
150 file.write(bytes) | 173 file.write(bytes) |
151 | 174 |
152 | 175 |
153 def HandleMachOFile(file, offset=0): | 176 def HandleMachOFile(file, options, offset=0): |
154 """Seeks the file-like |file| object to |offset|, reads its |mach_header|, | 177 """Seeks the file-like |file| object to |offset|, reads its |mach_header|, |
155 and rewrites the header's |flags| field if appropriate. The header's | 178 and rewrites the header's |flags| field if appropriate. The header's |
156 endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported | 179 endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported |
157 (mach_header and mach_header_64). Raises MachOError if used on a header that | 180 (mach_header and mach_header_64). Raises MachOError if used on a header that |
158 does not have a known magic number or is not of type MH_EXECUTE. The | 181 does not have a known magic number or is not of type MH_EXECUTE. The |
159 MH_NO_HEAP_EXECUTION is set in the |flags| field and written to |file| if | 182 MH_PIE and MH_NO_HEAP_EXECUTION bits are set or cleared in the |flags| field |
160 not already set. If already set, nothing is written.""" | 183 according to |options| and written to |file| if any changes need to be made. |
| 184 If already set or clear as specified by |options|, nothing is written.""" |
161 | 185 |
162 CheckedSeek(file, offset) | 186 CheckedSeek(file, offset) |
163 magic = ReadUInt32(file, '<') | 187 magic = ReadUInt32(file, '<') |
164 if magic == MH_MAGIC or magic == MH_MAGIC_64: | 188 if magic == MH_MAGIC or magic == MH_MAGIC_64: |
165 endian = '<' | 189 endian = '<' |
166 elif magic == MH_CIGAM or magic == MH_CIGAM_64: | 190 elif magic == MH_CIGAM or magic == MH_CIGAM_64: |
167 endian = '>' | 191 endian = '>' |
168 else: | 192 else: |
169 raise MachOError, \ | 193 raise MachOError, \ |
170 'Mach-O file at offset %d has illusion of magic' % offset | 194 'Mach-O file at offset %d has illusion of magic' % offset |
171 | 195 |
172 CheckedSeek(file, offset) | 196 CheckedSeek(file, offset) |
173 magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ | 197 magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ |
174 ReadMachHeader(file, endian) | 198 ReadMachHeader(file, endian) |
175 assert magic == MH_MAGIC or magic == MH_MAGIC_64 | 199 assert magic == MH_MAGIC or magic == MH_MAGIC_64 |
176 if filetype != MH_EXECUTE: | 200 if filetype != MH_EXECUTE: |
177 raise MachOError, \ | 201 raise MachOError, \ |
178 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \ | 202 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \ |
179 (offset, filetype) | 203 (offset, filetype) |
180 | 204 |
181 if not flags & MH_NO_HEAP_EXECUTION: | 205 original_flags = flags |
| 206 |
| 207 if options.no_heap_execution: |
182 flags |= MH_NO_HEAP_EXECUTION | 208 flags |= MH_NO_HEAP_EXECUTION |
| 209 else: |
| 210 flags &= ~MH_NO_HEAP_EXECUTION |
| 211 |
| 212 if options.pie: |
| 213 flags |= MH_PIE |
| 214 else: |
| 215 flags &= ~MH_PIE |
| 216 |
| 217 if flags != original_flags: |
183 CheckedSeek(file, offset + 24) | 218 CheckedSeek(file, offset + 24) |
184 WriteUInt32(file, flags, endian) | 219 WriteUInt32(file, flags, endian) |
185 | 220 |
186 | 221 |
187 def HandleFatFile(file, fat_offset=0): | 222 def HandleFatFile(file, options, fat_offset=0): |
188 """Seeks the file-like |file| object to |offset| and loops over its | 223 """Seeks the file-like |file| object to |offset| and loops over its |
189 |fat_header| entries, calling HandleMachOFile for each.""" | 224 |fat_header| entries, calling HandleMachOFile for each.""" |
190 | 225 |
191 CheckedSeek(file, fat_offset) | 226 CheckedSeek(file, fat_offset) |
192 magic = ReadUInt32(file, '>') | 227 magic = ReadUInt32(file, '>') |
193 assert magic == FAT_MAGIC | 228 assert magic == FAT_MAGIC |
194 | 229 |
195 nfat_arch = ReadUInt32(file, '>') | 230 nfat_arch = ReadUInt32(file, '>') |
196 | 231 |
197 for index in xrange(0, nfat_arch): | 232 for index in xrange(0, nfat_arch): |
198 cputype, cpusubtype, offset, size, align = ReadFatArch(file) | 233 cputype, cpusubtype, offset, size, align = ReadFatArch(file) |
199 assert size >= 28 | 234 assert size >= 28 |
200 | 235 |
201 # HandleMachOFile will seek around. Come back here after calling it, in | 236 # HandleMachOFile will seek around. Come back here after calling it, in |
202 # case it sought. | 237 # case it sought. |
203 fat_arch_offset = file.tell() | 238 fat_arch_offset = file.tell() |
204 HandleMachOFile(file, offset) | 239 HandleMachOFile(file, options, offset) |
205 CheckedSeek(file, fat_arch_offset) | 240 CheckedSeek(file, fat_arch_offset) |
206 | 241 |
207 | 242 |
208 def main(me, args): | 243 def main(me, args): |
209 if len(args) != 1: | 244 parser = optparse.OptionParser('%prog [options] <executable_path>') |
210 print >>sys.stderr, 'usage: %s <executable_path>' % me | 245 parser.add_option('--executable-heap', action='store_false', |
| 246 dest='no_heap_execution', default=True, |
| 247 help='Clear the MH_NO_HEAP_EXECUTION bit') |
| 248 parser.add_option('--no-pie', action='store_false', |
| 249 dest='pie', default=True, |
| 250 help='Clear the MH_PIE bit') |
| 251 (options, loose_args) = parser.parse_args(args) |
| 252 if len(loose_args) != 1: |
| 253 parser.print_usage() |
211 return 1 | 254 return 1 |
212 | 255 |
213 executable_path = args[0] | 256 executable_path = loose_args[0] |
214 executable_file = open(executable_path, 'rb+') | 257 executable_file = open(executable_path, 'rb+') |
215 | 258 |
216 magic = ReadUInt32(executable_file, '<') | 259 magic = ReadUInt32(executable_file, '<') |
217 if magic == FAT_CIGAM: | 260 if magic == FAT_CIGAM: |
218 # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian. | 261 # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian. |
219 HandleFatFile(executable_file) | 262 HandleFatFile(executable_file, options) |
220 elif magic == MH_MAGIC or magic == MH_CIGAM or \ | 263 elif magic == MH_MAGIC or magic == MH_CIGAM or \ |
221 magic == MH_MAGIC_64 or magic == MH_CIGAM_64: | 264 magic == MH_MAGIC_64 or magic == MH_CIGAM_64: |
222 HandleMachOFile(executable_file) | 265 HandleMachOFile(executable_file, options) |
223 else: | 266 else: |
224 raise MachOError, '%s is not a Mach-O or fat file' % executable_file | 267 raise MachOError, '%s is not a Mach-O or fat file' % executable_file |
225 | 268 |
226 executable_file.close() | 269 executable_file.close() |
227 | 270 |
228 return 0 | 271 return 0 |
229 | 272 |
230 if __name__ == '__main__': | 273 if __name__ == '__main__': |
231 sys.exit(main(sys.argv[0], sys.argv[1:])) | 274 sys.exit(main(sys.argv[0], sys.argv[1:])) |
OLD | NEW |