def _get_rpath(self, header): """Returns a generator of RPATH string values in the header.""" for (idx, (lc, cmd, data)) in enumerate(header.commands): if lc.cmd == mach_o.LC_RPATH: ofs = cmd.path - sizeof(lc.__class__) - sizeof(cmd.__class__) yield data[ofs:data.find(b'\x00', ofs)].decode( sys.getfilesystemencoding())
def _add_rpath_to_header(header, rpath): """Add an LC_RPATH load command to a MachOHeader. Parameters ---------- header: MachOHeader instances A mach-o header to add rpath to rpath: str The rpath to add to the given header """ if header.header.magic in (macholib.mach_o.MH_MAGIC, macholib.mach_o.MH_CIGAM): pad_to = 4 else: pad_to = 8 data = macho_path_as_data(rpath, pad_to=pad_to) header_size = sizeof(macholib.mach_o.load_command) + sizeof( macholib.mach_o.rpath_command) rem = (header_size + len(data)) % pad_to if rem > 0: data += b'\x00' * (pad_to - rem) command_size = header_size + len(data) cmd = macholib.mach_o.rpath_command(header_size, _endian_=header.endian) lc = macholib.mach_o.load_command(macholib.mach_o.LC_RPATH, command_size, _endian_=header.endian) header.commands.append((lc, cmd, data)) header.header.ncmds += 1 header.changedHeaderSizeBy(command_size)
def _add_rpath_to_header(header, rpath): """Add an LC_RPATH load command to a MachOHeader. Parameters ---------- header: MachOHeader instances A mach-o header to add rpath to rpath: str The rpath to add to the given header """ if header.header.magic in (macholib.mach_o.MH_MAGIC, macholib.mach_o.MH_CIGAM): pad_to = 4 else: pad_to = 8 data = macho_path_as_data(rpath, pad_to=pad_to) header_size = sizeof(macholib.mach_o.load_command) + sizeof(macholib.mach_o.rpath_command) rem = (header_size + len(data)) % pad_to if rem > 0: data += b'\x00' * (pad_to - rem) command_size = header_size + len(data) cmd = macholib.mach_o.rpath_command(header_size, _endian_=header.endian) lc = macholib.mach_o.load_command(macholib.mach_o.LC_RPATH, command_size, _endian_=header.endian) header.commands.append((lc, cmd, data)) header.header.ncmds += 1 header.changedHeaderSizeBy(command_size)
def _add_dependency_to_header(header, new_dependency, compatibility_version, current_version): """Add an LC_LOAD_DYLIB load command to a MachOHeader. Parameters ---------- header: MachOHeader instances A mach-o header to add dependency to new_dependency: str Name of the dependency to be added to the header compatibility_version: str Compatibility Version of the new dependency Example: 1.2.3 current_version: str Current Version of the new dependency Example: 1.2.3 """ if header.header.magic in (macholib.mach_o.MH_MAGIC, macholib.mach_o.MH_CIGAM): pad_to = 4 else: pad_to = 8 data = macho_path_as_data(new_dependency, pad_to=pad_to) header_size = sizeof(macholib.mach_o.load_command) + sizeof( macholib.mach_o.dylib_command) rem = (header_size + len(data)) % pad_to if rem > 0: data += b'\x00' * (pad_to - rem) command_size = header_size + len(data) cmd = macholib.mach_o.dylib_command(header_size, _endian_=header.endian) # We assume that the compatibility version data is in the format major.minor.rev version numbers. Example: 1.2.3 version_array = compatibility_version.split('.') compatibility_version_object = macholib.mach_o.mach_version_helper( int(version_array[0]), int(version_array[1]), int(version_array[2])) cmd.compatibility_version = compatibility_version_object # We assume that the current version data is in the format major.minor.rev version numbers. Example: 1.2.3 version_array = current_version.split('.') current_version_object = macholib.mach_o.mach_version_helper( int(version_array[0]), int(version_array[1]), int(version_array[2])) cmd.current_version = current_version_object lc = macholib.mach_o.load_command(macholib.mach_o.LC_LOAD_DYLIB, command_size, _endian_=header.endian) header.commands.append((lc, cmd, data)) header.header.ncmds += 1 header.changedHeaderSizeBy(command_size)
def test_sizeof(self): self.assertEqual(ptypes.sizeof(b"foobar"), 6) self.assertRaises(ValueError, ptypes.sizeof, []) self.assertRaises(ValueError, ptypes.sizeof, {}) self.assertRaises(ValueError, ptypes.sizeof, b"foo".decode('ascii')) class M (object): pass m = M() m._size_ = 42 self.assertEqual(ptypes.sizeof(m), 42)
def test_sizeof(self): self.assertEqual(ptypes.sizeof(b"foobar"), 6) self.assertRaises(ValueError, ptypes.sizeof, []) self.assertRaises(ValueError, ptypes.sizeof, {}) self.assertRaises(ValueError, ptypes.sizeof, b"foo".decode('ascii')) class M(object): pass m = M() m._size_ = 42 self.assertEqual(ptypes.sizeof(m), 42)
def verifyType(self, ptype, size, pytype, values): self.assertEqual(ptypes.sizeof(ptype), size) self.assertIsSubclass(ptype, pytype) for v in values: pv = ptype(v) packed = pv.to_str() self.assertIsInstance(packed, bytes) self.assertEqual(len(packed), size) unp = ptype.from_str(packed) self.assertIsInstance(unp, ptype) self.assertEqual(unp, pv) fp = BytesIO(packed) unp = ptype.from_fileobj(fp) fp.close() self.assertIsInstance(unp, ptype) self.assertEqual(unp, pv) fp = BytesIO() pv.to_fileobj(fp) data = fp.getvalue() fp.close() self.assertEqual(data, packed) mm = mmap.mmap(-1, size+20) mm[:] = b'\x00' * (size+20) pv.to_mmap(mm, 10) self.assertEqual(ptype.from_mmap(mm, 10), pv) self.assertEqual(mm[:], (b'\x00'*10) + packed + (b'\x00'*10)) self.assertEqual(ptype.from_tuple((v,)), pv)
def verifyType(self, ptype, size, pytype, values): self.assertEqual(ptypes.sizeof(ptype), size) self.assertIsSubclass(ptype, pytype) for v in values: pv = ptype(v) packed = pv.to_str() self.assertIsInstance(packed, bytes) self.assertEqual(len(packed), size) unp = ptype.from_str(packed) self.assertIsInstance(unp, ptype) self.assertEqual(unp, pv) fp = BytesIO(packed) unp = ptype.from_fileobj(fp) fp.close() self.assertIsInstance(unp, ptype) self.assertEqual(unp, pv) fp = BytesIO() pv.to_fileobj(fp) data = fp.getvalue() fp.close() self.assertEqual(data, packed) mm = mmap.mmap(-1, size + 20) mm[:] = b'\x00' * (size + 20) pv.to_mmap(mm, 10) self.assertEqual(ptype.from_mmap(mm, 10), pv) self.assertEqual(mm[:], (b'\x00' * 10) + packed + (b'\x00' * 10)) self.assertEqual(ptype.from_tuple((v, )), pv)
def _add_dependency_to_header(header, new_dependency, compatibility_version, current_version): """Add an LC_LOAD_DYLIB load command to a MachOHeader. Parameters ---------- header: MachOHeader instances A mach-o header to add dependency to new_dependency: str Name of the dependency to be added to the header compatibility_version: str Compatibility Version of the new dependency Example: 1.2.3 current_version: str Current Version of the new dependency Example: 1.2.3 """ if header.header.magic in (macholib.mach_o.MH_MAGIC, macholib.mach_o.MH_CIGAM): pad_to = 4 else: pad_to = 8 data = macho_path_as_data(new_dependency, pad_to=pad_to) header_size = sizeof(macholib.mach_o.load_command) + sizeof(macholib.mach_o.dylib_command) rem = (header_size + len(data)) % pad_to if rem > 0: data += b'\x00' * (pad_to - rem) command_size = header_size + len(data) cmd = macholib.mach_o.dylib_command(header_size, _endian_=header.endian) # We assume that the compatibility version data is in the format major.minor.rev version numbers. Example: 1.2.3 version_array = compatibility_version.split('.') compatibility_version_object = macholib.mach_o.mach_version_helper(int(version_array[0]), int(version_array[1]), int(version_array[2])) cmd.compatibility_version = compatibility_version_object # We assume that the current version data is in the format major.minor.rev version numbers. Example: 1.2.3 version_array = current_version.split('.') current_version_object = macholib.mach_o.mach_version_helper(int(version_array[0]), int(version_array[1]), int(version_array[2])) cmd.current_version = current_version_object lc = macholib.mach_o.load_command(macholib.mach_o.LC_LOAD_DYLIB, command_size, _endian_=header.endian) header.commands.append((lc, cmd, data)) header.header.ncmds += 1 header.changedHeaderSizeBy(command_size)
def generate_dylib_load_command(header, libary_install_name): """ Generates a LC_LOAD_DYLIB command for the given header and a library install path. Note: the header must already contain at least one LC_LOAD_DYLIB command (see code comments). Returns a ready-for-use load_command in terms of macholib. """ # One can not simply create instances of `dylib_command` and `load_command` classes, # because that's just not the way macholib works. If we try then all we'll get is a bunch # of endian (big/little) issues when these objects are serialized into a file. # BUT THAT'S PROGRAMMING RIGHT? # So instead I'll iterate *existing* load commands, find a dyld_command, copy it # and modify this copy. This existing command is said to be fully initialized. lc = None cmd = None for (command, internal_cmd, data) in header.commands: if (command.cmd == LC_LOAD_DYLIB) and isinstance( internal_cmd, dylib_command): lc = deepcopy(command) cmd = deepcopy(internal_cmd) break if not lc or not cmd: raise Exception( "Invalid Mach-O file. I mean, there must be at least one LC_LOAD_DYLIB load command." ) return None # Well, now we just replace everything with our own stuff cmd.timestamp = 0 cmd.current_version = cmd.compatibility_version = 0x1000 # Since we store the library's path just after the load command itself, we need to find out it's offset. base = sizeof(load_command) + sizeof(dylib_command) # `name` is rather bad name for this property: actually it means a path string offset cmd.name = base # Also the whole thing must be aligned by 4 bytes on 32-bit arches and by 8 bytes on 64-bit arches align = 4 if header.header.magic == MH_MAGIC else 8 aligned_name = libary_install_name + (b'\x00' * (align - (len(libary_install_name) % align))) # So now we finally can say what size this load_command is lc.cmdsize = base + len(aligned_name) return (lc, cmd, aligned_name)
def testBasic(self): for endian in "><": kw = dict(_endian_=endian) MYSTRUCTURE = b"\x00\x01\x02\x03\xFF" for fn, args in [ ("from_str", (MYSTRUCTURE, )), ("from_mmap", (MYSTRUCTURE, 0)), ("from_fileobj", (BytesIO(MYSTRUCTURE), )), ]: with self.subTest("MYSTRUCTURE", endian=endian, fn=fn): myStructure = getattr(MyStructure, fn)(*args, **kw) if endian == ">": self.assertEqual(myStructure.foo, 0x00010203) else: self.assertEqual(myStructure.foo, 0x03020100) self.assertEqual(myStructure.bar, 0xFF) self.assertEqual(myStructure._endian_, endian) self.assertEqual(myStructure.to_str(), MYSTRUCTURE) MYFUNSTRUCTURE = b"!" + MYSTRUCTURE for fn, args in [ ("from_str", (MYFUNSTRUCTURE, )), ("from_mmap", (MYFUNSTRUCTURE, 0)), ("from_fileobj", (BytesIO(MYFUNSTRUCTURE), )), ]: with self.subTest("MYFUNSTRUCTURE", fn=fn): myFunStructure = getattr(MyFunStructure, fn)(*args, **kw) self.assertEqual(myFunStructure.mystruct, myStructure) self.assertEqual(myFunStructure.fun, b"!", (myFunStructure.fun, b"!")) self.assertEqual(myFunStructure.to_str(), MYFUNSTRUCTURE) sio = BytesIO() myFunStructure.to_fileobj(sio) self.assertEqual(sio.getvalue(), MYFUNSTRUCTURE) mm = mmap.mmap(-1, ptypes.sizeof(MyFunStructure) * 2) mm[:] = b"\x00" * (ptypes.sizeof(MyFunStructure) * 2) myFunStructure.to_mmap(mm, 0) self.assertEqual(MyFunStructure.from_mmap(mm, 0, **kw), myFunStructure) self.assertEqual(mm[:ptypes.sizeof(MyFunStructure)], MYFUNSTRUCTURE) self.assertEqual( mm[ptypes.sizeof(MyFunStructure):], b"\x00" * ptypes.sizeof(MyFunStructure), ) myFunStructure.to_mmap(mm, ptypes.sizeof(MyFunStructure)) self.assertEqual(mm[:], MYFUNSTRUCTURE + MYFUNSTRUCTURE) self.assertEqual( MyFunStructure.from_mmap(mm, ptypes.sizeof(MyFunStructure), **kw), myFunStructure, )
def generate_dylib_load_command(header, libary_install_name): """ Generates a LC_LOAD_DYLIB command for the given header and a library install path. Note: the header must already contain at least one LC_LOAD_DYLIB command (see code comments). Returns a ready-for-use load_command in terms of macholib. """ # One can not simply create instances of `dylib_command` and `load_command` classes, # because that's just not the way macholib works. If we try then all we'll get is a bunch # of endian (big/little) issues when these objects are serialized into a file. # BUT THAT'S PROGRAMMING RIGHT? # So instead I'll iterate *existing* load commands, find a dyld_command, copy it # and modify this copy. This existing command is said to be fully initialized. lc = None cmd = None for (command, internal_cmd, data) in header.commands: if (command.cmd == LC_LOAD_DYLIB) and isinstance(internal_cmd, dylib_command): lc = deepcopy(command) cmd = deepcopy(internal_cmd) break if not lc or not cmd: raise Exception("Invalid Mach-O file. I mean, there must be at least one LC_LOAD_DYLIB load command.") return None # Well, now we just replace everything with our own stuff cmd.timestamp = 0 cmd.current_version = cmd.compatibility_version = 0x1000 # Since we store the library's path just after the load command itself, we need to find out it's offset. base = sizeof(load_command) + sizeof(dylib_command) # `name` is rather bad name for this property: actually it means a path string offset cmd.name = base # Also the whole thing must be aligned by 4 bytes on 32-bit arches and by 8 bytes on 64-bit arches align = 4 if header.header.magic == MH_MAGIC else 8 aligned_name = libary_install_name + (b'\x00' * (align - (len(libary_install_name) % align))) # So now we finally can say what size this load_command is lc.cmdsize = base + len(aligned_name) return (lc, cmd, aligned_name)
def testBasic(self): for endian in '><': kw = dict(_endian_=endian) MYSTRUCTURE = b'\x00\x11\x22\x33\xFF' for fn, args in [ ('from_str', (MYSTRUCTURE, )), ('from_mmap', (MYSTRUCTURE, 0)), ('from_fileobj', (BytesIO(MYSTRUCTURE), )), ]: myStructure = getattr(MyStructure, fn)(*args, **kw) if endian == '>': self.assertEqual(myStructure.foo, 0x00112233) else: self.assertEqual(myStructure.foo, 0x33221100) self.assertEqual(myStructure.bar, 0xFF) self.assertEqual(myStructure.to_str(), MYSTRUCTURE) MYFUNSTRUCTURE = b'!' + MYSTRUCTURE for fn, args in [ ('from_str', (MYFUNSTRUCTURE, )), ('from_mmap', (MYFUNSTRUCTURE, 0)), ('from_fileobj', (BytesIO(MYFUNSTRUCTURE), )), ]: myFunStructure = getattr(MyFunStructure, fn)(*args, **kw) self.assertEqual(myFunStructure.mystruct, myStructure) self.assertEqual(myFunStructure.fun, b'!', (myFunStructure.fun, b'!')) self.assertEqual(myFunStructure.to_str(), MYFUNSTRUCTURE) sio = BytesIO() myFunStructure.to_fileobj(sio) self.assertEqual(sio.getvalue(), MYFUNSTRUCTURE) mm = mmap.mmap(-1, ptypes.sizeof(MyFunStructure) * 2) mm[:] = b'\x00' * (ptypes.sizeof(MyFunStructure) * 2) myFunStructure.to_mmap(mm, 0) self.assertEqual(MyFunStructure.from_mmap(mm, 0, **kw), myFunStructure) self.assertEqual(mm[:ptypes.sizeof(MyFunStructure)], MYFUNSTRUCTURE) self.assertEqual(mm[ptypes.sizeof(MyFunStructure):], b'\x00' * ptypes.sizeof(MyFunStructure)) myFunStructure.to_mmap(mm, ptypes.sizeof(MyFunStructure)) self.assertEqual(mm[:], MYFUNSTRUCTURE + MYFUNSTRUCTURE) self.assertEqual( MyFunStructure.from_mmap(mm, ptypes.sizeof(MyFunStructure), **kw), myFunStructure)
def testBasic(self): for endian in '><': kw = dict(_endian_=endian) MYSTRUCTURE = b'\x00\x11\x22\x33\xFF' for fn, args in [ ('from_str', (MYSTRUCTURE,)), ('from_mmap', (MYSTRUCTURE, 0)), ('from_fileobj', (BytesIO(MYSTRUCTURE),)), ]: myStructure = getattr(MyStructure, fn)(*args, **kw) if endian == '>': self.assertEqual(myStructure.foo, 0x00112233) else: self.assertEqual( myStructure.foo, 0x33221100) self.assertEqual(myStructure.bar, 0xFF) self.assertEqual(myStructure.to_str(), MYSTRUCTURE) MYFUNSTRUCTURE = b'!' + MYSTRUCTURE for fn, args in [ ('from_str', (MYFUNSTRUCTURE,)), ('from_mmap', (MYFUNSTRUCTURE, 0)), ('from_fileobj', (BytesIO(MYFUNSTRUCTURE),)), ]: myFunStructure = getattr(MyFunStructure, fn)(*args, **kw) self.assertEqual(myFunStructure.mystruct, myStructure) self.assertEqual(myFunStructure.fun, b'!', (myFunStructure.fun, b'!')) self.assertEqual(myFunStructure.to_str(), MYFUNSTRUCTURE) sio = BytesIO() myFunStructure.to_fileobj(sio) self.assertEqual(sio.getvalue(), MYFUNSTRUCTURE) mm = mmap.mmap(-1, ptypes.sizeof(MyFunStructure) * 2) mm[:] = b'\x00' * (ptypes.sizeof(MyFunStructure) * 2) myFunStructure.to_mmap(mm, 0) self.assertEqual(MyFunStructure.from_mmap(mm, 0, **kw), myFunStructure) self.assertEqual(mm[:ptypes.sizeof(MyFunStructure)], MYFUNSTRUCTURE) self.assertEqual(mm[ptypes.sizeof(MyFunStructure):], b'\x00' * ptypes.sizeof(MyFunStructure)) myFunStructure.to_mmap(mm, ptypes.sizeof(MyFunStructure)) self.assertEqual(mm[:], MYFUNSTRUCTURE + MYFUNSTRUCTURE) self.assertEqual(MyFunStructure.from_mmap(mm, ptypes.sizeof(MyFunStructure), **kw), myFunStructure)