Beispiel #1
0
    def test_parse_ivars(self) -> None:
        parser = MachoParser(TestObjcRuntimeDataParser.CATEGORY_PATH)
        binary = parser.get_arm64_slice()
        assert binary
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)

        # Given I read a class with a known ivar layout
        cls = [x for x in objc_parser.classes if x.name == "AamvaPDF417"][0]
        # If I read its parsed ivar layout
        parsed_ivars = [(ivar.name, ivar.class_name, ivar.field_offset)
                        for ivar in cls.ivars]
        correct_ivar_layout = [
            ("_fields", '@"NSMutableDictionary"', 8),
            ("fields_desc", '@"NSDictionary"', 16),
            ("found_bar_codes", '@"NSDictionary"', 24),
            ("source", '@"NSString"', 32),
            ("data_element_separator", "S", 40),
            ("record_separator", "S", 42),
            ("segment_terminator", "S", 44),
            ("file_type", '@"NSString"', 48),
            ("number_of_entries", "i", 56),
            ("header_length", "i", 60),
            ("aamva_version_number", "i", 64),
            ("jurisdiction_version_number", "i", 68),
            ("mandatory", '@"NSDictionary"', 72),
            ("optional", '@"NSDictionary"', 80),
            ("_failed", '@"NSMutableArray"', 88),
        ]
        # Then the correct data is provided
        assert sorted(parsed_ivars) == sorted(correct_ivar_layout)
Beispiel #2
0
    def test_add_load_command(self) -> None:
        # Given a binary with some known load-commands
        binary = MachoParser(self.CLASSLIST_DATA_CONST).get_arm64_slice()
        assert binary
        original_dylibs = [
            "/System/Library/Frameworks/Foundation.framework/Foundation",
            "/usr/lib/libobjc.A.dylib",
            "/usr/lib/libSystem.B.dylib",
            "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
            "/System/Library/Frameworks/Security.framework/Security",
            "/System/Library/Frameworks/UIKit.framework/UIKit",
        ]
        found_dylibs = [binary.dylib_name_for_library_ordinal(i + 1) for i in range(len(binary.load_dylib_commands))]
        assert found_dylibs == original_dylibs

        # If I create a new binary with an inserted load command
        modified_binary = binary.insert_load_dylib_cmd("@rpath/Frameworks/Interject.framework/Interject")
        modified_dylibs = [
            "/System/Library/Frameworks/Foundation.framework/Foundation",
            "/usr/lib/libobjc.A.dylib",
            "/usr/lib/libSystem.B.dylib",
            "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
            "/System/Library/Frameworks/Security.framework/Security",
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "@rpath/Frameworks/Interject.framework/Interject",
        ]
        found_dylibs = [
            modified_binary.dylib_name_for_library_ordinal(i + 1)
            for i in range(len(modified_binary.load_dylib_commands))
        ]
        assert found_dylibs == modified_dylibs
Beispiel #3
0
    def test_class_conforming_protocols(self) -> None:
        def check_class_conformed_protocols(
                class_name: str, correct_protocols: List[str]) -> None:
            cls = [x for x in objc_parser.classes if x.name == class_name][0]
            assert cls is not None
            conformed_protocol_names = [x.name for x in cls.protocols]
            assert conformed_protocol_names == correct_protocols

        parser = MachoParser(TestObjcRuntimeDataParser.CATEGORY_PATH)
        binary = parser.get_arm64_slice()
        assert binary
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)

        check_class_conformed_protocols(
            "CDVInAppBrowserViewController",
            [
                "CDVScreenOrientationDelegate", "WKNavigationDelegate",
                "WKUIDelegate", "WKScriptMessageHandler"
            ],
        )
        check_class_conformed_protocols(
            "_TtC26Digital_Advisory_Solutions21LicenceViewController",
            ["UITableViewDataSource"])
        # this class doesn't conform to any protocols
        check_class_conformed_protocols("BadFilesDetector", [])
