Ejemplo n.º 1
0
def main(exePath):
	print 'dyld_shared_cache decache Fix'
	print
	print 'During the production of this script,no Mach-O was killed'
	print
	print 'Designed by Naville Zhang'
	rawFile = open(exePath, 'r')
	rawFile.seek(-4, 2)
	#if cmp(rawFile.read(),'BOOM') == 0:
	#	print '#Error: Executable has been patched'
	#	exit()
	macho=MachO.MachO(exePath)
	for header in macho.headers:
		IterateLCInHeader(header)
		FixMachO(header)
		replaceDATA(header)
	print '[+]Generating new executable'
	spliceHeadersAndRawStuff(header,exePath)
	print '[+]New executable generated'

	print '[+]Overwriting raw executable'
	os.system('mv %s_tmp %s' % (exePath, exePath))

	print '[+]Giving execute permission to new executable'
	givex(exePath)
	print
	print '[+]All done.'
	return
Ejemplo n.º 2
0
def replace_methname(macho_file, methname_json, output_dir):
    """
    Map method names in Mach-O file with the JSON file
    """

    if not os.path.isfile(macho_file):
        raise("passing not exist file " + macho_file)
    if not os.path.isfile(methname_json):
        raise("passing not exist file " + methname_json)
    if output_dir is not None and not os.path.isdir(output_dir):
        raise("passing not exist dir " + output_dir)

    macho = MachO.MachO(macho_file)

    name_dict = None
    with open(methname_json) as json_file:
        name_dict = json.load(json_file)

    for header in macho.headers:
        ch_methname_sect(header, name_dict)
        ch_symtab(header, name_dict)

    ori_dir, filename = os.path.split(macho_file)
    if output_dir is None:
        output_dir = ori_dir
    output = os.path.join(output_dir, filename)
    
    try:
        copy2(macho_file, output_dir)
    except SameFileError:
        pass

    with open(output, 'r+b') as fp:
        macho.write(fp)
    os.chmod(output, 0o755)
Ejemplo n.º 3
0
 def __call__(self, path):
     package_file = self.package_file
     path_by_file = self.path_by_file
     libs_folder = self.libs_folder
     if path.startswith("@@HOMEBREW_CELLAR@@"):
         new_path = libs_folder.joinpath("/".join(path.split("/")[3:]))
         new_path = "@loader_path/" + relative_to(new_path, package_file.parent)
         logging.debug(f"{package_file}: {path} -> {new_path}")
         return new_path
     else:
         for new_path in path_by_file.get(path.split("/")[-1], [path]):
             if path == new_path:
                 logging.debug(f"{package_file}: {path} -> {new_path}")
                 return path
             elif new_path == str(package_file):
                 continue
             elif not str(new_path).endswith(".dylib") or not str(new_path).endswith(
                 ".so"
             ):
                 try:
                     m = MachO.MachO(str(new_path))
                 except Exception:
                     continue
                 if m.headers[0].filetype == "execute":
                     continue
             new_path = "@loader_path/" + relative_to(new_path, package_file.parent)
             logging.debug(f"{package_file}: {path} -> {new_path}")
             return new_path
         logging.debug(f"{package_file}: {path} -> {path}")
         return path
def hash_macho0(exe):
    headers = MachO.MachO(exe).headers
    if len(headers) > 1:
        logging.debug('Mach-O binary is FAT')
    with open(exe, 'rb') as f:
        data = bytes()
        for header in headers:
            f.seek(header.offset, 0)
            start, end = sys.maxsize, 0
            for (lc, segment, sections) in header.commands:
                if (mach_o.LC_CODE_SIGNATURE == lc.cmd):
                    logging.warning('Mach-O binary has a signature section')
                # The minimum section offset of all load commands is the start of VMP signing part
                if (lc.cmd in (mach_o.LC_SEGMENT_64, mach_o.LC_SEGMENT)
                        and segment.segname.startswith(
                            mach_o.SEG_TEXT.encode('utf-8'))):
                    for section in sections:
                        start = min(start, section.offset)
                # Expect the String Table is at the end of unsigned binary followed by the code
                # signature, so the end of String Table is the end of VMP signing part
                if (mach_o.LC_SYMTAB == lc.cmd):
                    end = segment.stroff + segment.strsize
            if (start >= end):
                logging.error(
                    'Failed to assemble VMP/Mach-O signing body: %d-%d', start,
                    end)
                raise ValueError(
                    'Failed to assemble VMP/Mach-O signing body: %d-%d' %
                    (start, end))
            f.seek(start, 1)
            data += f.read(end - start)
        return compute_sha512(data)
