Exemplo n.º 1
0
def test_write(tmp_path):
    binary_name = "crypt_and_hash"
    fat = lief.MachO.parse(
        get_sample(
            'MachO/9edfb04c55289c6c682a25211a4b30b927a86fe50b014610d04d6055bd4ac23d_crypt_and_hash.macho'
        ))
    target = fat.take(lief.MachO.CPU_TYPES.ARM64)

    output = f"{tmp_path}/{binary_name}.built"

    target.write(output)
    target = lief.parse(output)

    process(target)

    valid, err = lief.MachO.check_layout(target)
    assert valid, err

    if is_apple_m1():
        chmod_exe(output)
        sign(output)
        with subprocess.Popen([output],
                              universal_newlines=True,
                              stdout=subprocess.PIPE,
                              stderr=subprocess.STDOUT) as proc:
            stdout = proc.stdout.read()
            assert "CAMELLIA-256-CCM*-NO-TAG" in stdout
            assert "AES-128-CCM*-NO-TAG" in stdout
Exemplo n.º 2
0
def test_objc_arm64(tmp_path):
    bin_path = pathlib.Path(get_sample("MachO/test_objc_arm64.macho"))
    original = lief.parse(bin_path.as_posix())
    output = f"{tmp_path}/{bin_path.name}"

    for i in range(50):
        segment = lief.MachO.SegmentCommand(f"__LIEF_{i}", [i] * (0x457 + i))
        segment = original.add(segment)

    # Extend the symbols table
    for i in range(10):
        sym = f"_foooo_{i}"
        original.add_exported_function(original.imagebase + i * 8, sym)

        sym = f"_foooo2_{i}"
        original.add_local_symbol(original.entrypoint + i * 8, sym)

    functions = original.function_starts.functions
    functions *= 2
    sorted(functions)
    original.function_starts.functions = functions

    original.write(output)
    new = lief.parse(output)

    checked, err = lief.MachO.check_layout(new)
    assert checked, err

    if is_apple_m1():
        assert run_program(bin_path.as_posix())
        stdout = run_program(output)

        print(stdout)
        assert re.search(r'Printing Process Completed', stdout) is not None
Exemplo n.º 3
0
class TestExtendCommand(TestCase):
    def setUp(self):
        self.logger = logging.getLogger(__name__)

    @unittest.skipUnless(is_apple_m1(), "requires Apple Silicon (M1)")
    def test_id_m1(self):
        original = lief.MachO.parse(get_sample("/usr/bin/id"))
        original = original.take(lief.MachO.CPU_TYPES.ARM64)
        _, output = tempfile.mkstemp(prefix="lief_id_remove_cmd")

        # Extend UUID
        uuid_cmd = original[lief.MachO.LOAD_COMMAND_TYPES.UUID]
        original_size = uuid_cmd.size
        original.extend(uuid_cmd, 0x100)
        uuid_cmd = original[lief.MachO.LOAD_COMMAND_TYPES.UUID]

        # Extend __LINKEDIT (last one)
        original_linkedit_size = original.get_segment("__LINKEDIT").file_size
        original.extend_segment(original.get_segment("__LINKEDIT"), 0x30000)

        original.write(output)

        new = lief.parse(output)

        self.assertEqual(new.get_segment("__LINKEDIT").file_size, original_linkedit_size + 0x30000)
        self.assertEqual(new[lief.MachO.LOAD_COMMAND_TYPES.UUID].size, original_size + 0x100)

        # Run the modified binary
        stdout = run_program(output)
        self.logger.debug(stdout)
        self.assertIsNotNone(re.search(r'uid=', stdout))


    def test_id(self):
        original = lief.parse(get_sample("MachO/MachO64_x86-64_binary_id.bin"))
        _, output = tempfile.mkstemp(prefix="lief_id_remove_cmd")

        # Extend UUID
        uuid_cmd = original[lief.MachO.LOAD_COMMAND_TYPES.UUID]
        original_size = uuid_cmd.size
        original.extend(uuid_cmd, 0x100)
        uuid_cmd = original[lief.MachO.LOAD_COMMAND_TYPES.UUID]

        # Extend __LINKEDIT (last one)
        original_linkedit_size = original.get_segment("__LINKEDIT").file_size
        original.extend_segment(original.get_segment("__LINKEDIT"), 0x30000)

        original.remove_signature()
        original.write(output)

        new = lief.parse(output)

        self.assertEqual(new.get_segment("__LINKEDIT").file_size, original_linkedit_size + 0x30000)
        self.assertEqual(new[lief.MachO.LOAD_COMMAND_TYPES.UUID].size, original_size + 0x100)

        if is_osx() and not is_aarch64():
            stdout = run_program(output)
            self.logger.debug(stdout)
            self.assertIsNotNone(re.search(r'uid=', stdout))