Beispiel #4
0
    def test_ios13_absolute_method_lists(self):
        # Given a binary compiled with a minimum deployment target of iOS 13
        parser = MachoParser(
            TestObjcRuntimeDataParser.IOS13_ABSOLUTE_METHOD_LIST_BIN_PATH)
        binary = parser.get_arm64_slice()
        binary.get_minimum_deployment_target()

        # When the Objective C methods within the binary are parsed
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)
        selref_selector_map = objc_parser.selrefs_to_selectors()

        # Then the method structures are correctly parsed
        assert len(selref_selector_map) == 3

        s1 = selref_selector_map[VirtualMemoryPointer(0x10000D380)]
        assert s1.implementation is None
        assert s1.is_external_definition is True
        assert s1.name == "role"

        s2 = selref_selector_map[VirtualMemoryPointer(0x10000D388)]
        assert s2.implementation is None
        assert s2.is_external_definition is True
        assert s2.name == "initWithName:sessionRole:"

        s3 = selref_selector_map[VirtualMemoryPointer(0x10000D378)]
        assert s3.implementation == VirtualMemoryPointer(0x100006354)
        assert s3.is_external_definition is False
        assert s3.name == "viewDidLoad"
Beispiel #5
0
    def test_no_space_for_new_load_command(self) -> None:
        # Given a binary with 0x5630 bytes of free space at the end of the Mach-O header
        binary = MachoParser(self.THIN_PATH).get_arm64_slice()
        assert binary

        dylib_path = "@rpath/load_cmd_with_32_chrcters"
        # If I have a dylib load command which will take up `0x20 + len(dylib_path) = 0x38` bytes
        # Then I should be able to add this load command exactly 344 times before the binary runs out of space
        for _ in range(344):
            binary = binary.insert_load_dylib_cmd(dylib_path)
        with pytest.raises(NoEmptySpaceForLoadCommandError):
            binary.insert_load_dylib_cmd(dylib_path)
Beispiel #6
0
 def test_section_name_collision(self) -> None:
     # Given I provide a binary which has two sections with the same name
     binary = MachoParser(self.MULTIPLE_CONST_SECTIONS).get_arm64_slice()
     assert binary
     # If I read the two sections
     text_const = binary.section_with_name("__const", "__TEXT")
     data_const = binary.section_with_name("__const", "__DATA")
     # Then I get two sections
     assert text_const is not None
     assert data_const is not None
     # And each section contains the correct information
     assert text_const.address == 0x1A0D0
     assert data_const.address == 0x1C458
Beispiel #7
0
    def test_read_classlist_data_const_segment(self) -> None:
        # Given a binary which stores the __objc_classlist section in the __DATA_CONST segment
        binary_with_data_classlist = MachoParser(TestMachoBinary.CLASSLIST_DATA_CONST).get_arm64_slice()
        assert binary_with_data_classlist

        # If I read the __objc_classlist pointer section
        locations, entries = binary_with_data_classlist.read_pointer_section("__objc_classlist")
        correct_locations = [0x100008098, 0x1000080A0, 0x1000080A8]
        correct_entries = [0x10000D3C0, 0x10000D438, 0x10000D488]

        # Then I get the correct data
        assert sorted(locations) == sorted(correct_locations)
        assert sorted(entries) == sorted(correct_entries)
Beispiel #8
0
    def test_read_classlist_data_segment(self) -> None:
        # Given a binary which stores the __objc_classlist section in the __DATA segment
        binary_with_data_classlist = MachoParser(TestMachoBinary.THIN_PATH).get_arm64_slice()
        assert binary_with_data_classlist

        # If I read the __objc_classlist pointer section
        locations, entries = binary_with_data_classlist.read_pointer_section("__objc_classlist")
        correct_locations = [0x100008178, 0x100008180, 0x100008188, 0x100008190]
        correct_entries = [0x100009120, 0x100009170, 0x1000091E8, 0x100009238]

        # Then I get the correct data
        assert sorted(locations) == sorted(correct_locations)
        assert sorted(entries) == sorted(correct_entries)