Ejemplo n.º 5
0
def shared_libraries(binary_file):
    """
    Return a list of the shared libraries referenced by a Mach-O binary
    """
    result = list()

    try:
        headers = MachO.MachO(binary_file).headers
    except ValueError:
        warn("skipping {}, it is probably not a Mach-O file".format(
            binary_file))
        return []
    except IOError:
        warn("skipping {}, it is probably not a regular file".format(
            binary_file))
        return []

    for architecture_header in headers:
        if is_current_architecture(architecture_header.header):
            result.extend([
                ShlibRef(load_path, 'weak' in load_type) for _, load_type,
                load_path in architecture_header.walkRelocatables()
            ])
        else:
            warn("skipping header in {} with arch {}".format(
                binary_file,
                MachO.CPU_TYPE_NAMES[architecture_header.header.cputype]))

    return result
Ejemplo n.º 6
0
def rpath_entries(binary_file):
    """
    Return a list of rpath entries in the specified binary file

    The mach-O header should list contain a list of rpaths to be tried when
    attempting to process the actual load commands in the rest of the header

    This could easily be a list comprehension, but this is a bit more readable

    The original version was inspired by rpath.list_rpaths @:
    https://github.com/enthought/machotools/tree/master/machotools
    """
    result = list()

    for architecture_header in MachO.MachO(binary_file).headers:
        for _, command, data in architecture_header.commands:
            if type(command) is not mach_o.rpath_command:
                continue
            # the entry is null terminated, so we take that out.
            entry = data.rstrip(b'\x00')
            if entry.startswith('@loader_path'):
                # it is important to expand loader_path now because it refers
                # to the path of the file that contains the @loader_path entry
                entry = expand_load_variables(entry, binary_file)
            result.append(entry)

    return result
Ejemplo n.º 7
0
def load_macho(filename):
    file_data = open(filename).read()

    macho = MachO.MachO(filename)
    header = macho.headers[0]
    cputype = CPU_TYPE_NAMES[header.header.cputype]

    assert cputype == "ARM"
    assert header.header.filetype == MH_EXECUTE

    regions = []

    text_base = None
    entry_point = None

    segments = [
        cmd for _, cmd, _ in header.commands if type(cmd) == segment_command
    ]
    for cmd in segments:
        name = cmd.segname.replace("\x00", "")
        if name == SEG_PAGEZERO: continue

        if name == SEG_TEXT:
            assert cmd.fileoff == 0
            text_base = cmd.vmaddr

        filesize = align(cmd.filesize, 4096)
        vmsize = align(cmd.vmsize, 4096)

        regions.append((cmd.vmaddr, filesize,
                        file_data[cmd.fileoff:cmd.fileoff + filesize]))

        if vmsize != filesize:
            regions.append((cmd.vmaddr + filesize, vmsize - filesize, None))

    assert text_base is not None

    dyld_info = None
    symbols = {}

    for lc, cmd, data in header.commands:
        if type(cmd) == entry_point_command:
            # the entry point is given as a file offset.
            # assume it's in the text segment...
            entry_point = cmd.entryoff + text_base
        elif type(cmd) == dyld_info_command:
            dyld_info = DyldInfo(filename, cmd, segments)
        elif type(cmd) == symtab_command:
            #TODO: populate symbols
            pass

    #assert entry_point is not None

    stack_bottom = 0x70000000  # ?
    stack_size = 0x20000  # 128k ?
    regions.append((stack_bottom - stack_size, stack_size, None))

    # print [(hex(a), hex(b), c[:16] if c else c) for a, b, c in regions]

    return (regions, entry_point, stack_bottom, symbols, dyld_info)
Ejemplo n.º 8
0
 def test_known_load_command_should_succeed(self):
     macho_uuid = uuid.UUID("6894C0AE-C8B7-4E0B-A529-30BBEBA3703B")
     with temporary_macho_file([lc_uuid(macho_uuid)]) as macho_filename:
         macho = MachO.MachO(macho_filename,
                             allow_unknown_load_commands=True)
         self.assertEqual(len(macho.headers), 1)
         self.assertEqual(len(macho.headers[0].commands), 1)
         load_command, command, _ = macho.headers[0].commands[0]
         self.assertEqual(load_command.cmd, mach_o.LC_UUID)
         self.assertEqual(uuid.UUID(bytes=command.uuid), macho_uuid)
Ejemplo n.º 9
0
def get_macho(fn):
  # mod to make the header okay
  # MH_CIGAM_64 is good
  dat = open(fn, "rb").read()
  dat = b"\xcf\xfa\xed\xfe"+dat[4:]
  from tempfile import NamedTemporaryFile
  with NamedTemporaryFile(delete=False) as f:
    f.write(dat)
    f.close()
  return MachO.MachO(f.name)
