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