Beispiel #9
0
    def test_write_fat_binary(self) -> None:
        # Given I add a load command to the arm64 slice of an armv7/arm64 FAT file
        parser = MachoParser(self.FAT_PATH)
        binary = parser.get_arm64_slice()
        assert binary
        original_dylibs = [binary.dylib_name_for_library_ordinal(i + 1) for i in range(len(binary.load_dylib_commands))]
        new_dylib_name = "@rpath/Frameworks/Interject.framework/Interject"
        modified_binary = binary.insert_load_dylib_cmd(new_dylib_name)

        with TemporaryDirectory() as tempdir:
            output_binary_path = pathlib.Path(tempdir) / "modified_fat"
            armv7_binary = parser.get_armv7_slice()
            assert armv7_binary
            # If I write the FAT to disk with both slices, then parse the on-disk version
            MachoBinary.write_fat([armv7_binary, modified_binary], output_binary_path)
            on_disk_fat_parser = MachoParser(output_binary_path)

            assert len(on_disk_fat_parser.slices) == 2

            # Then I get a FAT with valid slices
            armv7 = on_disk_fat_parser.get_armv7_slice()
            assert armv7 is not None
            assert len(armv7.segments) == 4

            arm64 = on_disk_fat_parser.get_arm64_slice()
            assert arm64 is not None
            assert len(arm64.segments) == 4
            # And the arm64 segment contains the new load command
            new_dylibs = [arm64.dylib_name_for_library_ordinal(i + 1) for i in range(len(arm64.load_dylib_commands))]
            assert new_dylibs == original_dylibs + [new_dylib_name]
Beispiel #10
0
    def test_find_protocols(self) -> None:
        parser = MachoParser(TestObjcRuntimeDataParser.FAT_PATH)
        binary = parser.get_arm64_slice()
        assert binary
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)

        protocols = objc_parser.protocols
        assert len(protocols) == 3

        correct_protocols = [
            "NSObject", "NSURLSessionDelegate", "UIApplicationDelegate"
        ]
        for p in correct_protocols:
            assert p in [a.name for a in protocols]
Beispiel #11
0
 def setup_method(self) -> None:
     self.parser = MachoParser(pathlib.Path(TestMachoBinary.THIN_PATH))
     # ensure only one slice is returned with a thin Mach-O
     slices = self.parser.slices
     assert len(slices) == 1
     self.binary = self.parser.slices[0]
     assert self.binary is not None
Beispiel #12
0
def main():
    arg_parser = argparse.ArgumentParser(description="bitcode_retriever clone")
    arg_parser.add_argument("binary_path",
                            metavar="binary_path",
                            type=str,
                            help="Path to Bitcode binary")
    args = arg_parser.parse_args()

    parser = MachoParser(pathlib.Path(args.binary_path))

    print(f"Reading {len(parser.slices)} Mach-O slices...")
    for binary in parser.slices:
        # Does the slice contain a Bitcode archive?
        bitcode_segment = binary.segment_with_name("__LLVM")
        if not bitcode_segment:
            continue

        # Dump the Bitcode  adjacent to this file, named <arch>.xar
        bitcode_segment = binary.segment_with_name("__LLVM")
        if not bitcode_segment:
            raise ValueError(f"The provided Mach-O does not contain Bitcode.")
        xar_data = binary.get_bytes(bitcode_segment.fileoff,
                                    bitcode_segment.filesize)

        # Place the bitcode adjacent to this file, named <arch>.xar
        output_path = pathlib.Path(
            __file__).parent / f"{binary.cpu_type.name.lower()}.xar"
        with open(output_path, "xb") as f:
            f.write(xar_data)

        print(
            f"Dumped {binary.cpu_type.name.lower()} Bitcode XAR to {output_path}"
        )
Beispiel #13
0
def main():
    arg_parser = argparse.ArgumentParser(
        description="Add a load command to a binary")
    arg_parser.add_argument("binary_path", type=str, help="Path to binary")
    arg_parser.add_argument(
        "output_path",
        type=str,
        help="Path to write the modified binary (must not already exist)")
    arg_parser.add_argument(
        "load_path",
        type=str,
        help="The dylib load path to be added to the binary")
    args = arg_parser.parse_args()

    parser = MachoParser(pathlib.Path(args.binary_path))
    # Add the load command to each slice of the Mach-O
    modified_binaries = []
    for binary in parser.slices:
        # Inserting load commands is currently supported on 64-bit binaries only
        if not binary.is_64bit:
            continue
        modified_binary = binary.insert_load_dylib_cmd(args.load_path)
        modified_binaries.append(modified_binary)

    # Create an output FAT with each of the modified binaries
    MachoBinary.write_fat(modified_binaries, pathlib.Path(args.output_path))