Ejemplo n.º 10
0
 def test_unknown_load_command_should_succeed_with_flag(self):
     with temporary_macho_file([lc_unknown()]) as macho_filename:
         macho = MachO.MachO(macho_filename,
                             allow_unknown_load_commands=True)
         self.assertEqual(len(macho.headers), 1)
         self.assertEqual(len(macho.headers[0].commands), 1)
         load_command, command, data = macho.headers[0].commands[0]
         self.assertEqual(load_command.cmd, 0x707A11ED)
         self.assertIsInstance(command, mach_o.load_command)
         self.assertEqual(struct.unpack(">I", data), (42, ))
Ejemplo n.º 11
0
    def check_compatibility(cls, spec, main_obj):
        with stream_or_path(spec) as stream:
            if not cls.is_compatible(stream):
                return False

        # It's definitely a MachO
        parsed_macho = MachOLoader.MachO(spec)
        archs = set(MachO.get_arch_from_header(mach_header.header) for mach_header in parsed_macho.headers)
        if main_obj.arch.name.lower() in archs:
            return True

        return False
Ejemplo n.º 12
0
 def analyze_macho(self, data):
     '''
     start analyzing macho logic, add descriptions and get words and wordsstripped from the file
     '''
     macho = MachO.MachO(data["Location"]["File"])
     data["MACHO"] = deepcopy(self.datastruct)
     fbuffer = data["FilesDumps"][data["Location"]["File"]]
     data["MACHO"]["General"]: {}
     data["MACHO"]["Sections"] = self.get_sections(macho, fbuffer)
     data["MACHO"]["Libraries"] = self.get_libs(macho)
     data["MACHO"]["Symbols"] = self.get_symbols(macho)
     data["MACHO"]["Undefined Symbols"] = self.get_undef_symbols(macho)
     data["MACHO"]["External Symbols"] = self.get_extdef_symbols(macho)
     data["MACHO"]["Local Symbols"] = self.get_local_symbols(macho)
     add_description("ManHelp", data["MACHO"]["Symbols"], "Symbol")
     get_words(data, data["Location"]["File"])
Ejemplo n.º 13
0
 def test_mix_of_known_and_unknown_load_commands_should_allow_unknown_with_flag(
     self, ):
     macho_uuid = uuid.UUID("6894C0AE-C8B7-4E0B-A529-30BBEBA3703B")
     with temporary_macho_file([lc_unknown(),
                                lc_uuid(macho_uuid)]) as macho_filename:
         macho = MachO.MachO(macho_filename,
                             allow_unknown_load_commands=True)
         self.assertEqual(len(macho.headers), 1)
         self.assertEqual(len(macho.headers[0].commands), 2)
         load_command, command, data = macho.headers[0].commands[0]
         self.assertEqual(load_command.cmd, 0x707A11ED)
         self.assertIsInstance(command, mach_o.load_command)
         self.assertEqual(struct.unpack(">I", data), (42, ))
         load_command, command, _ = macho.headers[0].commands[1]
         self.assertEqual(load_command.cmd, mach_o.LC_UUID)
         self.assertEqual(uuid.UUID(bytes=command.uuid), macho_uuid)
Ejemplo n.º 14
0
def check_architectures(app):
    '''
    info检查是否支持64位
    demo:armv7, arm64, armv7s
    '''
    from macholib import MachO, mach_o

    m = MachO.MachO(app)
    arcs = []
    for header in m.headers:
        cpu_type = header.header.cputype
        cpu_subtype = header.header.cpusubtype
        arch = str(mach_o.CPU_TYPE_NAMES.get(cpu_type, cpu_type)).lower()
        if cpu_type == 12:
            if cpu_subtype == 0:
                arch = 'armall'
            elif cpu_subtype == 5:
                arch = 'armv4t'
            elif cpu_subtype == 6:
                arch = 'armv6'
            elif cpu_subtype == 7:
                arch = 'armv5tej'
            elif cpu_subtype == 8:
                arch = 'arm_xscale'
            elif cpu_subtype == 9:
                arch = 'armv7'
            elif cpu_subtype == 10:
                arch = 'armv7f'
            elif cpu_subtype == 11:
                arch = 'armv7s'
            elif cpu_subtype == 12:
                arch = 'armv7k'
            elif cpu_subtype == 13:
                arch = 'armv8'
            elif cpu_subtype == 14:
                arch = 'armv6m'
            elif cpu_subtype == 15:
                arch = 'armv7m'
            elif cpu_subtype == 16:
                arch = 'armv7em'

        elif cpu_type == 16777228:
            arch = 'arm64'

        arcs.append(arch)
    return arcs
