5"""Usage: change_mach_o_flags.py [--executable-heap] [--no-pie] <executablepath>
7Arranges for the executable at |executable_path| to have its data (heap)
8pages protected to prevent execution on Mac OS X 10.7 ("Lion"), and to have
9the PIE (position independent executable) bit set to enable ASLR (address
10space layout randomization). With --executable-heap or --no-pie, the
11respective bits are cleared instead of set, making the heap executable or
14This script is able to operate on thin (single-architecture) Mach-O files
15and fat (universal, multi-architecture) files. When operating on fat files,
16it will set or clear the bits for each architecture contained therein.
20Traditionally in Mac OS X, 32-bit processes did not have data pages set to
21prohibit execution. Although user programs could call mprotect and
22mach_vm_protect to deny execution of code in data pages, the kernel would
23silently ignore such requests without updating the page tables, and the
24hardware would happily execute code on such pages. 64-bit processes were
25always given proper hardware protection of data pages. This behavior was
26controllable on a system-wide level via the vm.allow_data_exec sysctl, which
27is set by default to 1. The bit with value 1 (set by default) allows code
28execution on data pages for 32-bit processes, and the bit with value 2
29(clear by default) does the same for 64-bit processes.
31In Mac OS X 10.7, executables can "opt in" to having hardware protection
32against code execution on data pages applied. This is done by setting a new
33bit in the |flags| field of an executable's |mach_header|. When
34MH_NO_HEAP_EXECUTION is set, proper protections will be applied, regardless
35of the setting of vm.allow_data_exec. See xnu-1699.22.73/osfmk/vm/vm_map.c
36override_nx and xnu-1699.22.73/bsd/kern/mach_loader.c load_machfile.
38The Apple toolchain has been revised to set the MH_NO_HEAP_EXECUTION when
39producing executables, provided that -allow_heap_execute is not specified
40at link time. Only linkers shipping with Xcode 4.0 and later (ld64-123.2 and
41later) have this ability. See ld64-123.2.1/src/ld/Options.cpp
42Options::reconfigureDefaults() and
43ld64-123.2.1/src/ld/HeaderAndLoadCommands.hpp
44HeaderAndLoadCommandsAtom<A>::flags().
46This script sets the MH_NO_HEAP_EXECUTION bit on Mach-O executables. It is
47intended for use with executables produced by a linker that predates Apple's
48modifications to set this bit itself. It is also useful for setting this bit
49for non-i386 executables, including x86_64 executables. Apple's linker only
50sets it for 32-bit i386 executables, presumably under the assumption that
51the value of vm.allow_data_exec is set in stone. However, if someone were to
52change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run
53without hardware protection against code execution on data pages. This
54script can set the bit for x86_64 executables, guaranteeing that they run
55with appropriate protection even when vm.allow_data_exec has been tampered
58POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION
60This script sets or clears the MH_PIE bit in an executable's Mach-O header,
61enabling or disabling position independence on Mac OS X 10.5 and later.
62Processes running position-independent executables have varying levels of
63ASLR protection depending on the OS release. The main executable's load
64address, shared library load addresses, and the heap and stack base
65addresses may be randomized. Position-independent executables are produced
66by supplying the -pie flag to the linker (or defeated by supplying -no_pie).
67Executables linked with a deployment target of 10.7 or higher have PIE on
70This script is never strictly needed during the build to enable PIE, as all
71linkers used are recent enough to support -pie. However, it's used to
72disable the PIE bit as needed on already-linked executables.
87MH_MAGIC_64 = 0xfeedfacf
88MH_CIGAM_64 = 0xcffaedfe
91MH_NO_HEAP_EXECUTION = 0x01000000
95 """A class for exceptions thrown by this module."""
101 """Seeks the file-like object at |file| to offset |offset| and raises a
102 MachOError if anything funny happens.
"""
104 file.seek(offset, os.SEEK_SET)
105 new_offset = file.tell()
106 if new_offset != offset:
108 'seek: expected offset %d, observed %d' % (offset, new_offset)
112 """Reads |count| bytes from the file-like |file| object, raising a
113 MachOError if any other number of bytes
is read.
"""
115 bytes = file.read(count)
116 if len(bytes) != count:
118 'read: expected length %d, observed %d' % (count,
len(bytes))
124 """Reads an unsigned 32-bit integer from the file-like |file| object,
125 treating it as having endianness specified by |endian| (per the |struct|
126 module),
and returns it
as a number. Raises a MachOError
if the proper
127 length of data can
't be read from |file|."""
131 (uint32,) = struct.unpack(endian + 'I', bytes)
136 """Reads an entire |mach_header| structure (<mach-o/loader.h>) from the
137 file-like |file| object, treating it as having endianness specified by
138 |endian| (per the |struct| module),
and returns a 7-tuple of its members
139 as numbers. Raises a MachOError
if the proper length of data can
't be read
144 magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
145 struct.unpack(endian + '7I', bytes)
146 return magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags
150 """Reads an entire |fat_arch| structure (<mach-o/fat.h>) from the file-like
151 |file| object, treating it as having endianness specified by |endian|
152 (per the |struct| module),
and returns a 5-tuple of its members
as numbers.
153 Raises a MachOError
if the proper length of data can
't be read from
158 cputype, cpusubtype, offset, size, align = struct.unpack('>5I', bytes)
159 return cputype, cpusubtype, offset, size, align
163 """Writes |uint32| as an unsigned 32-bit integer to the file-like |file|
164 object, treating it as having endianness specified by |endian| (per the
167 bytes = struct.pack(endian + 'I', uint32)
168 assert len(bytes) == 4
174 """Seeks the file-like |file| object to |offset|, reads its |mach_header|,
175 and rewrites the header
's |flags| field if appropriate. The header's
176 endianness
is detected. Both 32-bit
and 64-bit Mach-O headers are supported
177 (mach_header
and mach_header_64). Raises MachOError
if used on a header that
178 does
not have a known magic number
or is not of type MH_EXECUTE. The
179 MH_PIE
and MH_NO_HEAP_EXECUTION bits are set
or cleared
in the |flags| field
180 according to |options|
and written to |file|
if any changes need to be made.
181 If already set
or clear
as specified by |options|, nothing
is written.
"""
185 if magic == MH_MAGIC
or magic == MH_MAGIC_64:
187 elif magic == MH_CIGAM
or magic == MH_CIGAM_64:
191 'Mach-O file at offset %d has illusion of magic' % offset
194 magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
196 assert magic == MH_MAGIC
or magic == MH_MAGIC_64
197 if filetype != MH_EXECUTE:
199 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \
202 original_flags = flags
204 if options.no_heap_execution:
205 flags |= MH_NO_HEAP_EXECUTION
207 flags &= ~MH_NO_HEAP_EXECUTION
214 if flags != original_flags:
220 """Seeks the file-like |file| object to |offset| and loops over its
221 |fat_header| entries, calling HandleMachOFile for each.
"""
225 assert magic == FAT_MAGIC
229 for index
in xrange(0, nfat_arch):
230 cputype, cpusubtype, offset, size, align =
ReadFatArch(file)
235 fat_arch_offset = file.tell()
241 parser = optparse.OptionParser(
'%prog [options] <executable_path>')
244 action=
'store_false',
245 dest=
'no_heap_execution',
247 help=
'Clear the MH_NO_HEAP_EXECUTION bit')
250 action=
'store_false',
253 help=
'Clear the MH_PIE bit')
254 (options, loose_args) = parser.parse_args(args)
255 if len(loose_args) != 1:
259 executable_path = loose_args[0]
260 executable_file = open(executable_path,
'rb+')
263 if magic == FAT_CIGAM:
266 elif magic == MH_MAGIC
or magic == MH_CIGAM
or \
267 magic == MH_MAGIC_64
or magic == MH_CIGAM_64:
270 raise MachOError,
'%s is not a Mach-O or fat file' % executable_file
272 executable_file.close()
276if __name__ ==
'__main__':
277 sys.exit(
main(sys.argv[0], sys.argv[1:]))
def CheckedSeek(file, offset)
def HandleMachOFile(file, options, offset=0)
def CheckedRead(file, count)
def ReadMachHeader(file, endian)
def WriteUInt32(file, uint32, endian)
def HandleFatFile(file, options, fat_offset=0)
def ReadUInt32(file, endian)