Beispiel #14
0
 def test_protocol_32bit(self) -> None:
     parser = MachoParser(TestObjcRuntimeDataParser.PROTOCOL_32BIT_PATH)
     binary = parser.get_armv7_slice()
     assert binary
     dyld_info_parser = DyldInfoParser(binary)
     objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)
     assert len(objc_parser.classes) == 66
     test_cls = [
         x for x in objc_parser.classes
         if x.name == "Pepsico_iPhoneAppDelegate"
     ][0]
     assert len(test_cls.protocols) == 2
     proto_names = [x.name for x in test_cls.protocols]
     assert proto_names == [
         "UIApplicationDelegate", "UITabBarControllerDelegate"
     ]
Beispiel #15
0
def dump_memory(parser: MachoParser, start_address: int, size: int) -> None:
    # XXX(PT): Modified from strongarm-cli
    data = parser.get_bytes(StaticFilePointer(start_address), size)

    # split to 16 byte regions
    region_size = 16
    current_index = 0
    while True:
        if current_index >= size or current_index >= len(data):
            break
        # grab the next grouping of bytes
        byte_region = data[current_index : current_index + region_size]

        region_start = start_address + current_index
        print(f"{region_start:#011x}", end="\t\t")

        ascii_rep = "|"
        for idx, byte in enumerate(byte_region):
            print("{:02x}".format(byte), end=" ")
            # indent every 8 bytes
            if idx > 0 and (idx + 1) % 8 == 0:
                print("\t", end="")

            ascii_byte = chr(byte) if 32 <= byte < 127 else "."
            ascii_rep += ascii_byte
        ascii_rep += "|"
        print(ascii_rep)

        current_index += region_size
Beispiel #16
0
def main() -> None:
    # XXX(PT): Change this if you want to run a quick script! Write it in strongarm_script()
    script = False
    # end of config

    arg_parser = argparse.ArgumentParser(description="Mach-O Analyzer")
    arg_parser.add_argument("--verbose",
                            action="store_true",
                            help="Output extra info while analyzing")
    arg_parser.add_argument("binary_path",
                            metavar="binary_path",
                            type=str,
                            help="Path to binary to analyze")
    args = arg_parser.parse_args()

    def configure_logger() -> None:
        root = logging.getLogger()
        root.setLevel(logging.DEBUG)

        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO)
        formatter = logging.Formatter(
            "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        ch.setFormatter(formatter)
        root.addHandler(ch)

    configure_logger()

    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)

    print_header(args)

    parser = MachoParser(pathlib.Path(args.binary_path))

    # print slice info
    print("Slices:")
    for macho_slice in parser.slices:
        print(f"\t{macho_slice.cpu_type.name} Mach-O slice")

    binary = pick_macho_slice(parser)
    print(f"Reading {binary.cpu_type.name} slice\n\n")

    analyzer = MachoAnalyzer.get_analyzer(binary)
    shell = StrongarmShell(binary, analyzer)

    if script:
        print("Running provided script...\n\n")
        strongarm_script(binary, analyzer)
    else:
        autorun_cmd = "info metadata segments sections loads"
        print(f"Auto-running '{autorun_cmd}'\n\n")
        shell.run_command(autorun_cmd)

        # this will return False once the shell exists
        while shell.process_command():
            pass
    print("May your arms be beefy and your binaries unencrypted")
Beispiel #17
0
    def test_ios14_build_version_cmd(self):
        # Given a binary compiled with a minimum deployment target of iOS 14
        parser = MachoParser(
            TestObjcRuntimeDataParser.IOS14_RELATIVE_METHOD_LIST_BIN_PATH)
        binary = parser.get_arm64_slice()

        # When I query properties such as the minimum deployment target, deployment platform,
        # and build tool versions
        # Then the correct data is parsed and returned
        assert binary.get_minimum_deployment_target() == LooseVersion("14.0.0")
        assert binary.get_build_version_platform(
        ) == MachoBuildVersionPlatform.IOS

        build_tool_versions = binary.get_build_tool_versions()
        assert len(build_tool_versions) == 1
        ld_version = build_tool_versions[0]
        assert ld_version.tool == MachoBuildTool.LD
        assert ld_version.version == 0x2610000