Exemplo n.º 4
0
def test_arm64_all(tmp_path):
    bin_path = pathlib.Path(get_sample("MachO/MachO64_AArch64_binary_all.bin"))
    output = patch(tmp_path, bin_path)
    new = lief.parse(output)

    checked, err = lief.MachO.check_layout(new)
    assert checked, err

    if is_apple_m1():
        stdout = run_program(output)
        print(stdout)
        assert re.search(r'LIEF says hello :\)', stdout) is not None
Exemplo n.º 5
0
def test_crypt_and_hash(tmp_path):
    bin_path = pathlib.Path(
        get_sample(
            "MachO/9edfb04c55289c6c682a25211a4b30b927a86fe50b014610d04d6055bd4ac23d_crypt_and_hash.macho"
        ))
    output = patch(tmp_path, bin_path)
    new = lief.parse(output)

    checked, err = lief.MachO.check_layout(new)
    assert checked, err

    if is_apple_m1():
        stdout = run_program(output)
        print(stdout)
        assert re.search(r'LIEF says hello :\)', stdout) is not None
Exemplo n.º 6
0
    def test_all(self):
        sample = None
        if is_apple_m1():
            sample = get_sample('MachO/MachO64_Aarch64_binary_all.bin')

        if is_x86_64():
            sample = get_sample('MachO/MachO64_x86-64_binary_all.bin')

        original = lief.parse(sample)
        _, output = tempfile.mkstemp(prefix="lief_all_")

        original.add_library(self.library_path)

        original.write(output)

        if is_osx():
            stdout = run_program(output)
            self.logger.debug(stdout)
            self.assertIsNotNone(re.search(r'CTOR CALLED', stdout))
