mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-03-30 14:00:16 +02:00
285 lines
13 KiB
Python
Executable File
285 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import lief
|
|
import uuid
|
|
import argparse
|
|
import subprocess
|
|
from asn1crypto.cms import ContentInfo
|
|
import os
|
|
import sys
|
|
|
|
### --- I. MACH-O --- ###
|
|
class SnakeI:
|
|
def __init__(self, binaries):
|
|
'''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.'''
|
|
self.binary = self.parseFatBinary(binaries)
|
|
self.fat_offset = self.binary.fat_offset # For various calculations, if ARM64 Mach-O extracted from Universal Binary
|
|
self.prot_map = {
|
|
0: '---',
|
|
1: 'r--',
|
|
2: '-w-',
|
|
3: 'rw-',
|
|
4: '--x',
|
|
5: 'r-x',
|
|
6: '-wx',
|
|
7: 'rwx'
|
|
}
|
|
self.segment_flags_map = {
|
|
0x1: 'SG_HIGHVM',
|
|
0x2: 'SG_FVMLIB',
|
|
0x4: 'SG_NORELOC',
|
|
0x8: 'SG_PROTECTED_VERSION_1',
|
|
0x10: 'SG_READ_ONLY',
|
|
}
|
|
|
|
def mapProtection(self, numeric_protection):
|
|
'''Maps numeric protection to its string representation.'''
|
|
return self.prot_map.get(numeric_protection, 'Unknown')
|
|
|
|
def getSegmentFlags(self, flags):
|
|
'''Maps numeric segment flags to its string representation.'''
|
|
return self.segment_flags_map.get(flags, '')
|
|
#return " ".join(activated_flags)
|
|
|
|
def parseFatBinary(self, binaries):
|
|
'''Parse Mach-O file, whether compiled for multiple architectures or just for a single one. It returns the ARM64 binary if it exists. If not, it exits the program.'''
|
|
for binary in binaries:
|
|
if binary.header.cpu_type == lief.MachO.CPU_TYPES.ARM64:
|
|
arm64_bin = binary
|
|
if arm64_bin == None:
|
|
print('The specified Mach-O file is not in ARM64 architecture.')
|
|
exit()
|
|
return arm64_bin
|
|
|
|
def getFileType(self):
|
|
"""Extract and return the file type from a binary object's header."""
|
|
return self.binary.header.file_type.name
|
|
|
|
def getHeaderFlags(self):
|
|
'''Return binary header flags.'''
|
|
return self.binary.header.flags_list
|
|
|
|
def getEndianess(self):
|
|
'''Check the endianness of a binary based on the system and binary's magic number.'''
|
|
magic = self.binary.header.magic.name
|
|
endianness = sys.byteorder
|
|
if endianness == 'little' and (magic == 'MAGIC_64' or magic == 'MAGIC' or magic == 'FAT_MAGIC'):
|
|
return 'little'
|
|
else:
|
|
return 'big'
|
|
|
|
def getBinaryHeader(self):
|
|
'''https://lief-project.github.io/doc/stable/api/python/macho.html#header'''
|
|
return self.binary.header
|
|
|
|
def getLoadCommands(self):
|
|
'''https://lief-project.github.io/doc/stable/api/python/macho.html#loadcommand'''
|
|
return self.binary.commands
|
|
|
|
def getSegments(self):
|
|
'''Extract segmenents from binary and return a human readable string: https://lief-project.github.io/doc/stable/api/python/macho.html#lief.MachO.SegmentCommand'''
|
|
segment_info = []
|
|
for segment in self.binary.segments:
|
|
name = segment.name
|
|
va_start = '0x' + format(segment.virtual_address, '016x')
|
|
va_end = '0x' + format(int(va_start, 16) + segment.virtual_size, '016x')
|
|
file_start = hex(segment.file_size + self.fat_offset)
|
|
file_end = hex(int(file_start, 16) + segment.file_size)
|
|
init_prot = self.mapProtection(segment.init_protection)
|
|
max_prot = self.mapProtection(segment.max_protection)
|
|
flags = self.getSegmentFlags(segment.flags)
|
|
if flags != '':
|
|
segment_info.append(f'{name.ljust(16)}{init_prot}/{max_prot.ljust(8)} VM: {va_start}-{va_end.ljust(24)} FILE: {file_start}-{file_end} ({flags})')
|
|
else:
|
|
segment_info.append(f'{name.ljust(16)}{init_prot}/{max_prot.ljust(8)} VM: {va_start}-{va_end.ljust(24)} FILE: {file_start}-{file_end}')
|
|
return segment_info
|
|
|
|
def getSections(self):
|
|
'''Extract sections from binary and return in human readable format: https://lief-project.github.io/doc/stable/api/python/macho.html#lief.MachO.Section'''
|
|
sections_info = []
|
|
sections_info.append("SEGMENT".ljust(14) + "SECTION".ljust(20) + "TYPE".ljust(28) + "VIRTUAL MEMORY".ljust(32) + "FILE".ljust(26) + "FLAGS".ljust(40))
|
|
sections_info.append(len(sections_info[0])*"=")
|
|
for section in self.binary.sections:
|
|
segment_name = section.segment_name
|
|
section_name = section.fullname
|
|
section_type = section.type.name
|
|
section_va_start = hex(section.virtual_address)
|
|
section_va_end = hex(section.virtual_address + section.offset)
|
|
section_size_start = hex(section.offset + self.fat_offset)
|
|
section_size_end = hex(section.size + section.offset + self.fat_offset)
|
|
section_flags_list = section.flags_list
|
|
flags_strings = [flag.name for flag in section_flags_list]
|
|
flags = " ".join(flags_strings)
|
|
sections_info.append((f'{segment_name.ljust(14)}{section_name.ljust(20)}{section_type.ljust(28)}{section_va_start}-{section_va_end.ljust(20)}{section_size_start}-{section_size_end}\t\t({flags})'))
|
|
return sections_info
|
|
|
|
def getSymbols(self):
|
|
'''Get all symbols from the binary (LC_SYMTAB, Chained Fixups, Exports Trie): https://lief-project.github.io/doc/stable/api/python/macho.html#symbol'''
|
|
return self.binary.symbols
|
|
|
|
def getChainedFixups(self):
|
|
'''Return Chained Fixups information: https://lief-project.github.io/doc/latest/api/python/macho.html#chained-binding-info'''
|
|
return self.binary.dyld_chained_fixups
|
|
|
|
def getExportTrie(self):
|
|
'''Return Export Trie information: https://lief-project.github.io/doc/latest/api/python/macho.html#dyldexportstrie-command'''
|
|
try:
|
|
return self.binary.dyld_exports_trie.show_export_trie()
|
|
except:
|
|
return "NO EXPORT TRIE"
|
|
|
|
def getUUID(self):
|
|
'''Return UUID as string and in UUID format: https://lief-project.github.io/doc/stable/api/python/macho.html#uuidcommand'''
|
|
for cmd in self.binary.commands:
|
|
if isinstance(cmd, lief.MachO.UUIDCommand):
|
|
uuid_bytes = cmd.uuid
|
|
break
|
|
uuid_string = str(uuid.UUID(bytes=bytes(uuid_bytes)))
|
|
return uuid_string
|
|
|
|
def getMain(self):
|
|
'''Determine the entry point of an executable.'''
|
|
return self.binary.main_command
|
|
|
|
def getStringSection(self):
|
|
'''Return strings from the __cstring (string table).'''
|
|
extracted_strings = set()
|
|
for section in self.binary.sections:
|
|
if section.type == lief.MachO.SECTION_TYPES.CSTRING_LITERALS:
|
|
extracted_strings.update(section.content.tobytes().split(b'\x00'))
|
|
return extracted_strings
|
|
|
|
def findAllStringsInBinary(self):
|
|
'''Check every binary section to find strings.'''
|
|
extracted_strings = ""
|
|
byte_set = set()
|
|
for section in self.binary.sections:
|
|
byte_set.update(section.content.tobytes().split(b'\x00'))
|
|
for byte_item in byte_set:
|
|
try:
|
|
decoded_string = byte_item.decode('utf-8')
|
|
extracted_strings += decoded_string + "\n"
|
|
except UnicodeDecodeError:
|
|
pass
|
|
return extracted_strings
|
|
### --- --- --- ###
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Mach-O files parser for binary analysis.")
|
|
### --- I. MACH-O --- ###
|
|
parser.add_argument('-p', '--path', required=True, help="Path to the Mach-O file.")
|
|
parser.add_argument('--file_type', action='store_true', help="Print binary file type.")
|
|
parser.add_argument('--header_flags', action='store_true', help="Print binary header flags.")
|
|
parser.add_argument('--endian', action='store_true', help="Print binary endianess.")
|
|
parser.add_argument('--header', action='store_true', help="Print binary header.")
|
|
parser.add_argument('--load_commands', action='store_true', help="Print binary load commands names.")
|
|
parser.add_argument('--segments', action='store_true', help="Print binary segments in human friendly form.")
|
|
parser.add_argument('--sections', action='store_true', help="Print binary sections in human friendly form.")
|
|
parser.add_argument('--symbols', action='store_true', help="Print all binary symbols.")
|
|
parser.add_argument('--chained_fixups', action='store_true', help="Print Chained Fixups information.")
|
|
parser.add_argument('--exports_trie', action='store_true', help="Print Export Trie information.")
|
|
parser.add_argument('--uuid', action='store_true', help="Print UUID.")
|
|
parser.add_argument('--main', action='store_true', help="Print entry point and stack size.")
|
|
parser.add_argument('--strings_section', action='store_true', help="Print strings from __cstring section.")
|
|
parser.add_argument('--all_strings', action='store_true', help="Print strings from all sections.")
|
|
parser.add_argument('--save_strings', help="Parse all sections, detect strings and save them to a file.")
|
|
parser.add_argument('--info', action='store_true', default=False , help="Print header, load commands, segments, sections, symbols and strings.")
|
|
|
|
args = parser.parse_args()
|
|
file_path = os.path.abspath(args.path)
|
|
|
|
### --- I. MACH-O --- ###
|
|
try: # Check if the file is in a valid Mach-O format
|
|
if os.path.exists(file_path):
|
|
binaries = lief.MachO.parse(file_path)
|
|
snake_instance = SnakeI(binaries)
|
|
else:
|
|
print(f'The file {file_path} does not exist.')
|
|
exit()
|
|
except Exception as e: # Exit if not
|
|
print(f"An error occurred: {e}")
|
|
exit()
|
|
|
|
if args.file_type: # Print binary file type
|
|
print(f'File type: {snake_instance.getFileType()}')
|
|
|
|
if args.header_flags: # Print binary header flags
|
|
header_flag_list = snake_instance.getHeaderFlags()
|
|
print("Header flags:", " ".join(header_flag.name for header_flag in header_flag_list))
|
|
|
|
if args.endian: # Print binary endianess
|
|
print(f'Endianess: {snake_instance.getEndianess()}')
|
|
|
|
if args.header: # Print binary header
|
|
print(snake_instance.getBinaryHeader())
|
|
|
|
if args.load_commands: # Print binary load commands
|
|
load_commands_list = snake_instance.getLoadCommands()
|
|
print("Load Commands:", " ".join(load_command.command.name for load_command in load_commands_list))
|
|
|
|
if args.segments: # Print binary segments in human friendly form
|
|
for segment in snake_instance.getSegments():
|
|
print(segment)
|
|
|
|
if args.sections: # Print binary sections in human friendly form
|
|
for section in snake_instance.getSections():
|
|
print(section)
|
|
|
|
if args.symbols: # Print symbols
|
|
for symbol in snake_instance.getSymbols():
|
|
print(symbol.name)
|
|
|
|
if args.chained_fixups: # Print Chained Fixups information
|
|
print(snake_instance.getChainedFixups())
|
|
|
|
if args.exports_trie: # Print Exports Trie information
|
|
print(snake_instance.getExportTrie())
|
|
|
|
if args.uuid: # Print UUID
|
|
print(f'UUID: {snake_instance.getUUID()}')
|
|
|
|
if args.main: # Print entry point and stack size
|
|
print(f'Entry point: {hex(snake_instance.getMain().entrypoint)}')
|
|
print(f'Stack size: {hex(snake_instance.getMain().stack_size)}')
|
|
|
|
if args.strings_section: # Print strings from __cstring section
|
|
print('Strings from __cstring section:')
|
|
print('-------------------------------')
|
|
for string in (snake_instance.getStringSection()):
|
|
print(string)
|
|
|
|
if args.all_strings: # Print strings from all sections.
|
|
print(snake_instance.findAllStringsInBinary())
|
|
|
|
if args.save_strings: # Parse all sections, detect strings and save them to a file
|
|
extracted_strings = snake_instance.findAllStringsInBinary()
|
|
with open(args.save_strings, 'a') as f:
|
|
for s in extracted_strings:
|
|
f.write(s)
|
|
|
|
if args.info: # Print all info about the binary
|
|
print('\n<=== HEADER ===>')
|
|
print(snake_instance.getBinaryHeader())
|
|
print('\n<=== LOAD COMMANDS ===>')
|
|
for lcd in snake_instance.getLoadCommands():
|
|
print(lcd)
|
|
print("="*50)
|
|
print('\n<=== SEGMENTS ===>')
|
|
for segment in snake_instance.getSegments():
|
|
print(segment)
|
|
print('\n<=== SECTIONS ===>')
|
|
for section in snake_instance.getSections():
|
|
print(section)
|
|
print('\n<=== SYMBOLS ===>')
|
|
for symbol in snake_instance.getSymbols():
|
|
print(symbol.name)
|
|
print('\n<=== STRINGS ===>')
|
|
print('Strings from __cstring section:')
|
|
print('-------------------------------')
|
|
for string in (snake_instance.getStringSection()):
|
|
print(string)
|
|
print('\n<=== UUID ===>')
|
|
print(f'{snake_instance.getUUID()}')
|
|
print('\n<=== ENDIANESS ===>')
|
|
print(snake_instance.getEndianess())
|
|
print('\n<=== ENTRYPOINT ===>')
|
|
print(f'{hex(snake_instance.getMain().entrypoint)}') |