Beispiel #18
0
    def test_write_bytes_thin_virtual(self) -> None:
        # Given a thin binary with file_type == 0x2
        binary = MachoParser(pathlib.Path(TestMachoBinary.CLASSLIST_DATA_CONST)).get_arm64_slice()
        assert binary
        assert binary.file_type == 0x2

        # If I patch the virtual bytes of the file_type field to hold a different value
        new_file_type_val = 10
        # This field is a 32-bit little-endian encoded int
        new_file_type_bytes = new_file_type_val.to_bytes(4, "little")
        modified_binary = binary.write_bytes(new_file_type_bytes, 0x10000000C, virtual=True)

        # Then the modified binary's raw bytes contain the correct data
        modified_header = modified_binary.get_contents_from_address(0x100000000, 32, True)
        assert modified_header == bytearray(
            b"\xcf\xfa\xed\xfe\x0c\x00\x00\x01\x00\x00\x00\x00\x0a\x00\x00\x00\x18\x00\x00\x00H\x0b\x00\x00\x85\x00 \x00\x00\x00\x00\x00"  # noqa: E501
        )
        # And the MachoBinary attribute contains the correct value
        assert modified_binary.file_type == 10
Beispiel #19
0
    def test_parse_protocol(self) -> None:
        parser = MachoParser(TestObjcRuntimeDataParser.FAT_PATH)
        binary = parser.get_arm64_slice()
        assert binary
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)

        protocols = objc_parser.protocols
        # look at one protocol
        session_protocol = [
            p for p in protocols if p.name == "NSURLSessionDelegate"
        ][0]
        assert len(session_protocol.selectors) == 3
        correct_selectors = [
            "URLSession:didBecomeInvalidWithError:",
            "URLSession:didReceiveChallenge:completionHandler:",
            "URLSessionDidFinishEventsForBackgroundURLSession:",
        ]
        for s in correct_selectors:
            assert s in [sel.name for sel in session_protocol.selectors]
Beispiel #20
0
    def test_write_struct(self) -> None:
        # Given a thin binary with certain values for its first segment
        binary = MachoParser(pathlib.Path(TestMachoBinary.THIN_PATH)).get_arm64_slice()
        assert binary
        segment = binary.segments[0]
        assert segment.name == "__PAGEZERO"
        assert segment.vmaddr == 0x0
        assert segment.vmsize == 0x100000000
        assert segment.offset == 0x0
        assert segment.size == 0x0
        assert segment.maxprot == 0
        assert segment.initprot == 0
        assert segment.section_count == 0
        assert segment.flags == 0

        # If I create a new structure and overwrite the original structure with it
        new_segment = MachoSegmentCommand64()
        new_segment.cmd = 0x19
        new_segment.cmdsize = 0x48
        new_segment.segname = b"__FAKESEG"
        new_segment.vmaddr = 0x111
        new_segment.vmsize = 0x222
        new_segment.fileoff = 0x333
        new_segment.filesize = 0x444
        new_segment.maxprot = 5
        new_segment.initprot = 6
        new_segment.nsects = 7
        new_segment.flags = 8

        # Then we get a MachoBinary which can be successfully parsed, and contains the modified structure
        modified_binary = binary.write_struct(new_segment, segment.cmd.binary_offset)
        segment = modified_binary.segments[0]
        assert segment.name == "__FAKESEG"
        assert segment.vmaddr == 0x111
        assert segment.vmsize == 0x222
        assert segment.offset == 0x333
        assert segment.size == 0x444
        assert segment.maxprot == 5
        assert segment.initprot == 6
        assert segment.section_count == 7
        assert segment.flags == 8
