import os import subprocess import plistlib import sys import io FAT_MAGIC = b'\xbe\xba\xfe\xca' FAT_CIGAM = b'\xca\xfe\xba\xbe' MH_MAGIC = b'\xce\xfa\xed\xfe' MH_CIGAM = b'\xfe\xed\xfa\xce' MH_MAGIC_64 = b'\xcf\xfa\xed\xfe' MH_CIGAM_64 = b'\xfe\xed\xfa\xcd' LC_ENCRYPTION_INFO = 0x21 LC_ENCRYPTION_INFO_64 = 0x2C class Macho: class LoadCommand(): def __init__(self, cmd, buf): self.cmd = cmd self.buf = buf class EncryptionInfo(): def __init__(self, load_command): cryptid = load_command.buf[4*4:4*4+4] self.encrypted = cryptid != b'\x00\x00\x00\x00' def __init__(self, buf): self.buf = buf self.buf.seek(0) self.magic = self.buf.read(4) if self.magic in [MH_MAGIC, MH_MAGIC_64]: self.endian = "little" else: self.endian = "big" if self.magic in [MH_MAGIC, MH_CIGAM]: self.bittype = "32" else: self.bittype = "64" def get_encryption_info(self): for load_command in self.__traverse_load_commands(): if load_command.cmd in [LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64]: einfo = Macho.EncryptionInfo(load_command) return einfo.encrypted def __traverse_load_commands(self): ncmds = self.ncmds() self.buf.seek(self.load_commands_start()) for i in range(ncmds): old = self.buf.tell() b = self.buf.read(8) self.buf.seek(old) cmd = int.from_bytes(b[:4], self.endian) cmdsize = int.from_bytes(b[4:], self.endian) yield Macho.LoadCommand(cmd, self.buf.read(cmdsize)) def ncmds(self): self.buf.seek(16) ncmds = self.buf.read(4) return int.from_bytes(ncmds, self.endian) def load_commands_start(self): if self.bittype == "32": return 7 * 4 else: return 8 * 4 # return a list of Macho def load_binary(path): f = open(path, 'rb') magic = f.read(4) if magic in [FAT_CIGAM, FAT_MAGIC]: # do fat stuff print("fat not supported yet") pass else: f.seek(0) return [Macho(f)] return [] def encryption_status(path): machos = load_binary(path) for macho in machos: print(f"{path}: {macho.magic} | encryption:{macho.get_encryption_info()}") def get_app(app_folder): plist = os.path.join(app_folder, "Info.plist") plist = plistlib.load(open(plist, "rb")) binary_name = plist.get("CFBundleExecutable", None) return os.path.join(app_folder, binary_name) if __name__ == "__main__": app_folder = sys.argv[1] pjoin = os.path.join framework_folder = pjoin(app_folder, "Frameworks") plugin_folder = pjoin(app_folder, "Plugins") app = get_app(app_folder) encryption_status(app) for framework in os.listdir(framework_folder): app = get_app(pjoin(framework_folder, framework)) encryption_status(app)