Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Functions | Variables
change_mach_o_flags Namespace Reference

Classes

class  MachOError
 

Functions

 CheckedSeek (file, offset)
 
 CheckedRead (file, count)
 
 ReadUInt32 (file, endian)
 
 ReadMachHeader (file, endian)
 
 ReadFatArch (file)
 
 WriteUInt32 (file, uint32, endian)
 
 HandleMachOFile (file, options, offset=0)
 
 HandleFatFile (file, options, fat_offset=0)
 
 main (me, args)
 

Variables

int FAT_MAGIC = 0xcafebabe
 
int FAT_CIGAM = 0xbebafeca
 
int MH_MAGIC = 0xfeedface
 
int MH_CIGAM = 0xcefaedfe
 
int MH_MAGIC_64 = 0xfeedfacf
 
int MH_CIGAM_64 = 0xcffaedfe
 
int MH_EXECUTE = 0x2
 
int MH_PIE = 0x00200000
 
int MH_NO_HEAP_EXECUTION = 0x01000000
 

Detailed Description

Usage: change_mach_o_flags.py [--executable-heap] [--no-pie] <executablepath>

Arranges for the executable at |executable_path| to have its data (heap)
pages protected to prevent execution on Mac OS X 10.7 ("Lion"), and to have
the PIE (position independent executable) bit set to enable ASLR (address
space layout randomization). With --executable-heap or --no-pie, the
respective bits are cleared instead of set, making the heap executable or
disabling PIE/ASLR.

This script is able to operate on thin (single-architecture) Mach-O files
and fat (universal, multi-architecture) files. When operating on fat files,
it will set or clear the bits for each architecture contained therein.

NON-EXECUTABLE HEAP

Traditionally in Mac OS X, 32-bit processes did not have data pages set to
prohibit execution. Although user programs could call mprotect and
mach_vm_protect to deny execution of code in data pages, the kernel would
silently ignore such requests without updating the page tables, and the
hardware would happily execute code on such pages. 64-bit processes were
always given proper hardware protection of data pages. This behavior was
controllable on a system-wide level via the vm.allow_data_exec sysctl, which
is set by default to 1. The bit with value 1 (set by default) allows code
execution on data pages for 32-bit processes, and the bit with value 2
(clear by default) does the same for 64-bit processes.

In Mac OS X 10.7, executables can "opt in" to having hardware protection
against code execution on data pages applied. This is done by setting a new
bit in the |flags| field of an executable's |mach_header|. When
MH_NO_HEAP_EXECUTION is set, proper protections will be applied, regardless
of the setting of vm.allow_data_exec. See xnu-1699.22.73/osfmk/vm/vm_map.c
override_nx and xnu-1699.22.73/bsd/kern/mach_loader.c load_machfile.

The Apple toolchain has been revised to set the MH_NO_HEAP_EXECUTION when
producing executables, provided that -allow_heap_execute is not specified
at link time. Only linkers shipping with Xcode 4.0 and later (ld64-123.2 and
later) have this ability. See ld64-123.2.1/src/ld/Options.cpp
Options::reconfigureDefaults() and
ld64-123.2.1/src/ld/HeaderAndLoadCommands.hpp
HeaderAndLoadCommandsAtom<A>::flags().

This script sets the MH_NO_HEAP_EXECUTION bit on Mach-O executables. It is
intended for use with executables produced by a linker that predates Apple's
modifications to set this bit itself. It is also useful for setting this bit
for non-i386 executables, including x86_64 executables. Apple's linker only
sets it for 32-bit i386 executables, presumably under the assumption that
the value of vm.allow_data_exec is set in stone. However, if someone were to
change vm.allow_data_exec to 2 or 3, 64-bit x86_64 executables would run
without hardware protection against code execution on data pages. This
script can set the bit for x86_64 executables, guaranteeing that they run
with appropriate protection even when vm.allow_data_exec has been tampered
with.

POSITION-INDEPENDENT EXECUTABLES/ADDRESS SPACE LAYOUT RANDOMIZATION

This script sets or clears the MH_PIE bit in an executable's Mach-O header,
enabling or disabling position independence on Mac OS X 10.5 and later.
Processes running position-independent executables have varying levels of
ASLR protection depending on the OS release. The main executable's load
address, shared library load addresses, and the heap and stack base
addresses may be randomized. Position-independent executables are produced
by supplying the -pie flag to the linker (or defeated by supplying -no_pie).
Executables linked with a deployment target of 10.7 or higher have PIE on
by default.

This script is never strictly needed during the build to enable PIE, as all
linkers used are recent enough to support -pie. However, it's used to
disable the PIE bit as needed on already-linked executables.

Function Documentation

◆ CheckedRead()

change_mach_o_flags.CheckedRead (   file,
  count 
)
Reads |count| bytes from the file-like |file| object, raising a
MachOError if any other number of bytes is read.