Beispiel #21
0
    def test_path_for_external_symbol(self) -> None:
        parser = MachoParser(TestObjcRuntimeDataParser.FAT_PATH)
        binary = parser.slices[0]
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)

        correct_map = {
            "_NSLog":
            "/System/Library/Frameworks/Foundation.framework/Foundation",
            "_NSStringFromCGRect":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_NSStringFromClass":
            "/System/Library/Frameworks/Foundation.framework/Foundation",
            "_OBJC_CLASS_$_NSURLCredential":
            "/System/Library/Frameworks/Foundation.framework/Foundation",
            "_OBJC_CLASS_$_UIFont":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_OBJC_CLASS_$_UILabel":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_OBJC_CLASS_$_UIResponder":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_OBJC_CLASS_$_UIViewController":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_OBJC_METACLASS_$_NSObject": "/usr/lib/libobjc.A.dylib",
            "_OBJC_METACLASS_$_UILabel":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_OBJC_METACLASS_$_UIResponder":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_OBJC_METACLASS_$_UIViewController":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "_SecTrustEvaluate":
            "/System/Library/Frameworks/Security.framework/Security",
            "_UIApplicationMain":
            "/System/Library/Frameworks/UIKit.framework/UIKit",
            "___CFConstantStringClassReference":
            "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
            "__objc_empty_cache": "/usr/lib/libobjc.A.dylib",
            "_objc_autoreleasePoolPop": "/usr/lib/libobjc.A.dylib",
            "_objc_autoreleasePoolPush": "/usr/lib/libobjc.A.dylib",
            "_objc_msgSend": "/usr/lib/libobjc.A.dylib",
            "_objc_msgSendSuper2": "/usr/lib/libobjc.A.dylib",
            "_objc_release": "/usr/lib/libobjc.A.dylib",
            "_objc_retain": "/usr/lib/libobjc.A.dylib",
            "_objc_retainAutoreleasedReturnValue": "/usr/lib/libobjc.A.dylib",
            "_objc_storeStrong": "/usr/lib/libobjc.A.dylib",
            "_rand": "/usr/lib/libSystem.B.dylib",
            "dyld_stub_binder": "/usr/lib/libSystem.B.dylib",
        }
        for symbol in correct_map:
            assert objc_parser.path_for_external_symbol(
                symbol) == correct_map[symbol]
        assert objc_parser.path_for_external_symbol(
            "XXX_fake_symbol_XXX") is None
Beispiel #22
0
 def test_read_encrypted_info(self) -> None:
     encrypted_binary = MachoParser(TestMachoBinary.ENCRYPTED_PATH).get_armv7_slice()
     assert encrypted_binary
     with pytest.raises(BinaryEncryptedError):
         # encrypted region is 0x4000 to 0x18000
         encrypted_binary.get_bytes(StaticFilePointer(0x5000), 0x1000)
     # read from unencrypted section should not raise
     encrypted_binary.get_bytes(StaticFilePointer(0x3000), 0x500)
    def setup_method(self) -> None:
        parser = MachoParser(TestFunctionAnalyzer.FAT_PATH)
        self.binary = parser.slices[0]
        self.analyzer = MachoAnalyzer.get_analyzer(self.binary)

        self.implementations = self.analyzer.get_imps_for_sel(
            "URLSession:didReceiveChallenge:completionHandler:")
        self.instructions = self.implementations[0].instructions

        self.imp_addr = self.instructions[0].address
        assert self.imp_addr == TestFunctionAnalyzer.URL_SESSION_DELEGATE_IMP_ADDR

        self.function_analyzer = ObjcFunctionAnalyzer(self.binary,
                                                      self.instructions)
Beispiel #24
0
 def test_function_starts_command(self) -> None:
     # Given a binary that contains functions
     binary_with_functions = MachoParser(TestMachoBinary.CLASSLIST_DATA_CONST).get_arm64_slice()
     assert binary_with_functions
     # And I get the function starts command
     function_starts = binary_with_functions._function_starts_cmd
     assert function_starts
     # The command has the expected attributes
     assert function_starts.cmd == 0x26
     assert function_starts.cmdsize == 0x10
     assert function_starts.dataoff == 0x10680
     assert function_starts.datasize == 0x18
     assert function_starts.sizeof == 0x10
     assert function_starts.binary_offset == 0xB38
