mirror of
https://github.com/NohamR/macho-diff.git
synced 2026-07-11 22:42:23 +00:00
Add macho_diff package with CLI and helpers
This commit is contained in:
142
macho_diff/cli.py
Normal file
142
macho_diff/cli.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
CLI module for the macho-diff tool.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import lief
|
||||
|
||||
from macho_diff.helpers import (
|
||||
parse_functions,
|
||||
compare_instructions,
|
||||
generate_batch_patches,
|
||||
)
|
||||
|
||||
lief.logging.disable()
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(description="Mach-O binary visual diff tool")
|
||||
parser.add_argument("src", help="Path to the original/source Mach-O binary")
|
||||
parser.add_argument("patched", help="Path to the patched Mach-O binary")
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--batch",
|
||||
action="store_true",
|
||||
help="Generate a batch patch file representing the differences.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_binary(filepath):
|
||||
"""Load a Mach-O binary from the given file path."""
|
||||
if not os.path.isfile(filepath):
|
||||
print(f"Error: File not found: '{filepath}'", file=sys.stderr)
|
||||
return None
|
||||
try:
|
||||
fat = lief.MachO.parse(filepath)
|
||||
return fat.at(0) if fat else None
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
print(f"Error parsing binary '{filepath}': {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def read_file_raw(filepath):
|
||||
"""Read the raw bytes of a file."""
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
return f.read()
|
||||
except OSError as e:
|
||||
print(f"Error reading file '{filepath}': {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def process_diffs(args, binary_src, src_raw, functions_src, functions_patched):
|
||||
"""Process and print the differences between functions."""
|
||||
diff_count = 0
|
||||
all_batch_patches = []
|
||||
|
||||
for addr, src_data in functions_src.items():
|
||||
patched_data = functions_patched.get(addr)
|
||||
if not patched_data:
|
||||
continue
|
||||
|
||||
if src_data["checksum"] != patched_data["checksum"]:
|
||||
if args.batch:
|
||||
patches = generate_batch_patches(
|
||||
binary_src, src_raw, addr, src_data["data"], patched_data["data"]
|
||||
)
|
||||
if patches:
|
||||
all_batch_patches.append((src_data["name"], patches))
|
||||
else:
|
||||
print(
|
||||
f"\n[DIFF] {src_data['name']} @ {hex(addr)} size={src_data['size']}"
|
||||
)
|
||||
compare_instructions(
|
||||
binary_src, addr, src_data["data"], patched_data["data"]
|
||||
)
|
||||
diff_count += 1
|
||||
|
||||
return diff_count, all_batch_patches
|
||||
|
||||
|
||||
def print_batch_patches(binary_src, all_batch_patches):
|
||||
"""Print the batch patches."""
|
||||
cputype_name = binary_src.header.cpu_type.name
|
||||
print("\nBatch Patches:" + "\n" + "=" * 40)
|
||||
print(f"{cputype_name}:\n")
|
||||
|
||||
for _, patches in all_batch_patches:
|
||||
for find_str, replace_str in patches:
|
||||
print(find_str)
|
||||
print("to")
|
||||
print(replace_str)
|
||||
print()
|
||||
print("=" * 40)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the CLI."""
|
||||
args = parse_args()
|
||||
|
||||
print(f"Loading '{args.src}'...")
|
||||
binary_src = load_binary(args.src)
|
||||
if not binary_src:
|
||||
return
|
||||
|
||||
print(f"Loading '{args.patched}'...")
|
||||
binary_patched = load_binary(args.patched)
|
||||
if not binary_patched:
|
||||
return
|
||||
|
||||
print("Reading file contents...")
|
||||
src_raw = read_file_raw(args.src)
|
||||
if not src_raw:
|
||||
return
|
||||
patched_raw = read_file_raw(args.patched)
|
||||
if not patched_raw:
|
||||
return
|
||||
|
||||
print("Parsing functions (this might take a moment)...")
|
||||
functions_src = parse_functions(binary_src, src_raw)
|
||||
functions_patched = parse_functions(binary_patched, patched_raw)
|
||||
|
||||
print(
|
||||
f"Found {len(functions_src)} functions in source binary "
|
||||
f"and {len(functions_patched)} in patched binary."
|
||||
)
|
||||
|
||||
diff_count, all_batch_patches = process_diffs(
|
||||
args, binary_src, src_raw, functions_src, functions_patched
|
||||
)
|
||||
|
||||
if args.batch:
|
||||
print_batch_patches(binary_src, all_batch_patches)
|
||||
|
||||
print(f"\nTotal diffs: {diff_count}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user