Ejemplo n.º 15
0
def main(executableName):
    print
    print 'MachO Headers Eraser'
    print
    print 'This script may cause some unpredictable issues, try delete things in the safe list if your program doesn\'t work after patched.'
    print
    print '[+]Checking if patched'
    rawFile = open(executableName, 'r')
    rawFile.seek(-4, 2)
    if cmp(rawFile.read(), 'BOOM') == 0:
        print '#Error: Executable has been patched'
        exit()

    print '[+]Making backup'
    os.system('cp %s %s_bak' % (executableName, executableName))
    print '[+]Reading raw executable'
    machoHeader = MachO.MachO(executableName)
    print '[+]%s readed' % machoHeader.filename
    for header in machoHeader.headers:
        eraseLoadCommandInHeader(header)

    print '[+]Generating new executable'
    spliceHeadersAndRawStuff(machoHeader, executableName)
    print '[+]New executable generated'

    print '[+]Overwriting raw executable'
    os.system('mv %s_tmp %s' % (executableName, executableName))

    #Insert SYMTAB Operations Here
    pathname = os.path.dirname(sys.argv[0])
    Command = pathname + '/SymbolCleaner ' + executableName + " " + str(
        SYMTAB["symoff"]) + " " + str(
            SYMTAB["nsyms"]) + " 0 0 " + executableName + "NoSymbol" + " 32"

    system(str(Command))

    print '[+]Giving execute permission to new executable'
    givex(executableName)
    givex(executableName + "NoSymbol")

    print
    print '[+]All done.'

    return
Ejemplo n.º 16
0
def get_archs(path: Path) -> List[str]:
    """Get architecture(s) of `path`"""
    m = None
    archs: List[str] = []
    try:
        m = MachO.MachO(path)
    except:
        return []
    for header in m.headers:
        cpu_type = header.header.cputype
        cpu_subtype = header.header.cpusubtype
        arch = str(mach_o.CPU_TYPE_NAMES.get(cpu_type, cpu_type)).lower()
        if cpu_type == 12:
            if cpu_subtype == 0:
                arch = 'armall'
            elif cpu_subtype == 5:
                arch = 'armv4t'
            elif cpu_subtype == 6:
                arch = 'armv6'
            elif cpu_subtype == 7:
                arch = 'armv5tej'
            elif cpu_subtype == 8:
                arch = 'arm_xscale'
            elif cpu_subtype == 9:
                arch = 'armv7'
            elif cpu_subtype == 10:
                arch = 'armv7f'
            elif cpu_subtype == 11:
                arch = 'armv7s'
            elif cpu_subtype == 12:
                arch = 'armv7k'
            elif cpu_subtype == 13:
                arch = 'armv8'
            elif cpu_subtype == 14:
                arch = 'armv6m'
            elif cpu_subtype == 15:
                arch = 'armv7m'
            elif cpu_subtype == 16:
                arch = 'armv7em'
        elif cpu_type == 16777228:
            arch = 'arm64'
        archs.append(arch)
    return archs
def MacCheckBinary(z, binary):
    with TempUnzip(z, 'Unvanquished.app/Contents/MacOS/' + binary) as path:
        macho = MachO.MachO(path)
        header, = macho.headers

        # Check PIE
        MH_PIE = 0x200000
        if not header.header.flags & MH_PIE:
            yield f"Mac binary '{binary}' is not PIE"

        # Check rpath
        rpaths = {
            command[2].rstrip(b'\0').rstrip(b'/')
            for command in header.commands
            if isinstance(command[1], mach_o.rpath_command)
        }
        rpaths.discard(b'@executable_path')
        if rpaths:
            yield f"Mac binary '{binary}' has unwanted rpaths {rpaths}"
Ejemplo n.º 18
0
def get_version_from_mach_binary(filename):
    from macholib import MachO  # pylint: disable=import-outside-toplevel

    machofile = MachO.MachO(filename)
    fpc_offset, fpc_size = 0, 0
    for (_load_cmd, _cmd, _data) in machofile.headers[0].commands:
        for data in _data:
            if data and hasattr(data, "sectname") and data.sectname:
                sectname = data.sectname.rstrip(b'\0')
                if sectname == b"fpc.resources":
                    fpc_offset = data.offset
                    fpc_size = data.size

    if fpc_offset > 0:
        with open(filename, "rb") as file:
            file.seek(fpc_offset)
            return read_fixed_file_info(file.read(fpc_size))

    raise ValueError(f"No version information embedded in '{filename}'")
