Update CrimsonUroboros with XNU

This commit is contained in:
Karmaz95
2024-12-26 16:47:29 +01:00
parent a0e9a1500f
commit 116c826b9c
2 changed files with 486 additions and 452 deletions

View File

@@ -2225,30 +2225,6 @@ class AMFIProcessor:
pass
def process(self, args):
if args.dump_prelink_info is not None: # nargs="?", const='PRELINK_info.txt' # Dump '__PRELINK_INFO,__info' to a given file (default: 'PRELINK_info.txt')
snake_instance.dumpPrelink_info(args.dump_prelink_info)
if args.dump_prelink_text is not None: # Dump '__PRELINK_TEXT,__text' to a given file (default: 'PRELINK_text.txt')
snake_instance.dumpPrelink_text(args.dump_prelink_text)
if args.dump_prelink_kext is not None: # Dump prelinked KEXT from decompressed Kernel Cache to a file named: prelinked_{kext_name}.bin
snake_instance.dumpKernelExtensionFromPRELINK_TEXT(args.dump_prelink_kext)
if args.kext_prelinkinfo: # Print _Prelink properties from PRELINK_INFO,__info for a give kext
snake_instance.printParsedPRELINK_INFO_plist(args.kext_prelinkinfo)
if args.kmod_info: # Print parsed kmod_info for the given kext
snake_instance.printParsedkmod_info(args.kmod_info)
if args.kext_entry: # Print kext entrypoint
snake_instance.printKextEntryPoint(args.kext_entry)
if args.kext_exit: # Print kext exitpoint
snake_instance.printKextExitPoint(args.kext_exit)
if args.mig: # Search for MIG subsystem and prints message handlers
snake_instance.printMIG()
if args.has_suid: # Print file SUID status
snake_instance.printHasSetUID()
@@ -2284,258 +2260,6 @@ class SnakeVI(SnakeV):
'applemobilefileintegrity.kext' : 'applemobilefileintegrity',
}
def loadPRELINK_INFOFromFile(self, prelink_info_filename): # Not used yet.
'''
Read PRELINK_INFO,__info section from file (with alignment).
The last line in the dumped section plist is broken, because of alignment.
This function remove it so the plistlib.loads work.
It returns loaded PLIST {prelink_info_plist}.
'''
prelink_info_plist_bytes = self.readBytesFromFile(prelink_info_filename)
prelink_as_bytes_without_last_line = self.removeNullBytesAlignment(prelink_info_plist_bytes)
prelink_info_plist = plistlib.loads(prelink_as_bytes_without_last_line)
return prelink_info_plist
def calcTwoComplement64(self, value):
''' Convert negative int to hex representation. '''
return hex((value + (1 << 64)) % (1 << 64))
def removeNullBytesAlignment(self, string_as_bytes):
'''
The last line in the PLISTs and other files dumped from memory will almost always be aligned with 0x00 bytes.
This function:
Detects lines in a given bytes {string_as_bytes}.
Removes the last line.
Returns a new {string_as_bytes}.
'''
decoded_string = string_as_bytes.decode('utf-8')
decoded_string_without_last_line = decoded_string[:decoded_string.rfind('\n')]
string_as_bytes_without_last_line = decoded_string_without_last_line.encode()
return string_as_bytes_without_last_line
def dumpPrelink_info(self, filename):
''' Dump '__PRELINK_INFO,__info' to a given file (default: 'PRELINK_info.txt') '''
segment_name = '__PRELINK_INFO'
section_name = '__info'
if self.dumpSection(segment_name, section_name, filename):
print("SUCCESS: __PRELINK_INFO,__info dump")
def dumpPrelink_text(self, filename):
''' Dump '__PRELINK_TEXT,__text' to a given file (default: 'PRELINK_text.txt') '''
segment_name = '__PRELINK_TEXT'
section_name = '__text'
if self.dumpSection(segment_name, section_name, filename):
print("SUCCESS: __PRELINK_TEXT,__text dump")
def extractPRELINK_INFO_plist(self):
''' Extract '__PRELINK_INFO,__info' and return it. '''
segment_name = '__PRELINK_INFO'
section_name = '__info'
extracted_bytes = self.extractSection(segment_name, section_name)
return extracted_bytes
def parsePRELINK_INFO_plist(self, kext_name):
''' Extract PLIST properties values from '__PRELINK_INFO,__info' section for the given {kext_name}:
_PrelinkBundlePath
_PrelinkExecutableLoadAddr
_PrelinkExecutableRelativePath
_PrelinkExecutableSize
_PrelinkExecutableSourceAddr
_PrelinkKmodInfo
'''
#prelink_info_plist = self.loadPRELINK_INFO(prelink_info_filename) # For loading PRELINK_INFO from file
prelink_as_bytes = self.extractPRELINK_INFO_plist()
prelink_as_bytes_without_last_line = self.removeNullBytesAlignment(prelink_as_bytes)
prelink_info_plist = plistlib.loads(prelink_as_bytes_without_last_line)
kext_name = kext_name.lower()
if kext_name in self.kext_map:
kext_name = self.kext_map[kext_name]
# Iterate over the parsed dictionary
for item in prelink_info_plist['_PrelinkInfoDictionary']:
PrelinkExecutableRelativePath = item.get('_PrelinkExecutableRelativePath', '').lower()
# Check if the '_PrelinkExecutableRelativePath' contains {kext_name} in its path
if kext_name in PrelinkExecutableRelativePath:
# Extract the desired keys and their corresponding values
bundle_path = item.get('_PrelinkBundlePath')
executable_load_addr = str(item.get('_PrelinkExecutableLoadAddr')).lower()
if executable_load_addr.startswith("0x"):
executable_load_addr = int(executable_load_addr, 16)
elif executable_load_addr.startswith("-"):
executable_load_addr = self.calcTwoComplement64(int(executable_load_addr))
executable_relative_path = item.get('_PrelinkExecutableRelativePath')
executable_size = str(item.get('_PrelinkExecutableSize')).lower()
if executable_size.startswith("0x"):
executable_size = int(executable_size, 16)
elif executable_size.startswith("-"):
executable_size = self.calcTwoComplement64(int(executable_size))
source_addr = str(item.get('_PrelinkExecutableSourceAddr')).lower()
if source_addr.startswith("0x"):
source_addr = int(source_addr, 16)
elif source_addr.startswith("-"):
source_addr = self.calcTwoComplement64(int(source_addr))
kmod_info = str(item.get('_PrelinkKmodInfo')).lower()
if kmod_info.startswith("0x"):
kmod_info = int(kmod_info, 16)
elif kmod_info.startswith("-"):
kmod_info = self.calcTwoComplement64(int(kmod_info))
return bundle_path, executable_load_addr, executable_relative_path, executable_size, source_addr, kmod_info
def printParsedPRELINK_INFO_plist(self, kext_name):
''' Print extracted properties for PRELINK_INFO Plist for a given kext. '''
bundle_path, executable_load_addr, executable_relative_path, executable_size, source_addr, kmod_info = self.parsePRELINK_INFO_plist(kext_name)
print(f'_PrelinkBundlePath: {bundle_path}')
print(f'_PrelinkExecutableLoadAddr: {executable_load_addr}')
print(f'_PrelinkExecutableRelativePath: {executable_relative_path}')
print(f'_PrelinkExecutableSize: {hex(int(executable_size))}')
print(f'_PrelinkExecutableSourceAddr: {source_addr}')
print(f'_PrelinkKmodInfo: {kmod_info}')
def dumpKernelExtensionFromPRELINK_TEXT(self, kext_name):
''' Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment -p {file_path} to a file named: prelinked_{kext_name}.bin '''
segment_section = '__PRELINK_TEXT,__text'
if not self.hasSection(segment_section): # If segment does not exist - break
print(f'Specified binary file does not have {segment_section} - the extension was not dumped.')
return False
_, kext_load_addr, _, kext_size, source_addr, _ = self.parsePRELINK_INFO_plist(kext_name)
kext_load_addr = int(kext_load_addr, 16)
kext_size = int(kext_size, 16)
output_path = f'prelinked_{kext_name}.bin'
kext_offset = self.calcRealAddressFromVM(kext_load_addr)
self.dumpData(kext_offset, kext_size, output_path)
def parsekmod_info(self, kext_name):
''' Parse kmod_info structure for the given {kext_name} from Kernel Cache '''
_, _, _, _, _, kmod_info_vm_addr = self.parsePRELINK_INFO_plist(kext_name)
kmod_info_in_file = self.calcRealAddressFromVM(kmod_info_vm_addr)
kmod_info_size = ctypes.sizeof(AppleStructuresManager.kmod_info)
extracted_kmod_info_bytes = self.extractBytesAtOffset(kmod_info_in_file, kmod_info_size)
# debug +
#Utils.printQuadWordsLittleEndian64(extracted_kmod_info_bytes)
# debug -
kmod_info_as_dict = AppleStructuresManager.kmod_info.parse(extracted_kmod_info_bytes)
return kmod_info_as_dict
def printParsedkmod_info(self, kext_name):
''' Printing function for --kmod_info '''
kmod_info_as_dict = self.parsekmod_info(kext_name)
for k, v in kmod_info_as_dict.items():
print(f'{k.ljust(16)}: {v}')
def calcKextEntryPoint(self, kext_name):
''' Calculate the __start for the given {kext_name} Kernel Extension '''
kmod_info_as_dict = self.parsekmod_info(kext_name)
start = int(kmod_info_as_dict['start'], 16) & 0xFFFFFFFF
kernelcache_text_segment = self.getSegment('__TEXT')
kernelcache_text_segment_base = kernelcache_text_segment.virtual_address
return start + kernelcache_text_segment_base
def printKextEntryPoint(self, kext_name):
''' Printing function for --kext_entry flag. '''
kext_entrypoint = hex(self.calcKextEntryPoint(kext_name))
print(f'{kext_name} entrypoint: {kext_entrypoint}')
def calcKextExitPoint(self, kext_name):
''' Calculate the __stop for the given {kext_name} Kernel Extension '''
kmod_info_as_dict = self.parsekmod_info(kext_name)
stop = int(kmod_info_as_dict['stop'], 16) & 0xFFFFFFFF
kernelcache_text_segment = self.getSegment('__TEXT')
kernelcache_text_segment_base = kernelcache_text_segment.virtual_address
return stop + kernelcache_text_segment_base
def printKextExitPoint(self, kext_name):
''' Printing function for --kext_exit flag. '''
kext_exitpoint = hex(self.calcKextEntryPoint(kext_name))
print(f'{kext_name} exitpoint: {kext_exitpoint}')
def parseMIG(self):
''' Search for MIG subsystem messages. I was using this Hopper script as an inspiration: https://github.com/knightsc/hopper/blob/master/scripts/MIG%20Detect.py
Returns a dictionary like: {'_MIG_subsystem_1000': {'_MIG_msg_1000': routine_for_msg}}
'''
va_start = self.getVirtualMemoryStartingAddress()
mig_subsystem_size = ctypes.sizeof(AppleStructuresManager.mig_subsystem)
routine_descriptor_size = ctypes.sizeof(AppleStructuresManager.routine_descriptor)
mig_subsystems = {}
# The MIG should be in __DATA,__const | __DATA_CONST,__const | __CONST,__constdata, but it is not always the case.
# Great example is decompressed kernelcache, there are no __const section. Conclusion, would be to iterate over each segment, but there is a problem with alignment.
for section in self.binary.sections:
#if ('const' in section.name and 'DATA' in section.segment.name):
section_bytes = section.content.tobytes()
section_size = section.size
alignment = pow(2,section.alignment)
# Loop through section bytes using alignment to speed up
current_offset = 0
while current_offset < section_size:
chunk = section_bytes[current_offset:current_offset+mig_subsystem_size]
mig_subsystem_dict = AppleStructuresManager.mig_subsystem.parse(chunk)
number_of_msgs = mig_subsystem_dict['end'] - mig_subsystem_dict['start']
# Check for possible mig_subsystem structure:
if (number_of_msgs > 0 and
number_of_msgs < 1024 and
mig_subsystem_dict['server'] != 0 and
mig_subsystem_dict['start'] > 0 and
mig_subsystem_dict['end'] > 0 and
mig_subsystem_dict['reserved'] == 0 and
mig_subsystem_dict['routine_0'] == 0):
'''
# print(f'{hex(mig_subsystem_dict["server"])} {hex(mig_subsystem_dict["start"])}')
# At this stage I get 0x8028000000007e74 instead of 0x100007e74 and I do not know why. The same goes for every impl_routine later too...
# I can manually repair it by: & 0xFFFFFFFF | __TEXT
# It is temp fix, there must be a "proper way" - todo
'''
mig_subsystem_dict['server'] = mig_subsystem_dict['server'] & 0xFFFFFFFF | va_start # Fix according to the above comment
mig_subsystem_number = mig_subsystem_dict['start']
subsystem_name = "MIG_subsystem_{0}".format(mig_subsystem_number)
mig_subsystems[subsystem_name] = {}
current_offset += mig_subsystem_size
# If mig_subsystem structure was found, iterate over all routines
msg = 0
while msg < number_of_msgs:
routine_name = "MIG_msg_{0}".format(mig_subsystem_number+msg)
chunk = section_bytes[current_offset:current_offset+routine_descriptor_size]
routine_descriptor_dict = AppleStructuresManager.routine_descriptor.parse(chunk)
if routine_descriptor_dict['impl_routine'] != 0:
routine_descriptor_dict['impl_routine'] = routine_descriptor_dict['impl_routine'] & 0xFFFFFFFF | va_start # Fix like subsystem
mig_subsystems[subsystem_name].update({routine_name: routine_descriptor_dict})
current_offset += routine_descriptor_size
msg += 1
continue # To find more subsystems we continue the parent while without adding below alignment, because we added routine_descriptor_size
current_offset += alignment
return(mig_subsystems)
def printMIG(self):
''' Iterates over each subsystem and its associated messages, printing them in the nice format. '''
mig_subsystems = self.parseMIG()
for subsystem, messages in mig_subsystems.items():
print(subsystem + ":")
for message, details in messages.items():
print(f"- {message}: {hex(details['impl_routine'])}")
def hasSetUID(self):
"""
Check if a file has the SUID (Set User ID) bit set.
@@ -2889,6 +2613,9 @@ class SandboxProcessor:
if args.extract_sandbox_operations: # Extract sandbox operations from the kernelcache.decompressed file
snake_instance.printSandboxOperations()
if args.extract_sandbox_platform_profile: # Extract sandbox platform profile from the sandbox.kext file
snake_instance.extractSandboxPlatformProfile()
class SnakeVIII(SnakeVII):
def __init__(self, binaries, file_path):
super().__init__(binaries, file_path)
@@ -3118,6 +2845,15 @@ class SnakeVIII(SnakeVII):
for operation in operations:
print(operation)
def extractSandboxPlatformProfile(self):
''' Extract the sandbox platform profile from the sandbox.kext. '''
start_address = self.binary.get_symbol("_platform_profile_data").value
end_address = self.binary.get_symbol("_collection_data").value
data = self.binary.get_content_from_virtual_address(start_address, end_address - start_address)
sys.stdout.buffer.write(data)
### ---- IX. TCC --- ###
class TCCProcessor:
def __init__(self):
@@ -3416,12 +3152,33 @@ class XNUProcessor:
def process(self, args):
if args.xnu:
snake_instance.printXNU()
if args.parse_mpo:
snake_instance.printMPO(args.parse_mpo)
if args.dump_prelink_info is not None: # nargs="?", const='PRELINK_info.txt' # Dump '__PRELINK_INFO,__info' to a given file (default: 'PRELINK_info.txt')
snake_instance.dumpPrelink_info(args.dump_prelink_info)
if args.dump_prelink_text is not None: # Dump '__PRELINK_TEXT,__text' to a given file (default: 'PRELINK_text.txt')
snake_instance.dumpPrelink_text(args.dump_prelink_text)
if args.dump_prelink_kext is not None: # Dump prelinked KEXT from decompressed Kernel Cache to a file named: prelinked_{kext_name}.bin
snake_instance.dumpKernelExtensionFromPRELINK_TEXT(args.dump_prelink_kext)
if args.kext_prelinkinfo: # Print _Prelink properties from PRELINK_INFO,__info for a give kext
snake_instance.printParsedPRELINK_INFO_plist(args.kext_prelinkinfo)
if args.kmod_info: # Print parsed kmod_info for the given kext
snake_instance.printParsedkmod_info(args.kmod_info)
if args.kext_entry: # Print kext entrypoint
snake_instance.printKextEntryPoint(args.kext_entry)
if args.kext_exit: # Print kext exitpoint
snake_instance.printKextExitPoint(args.kext_exit)
if args.mig: # Search for MIG subsystem and prints message handlers
snake_instance.printMIG()
class SnakeX(SnakeIX):
def __init__(self, binaries, file_path):
super().__init__(binaries, file_path)
@@ -3481,8 +3238,257 @@ class SnakeX(SnakeIX):
else:
print("No MPOs defined in the given address.")
def printXNU(self):
print("XNU related functions are not implemented yet.")
def loadPRELINK_INFOFromFile(self, prelink_info_filename): # Not used yet.
'''
Read PRELINK_INFO,__info section from file (with alignment).
The last line in the dumped section plist is broken, because of alignment.
This function remove it so the plistlib.loads work.
It returns loaded PLIST {prelink_info_plist}.
'''
prelink_info_plist_bytes = self.readBytesFromFile(prelink_info_filename)
prelink_as_bytes_without_last_line = self.removeNullBytesAlignment(prelink_info_plist_bytes)
prelink_info_plist = plistlib.loads(prelink_as_bytes_without_last_line)
return prelink_info_plist
def calcTwoComplement64(self, value):
''' Convert negative int to hex representation. '''
return hex((value + (1 << 64)) % (1 << 64))
def removeNullBytesAlignment(self, string_as_bytes):
'''
The last line in the PLISTs and other files dumped from memory will almost always be aligned with 0x00 bytes.
This function:
Detects lines in a given bytes {string_as_bytes}.
Removes the last line.
Returns a new {string_as_bytes}.
'''
decoded_string = string_as_bytes.decode('utf-8')
decoded_string_without_last_line = decoded_string[:decoded_string.rfind('\n')]
string_as_bytes_without_last_line = decoded_string_without_last_line.encode()
return string_as_bytes_without_last_line
def dumpPrelink_info(self, filename):
''' Dump '__PRELINK_INFO,__info' to a given file (default: 'PRELINK_info.txt') '''
segment_name = '__PRELINK_INFO'
section_name = '__info'
if self.dumpSection(segment_name, section_name, filename):
print("SUCCESS: __PRELINK_INFO,__info dump")
def dumpPrelink_text(self, filename):
''' Dump '__PRELINK_TEXT,__text' to a given file (default: 'PRELINK_text.txt') '''
segment_name = '__PRELINK_TEXT'
section_name = '__text'
if self.dumpSection(segment_name, section_name, filename):
print("SUCCESS: __PRELINK_TEXT,__text dump")
def extractPRELINK_INFO_plist(self):
''' Extract '__PRELINK_INFO,__info' and return it. '''
segment_name = '__PRELINK_INFO'
section_name = '__info'
extracted_bytes = self.extractSection(segment_name, section_name)
return extracted_bytes
def parsePRELINK_INFO_plist(self, kext_name):
''' Extract PLIST properties values from '__PRELINK_INFO,__info' section for the given {kext_name}:
_PrelinkBundlePath
_PrelinkExecutableLoadAddr
_PrelinkExecutableRelativePath
_PrelinkExecutableSize
_PrelinkExecutableSourceAddr
_PrelinkKmodInfo
'''
#prelink_info_plist = self.loadPRELINK_INFO(prelink_info_filename) # For loading PRELINK_INFO from file
prelink_as_bytes = self.extractPRELINK_INFO_plist()
prelink_as_bytes_without_last_line = self.removeNullBytesAlignment(prelink_as_bytes)
prelink_info_plist = plistlib.loads(prelink_as_bytes_without_last_line)
kext_name = kext_name.lower()
if kext_name in self.kext_map:
kext_name = self.kext_map[kext_name]
# Iterate over the parsed dictionary
for item in prelink_info_plist['_PrelinkInfoDictionary']:
PrelinkExecutableRelativePath = item.get('_PrelinkExecutableRelativePath', '').lower()
# Check if the '_PrelinkExecutableRelativePath' contains {kext_name} in its path
if kext_name in PrelinkExecutableRelativePath:
# Extract the desired keys and their corresponding values
bundle_path = item.get('_PrelinkBundlePath')
executable_load_addr = str(item.get('_PrelinkExecutableLoadAddr')).lower()
if executable_load_addr.startswith("0x"):
executable_load_addr = int(executable_load_addr, 16)
elif executable_load_addr.startswith("-"):
executable_load_addr = self.calcTwoComplement64(int(executable_load_addr))
executable_relative_path = item.get('_PrelinkExecutableRelativePath')
executable_size = str(item.get('_PrelinkExecutableSize')).lower()
if executable_size.startswith("0x"):
executable_size = int(executable_size, 16)
elif executable_size.startswith("-"):
executable_size = self.calcTwoComplement64(int(executable_size))
source_addr = str(item.get('_PrelinkExecutableSourceAddr')).lower()
if source_addr.startswith("0x"):
source_addr = int(source_addr, 16)
elif source_addr.startswith("-"):
source_addr = self.calcTwoComplement64(int(source_addr))
kmod_info = str(item.get('_PrelinkKmodInfo')).lower()
if kmod_info.startswith("0x"):
kmod_info = int(kmod_info, 16)
elif kmod_info.startswith("-"):
kmod_info = self.calcTwoComplement64(int(kmod_info))
return bundle_path, executable_load_addr, executable_relative_path, executable_size, source_addr, kmod_info
def printParsedPRELINK_INFO_plist(self, kext_name):
''' Print extracted properties for PRELINK_INFO Plist for a given kext. '''
bundle_path, executable_load_addr, executable_relative_path, executable_size, source_addr, kmod_info = self.parsePRELINK_INFO_plist(kext_name)
print(f'_PrelinkBundlePath: {bundle_path}')
print(f'_PrelinkExecutableLoadAddr: {executable_load_addr}')
print(f'_PrelinkExecutableRelativePath: {executable_relative_path}')
print(f'_PrelinkExecutableSize: {hex(int(executable_size))}')
print(f'_PrelinkExecutableSourceAddr: {source_addr}')
print(f'_PrelinkKmodInfo: {kmod_info}')
def dumpKernelExtensionFromPRELINK_TEXT(self, kext_name):
''' Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment -p {file_path} to a file named: prelinked_{kext_name}.bin '''
segment_section = '__PRELINK_TEXT,__text'
if not self.hasSection(segment_section): # If segment does not exist - break
print(f'Specified binary file does not have {segment_section} - the extension was not dumped.')
return False
_, kext_load_addr, _, kext_size, source_addr, _ = self.parsePRELINK_INFO_plist(kext_name)
kext_load_addr = int(kext_load_addr, 16)
kext_size = int(kext_size, 16)
output_path = f'prelinked_{kext_name}.bin'
kext_offset = self.calcRealAddressFromVM(kext_load_addr)
self.dumpData(kext_offset, kext_size, output_path)
def parsekmod_info(self, kext_name):
''' Parse kmod_info structure for the given {kext_name} from Kernel Cache '''
_, _, _, _, _, kmod_info_vm_addr = self.parsePRELINK_INFO_plist(kext_name)
kmod_info_in_file = self.calcRealAddressFromVM(kmod_info_vm_addr)
kmod_info_size = ctypes.sizeof(AppleStructuresManager.kmod_info)
extracted_kmod_info_bytes = self.extractBytesAtOffset(kmod_info_in_file, kmod_info_size)
# debug +
#Utils.printQuadWordsLittleEndian64(extracted_kmod_info_bytes)
# debug -
kmod_info_as_dict = AppleStructuresManager.kmod_info.parse(extracted_kmod_info_bytes)
return kmod_info_as_dict
def printParsedkmod_info(self, kext_name):
''' Printing function for --kmod_info '''
kmod_info_as_dict = self.parsekmod_info(kext_name)
for k, v in kmod_info_as_dict.items():
print(f'{k.ljust(16)}: {v}')
def calcKextEntryPoint(self, kext_name):
''' Calculate the __start for the given {kext_name} Kernel Extension '''
kmod_info_as_dict = self.parsekmod_info(kext_name)
start = int(kmod_info_as_dict['start'], 16) & 0xFFFFFFFF
kernelcache_text_segment = self.getSegment('__TEXT')
kernelcache_text_segment_base = kernelcache_text_segment.virtual_address
return start + kernelcache_text_segment_base
def printKextEntryPoint(self, kext_name):
''' Printing function for --kext_entry flag. '''
kext_entrypoint = hex(self.calcKextEntryPoint(kext_name))
print(f'{kext_name} entrypoint: {kext_entrypoint}')
def calcKextExitPoint(self, kext_name):
''' Calculate the __stop for the given {kext_name} Kernel Extension '''
kmod_info_as_dict = self.parsekmod_info(kext_name)
stop = int(kmod_info_as_dict['stop'], 16) & 0xFFFFFFFF
kernelcache_text_segment = self.getSegment('__TEXT')
kernelcache_text_segment_base = kernelcache_text_segment.virtual_address
return stop + kernelcache_text_segment_base
def printKextExitPoint(self, kext_name):
''' Printing function for --kext_exit flag. '''
kext_exitpoint = hex(self.calcKextEntryPoint(kext_name))
print(f'{kext_name} exitpoint: {kext_exitpoint}')
def parseMIG(self):
''' Search for MIG subsystem messages. I was using this Hopper script as an inspiration: https://github.com/knightsc/hopper/blob/master/scripts/MIG%20Detect.py
Returns a dictionary like: {'_MIG_subsystem_1000': {'_MIG_msg_1000': routine_for_msg}}
'''
va_start = self.getVirtualMemoryStartingAddress()
mig_subsystem_size = ctypes.sizeof(AppleStructuresManager.mig_subsystem)
routine_descriptor_size = ctypes.sizeof(AppleStructuresManager.routine_descriptor)
mig_subsystems = {}
# The MIG should be in __DATA,__const | __DATA_CONST,__const | __CONST,__constdata, but it is not always the case.
# Great example is decompressed kernelcache, there are no __const section. Conclusion, would be to iterate over each segment, but there is a problem with alignment.
for section in self.binary.sections:
#if ('const' in section.name and 'DATA' in section.segment.name):
section_bytes = section.content.tobytes()
section_size = section.size
alignment = pow(2,section.alignment)
# Loop through section bytes using alignment to speed up
current_offset = 0
while current_offset < section_size:
chunk = section_bytes[current_offset:current_offset+mig_subsystem_size]
mig_subsystem_dict = AppleStructuresManager.mig_subsystem.parse(chunk)
number_of_msgs = mig_subsystem_dict['end'] - mig_subsystem_dict['start']
# Check for possible mig_subsystem structure:
if (number_of_msgs > 0 and
number_of_msgs < 1024 and
mig_subsystem_dict['server'] != 0 and
mig_subsystem_dict['start'] > 0 and
mig_subsystem_dict['end'] > 0 and
mig_subsystem_dict['reserved'] == 0 and
mig_subsystem_dict['routine_0'] == 0):
'''
# print(f'{hex(mig_subsystem_dict["server"])} {hex(mig_subsystem_dict["start"])}')
# At this stage I get 0x8028000000007e74 instead of 0x100007e74 and I do not know why. The same goes for every impl_routine later too...
# I can manually repair it by: & 0xFFFFFFFF | __TEXT
# It is temp fix, there must be a "proper way" - todo
'''
mig_subsystem_dict['server'] = mig_subsystem_dict['server'] & 0xFFFFFFFF | va_start # Fix according to the above comment
mig_subsystem_number = mig_subsystem_dict['start']
subsystem_name = "MIG_subsystem_{0}".format(mig_subsystem_number)
mig_subsystems[subsystem_name] = {}
current_offset += mig_subsystem_size
# If mig_subsystem structure was found, iterate over all routines
msg = 0
while msg < number_of_msgs:
routine_name = "MIG_msg_{0}".format(mig_subsystem_number+msg)
chunk = section_bytes[current_offset:current_offset+routine_descriptor_size]
routine_descriptor_dict = AppleStructuresManager.routine_descriptor.parse(chunk)
if routine_descriptor_dict['impl_routine'] != 0:
routine_descriptor_dict['impl_routine'] = routine_descriptor_dict['impl_routine'] & 0xFFFFFFFF | va_start # Fix like subsystem
mig_subsystems[subsystem_name].update({routine_name: routine_descriptor_dict})
current_offset += routine_descriptor_size
msg += 1
continue # To find more subsystems we continue the parent while without adding below alignment, because we added routine_descriptor_size
current_offset += alignment
return(mig_subsystems)
def printMIG(self):
''' Iterates over each subsystem and its associated messages, printing them in the nice format. '''
mig_subsystems = self.parseMIG()
for subsystem, messages in mig_subsystems.items():
print(subsystem + ":")
for message, details in messages.items():
print(f"- {message}: {hex(details['impl_routine'])}")
### --- ARGUMENT PARSER --- ###
class ArgumentParser:
@@ -3607,14 +3613,6 @@ class ArgumentParser:
def addAMFIArgs(self):
amfi_group = self.parser.add_argument_group('AMFI ARGS')
amfi_group.add_argument('--dump_prelink_info', metavar='(optional) out_name', nargs="?", const='PRELINK_info.txt', help='Dump "__PRELINK_INFO,__info" to a given file (default: "PRELINK_info.txt")')
amfi_group.add_argument('--dump_prelink_text', metavar='(optional) out_name', nargs="?", const='PRELINK_text.txt', help='Dump "__PRELINK_TEXT,__text" to a given file (default: "PRELINK_text.txt")')
amfi_group.add_argument('--dump_prelink_kext', metavar='kext_name', nargs="?", help='Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment to a file named: prelinked_{kext_name}.bin')
amfi_group.add_argument('--kext_prelinkinfo', metavar='kext_name', nargs="?", help='Print _Prelink properties from PRELINK_INFO,__info for a give {kext_name}')
amfi_group.add_argument('--kmod_info', metavar='kext_name', help="Parse kmod_info structure for the given {kext_name} from Kernel Cache")
amfi_group.add_argument('--kext_entry', metavar='kext_name', help="Calculate the virtual memory address of the __start (entrypoint) for the given {kext_name} Kernel Extension")
amfi_group.add_argument('--kext_exit', metavar='kext_name', help="Calculate the virtual memory address of the __stop (exitpoint) for the given {kext_name} Kernel Extension")
amfi_group.add_argument('--mig', action='store_true', help="Search for MIG subsystem and prints message handlers")
amfi_group.add_argument('--has_suid', action='store_true', help="Check if the file has SetUID bit set")
amfi_group.add_argument('--has_sgid', action='store_true', help="Check if the file has SetGID bit set")
amfi_group.add_argument('--has_sticky', action='store_true', help="Check if the file has sticky bit set")
@@ -3648,6 +3646,7 @@ class ArgumentParser:
sandbox_group.add_argument('--sandbox_profile_data', action='store_true', help="Print raw bytes ofthe sandbox profile data from the sandbox container metadata")
sandbox_group.add_argument('--dump_kext', help="Dump the kernel extension binary from the kernelcache.decompressed file", metavar='kext_name')
sandbox_group.add_argument('--extract_sandbox_operations', action='store_true', help="Extract sandbox operations from the Sandbox.kext file")
sandbox_group.add_argument('--extract_sandbox_platform_profile', action='store_true', help="Extract sandbox platform profile from the Sandbox.kext file")
def addTCCArgs(self):
tcc_group = self.parser.add_argument_group('TCC ARGS')
@@ -3670,8 +3669,15 @@ class ArgumentParser:
def addXNUArgs(self):
xnu_group = self.parser.add_argument_group('XNU ARGS')
xnu_group.add_argument('--xnu', action='store_true', help="Print XNU related information")
xnu_group.add_argument('--parse_mpo', metavar='mpo_addr', help="Parse mac_policy_ops at given address from Kernel Cache and print pointers in use (not zeroed)")
xnu_group.add_argument('--dump_prelink_info', metavar='(optional) out_name', nargs="?", const='PRELINK_info.txt', help='Dump "__PRELINK_INFO,__info" to a given file (default: "PRELINK_info.txt")')
xnu_group.add_argument('--dump_prelink_text', metavar='(optional) out_name', nargs="?", const='PRELINK_text.txt', help='Dump "__PRELINK_TEXT,__text" to a given file (default: "PRELINK_text.txt")')
xnu_group.add_argument('--dump_prelink_kext', metavar='kext_name', nargs="?", help='Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment to a file named: prelinked_{kext_name}.bin')
xnu_group.add_argument('--kext_prelinkinfo', metavar='kext_name', nargs="?", help='Print _Prelink properties from PRELINK_INFO,__info for a give {kext_name}')
xnu_group.add_argument('--kmod_info', metavar='kext_name', help="Parse kmod_info structure for the given {kext_name} from Kernel Cache")
xnu_group.add_argument('--kext_entry', metavar='kext_name', help="Calculate the virtual memory address of the __start (entrypoint) for the given {kext_name} Kernel Extension")
xnu_group.add_argument('--kext_exit', metavar='kext_name', help="Calculate the virtual memory address of the __stop (exitpoint) for the given {kext_name} Kernel Extension")
xnu_group.add_argument('--mig', action='store_true', help="Search for MIG subsystem and prints message handlers")
def parseArgs(self):
args = self.parser.parse_args()