mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-07-10 19:43:40 +02:00
Preparing CrimsonUroboros for SnakeVIII - Sandbox article
This commit is contained in:
@@ -17,29 +17,140 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import xattr
|
import xattr
|
||||||
|
|
||||||
|
### --- APP BUNDLE EXTENSION --- ###
|
||||||
|
class BundleProcessor:
|
||||||
|
def __init__(self, bundle_path):
|
||||||
|
'''This class contains part of the code related to the App Bundle Extension.
|
||||||
|
It extends the Snake instance abilities beyond only Mach-O analysis.
|
||||||
|
When -b/--bundle flag is used, it will analyze the App Bundle when an instance of this (BundleProcessor) class is created.
|
||||||
|
Then, it can be communicated from Snake object with the new methods dependent on the files in App Bundle.'''
|
||||||
|
self.bundle_path = os.path.abspath(bundle_path)
|
||||||
|
self.info_plist_path = os.path.join(self.bundle_path, 'Contents/Info.plist')
|
||||||
|
self.info_plist_exists = self.hasInfoPlist()
|
||||||
|
self.frameworks_path = os.path.join(self.bundle_path, 'Contents/Frameworks')
|
||||||
|
self.frameworks_exists = self.hasFrameworks()
|
||||||
|
self.plugins_path = os.path.join(self.bundle_path, 'Contents/PlugIns')
|
||||||
|
self.plugins_exists = self.hasPlugIns()
|
||||||
|
|
||||||
|
def hasInfoPlist(self):
|
||||||
|
''' Return True if Info.plist exists in the bundle. '''
|
||||||
|
if os.path.exists(self.info_plist_path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getBundleInfoCFBundleExecutableValue(self):
|
||||||
|
''' Return the CFBundleExecutable value from the Info.plist file if it exists. Otherwise, return None. '''
|
||||||
|
if self.info_plist_exists:
|
||||||
|
with open(self.info_plist_path, 'rb') as f:
|
||||||
|
plist_data = plistlib.load(f)
|
||||||
|
return plist_data.get('CFBundleExecutable', None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getBundleStructure(self):
|
||||||
|
''' Return the structure of the bundle in tree format, including hidden files and with permissions. '''
|
||||||
|
return os.popen(f"tree -ACp '{self.bundle_path}'").read()
|
||||||
|
|
||||||
|
def getBundleInfo(self):
|
||||||
|
''' Return the info of the bundle in a more readable JSON format. '''
|
||||||
|
if self.info_plist_exists:
|
||||||
|
with open(self.info_plist_path, 'rb') as f:
|
||||||
|
plist_data = plistlib.load(f)
|
||||||
|
return json.dumps(plist_data, indent=4)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def checkInfoPropertyListSyntaxErrors(self, info_plist_path):
|
||||||
|
''' Check the named property list file for syntax errors using /usr/bin/plutil .'''
|
||||||
|
plutil_command = ["/usr/bin/plutil", info_plist_path]
|
||||||
|
plutil_result = subprocess.run(plutil_command, capture_output=True)
|
||||||
|
if plutil_result.returncode == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def checkBundleInfoSyntax(self):
|
||||||
|
if self.info_plist_exists:
|
||||||
|
if self.checkInfoPropertyListSyntaxErrors(self.info_plist_path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def hasFrameworks(self):
|
||||||
|
''' Return True if Frameworks directory exists in the bundle and contains at least one framework. '''
|
||||||
|
if os.path.exists(self.frameworks_path):
|
||||||
|
if os.listdir(self.frameworks_path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getFrameworks(self):
|
||||||
|
''' Return list of frameworks in the Frameworks directory. '''
|
||||||
|
if self.frameworks_exists:
|
||||||
|
return os.listdir(self.frameworks_path)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def hasPlugIns(self):
|
||||||
|
''' Return True if PlugIns directory exists in the bundle and contains at least one plug-in. '''
|
||||||
|
if os.path.exists(self.plugins_path):
|
||||||
|
if os.listdir(self.plugins_path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getPlugIns(self):
|
||||||
|
''' Return list of plug-ins in the PlugIns directory. '''
|
||||||
|
if self.plugins_exists:
|
||||||
|
return os.listdir(self.plugins_path)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
### --- I. MACH-O --- ###
|
### --- I. MACH-O --- ###
|
||||||
class MachOProcessor:
|
class MachOProcessor:
|
||||||
def __init__(self, file_path):
|
def __init__(self, file_path, bundle_processor):
|
||||||
'''This class contains part of the code from the main() for the SnakeI: Mach-O part.'''
|
'''This class contains part of the code from the main() for the SnakeI: Mach-O part.'''
|
||||||
self.file_path = os.path.abspath(file_path)
|
self.file_path = os.path.abspath(file_path)
|
||||||
|
self.macho_and_fat_magic_numbers = {
|
||||||
|
0xfeedface, # 32-bit Mach-O
|
||||||
|
0xfeedfacf, # 64-bit Mach-O
|
||||||
|
0xcefaedfe, # 32-bit Mach-O, byte-swapped
|
||||||
|
0xcffaedfe, # 64-bit Mach-O, byte-swapped
|
||||||
|
0xcafebabe, # Fat binary
|
||||||
|
0xbebafeca # Fat binary, byte-swapped
|
||||||
|
}
|
||||||
|
self.bundle_processor = bundle_processor
|
||||||
|
|
||||||
|
def isFileMachO(self, filepath):
|
||||||
|
'''Check if file is Mach-O. '''
|
||||||
|
try:
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
magic = f.read(4)
|
||||||
|
if len(magic) < 4:
|
||||||
|
return False
|
||||||
|
magic_number = int.from_bytes(magic, byteorder='big')
|
||||||
|
return magic_number in self.macho_and_fat_magic_numbers
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
def parseFatBinary(self):
|
def parseFatBinary(self):
|
||||||
'''Return Fat Binary object.'''
|
'''Return Fat Binary object if file exists.'''
|
||||||
return lief.MachO.parse(self.file_path)
|
if os.path.exists(self.file_path):
|
||||||
|
if self.isFileMachO(self.file_path):
|
||||||
|
return lief.MachO.parse(self.file_path)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def process(self, args):
|
def process(self, args):
|
||||||
'''Executes the code for the SnakeI: Mach-O.'''
|
'''Executes the code for the SnakeI: Mach-O.'''
|
||||||
if not os.path.exists(self.file_path): # Check if file_path specified in the --path argument exists.
|
if not os.path.exists(self.file_path) and self.bundle_processor is None: # Check if file_path specified in the --path argument exists and if bundle is not specified.
|
||||||
print(f'The file {self.file_path} does not exist.')
|
print(f'The file {self.file_path} does not exist.')
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
global binaries # It must be global, becuase after the MachOProcessor object is destructed, the snake_instance would point to invalid memory ("binary" is dependant on "binaries").
|
global binaries # It must be global, becuase after the MachOProcessor object is destructed, the snake_instance would point to invalid memory ("binary" is dependant on "binaries").
|
||||||
global snake_instance # Must be global for further processors classes.
|
global snake_instance # Must be global for further processors classes.
|
||||||
|
global bundle_processor # Same situation as with binaries object.
|
||||||
|
|
||||||
|
bundle_processor = self.bundle_processor # Transfer the bundle_processor to the global scope
|
||||||
binaries = self.parseFatBinary()
|
binaries = self.parseFatBinary()
|
||||||
|
|
||||||
if binaries is None:
|
if binaries is None and self.bundle_processor is None:
|
||||||
exit() # Exit if the file is not valid macho
|
exit() # Exit if the file is not valid macho and bundle is not specified
|
||||||
|
|
||||||
snake_instance = SnakeVII(binaries, self.file_path) # Initialize the latest Snake class
|
snake_instance = SnakeVII(binaries, self.file_path) # Initialize the latest Snake class
|
||||||
|
|
||||||
@@ -160,20 +271,39 @@ class MachOProcessor:
|
|||||||
if args.constructors: # Print constructors
|
if args.constructors: # Print constructors
|
||||||
snake_instance.printConstructors()
|
snake_instance.printConstructors()
|
||||||
|
|
||||||
|
if args.dump_section: # Dump section to a stdout
|
||||||
|
snake_instance.dumpSectionToStdout(args.dump_section)
|
||||||
|
|
||||||
|
if args.bundle_structure: # Print bundle structure
|
||||||
|
snake_instance.printBundleStructure()
|
||||||
|
|
||||||
|
if args.bundle_info: # Print bundle info (XML -> JSON)
|
||||||
|
snake_instance.printBundleInfo()
|
||||||
|
|
||||||
|
if args.bundle_info_syntax_check: # Check if bundle info syntax is valid
|
||||||
|
snake_instance.printBundleInfoSyntax()
|
||||||
|
|
||||||
|
if args.bundle_frameworks: # Print bundle frameworks
|
||||||
|
snake_instance.printBundleFrameworks()
|
||||||
|
|
||||||
|
if args.bundle_plugins: # Print bundle plugins
|
||||||
|
snake_instance.printBundlePlugIns()
|
||||||
|
|
||||||
class SnakeI:
|
class SnakeI:
|
||||||
def __init__(self, binaries, file_path):
|
def __init__(self, binaries, file_path):
|
||||||
'''
|
'''
|
||||||
When initiated, the program parses a Universal binary (binaries parameter) and extracts the ARM64 Mach-O.
|
When initiated, the program parses a Universal binary (binaries parameter) and extracts the ARM64 Mach-O.
|
||||||
If the file is not in a universal format but is a valid ARM64 Mach-O, it is taken as a binary parameter during initialization.
|
If the file is not in a universal format but is a valid ARM64 Mach-O, it is taken as a binary parameter during initialization.
|
||||||
'''
|
'''
|
||||||
self.binary = self.parseFatBinary(binaries)
|
if binaries is not None: # Exception for bundles where binaries are not valid Mach-O
|
||||||
|
self.binary = self.parseFatBinary(binaries)
|
||||||
|
self.segments_count, self.file_start, self.file_size, self.file_end = self.getSegmentsInfo()
|
||||||
|
self.load_commands = self.getLoadCommands()
|
||||||
|
self.endianess = self.getEndianess()
|
||||||
|
self.format_specifier = '<I' if self.getEndianess() == 'little' else '>I' # For struct.pack
|
||||||
|
self.reversed_format_specifier = '>I' if self.getEndianess() == 'little' else '<I' # For CS blob which is in Big Endian.
|
||||||
|
self.fat_offset = self.binary.fat_offset # For various calculations, if ARM64 Mach-O extracted from Universal Binary
|
||||||
self.file_path = file_path
|
self.file_path = file_path
|
||||||
self.segments_count, self.file_start, self.file_size, self.file_end = self.getSegmentsInfo()
|
|
||||||
self.load_commands = self.getLoadCommands()
|
|
||||||
self.endianess = self.getEndianess()
|
|
||||||
self.format_specifier = '<I' if self.getEndianess() == 'little' else '>I' # For struct.pack
|
|
||||||
self.reversed_format_specifier = '>I' if self.getEndianess() == 'little' else '<I' # For CS blob which is in Big Endian.
|
|
||||||
self.fat_offset = self.binary.fat_offset # For various calculations, if ARM64 Mach-O extracted from Universal Binary
|
|
||||||
self.prot_map = {
|
self.prot_map = {
|
||||||
0: '---',
|
0: '---',
|
||||||
1: 'r--',
|
1: 'r--',
|
||||||
@@ -205,6 +335,7 @@ class SnakeI:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def getSegmentsInfo(self):
|
def getSegmentsInfo(self):
|
||||||
''' Helper function for gathering various initialization information about the binary if extracted from FAT. '''
|
''' Helper function for gathering various initialization information about the binary if extracted from FAT. '''
|
||||||
segments_count = 0
|
segments_count = 0
|
||||||
@@ -325,8 +456,10 @@ class SnakeI:
|
|||||||
Return section start and end file offset.
|
Return section start and end file offset.
|
||||||
If there is no such section return False, False.
|
If there is no such section return False, False.
|
||||||
'''
|
'''
|
||||||
|
segment_name = segment_name.lower()
|
||||||
|
section_name = section_name.lower()
|
||||||
for section in self.binary.sections:
|
for section in self.binary.sections:
|
||||||
if segment_name == section.segment_name:
|
if segment_name == section.segment_name.lower():
|
||||||
if section_name == section.fullname.decode():
|
if section_name == section.fullname.decode():
|
||||||
section_offset_start, section_offset_end = self.calcSectionRange(section)
|
section_offset_start, section_offset_end = self.calcSectionRange(section)
|
||||||
return section_offset_start, section_offset_end
|
return section_offset_start, section_offset_end
|
||||||
@@ -596,6 +729,8 @@ class SnakeI:
|
|||||||
Dump '__SEGMENT,__section' to a given file.
|
Dump '__SEGMENT,__section' to a given file.
|
||||||
Reutrn False if the section does not exist.
|
Reutrn False if the section does not exist.
|
||||||
'''
|
'''
|
||||||
|
segment_name = segment_name.lower()
|
||||||
|
section_name = section_name.lower()
|
||||||
extracted_bytes = self.extractSection(segment_name, section_name)
|
extracted_bytes = self.extractSection(segment_name, section_name)
|
||||||
if extracted_bytes:
|
if extracted_bytes:
|
||||||
self.saveBytesToFile(extracted_bytes, filename)
|
self.saveBytesToFile(extracted_bytes, filename)
|
||||||
@@ -658,6 +793,49 @@ class SnakeI:
|
|||||||
for ctor in self.binary.ctor_functions:
|
for ctor in self.binary.ctor_functions:
|
||||||
print(ctor)
|
print(ctor)
|
||||||
|
|
||||||
|
def dumpSectionToStdout(self, segment_section):
|
||||||
|
''' Dump '__SEGMENT,__section' to stdout. '''
|
||||||
|
segment_section = segment_section.lower().split(',')
|
||||||
|
segment_name = segment_section[0]
|
||||||
|
section_name = segment_section[1]
|
||||||
|
extracted_bytes = self.extractSection(segment_name, section_name)
|
||||||
|
print(extracted_bytes)
|
||||||
|
|
||||||
|
def printBundleStructure(self):
|
||||||
|
''' Print the structure of the bundle. '''
|
||||||
|
print(bundle_processor.getBundleStructure())
|
||||||
|
|
||||||
|
def printBundleInfo(self):
|
||||||
|
''' Print the info of the bundle. '''
|
||||||
|
bundle_info = bundle_processor.getBundleInfo()
|
||||||
|
if bundle_info:
|
||||||
|
print(bundle_info)
|
||||||
|
else:
|
||||||
|
print("No bundle Info.plist found.")
|
||||||
|
|
||||||
|
def printBundleInfoSyntax(self):
|
||||||
|
''' Print if Info.plist syntax is valid. '''
|
||||||
|
if bundle_processor.checkBundleInfoSyntax():
|
||||||
|
print("Valid Bundle Info.plist syntax")
|
||||||
|
else:
|
||||||
|
print(f"Invalid Bundle Info.plist syntax (use plutil {bundle_processor.info_plist_path} to see the error)")
|
||||||
|
|
||||||
|
def printBundleFrameworks(self):
|
||||||
|
''' Print the list of frameworks in the bundle. '''
|
||||||
|
if bundle_processor.getFrameworks():
|
||||||
|
for framework in bundle_processor.getFrameworks():
|
||||||
|
print(framework)
|
||||||
|
else:
|
||||||
|
print("No frameworks found.")
|
||||||
|
|
||||||
|
def printBundlePlugIns(self):
|
||||||
|
''' Print the list of plugins in the bundle. '''
|
||||||
|
if bundle_processor.getPlugIns():
|
||||||
|
for plugin in bundle_processor.getPlugIns():
|
||||||
|
print(plugin)
|
||||||
|
else:
|
||||||
|
print("No plugins found.")
|
||||||
|
|
||||||
### --- II. CODE SIGNING --- ###
|
### --- II. CODE SIGNING --- ###
|
||||||
class CodeSigningProcessor:
|
class CodeSigningProcessor:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -678,7 +856,7 @@ class CodeSigningProcessor:
|
|||||||
print(snake_instance.getCodeSignatureRequirements(snake_instance.file_path).decode('utf-8'))
|
print(snake_instance.getCodeSignatureRequirements(snake_instance.file_path).decode('utf-8'))
|
||||||
|
|
||||||
if args.entitlements: # Print Entitlements.
|
if args.entitlements: # Print Entitlements.
|
||||||
print(snake_instance.getEntitlementsFromCodeSignature(snake_instance.file_path, args.entitlements))
|
snake_instance.printEntitlements(snake_instance.file_path, args.entitlements)
|
||||||
|
|
||||||
if args.extract_cms: # Extract the CMS Signature and save it to a given file.
|
if args.extract_cms: # Extract the CMS Signature and save it to a given file.
|
||||||
cms_signature = snake_instance.extractCMS()
|
cms_signature = snake_instance.extractCMS()
|
||||||
@@ -699,6 +877,12 @@ class CodeSigningProcessor:
|
|||||||
if args.cs_flags: # Print Code Signature flags
|
if args.cs_flags: # Print Code Signature flags
|
||||||
snake_instance.printCodeSignatureFlags()
|
snake_instance.printCodeSignatureFlags()
|
||||||
|
|
||||||
|
if args.verify_bundle_signature: # Verify if Code Signature match the bundle content
|
||||||
|
snake_instance.printIsSigValidInAppBundle()
|
||||||
|
|
||||||
|
if args.remove_sig_from_bundle: # Remove the Code Signature from the App Bundle
|
||||||
|
snake_instance.removeSignatureFromBundle()
|
||||||
|
|
||||||
class SnakeII(SnakeI):
|
class SnakeII(SnakeI):
|
||||||
def __init__(self, binaries, file_path):
|
def __init__(self, binaries, file_path):
|
||||||
super().__init__(binaries, file_path)
|
super().__init__(binaries, file_path)
|
||||||
@@ -733,6 +917,14 @@ class SnakeII(SnakeI):
|
|||||||
result = subprocess.run(["codesign", "-d", "--entitlements", "-", "--der", file_path], capture_output=True)
|
result = subprocess.run(["codesign", "-d", "--entitlements", "-", "--der", file_path], capture_output=True)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
|
def printEntitlements(self, file_path, format=None):
|
||||||
|
''' Helper function for printing entitlements. '''
|
||||||
|
entitlements = self.getEntitlementsFromCodeSignature(file_path, format)
|
||||||
|
try:
|
||||||
|
print(entitlements.decode('utf-8'))
|
||||||
|
except:
|
||||||
|
print(entitlements)
|
||||||
|
|
||||||
def extractCMS(self):
|
def extractCMS(self):
|
||||||
'''Find the offset of magic bytes in a binary using LIEF.'''
|
'''Find the offset of magic bytes in a binary using LIEF.'''
|
||||||
cs = self.binary.code_signature
|
cs = self.binary.code_signature
|
||||||
@@ -818,6 +1010,25 @@ class SnakeII(SnakeI):
|
|||||||
def printCodeSignatureFlags(self):
|
def printCodeSignatureFlags(self):
|
||||||
print(f'CS_FLAGS: {hex(self.getCodeSignatureFlags())}')
|
print(f'CS_FLAGS: {hex(self.getCodeSignatureFlags())}')
|
||||||
|
|
||||||
|
def isSigValidInAppBundle(self):
|
||||||
|
''' Check if the Code Signature is valid (if the contents of the binary have been modified.)'''
|
||||||
|
result = subprocess.run(["codesign", "-v", bundle_processor.bundle_path], capture_output=True)
|
||||||
|
if result.stderr == b'':
|
||||||
|
return True
|
||||||
|
return result.stderr
|
||||||
|
|
||||||
|
def printIsSigValidInAppBundle(self):
|
||||||
|
'''Helper function for printing if the Code Signature is valid in the App Bundle.'''
|
||||||
|
verification_result = self.isSigValidInAppBundle()
|
||||||
|
if verification_result == True:
|
||||||
|
print("Valid Bundle Code Signature (matches the content)")
|
||||||
|
else:
|
||||||
|
print(f"Invalid Bundle Code Signature:\n {verification_result.decode('utf-8')}")
|
||||||
|
|
||||||
|
def removeSignatureFromBundle(self):
|
||||||
|
''' Remove the Code Signature from the App Bundle (it does not remove the signature from bundled frameworks). '''
|
||||||
|
subprocess.run(["codesign", "--remove-signature", bundle_processor.bundle_path], capture_output=True)
|
||||||
|
|
||||||
### --- III. CHECKSEC --- ###
|
### --- III. CHECKSEC --- ###
|
||||||
class ChecksecProcessor:
|
class ChecksecProcessor:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -1147,10 +1358,11 @@ class SnakeIV(SnakeIII):
|
|||||||
'PREBOUND_DYLIB',
|
'PREBOUND_DYLIB',
|
||||||
'REEXPORT_DYLIB',
|
'REEXPORT_DYLIB',
|
||||||
}
|
}
|
||||||
self.dylib_id_path = self.getPathFromDylibID() # Get Dylib ID for @loader_path resolving
|
if binaries is not None: # Exception for bundles where binaries are not valid Mach-O
|
||||||
self.dylib_loading_commands, self.dylib_loading_commands_names = self.getDylibLoadCommands() # 1. Get dylib specific load commands
|
self.dylib_id_path = self.getPathFromDylibID() # Get Dylib ID for @loader_path resolving
|
||||||
self.rpath_list = self.resolveRunPathLoadCommands() # 2. Get LC_RPATH list
|
self.dylib_loading_commands, self.dylib_loading_commands_names = self.getDylibLoadCommands() # 1. Get dylib specific load commands
|
||||||
self.absolute_paths = self.resolveDylibPaths() # 3. Get all dylib absolute paths dictionary {dylib_name[dylib_paths]}
|
self.rpath_list = self.resolveRunPathLoadCommands() # 2. Get LC_RPATH list
|
||||||
|
self.absolute_paths = self.resolveDylibPaths() # 3. Get all dylib absolute paths dictionary {dylib_name[dylib_paths]}
|
||||||
self.dyld_share_cache_path = '/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e'
|
self.dyld_share_cache_path = '/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e'
|
||||||
|
|
||||||
def getSharedLibraries(self, only_names=True):
|
def getSharedLibraries(self, only_names=True):
|
||||||
@@ -2533,7 +2745,9 @@ class ArgumentParser:
|
|||||||
self.addAntivirusArgs()
|
self.addAntivirusArgs()
|
||||||
|
|
||||||
def addGeneralArgs(self):
|
def addGeneralArgs(self):
|
||||||
self.parser.add_argument('-p', '--path', required=True, help="Path to the Mach-O file")
|
general_group = self.parser.add_argument_group('GENERAL ARGS')
|
||||||
|
general_group.add_argument('-p', '--path', help="Path to the Mach-O file")
|
||||||
|
general_group.add_argument('-b', '--bundle', help="Path to the App Bundle (can be used with -p to change path of binary which is by default set to: /target.app/Contents/MacOS/target)")
|
||||||
|
|
||||||
def addMachOArgs(self):
|
def addMachOArgs(self):
|
||||||
macho_group = self.parser.add_argument_group('MACH-O ARGS')
|
macho_group = self.parser.add_argument_group('MACH-O ARGS')
|
||||||
@@ -2563,6 +2777,12 @@ class ArgumentParser:
|
|||||||
macho_group.add_argument('--dump_data', help="Dump {size} bytes starting from {offset} to a given {filename} (e.g. '0x1234,0x1000,out.bin')", metavar=('offset,size,output_path'), nargs="?")
|
macho_group.add_argument('--dump_data', help="Dump {size} bytes starting from {offset} to a given {filename} (e.g. '0x1234,0x1000,out.bin')", metavar=('offset,size,output_path'), nargs="?")
|
||||||
macho_group.add_argument('--calc_offset', help="Calculate the real address (file on disk) of the given Virtual Memory {vm_offset} (e.g. 0xfffffe000748f580)", metavar='vm_offset')
|
macho_group.add_argument('--calc_offset', help="Calculate the real address (file on disk) of the given Virtual Memory {vm_offset} (e.g. 0xfffffe000748f580)", metavar='vm_offset')
|
||||||
macho_group.add_argument('--constructors', action='store_true', help="Print binary constructors")
|
macho_group.add_argument('--constructors', action='store_true', help="Print binary constructors")
|
||||||
|
macho_group.add_argument('--dump_section', help="Dump '__SEGMENT,__section' to standard output as a raw bytes", metavar='__SEGMENT,__section')
|
||||||
|
macho_group.add_argument('--bundle_structure', action='store_true', help="Print the structure of the app bundle")
|
||||||
|
macho_group.add_argument('--bundle_info', action='store_true', help="Print the Info.plist content of the app bundle (JSON format)")
|
||||||
|
macho_group.add_argument('--bundle_info_syntax_check', action='store_true', help="Check if bundle info syntax is valid")
|
||||||
|
macho_group.add_argument('--bundle_frameworks', action='store_true', help="Print the list of frameworks in the bundle")
|
||||||
|
macho_group.add_argument('--bundle_plugins', action='store_true', help="Print the list of plugins in the bundle")
|
||||||
|
|
||||||
def addCodeSignArgs(self):
|
def addCodeSignArgs(self):
|
||||||
codesign_group = self.parser.add_argument_group('CODE SIGNING ARGS')
|
codesign_group = self.parser.add_argument_group('CODE SIGNING ARGS')
|
||||||
@@ -2576,6 +2796,8 @@ class ArgumentParser:
|
|||||||
codesign_group.add_argument('--sign_binary', help="Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the identity (default: adhoc)", nargs='?', const='adhoc', metavar='adhoc|identity')
|
codesign_group.add_argument('--sign_binary', help="Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the identity (default: adhoc)", nargs='?', const='adhoc', metavar='adhoc|identity')
|
||||||
codesign_group.add_argument('--cs_offset', action='store_true', help="Print Code Signature file offset")
|
codesign_group.add_argument('--cs_offset', action='store_true', help="Print Code Signature file offset")
|
||||||
codesign_group.add_argument('--cs_flags', action='store_true', help="Print Code Signature flags")
|
codesign_group.add_argument('--cs_flags', action='store_true', help="Print Code Signature flags")
|
||||||
|
codesign_group.add_argument('--verify_bundle_signature', action='store_true', help="Code Signature verification (if the contents of the bundle have been modified)")
|
||||||
|
codesign_group.add_argument('--remove_sig_from_bundle', action='store_true', help="Remove Code Signature from the bundle")
|
||||||
|
|
||||||
def addChecksecArgs(self):
|
def addChecksecArgs(self):
|
||||||
checksec_group = self.parser.add_argument_group('CHECKSEC ARGS')
|
checksec_group = self.parser.add_argument_group('CHECKSEC ARGS')
|
||||||
@@ -2648,10 +2870,13 @@ class ArgumentParser:
|
|||||||
antivirus_group.add_argument('--remove_quarantine', action='store_true', help="Remove com.apple.quarantine extended attribute from the file")
|
antivirus_group.add_argument('--remove_quarantine', action='store_true', help="Remove com.apple.quarantine extended attribute from the file")
|
||||||
antivirus_group.add_argument('--add_quarantine', action='store_true', help="Add com.apple.quarantine extended attribute to the file")
|
antivirus_group.add_argument('--add_quarantine', action='store_true', help="Add com.apple.quarantine extended attribute to the file")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parseArgs(self):
|
def parseArgs(self):
|
||||||
return self.parser.parse_args()
|
args = self.parser.parse_args()
|
||||||
|
|
||||||
|
if not args.path and not args.bundle:
|
||||||
|
self.parser.error('One of arguments: -p/--path or -b/--bundle is required.')
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
def printAllArgs(self, args):
|
def printAllArgs(self, args):
|
||||||
'''Just for debugging. This method is a utility designed to print all parsed arguments and their corresponding values.'''
|
'''Just for debugging. This method is a utility designed to print all parsed arguments and their corresponding values.'''
|
||||||
@@ -3014,10 +3239,26 @@ if __name__ == "__main__":
|
|||||||
arg_parser = ArgumentParser()
|
arg_parser = ArgumentParser()
|
||||||
args = arg_parser.parseArgs()
|
args = arg_parser.parseArgs()
|
||||||
|
|
||||||
file_path = os.path.abspath(args.path)
|
### --- APP BUNDLE EXTENSION --- ###
|
||||||
|
if args.bundle is not None:
|
||||||
|
bundle_path = os.path.abspath(args.bundle)
|
||||||
|
bundle_processor = BundleProcessor(bundle_path)
|
||||||
|
|
||||||
|
if os.path.exists(bundle_processor.info_plist_path) and args.path is None:
|
||||||
|
bundle_executable = bundle_processor.getBundleInfoCFBundleExecutableValue()
|
||||||
|
file_path = os.path.join(bundle_processor.bundle_path, 'Contents/MacOS', bundle_executable)
|
||||||
|
|
||||||
|
elif not os.path.exists(bundle_processor.info_plist_path) and args.path is None:
|
||||||
|
file_path = os.path.join(args.bundle, 'Contents/MacOS', os.path.basename(args.bundle).split('.')[0])
|
||||||
|
|
||||||
|
else:
|
||||||
|
file_path = os.path.abspath(args.path)
|
||||||
|
else:
|
||||||
|
#bundle_processor = None # It must be defined for further processors classes logic.
|
||||||
|
file_path = os.path.abspath(args.path)
|
||||||
|
|
||||||
### --- I. MACH-O --- ###
|
### --- I. MACH-O --- ###
|
||||||
macho_processor = MachOProcessor(file_path)
|
macho_processor = MachOProcessor(file_path, bundle_processor)
|
||||||
macho_processor.process(args)
|
macho_processor.process(args)
|
||||||
|
|
||||||
### --- II. CODE SIGNING --- ###
|
### --- II. CODE SIGNING --- ###
|
||||||
|
|||||||
Reference in New Issue
Block a user