Ejemplo n.º 19
0
 def __init__(self, arch, path, baseAddr):
     self.arch = arch
     self.path = path
     self.baseAddr = baseAddr
     self.defaultBaseAddr = 0
     self.macho = None
     self.sections = {}
     self.symbols = {}
     fullPath = os.path.join(self.arch.root, path)
     macho = MachO.MachO(fullPath)
     for h in macho.headers:
         if h.header.cputype == self.arch.cpuType:
             self.macho = h
             # find the endAddr
             endAddr = 0
             for c in self.macho.commands:
                 if c[0].cmd == MachO.LC_SEGMENT or c[
                         0].cmd == MachO.LC_SEGMENT_64:
                     if c[1].segname.strip("\0") == "__TEXT":
                         self.defaultBaseAddr = c[1].vmaddr
                     if (c[1].vmaddr + c[1].vmsize) > endAddr:
                         endAddr = c[1].vmaddr + c[1].vmsize
                     if c[1].maxprot & 0x4:  # VM_PROT_EXECUTE
                         # add all the sections
                         for section in c[2]:
                             self.sections[section.addr] = section
             self.endAddr = self.baseAddr + endAddr
             #print "%s: %x - %x" % (self.path, self.baseAddr, self.endAddr)
     # get the symbols (not the best way)
     if self.arch.cpuType & 0x01000000:
         arch = "x86_64"  # out version doesn't have the 64bit support
     else:
         arch = MachO.CPU_TYPE_NAMES[self.arch.cpuType]
     symbolText = subprocess.check_output(
         ['/usr/bin/otool', '-Iv', '-arch', arch, fullPath])
     symbolLines = symbolText.split("\n")
     for l in symbolLines:
         parts = l.split()
         if len(parts) == 3 and len(parts[0]) > 2:
             if parts[0][:2] == "0x":
                 offset = int(parts[0][2:], 16) - self.defaultBaseAddr
                 self.symbols[offset] = parts[2].strip()
Ejemplo n.º 20
0
def check_architetures(app):
    """
    架构检查  arm64  arm7  arm7s  i386 x864

    以下是支付SDK的
    ['ARM_CPU_SUBTYPE_ARM_V7_32-bit', 'ARM64_CPU_SUBTYPE_ARM64_ALL_64-bit']
    """
    m = MachO.MachO(app)
    archs = []
    for header in m.headers:
        if header.MH_MAGIC == mach_o.MH_MAGIC_64 or header.MH_MAGIC == mach_o.MH_CIGAM_64:
            sz = '64-bit'
        else:
            sz = '32-bit'

        arch = mach_o.CPU_TYPE_NAMES.get(header.header.cputype,
                                         header.header.cputype)
        subarch = mach_o.get_cpu_subtype(header.header.cputype,
                                         header.header.cpusubtype)

        archs.append('_'.join((arch, sz)))

    return archs
Ejemplo n.º 21
0
def main(executableName):
    print
    print 'MachO Headers Eraser'
    print
    print 'This script may cause some unpredictable issues, try delete things in the safe list if your program doesn\'t work after patched.'
    print
    print '[+]Checking if patched'
    rawFile = open(executableName, 'r')
    rawFile.seek(-4, 2)
    if cmp(rawFile.read(), 'BOOM') == 0:
        print '#Error: Executable has been patched'
        exit()

    print '[+]Making backup'
    os.system('cp %s %s_bak' % (executableName, executableName))
    print '[+]Reading raw executable'
    machoHeader = MachO.MachO(executableName)
    print '[+]%s readed' % machoHeader.filename

    for header in machoHeader.headers:
        eraseLoadCommandInHeader(header)

    print '[+]Generating new executable'
    spliceHeadersAndRawStuff(machoHeader, executableName)
    print '[+]New executable generated'

    print '[+]Overwriting raw executable'
    os.system('mv %s_tmp %s' % (executableName, executableName))

    print '[+]Giving execute permission to new executable'
    givex(executableName)

    print
    print '[+]All done.'

    return
Ejemplo n.º 22
0
 def __init__(self, path):
     self.path = path
     self.macho = MachO.MachO(self.path)
Ejemplo n.º 23
0
 def test_unknown_load_command_should_fail(self):
     with temporary_macho_file([lc_unknown()]) as macho_filename:
         with self.assertRaises(ValueError) as assert_context:
             MachO.MachO(macho_filename)
