Created
July 6, 2023 16:10
-
-
Save carlospolop/ef26f8eb9fafd4bc22e69e1a32b81da4 to your computer and use it in GitHub Desktop.
Revisions
-
carlospolop created this gist
Jul 6, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,196 @@ import plistlib import struct import logging import lief import sys from typing import List from macholib import MachO, mach_o logger = logging.getLogger(__name__) # SIGNATURE FLAGS def decode_flags(flags: int) -> str: flags_dict = { 0x00000001: 'CS_VALID', 0x00000002: 'CS_ADHOC', 0x00000004: 'CS_GET_TASK_ALLOW', 0x00000008: 'CS_INSTALLER', 0x00000010: 'CS_FORCED_LV', 0x00000020: 'CS_INVALID_ALLOWED', 0x00000100: 'CS_HARD', 0x00000200: 'CS_KILL', 0x00000400: 'CS_CHECK_EXPIRATION', 0x00000800: 'CS_RESTRICT', 0x00001000: 'CS_ENFORCEMENT', 0x00002000: 'CS_REQUIRE_LV', 0x00004000: 'CS_ENTITLEMENTS_VALIDATED', 0x00008000: 'CS_NVRAM_UNRESTRICTED', 0x00010000: 'CS_RUNTIME', 0x00020000: 'CS_LINKER_SIGNED', 0x00100000: 'CS_EXEC_SET_HARD', 0x00200000: 'CS_EXEC_SET_KILL', 0x00400000: 'CS_EXEC_SET_ENFORCEMENT', 0x00800000: 'CS_EXEC_INHERIT_SIP', 0x01000000: 'CS_KILLED', 0x02000000: 'CS_DYLD_PLATFORM', 0x04000000: 'CS_PLATFORM_BINARY', 0x08000000: 'CS_PLATFORM_PATH', 0x10000000: 'CS_DEBUGGED', 0x20000000: 'CS_SIGNED', 0x40000000: 'CS_DEV_CODE', 0x80000000: 'CS_DATAVAULT_CONTROLLER', } return ', '.join(name for bit, name in flags_dict.items() if flags & bit) def find_lc_code_signature_and_header_offset(binary_path): m = MachO.MachO(binary_path) header = m.headers[0] for header in m.headers: if header.MH_MAGIC == MachO.MH_MAGIC_64 or header.MH_MAGIC == MachO.MH_CIGAM_64: header_offset = header.offset else: return None, None for cmd in header.commands: if cmd[0].cmd == 0x1d: offset = cmd[1].dataoff size = cmd[1].datasize return header_offset + offset, size return None, None def get_macho_formats(binary_path): try: macho_file = MachO.MachO(binary_path) formats = set() for header in macho_file.headers: cpu_type = header.header.cputype cpu_subtype = header.header.cpusubtype formats.add(mach_o.get_cpu_subtype(cpu_type, cpu_subtype)) return formats except Exception as e: return set() def get_signature_and_entitlements(binary_path, offset, size): bin_info = { "version": "", "size": "", "flags": "", "hashes": "", "identifier": "", "entitlements": {} } with open(binary_path, 'rb') as f: f.seek(offset) data = f.read(size) multi_blob = data[:4].hex() if multi_blob != "fade0cc0": return None count = int.from_bytes(data[8:12], byteorder='big') for i in range(count): start = 12 + i * 8 blob_type = int.from_bytes(data[start:start+4], byteorder='big') blob_offset = int.from_bytes(data[start+4:start+8], byteorder='big') blob_magic = int.from_bytes(data[blob_offset:blob_offset+4], byteorder='big') if blob_magic == 0xfade7171: blob_length = int.from_bytes(data[blob_offset+4:blob_offset+8], byteorder='big') blob_data = data[blob_offset+8:blob_offset+blob_length] entitlements = plistlib.loads(blob_data) bin_info['entitlements'] = entitlements elif blob_magic == 0xfade0c02: format_str = ">IIIIIIIIIBBBBI" values = struct.unpack_from(format_str, data, blob_offset) cd = { 'magic': values[0], 'length': values[1], 'version': values[2], 'flags': values[3], 'hashOffset': values[4], 'identOffset': values[5], 'nSpecialSlots': values[6], 'nCodeSlots': values[7], 'codeLimit': values[8], 'hashSize': values[9], 'hashType': values[10], 'spare1': values[11], 'pageSize': values[12], 'spare2': values[13], } ident_start = blob_offset + cd['identOffset'] ident_end = data.index(b'\0', ident_start) identifier = data[ident_start:ident_end].decode('utf-8') bin_info["version"] = cd['version'] bin_info["size"] = cd['length'] bin_info["flags"] = f"0x{cd['flags']:08x}({decode_flags(cd['flags'])})" bin_info["hashes"] = f"{cd['nCodeSlots']}+{cd['nSpecialSlots']}" bin_info["identifier"] = identifier return bin_info def get_imports_and_symbols(binary_path): bin_data = { "imports": [], "symbols": [], "rpaths": [] } binary = lief.parse(binary_path) for dylib in binary.libraries: bin_data["imports"].append(dylib.name) for command in binary.commands: if isinstance(command, lief.MachO.RPathCommand): bin_data["rpaths"].append(command.path) for symbol in binary.symbols: bin_data["symbols"].append(symbol.name) return bin_data def print_macho_info(macho_info): for k,v in macho_info.items(): if k == "entitlements": print(f"{k.capitalize()}:") for k2,v2 in v.items(): print(f"\t{k2}: {v2}") else: print(f"{k.capitalize()}: {v}") def machoreader(input_file: str): machoreader_results = { "version": "", "size": "", "flags": "", "hashes": "", "identifier": "", "entitlements": {}, "formats": [], "imports": [], "symbols": [], "rpaths": [] } try: formats = get_macho_formats(input_file) machoreader_results["formats"] = formats except Exception as e: logger.error(f"Error in machoreader trying to get formats with file {input_file}: {e}") try: offset, size = find_lc_code_signature_and_header_offset(input_file) if offset and size: bin_info = get_signature_and_entitlements(input_file, offset, size) machoreader_results["version"] = bin_info["version"] machoreader_results["size"] = bin_info["size"] machoreader_results["flags"] = bin_info["flags"] machoreader_results["hashes"] = bin_info["hashes"] machoreader_results["identifier"] = bin_info["identifier"] machoreader_results["entitlements"] = bin_info["entitlements"] except Exception as e: logger.error(f"Error in machoreader trying to get signature and entitlements with file {input_file}: {e}") try: bin_data = get_imports_and_symbols(input_file) machoreader_results["imports"] = bin_data["imports"] machoreader_results["symbols"] = bin_data["symbols"] machoreader_results["rpaths"] = bin_data["rpaths"] except Exception as e: logger.error(f"Error in machoreader trying to get imports and symbols with file {input_file}: {e}") print_macho_info(machoreader_results) if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python machoreader.py <macho_binary_path>") sys.exit(1) macho_path = sys.argv[1] machoreader(macho_path)