Definition at line 111 of file change_mach_o_flags.py.

111def CheckedRead(file, count):
112 """Reads |count| bytes from the file-like |file| object, raising a
113 MachOError if any other number of bytes is read."""
114
115 bytes = file.read(count)
116 if len(bytes) != count:
117 raise MachOError, \
118 'read: expected length %d, observed %d' % (count, len(bytes))
119
120 return bytes
121
122

◆ CheckedSeek()

change_mach_o_flags.CheckedSeek (   file,
  offset 
)
Seeks the file-like object at |file| to offset |offset| and raises a
MachOError if anything funny happens.

Definition at line 100 of file change_mach_o_flags.py.

100def CheckedSeek(file, offset):
101 """Seeks the file-like object at |file| to offset |offset| and raises a
102 MachOError if anything funny happens."""
103
104 file.seek(offset, os.SEEK_SET)
105 new_offset = file.tell()
106 if new_offset != offset:
107 raise MachOError, \
108 'seek: expected offset %d, observed %d' % (offset, new_offset)
109
110

◆ HandleFatFile()

change_mach_o_flags.HandleFatFile (   file,
  options,
  fat_offset = 0 
)
Seeks the file-like |file| object to |offset| and loops over its
|fat_header| entries, calling HandleMachOFile for each.

Definition at line 219 of file change_mach_o_flags.py.

219def HandleFatFile(file, options, fat_offset=0):
220 """Seeks the file-like |file| object to |offset| and loops over its
221 |fat_header| entries, calling HandleMachOFile for each."""
222
223 CheckedSeek(file, fat_offset)
224 magic = ReadUInt32(file, '>')
225 assert magic == FAT_MAGIC
226
227 nfat_arch = ReadUInt32(file, '>')
228
229 for index in xrange(0, nfat_arch):
230 cputype, cpusubtype, offset, size, align = ReadFatArch(file)
231 assert size >= 28
232
233 # HandleMachOFile will seek around. Come back here after calling it, in
234 # case it sought.
235 fat_arch_offset = file.tell()
236 HandleMachOFile(file, options, offset)
237 CheckedSeek(file, fat_arch_offset)
238
239

◆ HandleMachOFile()

change_mach_o_flags.HandleMachOFile (   file,
  options,
  offset = 0 
)
Seeks the file-like |file| object to |offset|, reads its |mach_header|,
and rewrites the header's |flags| field if appropriate. The header's
endianness is detected. Both 32-bit and 64-bit Mach-O headers are supported
(mach_header and mach_header_64). Raises MachOError if used on a header that
does not have a known magic number or is not of type MH_EXECUTE. The
MH_PIE and MH_NO_HEAP_EXECUTION bits are set or cleared in the |flags| field
according to |options| and written to |file| if any changes need to be made.
If already set or clear as specified by |options|, nothing is written.

Definition at line 173 of file change_mach_o_flags.py.

173def HandleMachOFile(file, options, offset=0):
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."""
182
183 CheckedSeek(file, offset)
184 magic = ReadUInt32(file, '<')
185 if magic == MH_MAGIC or magic == MH_MAGIC_64:
186 endian = '<'
187 elif magic == MH_CIGAM or magic == MH_CIGAM_64:
188 endian = '>'
189 else:
190 raise MachOError, \
191 'Mach-O file at offset %d has illusion of magic' % offset
192
193 CheckedSeek(file, offset)
194 magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
195 ReadMachHeader(file, endian)
196 assert magic == MH_MAGIC or magic == MH_MAGIC_64
197 if filetype != MH_EXECUTE:
198 raise MachOError, \
199 'Mach-O file at offset %d is type 0x%x, expected MH_EXECUTE' % \
200 (offset, filetype)
201
202 original_flags = flags
203
204 if options.no_heap_execution:
205 flags |= MH_NO_HEAP_EXECUTION
206 else:
207 flags &= ~MH_NO_HEAP_EXECUTION
208
209 if options.pie:
210 flags |= MH_PIE
211 else:
212 flags &= ~MH_PIE
213
214 if flags != original_flags:
215 CheckedSeek(file, offset + 24)
216 WriteUInt32(file, flags, endian)
217
218

◆ main()

change_mach_o_flags.main (   me,
  args 
)

Definition at line 240 of file change_mach_o_flags.py.

240def main(me, args):
241 parser = optparse.OptionParser('%prog [options] <executable_path>')
242 parser.add_option(
243 '--executable-heap',
244 action='store_false',
245 dest='no_heap_execution',
246 default=True,
247 help='Clear the MH_NO_HEAP_EXECUTION bit')
248 parser.add_option(
249 '--no-pie',
250 action='store_false',
251 dest='pie',
252 default=True,
253 help='Clear the MH_PIE bit')
254 (options, loose_args) = parser.parse_args(args)
255 if len(loose_args) != 1:
256 parser.print_usage()
257 return 1
258
259 executable_path = loose_args[0]
260 executable_file = open(executable_path, 'rb+')
261
262 magic = ReadUInt32(executable_file, '<')
263 if magic == FAT_CIGAM:
264 # Check FAT_CIGAM and not FAT_MAGIC because the read was little-endian.
265 HandleFatFile(executable_file, options)
266 elif magic == MH_MAGIC or magic == MH_CIGAM or \
267 magic == MH_MAGIC_64 or magic == MH_CIGAM_64:
268 HandleMachOFile(executable_file, options)
269 else:
270 raise MachOError, '%s is not a Mach-O or fat file' % executable_file
271
272 executable_file.close()
273 return 0
274
275
Definition main.py:1

◆ ReadFatArch()

change_mach_o_flags.ReadFatArch (   file)
Reads an entire |fat_arch| structure (<mach-o/fat.h>) from the file-like
|file| object, treating it as having endianness specified by |endian|
(per the |struct| module), and returns a 5-tuple of its members as numbers.
Raises a MachOError if the proper length of data can't be read from
|file|.

Definition at line 149 of file change_mach_o_flags.py.

149def ReadFatArch(file):
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
154 |file|."""
155
156 bytes = CheckedRead(file, 20)
157
158 cputype, cpusubtype, offset, size, align = struct.unpack('>5I', bytes)
159 return cputype, cpusubtype, offset, size, align
160
161