def _processNode(nodes: queue.Queue, node: dict, ignoreSystem: bool) -> list:
    paragraph = []
    path = node["path"]

    node["exists"] = os.path.exists(path)
    if node["exists"]:
        try:
            macho = MachO.MachO(path)
        except:
            macho = None

    node["parsed"] = macho is not None

    if node["parsed"]:
        node["@loader_path"] = os.path.dirname(path)
        node["arch"] = {}

        for header in macho.headers:
            archName = mach_o.CPU_TYPE_NAMES.get(header.header.cputype,
                                                 header.header.cputype)

            if "restrictarch" in node and archName != node["restrictarch"]:
                continue

            node["arch"][archName] = {}
            node["arch"][archName]["name"] = archName
            node["arch"][archName]["rpaths"] = []
            node["arch"][archName]["dependencies"] = []

            if header.header.filetype == mach_o.MH_EXECUTE:
                node["@executable_path"] = node["@loader_path"]

            for command in header.commands:
                if command[0].cmd == mach_o.LC_RPATH:
                    node["arch"][archName]["rpaths"].append(
                        command[2][0:command[2].find(b'\x00', 0)].decode(
                            sys.getfilesystemencoding()))

            # @rpath/
            # Dyld  maintains  a current stack of paths called the run path list.  When @rpath is encountered it is substituted with each path in the run path list until a loadable dylib if found.  The run path stack is built from the LC_RPATH
            # load commands in the dependency chain that lead to the current dylib load.  You can add an LC_RPATH load command to an image with the -rpath option to ld(1).  You can even add  a  LC_RPATH  load  command  path  that  starts  with
            # @loader_path/,  and  it  will push a path on the run path stack that relative to the image containing the LC_RPATH.  The use of @rpath is most useful when you have a complex directory structure of programs and dylibs which can be
            # installed anywhere, but keep their relative positions.  This scenario could be implemented using @loader_path, but every client of a dylib could need a different load path because its relative position in the file system is  dif-
            # ferent.  The use of @rpath introduces a level of indirection that simplfies things.  You pick a location in your directory structure as an anchor point.  Each dylib then gets an install path that starts with @rpath and is the path
            # to the dylib relative to the anchor point. Each main executable is linked with -rpath @loader_path/zzz, where zzz is the path from the executable to the anchor point.  At runtime dyld sets it run path to be the anchor point, then
            # each dylib is found relative to the anchor point.
            # TL;DR -- expanded rpaths are cascading down dependency chain
            rpathStack = []
            if "parentRpathStack" in node:
                rpathStack = list(node["parentRpathStack"])
            for rpath in node["arch"][archName]["rpaths"]:
                exists, resolvedPath = _resolvePath(node, rpath)
                if resolvedPath not in rpathStack:
                    rpathStack.append(resolvedPath)

            for index, name, fileName in header.walkRelocatables():
                node["arch"][archName]["dependencies"].append(
                    {"name": fileName})

                exists = False

                newNodePath = None
                if "@rpath" in fileName:
                    for rpath in rpathStack:
                        exists, fullPath = _resolvePath(
                            node, fileName.replace("@rpath", rpath))
                        if exists:
                            newNodePath = fullPath
                            break
                else:
                    exists, newNodePath = _resolvePath(node, fileName)

                if exists:
                    newNode = {
                        "path": newNodePath,
                        "parentRpathStack": rpathStack,
                        "restrictarch": archName,
                        "system": _isSystemLib(newNodePath)
                    }

                    if "@executable_path" in node:
                        newNode["@executable_path"] = node["@executable_path"]

                    node["arch"][archName]["dependencies"][-1].update(newNode)

                    if not ignoreSystem or not newNode["system"]:
                        nodes.put(newNode)
    else:
        paragraph.append(Line(LogLevel.WARNING, 4, 1, "not Mach-O"))

    return paragraph
Ejemplo n.º 25
0
#!/usr/bin/env python3

from hexdump import hexdump

# mod to make the header okay
# MH_CIGAM_64 is good
from macholib import MachO
a = MachO.MachO("model.hwx")

# load commands
for c in a.headers[0].commands:
    print(c[0])
    if c[0].cmd == 25:
        print(c[1])
        for section in c[2]:
            print(section.segname.strip(b'\0'), section.sectname.strip(b'\0'),
                  hex(section.addr), hex(section.size), "@", hex(c[1].fileoff))
            #print(dir(section))
            if c[1].filesize > 0:
                hexdump(section.section_data)

# this parser is wrong (fixed with 64-bit one)
from macholib import SymbolTable
sym = SymbolTable.SymbolTable(a)

syms = {}
for l in sym.nlists:
    print(l)
    if l[0].n_value != 0:
        syms[l[1]] = l[0].n_value