Beispiel #25
0
def main():
    arg_parser = argparse.ArgumentParser(description="hexdump clone")
    arg_parser.add_argument("binary_path", type=str, help="Path to binary")
    arg_parser.add_argument(
        "-s", dest="start_address_str", type=str, help="Byte-count to skip before beginning hexdump (base16)"
    )
    arg_parser.add_argument("-n", dest="count", type=int, help="Number of bytes to hex-dump")
    arg_parser.set_defaults(start_address_str="0x0", count=0x100000000)
    args = arg_parser.parse_args()

    parser = MachoParser(pathlib.Path(args.binary_path))
    # Convert hex string to int
    start_address = int(args.start_address_str, 16)
    dump_memory(parser, start_address, args.count)
Beispiel #26
0
    def test_ios14_relative_method_lists(self):
        # Given a binary compiled with a minimum deployment target of iOS 14
        parser = MachoParser(
            TestObjcRuntimeDataParser.IOS14_RELATIVE_METHOD_LIST_BIN_PATH)
        binary = parser.get_arm64_slice()
        binary.get_minimum_deployment_target()

        # When the Objective C methods within the binary are parsed
        dyld_info_parser = DyldInfoParser(binary)
        objc_parser = ObjcRuntimeDataParser(binary, dyld_info_parser)
        selref_selector_map = objc_parser.selrefs_to_selectors()

        # Then the method structures are correctly parsed
        assert len(selref_selector_map) == 7

        external_sel = selref_selector_map[VirtualMemoryPointer(0x10000C0E0)]
        assert external_sel.implementation is None
        assert external_sel.is_external_definition is True
        assert external_sel.name == "evaluateJavaScript:inFrame:inContentWorld:completionHandler:"

        internal_sel = selref_selector_map[VirtualMemoryPointer(0x10000C0B0)]
        assert internal_sel.implementation == VirtualMemoryPointer(0x100007BFC)
        assert internal_sel.is_external_definition is False
        assert internal_sel.name == "usesWebView"
Beispiel #27
0
def main():
    arg_parser = argparse.ArgumentParser(
        description="dump a binary's entitlements")
    arg_parser.add_argument("binary_path",
                            metavar="binary_path",
                            type=str,
                            help="Path to binary")
    args = arg_parser.parse_args()

    parser = MachoParser(pathlib.Path(args.binary_path))

    for binary in parser.slices:
        print(f"Entitlements of {binary.cpu_type.name.lower()} slice:")
        print(binary.get_entitlements().decode())
        print()
Beispiel #28
0
 def test_get_dylib_id(self) -> None:
     # Given an executable binary, it has no dylib ID
     binary = MachoParser(self.THIN_PATH).get_arm64_slice()
     assert binary
     assert not binary.dylib_id()
     # Given a dylib, it has a dylib ID which is parsed correctly
     expected_dylib_id = "@rpath/BroadSoftDialpadFramework.framework/BroadSoftDialpadFramework"
     binary = MachoParser(self.MULTIPLE_CONST_SECTIONS).get_arm64_slice()
     assert binary
     assert binary.dylib_id() == expected_dylib_id
Beispiel #29
0
def main():
    arg_parser = argparse.ArgumentParser(description="nm clone")
    arg_parser.add_argument(
        "binary_path",
        metavar="binary_path",
        type=str,
        help="Path to binary whose symbol table should be output")
    arg_parser.add_argument(
        "-m",
        action="store_true",
        help=
        "Increase verbosity. Display the source library of imported symbols & list sections of exported symbols.",
    )
    args = arg_parser.parse_args()

    parser = MachoParser(pathlib.Path(args.binary_path))
    for fat_slice in parser.slices:
        print_binary_symbols(fat_slice, args.verbose)
Beispiel #30
0
def main():
    arg_parser = argparse.ArgumentParser(description="strings clone")
    arg_parser.add_argument(
        "binary_path",
        metavar="binary_path",
        type=str,
        help="Path to binary whose strings should be printed")
    args = arg_parser.parse_args()

    parser = MachoParser(pathlib.Path(args.binary_path))

    # Get the unique strings from all slices
    all_strings = set()
    for fat_slice in parser.slices:
        # Parsing the string table requires a MachoAnalyzer
        analyzer = MachoAnalyzer.get_analyzer(fat_slice)
        all_strings.update(analyzer.strings())

    for string in all_strings:
        print(string)