Exemplo n.º 7
0
def test_linkedit_shift(tmp_path):
    binary_name = "crypt_and_hash"
    fat = lief.MachO.parse(get_sample('MachO/9edfb04c55289c6c682a25211a4b30b927a86fe50b014610d04d6055bd4ac23d_crypt_and_hash.macho'))
    target: lief.MachO.Binary = fat.take(lief.MachO.CPU_TYPES.ARM64)

    # Shift content
    target.shift_linkedit(0x4000)

    output = f"{tmp_path}/{binary_name}.built"
    target.remove_signature()
    target.write(output)

    process_crypt_and_hash(output)

    if is_apple_m1():
        chmod_exe(output)
        sign(output)
        with subprocess.Popen([output], universal_newlines=True,
                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
            stdout = proc.stdout.read()
            assert "CAMELLIA-256-CCM*-NO-TAG" in stdout
            assert "AES-128-CCM*-NO-TAG" in stdout
Exemplo n.º 8
0
def run_program(path, args=None):
    if is_apple_m1():
        sign(path)

    # Make sure the program has exec permission
    chmod_exe(path)
    dyld_check(path)

    env = os.environ
    env["DYLD_PRINT_APIS"] = "1"
    env["DYLD_PRINT_WARNINGS"] = "1"

    kwargs = {
        "universal_newlines": True,
        "stdout": subprocess.PIPE,
        "stderr": subprocess.STDOUT,
        "env": env,
    }

    prog_args = path if args is None else [path] + args
    with Popen(prog_args, **kwargs) as proc:
        proc.poll()
        print(f"{path} exited with {proc.returncode}")
        return proc.stdout.read()
Exemplo n.º 9
0
    original.add_library(library_path)

    original.remove_signature()
    original.write(output)
    new = lief.parse(output)

    checked, err = lief.MachO.check_layout(new)
    assert checked, err

    stdout = run_program(output, ["--help"])
    print(stdout)

    assert re.search(r'CTOR CALLED', stdout) is not None


@pytest.mark.skipif(not is_apple_m1(), reason="requires Apple M1")
def test_crypt_and_hash(tmp_path):
    bin_path = pathlib.Path(
        get_sample(
            "MachO/9edfb04c55289c6c682a25211a4b30b927a86fe50b014610d04d6055bd4ac23d_crypt_and_hash.macho"
        ))
    original = lief.parse(bin_path.as_posix())
    output = f"{tmp_path}/crypt_and_hash.bin"
    library_path = f"{tmp_path}/libexample.dylib"
    compile(library_path, extra_flags=["-arch", "arm64"])

    original.add_library(library_path)

    original.remove_signature()
    original.write(output)
    new = lief.parse(output)
Exemplo n.º 10
0
def test_break(tmp_path):
    FILES = [
        "MachO/mbedtls_selftest_arm64.bin", "MachO/mbedtls_selftest_x86_64.bin"
    ]

    def swap(target: lief.MachO.Binary, lhs: str, rhs: str):
        lhs_sec = target.get_section(lhs)
        if lhs_sec is None:
            print(f"Can't find section '{lhs_sec}'")
            return
        rhs_sec = target.get_section(rhs)
        if rhs_sec is None:
            print(f"Can't find section '{rhs_sec}'")
            return

        tmp = lhs_sec.name
        rhs_sec.name = tmp

    def shuffle(target: lief.MachO.Binary, name: str):
        section = target.get_section(name)
        if section is None:
            return
        print(f"[+] Shuffling '{name}'")
        section_content = list(section.content)
        random.shuffle(section_content)
        section.content = section_content

    def corrupt_function_starts(bin: lief.MachO.Binary,
                                break_alignment: bool = False):
        fstart = bin[lief.MachO.LOAD_COMMAND_TYPES.FUNCTION_STARTS]
        if fstart is None:
            return
        fstart.functions = [f + 5 for f in fstart.functions]

    def process_exports(bin: lief.MachO.Binary, sym: lief.MachO.Symbol):
        #print(sym.export_info.address)
        original_name = sym.export_info.symbol.name
        name = list(original_name)
        random.shuffle(name)
        new_name = "_" + "".join(name)
        address = sym.export_info.address
        bin.add_local_symbol(address, new_name)

    def process_imports(bin: lief.MachO.Binary, sym: lief.MachO.Symbol):
        original_name = sym.binding_info.symbol.name
        name = list(original_name)
        random.shuffle(name)
        new_name = "_" + "".join(name)
        address = sym.binding_info.address - bin.imagebase
        bin.add_local_symbol(address, new_name)

    def process_local_symbol(bin: lief.MachO.Binary, sym: lief.MachO.Symbol):
        original_name = sym.name
        name = list(sym.name)
        random.shuffle(name)
        sym.name = "_" + "".join(name)
        sym.type = 0xf
        sym.description = 0x300
        sym.numberof_sections = 1
        sym.value += 2

    def process_symbols(bin: lief.MachO.Binary):
        exports = []
        imports = []
        for sym in bin.symbols:
            if sym.has_export_info:
                #print(f"[EXPORT]: {sym.name}")
                exports.append(sym)
            elif sym.has_binding_info:
                #print(f"[IMPORT]: {sym.name}")
                imports.append(sym)
            else:
                # "classical" symbol
                process_local_symbol(bin, sym)

        for sym in exports:
            process_exports(bin, sym)

        for sym in imports:
            process_imports(bin, sym)

    def fake_objc(bin: lief.MachO.Binary):
        segment = lief.MachO.SegmentCommand("__DATA_LIEF")

        __objc_classlist = lief.MachO.Section(
            "__objc_classlist", [random.randint(0, 255) for _ in range(0x100)])
        __objc_imageinfo = lief.MachO.Section(
            "__objc_imageinfo", [random.randint(0, 255) for _ in range(0x100)])
        __objc_const = lief.MachO.Section(
            "__objc_const", [random.randint(0, 255) for _ in range(0x100)])
        __objc_classrefs = lief.MachO.Section(
            "__objc_classrefs", [random.randint(0, 255) for _ in range(0x100)])

        __objc_classlist = segment.add_section(__objc_classlist)
        __objc_imageinfo = segment.add_section(__objc_imageinfo)
        __objc_const = segment.add_section(__objc_const)
        __objc_classrefs = segment.add_section(__objc_classrefs)

        objc_section = [
            __objc_classlist, __objc_imageinfo, __objc_const, __objc_classrefs
        ]
        section: lief.MachO.Section
        for section in objc_section:
            section.type = lief.MachO.SECTION_TYPES.REGULAR
            section.flags = lief.MachO.SECTION_FLAGS.NO_DEAD_STRIP
            section.alignment = 0x3

        __data_lief: lief.MachO.SegmentCommand = bin.add(segment)
        __data_lief.init_protection = 3
        __data_lief.max_protection = 3

    for file in FILES:
        bin_path = pathlib.Path(get_sample(file))
        original = lief.parse(bin_path.as_posix())
        output = f"{tmp_path}/{bin_path.name}"

        SWAP_LIST = [
            ("__text", "__stubs"),
            ("__cstring", "__unwind_info"),
        ]
        for (lhs, rhs) in SWAP_LIST:
            swap(original, lhs, rhs)

        process_symbols(original)
        fake_objc(original)
        corrupt_function_starts(original)

        original.write(output)
        new = lief.parse(output)

        checked, err = lief.MachO.check_layout(new)
        assert checked, err
        should_run = (original.header.cpu_type == lief.MachO.CPU_TYPES.x86_64 and is_osx()) or \
                     (original.header.cpu_type == lief.MachO.CPU_TYPES.ARM64 and is_apple_m1())

        if should_run:
            assert run_program(bin_path.as_posix())
            stdout = run_program(output)

            print(stdout)
            assert re.search(r'All tests PASS', stdout) is not None