Ejemplo n.º 26
0
def main():
    json_fname = pathlib.Path(sys.argv[1])
    base_path = json_fname.parent
    with open(json_fname) as json_input:
        dict_json = json.load(json_input)

    app_name = dict_json["app_name"]
    version = dict_json["version"]
    identifier = dict_json["identifier"]
    icon = dict_json["icon"]
    packages = dict_json["packages"]
    ignore_packages = dict_json.get("ignore_packages", [])
    pip_packages = dict_json.get("pip_packages", [])
    commands = dict_json.get("commands", [])
    mac_version = dict_json["mac_version"]
    package_type = dict_json["app_package"]["source"]["type"]
    if package_type == "file":
        package_path = base_path.joinpath(dict_json["app_package"]["source"]["path"])
    else:
        package_url = dict_json["app_package"]["source"]["path"]
        sha = dict_json["app_package"]["source"].get("sha", None)
        package_path = download_and_check(package_url, sha)
    patches = dict_json["app_package"]["source"].get("patches", [])
    start_script = dict_json["app_package"]["start_script"]
    build_commands = dict_json["app_package"].get("build_commands", [])
    cleanup_patterns = dict_json.get("cleanup", [])

    # Creating folders
    app_folder = base_path.joinpath(app_name + ".app")
    app_folder.mkdir(parents=True, exist_ok=True)

    resources_folder = app_folder.joinpath("Contents/Resources")
    resources_folder.mkdir(parents=True, exist_ok=True)

    binary_folder = app_folder.joinpath("Contents/MacOS")
    binary_folder.mkdir(parents=True, exist_ok=True)

    libs_folder = resources_folder.joinpath("libs")
    libs_folder.mkdir(parents=True, exist_ok=True)

    application_folder = resources_folder.joinpath("app")
    application_folder.mkdir(parents=True, exist_ok=True)

    start_script_path = application_folder.joinpath(start_script)
    start_script_folder = start_script_path.parent

    package_files = download_packages(packages, mac_version, ignore_packages)
    extracted_files = []
    for package in package_files:
        extracted_files.extend(
            extract_files(package_files[package]["file"], libs_folder)
        )

    if "python" in package_files:
        logging.info("Creating symlink")
        major, minor, _ = package_files["python"]["version"].split(".")
        site_packages = libs_folder.joinpath(f"lib/python{major}.{minor}/site-packages")
        link_site_packages = libs_folder.joinpath(
            f"Frameworks/Python.framework/Versions/{major}.{minor}/lib/python{major}.{minor}/site-packages"
        )
        os.symlink(
            relative_to(site_packages, link_site_packages.parent), link_site_packages
        )

    path_by_file = {}
    for extracted_file in extracted_files:
        #  path_by_file["/".join(extracted_file.parts[-2:])] = extracted_file
        try:
            path_by_file[str(extracted_file.parts[-1])].append(extracted_file)
        except:
            path_by_file[str(extracted_file.parts[-1])] = [extracted_file]

    #  print("writing json file")
    #  with open("/tmp/files.json", "w") as f:
    #  #  f.write(JSONEncoder().encode(path_by_file))
    #  json.dump(path_by_file, f)

    for package_file in extracted_files:
        extension = package_file.suffixes
        if package_file.is_file() and not package_file.is_symlink():
            try:
                # print(package_file)
                macho = MachO.MachO(str(package_file))
            except Exception:
                # Not lib or executable
                continue
            rewrote = False
            changer = SharedLibraryChanger(package_file, path_by_file, libs_folder)
            for header in macho.headers:
                if macho.rewriteLoadCommands(changer):
                    rewrote = True

            if rewrote:
                with package_file.open("rb+") as f:
                    f.seek(0)
                    macho.write(f)

    # Installing python packages using pip
    PYTHON_EXEC = libs_folder.joinpath("bin/python3")
    download_and_install_pip(PYTHON_EXEC)
    for pip_package in pip_packages:
        pip_install(PYTHON_EXEC, pip_package)

    logging.info("Running commands")
    for command in commands:
        logging.info(f"\t{command}")
        command = shlex.split(command)
        run_cmd(command, cwd=str(resources_folder))

    # Copying icon in the package
    shutil.copy2(base_path.joinpath(icon), resources_folder)

    # Creating Info.plist file
    create_app_info(app_folder, identifier, app_name, version, icon)

    # Copying or extracting app files inside the package
    if package_type == "file":
        shutil.copy2(package_path, str(application_folder))
    else:
        extract_files(package_path, start_script_folder, 1)

    # applying patch
    logging.info("Applying patches")
    for patch in patches:
        patch = base_path.joinpath(patch).resolve()
        logging.info(patch)
        run_cmd(["patch", "-p", "1", "-i", str(patch)], cwd=str(start_script_folder))

    c_libs_folders = [libs_folder.joinpath("lib").resolve()]
    c_include_folders = [libs_folder.joinpath("include").resolve()]
    my_env = os.environ.copy()
    my_env["PATH"] = str(PYTHON_EXEC.parent.resolve()) + ":" + my_env["PATH"]
    my_env["CFLAGS"] = (
        " ".join("-I{}".format(i) for i in c_include_folders)
        + " "
        + " ".join("-L{}".format(i) for i in c_libs_folders)
    )
    my_env["CXXFLAGS"] = (
        " ".join("-I{}".format(i) for i in c_include_folders)
        + " "
        + " ".join("-L{}".format(i) for i in c_libs_folders)
    )
    logging.info("Running scripts")
    logging.debug("PATH=" + my_env["PATH"])
    logging.debug("CFLAGS=" + my_env["CFLAGS"])
    logging.debug("CXXFLAGS=" + my_env["CXXFLAGS"])
    for build_command in build_commands:
        logging.info(build_command)
        build_command = shlex.split(build_command)
        run_cmd(build_command, env=my_env, cwd=str(start_script_folder))

    # Rewriting any shared lib in app folder
    for package_file in start_script_folder.glob("**/*"):
        extension = package_file.suffixes
        if package_file.is_file() and not package_file.is_symlink():
            try:
                # print(package_file)
                macho = MachO.MachO(str(package_file))
            except Exception:
                # Not lib or executable
                continue
            rewrote = False
            changer = SharedLibraryChanger(package_file, path_by_file, libs_folder)
            for header in macho.headers:
                if macho.rewriteLoadCommands(changer):
                    rewrote = True

            if rewrote:
                with package_file.open("rb+") as f:
                    f.seek(0)
                    macho.write(f)

    create_launcher(
        app_folder,
        app_name,
        start_script_path,
        relative_to(PYTHON_EXEC, start_script_folder),
    )

    if cleanup_patterns:
        exclude_files(resources_folder, cleanup_patterns)
