Skip to content

Instantly share code, notes, and snippets.

@carlospolop
Created July 6, 2023 16:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save carlospolop/ef26f8eb9fafd4bc22e69e1a32b81da4 to your computer and use it in GitHub Desktop.
Save carlospolop/ef26f8eb9fafd4bc22e69e1a32b81da4 to your computer and use it in GitHub Desktop.
Print information about a macho binary using python
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment