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
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
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))
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
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
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))
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
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()
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)
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