◆ ReadMachHeader()

change_mach_o_flags.ReadMachHeader (   file,
  endian 
)
Reads an entire |mach_header| structure (<mach-o/loader.h>) from the
file-like |file| object, treating it as having endianness specified by
|endian| (per the |struct| module), and returns a 7-tuple of its members
as numbers. Raises a MachOError if the proper length of data can't be read
from |file|.

Definition at line 135 of file change_mach_o_flags.py.

135def ReadMachHeader(file, endian):
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
140 from |file|."""
141
142 bytes = CheckedRead(file, 28)
143
144 magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
145 struct.unpack(endian + '7I', bytes)
146 return magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags
147
148

◆ ReadUInt32()

change_mach_o_flags.ReadUInt32 (   file,
  endian 
)
Reads an unsigned 32-bit integer from the file-like |file| object,
treating it as having endianness specified by |endian| (per the |struct|
module), and returns it as a number. Raises a MachOError if the proper
length of data can't be read from |file|.

Definition at line 123 of file change_mach_o_flags.py.

123def ReadUInt32(file, endian):
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|."""
128
129 bytes = CheckedRead(file, 4)
130
131 (uint32,) = struct.unpack(endian + 'I', bytes)
132 return uint32
133
134

◆ WriteUInt32()

change_mach_o_flags.WriteUInt32 (   file,
  uint32,
  endian 
)
Writes |uint32| as an unsigned 32-bit integer to the file-like |file|
object, treating it as having endianness specified by |endian| (per the
|struct| module).

Definition at line 162 of file change_mach_o_flags.py.

162def WriteUInt32(file, uint32, endian):
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
165 |struct| module)."""
166
167 bytes = struct.pack(endian + 'I', uint32)
168 assert len(bytes) == 4
169
170 file.write(bytes)
171
172

Variable Documentation

◆ FAT_CIGAM

int change_mach_o_flags.FAT_CIGAM = 0xbebafeca

Definition at line 82 of file change_mach_o_flags.py.

◆ FAT_MAGIC

int change_mach_o_flags.FAT_MAGIC = 0xcafebabe

Definition at line 81 of file change_mach_o_flags.py.

◆ MH_CIGAM

int change_mach_o_flags.MH_CIGAM = 0xcefaedfe

Definition at line 86 of file change_mach_o_flags.py.

◆ MH_CIGAM_64

int change_mach_o_flags.MH_CIGAM_64 = 0xcffaedfe

Definition at line 88 of file change_mach_o_flags.py.

◆ MH_EXECUTE

int change_mach_o_flags.MH_EXECUTE = 0x2

Definition at line 89 of file change_mach_o_flags.py.

◆ MH_MAGIC

int change_mach_o_flags.MH_MAGIC = 0xfeedface

Definition at line 85 of file change_mach_o_flags.py.

◆ MH_MAGIC_64

int change_mach_o_flags.MH_MAGIC_64 = 0xfeedfacf

Definition at line 87 of file change_mach_o_flags.py.

◆ MH_NO_HEAP_EXECUTION

int change_mach_o_flags.MH_NO_HEAP_EXECUTION = 0x01000000

Definition at line 91 of file change_mach_o_flags.py.

◆ MH_PIE

int change_mach_o_flags.MH_PIE = 0x00200000

Definition at line 90 of file change_mach_o_flags.py.