Ejemplo n.º 27
0
    def __init__(self, binary, target_arch=None, **kwargs):
        l.warning('The Mach-O backend is not well-supported. Good luck!')

        super(MachO, self).__init__(binary, **kwargs)

        parsed_macho = MachOLoader.MachO(self.binary)

        # First try to see which arch the main binary has
        # Then try to match it to its dependencies
        if not self.is_main_bin:
            # target_arch prefrence goes to main bin...
            target_arch = self.loader.main_object.arch.name.lower()

        # If we have a target_arch, try to match it up with one of the FAT slices
        if target_arch:
            self._header = self.match_target_arch_to_header(target_arch, parsed_macho.headers)
            if not self._header:
                print(self.binary)
                # Print out all architectures found?
                raise CLEError("Couldn't find architecture %s" % target_arch)

        # Otherwise, we'll just pick one..
        else:
            if len(parsed_macho.headers) > 1:
                l.warning('No target slice specified. Picking one at random.. Good luck!')
            self._header = parsed_macho.headers[0]

        arch_ident = self.get_arch_from_header(self._header.header)

        # TODO: add lsb/msb support here properly. self._header.endian is exactly it
        self.set_arch(archinfo.arch_from_id(arch_ident, endness="lsb"))
        self.struct_byteorder = self._header.endian
                
        # self._header.filetype == 6 indicates its a dylib too
        # https://llvm.org/doxygen/Support_2MachO_8h_source.html#36
        if self._header.filetype == 'dylib':
            self.pic = True
        else:
            # XXX: Handle other filetypes. i.e. self._header.filetype
            # This means we can't load multiple `executables` into the same process
            # .. Seems fine
            self.pic = bool(self._header.header.flags & 0x200000) if self.is_main_bin else True 

        self.flags = None  # binary flags
        self.imported_libraries = ["Self"]  # ordinal 0 = SELF_LIBRARY_ORDINAL

        # This was what was historically done: self.sections_by_ordinal.extend(seg.sections)
        self.sections_by_ordinal = [None] # ordinal 0 = None == Self

        self.exports_by_name = {}  # note exports is currently a raw and unprocessed datastructure.
        # If we intend to use it we must first upgrade it to a class or somesuch
        self.entryoff = None
        self.unixthread_pc = None
        self.os = "Darwin"
        
        self.export_blob = None  # exports trie
        self.binding_blob = None  # binding information
        self.lazy_binding_blob = None  # lazy binding information
        self.weak_binding_blob = None  # weak binidng information
        self.binding_done = False # if true binding was already done and do_bind will be a no-op

        # Module level constructors / destructors
        self.mod_init_func_pointers = []
        self.mod_term_func_ponters = []

        # Library dependencies.
        self.linking = 'dynamic' # static is impossible in macos... kinda 

        # For some analysis the insertion order of the symbols is relevant and needs to be kept.
        # This is has to be separate from self.symbols because the latter is sorted by address
        self._ordered_symbols = []

        self.segments = []

        if self.is_main_bin:
            self.linked_base = 0x0100000000 

        # File is read, begin populating internal fields
        self._parse_load_cmds()
        #self._parse_symbols(binary_file)
        self._parse_mod_funcs()
        self._handle_rebase_info()

        if self.is_main_bin:
            # XXX: remove this after binding_helper is fixed.
            # This is only for helping debug it
            self._handle_bindings()