Skip to content

Releases: Karmaz95/Snake_Apple

Snake&Apple VIII — App Sandbox

19 Sep 15:02
Compare
Choose a tag to compare

MAJOR

  • Added SnakeVIII class.
  • Added spbl_compilator_wrapper.c for compiling Sandbox Profile files .sb.
  • Added make_plist.py for converting XML back to PLIST.
  • Added sandbox_inspector for various tasks related to App Sandbox (it is standalone, but I also implemented all functionalities to the latest SnakeVIII)
  • Added sandbox_validator for checking if a given operation is allowed for the sandboxed process
  • Added sandbox_detector for checking if the process is sandboxed
  • Some modifications & additions to the current code (see below).

SnakeI

  • Added --dump_binary for extracting binary from Fat archives.
  • Modified --dump_section to dump raw bytes (no more b'\x01......') just raw binary to stdout.
  • Modified getStringSection so it now returns strings in the order they appear in the binary (not in random order like previously)
    def getStringSection(self):
        '''Return strings from the __cstring (string table).'''
        extracted_strings = []
        for section in self.binary.sections:
            if section.type == lief.MachO.SECTION_TYPES.CSTRING_LITERALS:
                strings_bytes = section.content.tobytes()
                strings = strings_bytes.decode('utf-8', errors='ignore')
                extracted_strings.extend(strings.split('\x00'))
        return extracted_strings
  • Bug patch in MachOFileFinder.py, it did not print file type correctly, due to lief update.
print(f"{binary.header.file_type.__name__}:{file_path}")

SnakeAppExtension

  • Added --bundle_id flag for printing the CFBundleIdentifier value from the Info.plist file if it exists.

MINOR

  • Added decompiled code of Sandbox components.
  • Added sandbox_operations_extractor.py a simple script for extracting Sandbox Operations from Sandbox.kext
  • Added sonoma_sandbox_operations.txt list of all Sandbox Operations extracted from Sandbox.kext on Sonoma using sandbox_operations_extractor.py
  • Added SBPL Compilator article link.
  • Added Sandbox Detector article link.
  • Added Sandbox Validator article link.
  • Added Unexpected but expected behavior article link.
  • Updated README.md
  • Patched one of the helper testing functions because it could not handle some bytes while decoding. Now it looks like this:
def run_and_get_stdout(command):
    command_with_stdout = f"{command} 2>&1"
    # Run the command and capture the output in bytes
    result = subprocess.run(command_with_stdout, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    
    # Decode with utf-8, ignoring invalid characters or replacing them
    return result.stdout.decode('utf-8', errors='replace').strip()

Snake & Apple: App Bundle Extension

26 Jul 11:26
Compare
Choose a tag to compare

MAJOR

'''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.'''
  • Now tool can work without -p, if -b is used (it supports Codeless bundles - if no valid executable is in a bundle):
def parseArgs(self):
 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
  • We can use -p and -b together if we want to change the path of analyzed binary in context of the bundled app by default binary path is taken from Info.plist or if it not exists from: /target.app/Contents/MacOS/target. The logic behind it lies in a new class method: SnakeHatchery.hatch()
    def hatch(self):
        ''' This function initiates 3 global classes:
        1. bundle_processor - BundleProcessor class instance, which is used to parse the App Bundle.
        2. binaries - Universal binary from wich arm64 Mach-O is extracted (used for most of the flags in CrimsonUroboros).
        3. snake_instance - the latest Snake class instance, which holds all the CrimsonUroboros flags logic (inherited starting from SnakeAppBundleExtension).
        
        In the end, it process the arguments related to --bundle flag.
        '''
        self.pathExistanceCheck()

        if self.bundleInit():
            self.filePathInit()

        self.binaryInit()

        if binaries is None and bundle_processor is None:
            print('QUITING: The file used in -p is not a valid Mach-O and you did not specify a bundle (-b).')
            print('It will only work if bundle is specified AND|OR file is a valid Mach-O.')
            exit() # Exit if the file is not valid macho and bundle is not specified

        global snake_instance # Must be global for further processors classes.
        snake_instance = self.snake_class(binaries, self.file_path)

        if bundle_processor is not None:
            bundle_processor.process(self.args)
  • The biggest change is the new class SnakeHatchery. All starting logic from MachOAnalyzer was moved to this class. Basically, the whole startup is rebuild.
  • Usually process methods are exeuted in main. There is one exception in with BundleProcessor class, as it is executed inside SnakeHatchery in the end of hatch method.
if __name__ == "__main__":
    arg_parser = ArgumentParser()
    args = arg_parser.parseArgs()

    ### --- APP BUNDLE EXTENSION --- ###
    snake_hatchery = SnakeHatchery(args, SnakeVII)
    snake_hatchery.hatch()

    ### --- I. MACH-O --- ###
    macho_processor = MachOProcessor()
    macho_processor.process(args)
  • The extension itself does not provide much flags, instead, there are new options for existing modules that uses information stored in the object of the extension class bundle_processor = BundleProcessor(bundle_path)

SnakeAppExtension

  • This is a new class, it is a part of APP BUNDLE EXTENSION and it stores only logic for CrimsonUroboros flags. Most of the code related to parsing and extracting data from App Bundle is in BundleProcessor class:
* Added `--bundle_structure` for printing bundle structure in Tree format.
* Added `--bundle_info` for printing `Info.plist` in JSON format.
* Added `--bundle_info_syntax_check`for checking inf `Info.plist` has correct sytnax.
* Added `--bundle_frameworks` for enumerating bundled `Frameworks`.
* Added `--bundle_plugins` for enumerating bundled `PlugIns`.

SnakeI

  • Added --dump_section for dumping section by its name.
  • Added logic for Mach-O verification:
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 
  • The exception related to the wrong Mach-O file specified in the path can now be bypassed by specifying -b for bundle analysis (it is because the executable in the bundle can be a script, for instance).
if binaries is None and bundle_processor.bundle_path is None:
    exit() # Exit if the file is not valid macho and bundle is not specified
  • Bug fixes related to section dumping - the code changes are implemented in SnakeI, but they are related to usage of these functions in the SnakeIV.
def dumpSection(self, segment_name, section_name, filename):
...
 segment_name = segment_name.lower()
 section_name = section_name.lower()
...
def getSectionRange(self, segment_name, section_name):
...
 segment_name = segment_name.lower()
 section_name = section_name.lower()
...
  • Added printEntitlements for printing XML entitlements without b'...\n'.
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)

SnakeII

  • Added --remove_sig_from_bundle for removing the signature from application.
  • Added -verify_bundle_signature for validating the app's code signature.

SnakeIV

  • Added similar logic as in SnakeI for handling Codeless bundles.
if binaries is not None: # Exception for bundles where binaries are not valid Mach-O
    self.dylib_id_path = self.getPathFromDylibID() # Get Dylib ID for @loader_path resolving
    self.dylib_loading_commands, self.dylib_loading_commands_names = self.getDylibLoadCommands() # 1. Get dylib specific load commands
    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]}

MINOR

  • Added new tests for all new flags.
  • Changed all tests to inlcude app bundle extension, the big change is how bundle_processor is passed to the MachOProcessor during initial logic:
    • In main we pass bundle_processor
   macho_processor = MachOProcessor(file_path, bundle_processor)
* It is then updated to global scope in MachOProcessor:
       global bundle_processor 
    bundle_processor = self.bundle_processor
  • Added Cracking macOS apps article link.
  • Added App Bundle Extension article link.
  • Created an Article_tags.md for hashtags related to the articles, because the list of articles did not look very aesthetic.
  • Until now, only the Dyld series had hashtags - I added them for the rest of the articles.
  • README.md update

Snake & Apple: Antivirus

28 Jun 11:06
Compare
Choose a tag to compare

MAJOR

  • Repaired bug in checkDyldInsertLibraries that was missing the case for 0x10000 + insecure entitlements

  • I repaired all the bugs I missed after the latest updates to the lief library.

  • All tests were successful, so it should work for the latest lief version.

  • Added testDyldSLC and printtestDyldSLC to support --test_dyld_SLC option to test for code injection using DYLD_SHARED_CACHE_DIR.

  • New Snake VII. Antivirus class with bunch of new functionalities.

  • Added AMFI_test.sh script.

MINOR

  • Patched Type-error because of changes in lief library:
    if arm64_bin == None:
       ^^^^^^^^^^^^^^^^^
TypeError: __eq__(): incompatible function arguments. The following argument types are supported:
    1. __eq__(self, arg: lief._lief.Object, /) -> bool

Invoked with types: lief._lief.MachO.Binary, NoneType

Now it cannot use == as the operator arm64_bin is None.

  • Patched the getEndianess function because of changes in the lief library.
  File "/Users/karmaz/.local/bin/CrimsonUroboros", line 261, in getEndianess
    magic = self.binary.header.magic.name
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'lief._lief.MachO.MACHO_TYPES' object has no attribute 'name

Now we must use __name__ property.

  • __name__ patch in getDylibID
  File "/Users/karmaz/.local/bin/CrimsonUroboros", line 1365, in getDylibID
    if cmd.command.name == 'ID_DYLIB':
       ^^^^^^^^^^^^^^^^
AttributeError: 'lief._lief.MachO.LOAD_COMMAND_TYPES' object has no attribute 'name'
  • __name__ patch in getDylibLoadCommands
  File "/Users/karmaz/.local/bin/CrimsonUroboros", line 1176, in getDylibLoadCommands
    cmd_name = cmd.command.name
               ^^^^^^^^^^^^^^^^
AttributeError: 'lief._lief.MachO.LOAD_COMMAND_TYPES' object has no attribute 'name'
  • __name__ patch in getUnresolvedRunPathLoadCommandsPaths
  File "/Users/karmaz/.local/bin/CrimsonUroboros", line 1188, in getUnresolvedRunPathLoadCommandsPaths
    return [cmd.path for cmd in self.load_commands if cmd.command.name == 'RPATH']
                                                      ^^^^^^^^^^^^^^^^
AttributeError: 'lief._lief.MachO.LOAD_COMMAND_TYPES' object has no attribute 'name'
  • ... and other places where __name__ should be used instead of .name as from lief verion 14.0
  • Minor bug patch with getSections where byte string was returned instead of decoded utf.
  • Added information about success in dumpPrelink_info and dumpPrelink_text
  • Patched getSectionRange bug with if section_name == section.fullname - lack od .decode() after changes in lief.
    def getSectionRange(self, segment_name, section_name):
        '''
            Return section start and end file offset. 
            If there is no such section return False, False.
        '''
        for section in self.binary.sections:
            if segment_name == section.segment_name:
                if section_name == section.fullname.decode():
  • Modified some tests.

  • Added some RE to Dyld according to DYLD VII.

  • Fix the name in Arg parser for amfi_group.

  • Added X. NU directory for storing materials related to macOS kernel.

  • Added some presentations and decompiled code to the mac directory of Antivirus

Snake & Apple: AMFI

25 Mar 10:35
Compare
Choose a tag to compare

MAJOR

  • Added SnakeVI class!
  • Added decompiled AMFI code to the VI. AMFI/mac/AMFI_RE directory (the ones with PSEUDO_ prefix are my own more readable interpretation)
  • Added AppleStructuresManager class, which stores Apple structures and their parsers
  • Added Utils class, which stores custom tools for debugging CrimsonUroboros code
  • Partially did one of the TODO points from README.md about building my own Code Signature parser in Python by implementing the parseCodeDirectoryBlob method in SnakeII and the CodeDirectory class in AppleStructuresManager

SNAKE I: Mach-O

  • Added calcSectionRange, getSectionRange, extractSection and dumpSection
  • Modified getSections with calcSectionRange
  • Modified getMain so now it also finds Thread Command (LC_THREAD / LC_UNIXTHREAD)
  • Added hasLoadCommand and printHasLoadCommand for a new option --has_cmd
  • Added hasSection and printHasSection for a new option --has_section
  • Moved the saveBytesToFile method from SnakeII to SnakeI
  • Added dumpData and dumpDataArgParser for a new option --dump_data
  • Added hasSegment and printHasSegment for a new option --has_segment
  • Added getVirtualMemoryStartingAddress, calcRealAddressFromVM
  • Added calcRealAddressFromVM and printCalcRealAddressFromVM for a new option --calc_offset
  • Added getSection and getSegment functions
  • Added self.symbol_types that stores symbol types masks
  • Added getImports and printImports for a new option --imports
  • Added getExports and printExports for a new option --exports
  • Added getSegmentsInfo helper function for initialization so we can access properties: segments_count, file_start, file_size, file_end
  • Added printConstructors for a new option --constructors

SNAKE II: Code Signing

  • Added getCodeSignatureOffset and printCodeSignatureOffset for a new option --cs_offset
  • Added printCodeSignatureOffset, extractCodeSignatureBytes, findBytes, parseCodeDirectoryBlob, getCodeSignatureOffset and printCodeSignatureFlags for a new option --cs_flags

MINOR

  • Added tags to the DYLD article series
  • Added self. prefix to file_path in extractBytesAtOffset.
  • Added some new TODO's to README.md
  • Added tests for all new flags
  • Added crimson_stalker to SourceCodeManager
  • Added MIG_detect.py from knightsc repo
  • Added check_amfi.py script for calculating amfiFlags (described here in ProcessConfig — AMFI properties)

Snake & Apple: Dyld

18 Feb 21:36
Compare
Choose a tag to compare

MAJOR

  • Added SnakeV class!
  • Implemented tests

SNAKE I: Mach-O

  • I added the --imported_symbols flag, which prints external symbols grouped, sorted, and in a grepable form. Example:
symbol_name : library1
symbol_name : library1
symbol_name : library2
symbol_name : library3

SNAKE IV: DYLIBS

  • Added --dylib_hijacking_a flag - it prints only possible Dylib Hijacking vectors
❯ CrimsonUroboros --dylib_hijacking_a -p executable
VULNERABLE ROOT BINARY: /Users/karmaz95/t/indirect_dylib_hijacking/executable
WRITEABLE EXISTING PATHS: /Users/karmaz95/t/indirect_dylib_hijacking/lib1.dylib
VULNERABLE DEPENDENCY: /Users/karmaz95/t/indirect_dylib_hijacking/lib1.dylib
WRITEABLE EXISTING PATHS: /Users/karmaz95/t/indirect_dylib_hijacking/lib2.dylib
  • Cosmetic changes to --dylib_hijacking flag. Now it prints if the binary is ROOT and starts from the status, then path:
CrimsonUroboros -p executable --dylib_hijacking
ROOT BINARY NOT PROTECTED: /Users/karmaz95/t/indirect_dylib_hijacking/executable
WRITEABLE EXISTING PATHS: /Users/karmaz95/t/indirect_dylib_hijacking/lib1.dylib
----------------------------
NOT PROTECTED: /Users/karmaz95/t/indirect_dylib_hijacking/lib1.dylib
WRITEABLE EXISTING PATHS: /Users/karmaz95/t/indirect_dylib_hijacking/lib2.dylib
----------------------------
NOT PROTECTED: /Users/karmaz95/t/indirect_dylib_hijacking/lib2.dylib
----------------------------
  • I repaired the prepareRogueDylib function for --prepare_dylib. The option was broken because it works on absolute paths, while DYLIB_ID could contain unresolved paths like @rpath/something.dylib. For this reason, the function now works on dylib names:
--prepare_dylib target_dylib_name

SNAKE III: CHECKSEC

  • Added Library Validation --has_lv.
❯ CrimsonUroboros --has_lv -p executable
LIBRARY VALIDATION: False
  • I added LV (Library Validation) to the --checksec option.
CrimsonUroboros --checksec -p executable
<==== CHECKSEC ======
PIE:            True
ARC:            False
STRIPPED:       False
CANARY:         False
NX STACK:       True
NX HEAP:        False
XN:             True
NOTARIZED:      False
ENCRYPTED:      False
RESTRICTED:     False
HARDENED:       False
APP SANDBOX:    False
FORTIFIED:      False
RPATH:          False
LV:             False
=====================>
  • Added check for CS_RESTRICT (0x800) in --checksec to RESTRICTED; now, it returns True if the __RESTRICT segment is used or the 0x800 flag is set.

MINOR

  • Repaired install section (removed uninstallable requirements)
  • Completed TODO tasks
    • Add check for DYLIB HIJACKING to --checksec
    • Add check for CS_RESTRICT (0x800) in --checksec to RESTRICTED
  • Updated README.md
  • Overall code maintenance for a better reader experience
  • Removed try-except blocks in each Snake class for more verbose error logging.
  • Added args argument to all .process() methods to make the test_CrimsonUroboros.py code simpler. This is just passing the args to the method.

Snake & Apple: Dylibs

17 Jan 12:53
Compare
Choose a tag to compare

MAJOR

  • Added SnakeIV class!
  • Added MachODylibLoadCommandsFinder
  • Added the ### --- SOURCE CODE --- ### section with the SourceCodeManager class for the C code storage to keep it in one Python file. This class stores only the dylib_hijacking C code and compiles rogue dylib. In the future, the class will store Assembly code and other tricks for injections.

MINOR

  • Added self.file_path as the property for Snakes classes. From now on, it must be initialized for every Snake. So it is now:
def __init__(self, binaries, file_path):
    super().__init__(binaries, file_path)
  • Changed handling any unexpected errors in except Exception as e: in each processor class to print the name for the Snake. For example:
print(f"An error occurred during SnakeI: Mach-O processing: {e}")
  • Added load_commands and endianness properties to Snake class:
self.load_commands = self.getLoadCommands()
self.endianess = self.getEndianess()
  • Added dyld-shared-cache-extractor to the INSTALL section in README.md.
  • Added ipsw to the INSTALL section in README.md.
  • Added --dylib_hijacking and --dylibtree flags to the LIMITATIONS section in README.md.
  • Added some points to the TODO - IDEAS / IMPROVES section in README.md.

Snake & Apple: Checksec

07 Jan 12:53
Compare
Choose a tag to compare

MAJOR

  • Added SnakeIII class!
  • Added LCFinder!
  • Added ModifyMachOFlags!
  • Added getEncryptionInfo() to the SnakeI: Mach-O class and --encryption_info flag for this functionality
    • If the encryption info load command exists, the information will be printed in the --info flag
    • The user can also specify the output path if he wants to dump the encrypted (or not if cryptid=0) data sector from the file (it works with fat binaries)

MINOR

  • Wrapped lief.MachO.parse(self.file_path) in a parseFatBinary() method inside a MachOProcessor class.

Snake & Apple: Code Signing

03 Jan 14:43
Compare
Choose a tag to compare

MAJOR

  • I created an additional class, ArgumentParser, that stores the code from the main() responsible for argument parsing from CLI
  • I created processor classes for each article/snake - this class stores the code from the main. I did it to make the code cleaner in the main(), and it is better for the reader because he can read Snake class and then code from the main rather than scrolling to the main to see it
  • Main() stores only the initialization of the class and processor method to execute the code
  • Added SnakeII class!
  • Added TrustCacheParser program!
  • Added SingatureReader program!
  • Added extract_cms.sh script!

MINOR

  • Grouped arguments in argument parser for better readability for the end-user in helper and easier code maintenance
  • Added ADDITIONAL LINKS to the README.md
  • Updated description in README.md
  • Removed bug in SnakeI: Mach-O code, where program could run even when binaries = lief.MachO.parse(file_path) return None (not Mach-O).
  • Added the file_path variable for snake_instance initialization so it can be used locally in classes.
  • Added TrustCacheParser, SingatureReader and extract_cms.sh to README.md
  • Added .gitignore
  • Added requirements.txt
  • Made README.md better
  • Updated links to articles

Snake & Apple: Mach-O

23 Dec 20:21
Compare
Choose a tag to compare

MAJOR

MINOR

  • Chaged LICENSE to GNU General Public License v3.0
  • Added img directory for README.md images
  • Added Article and Tools section to the README.md
  • Added CrimsonUroboros and MachOFileFinder usage to the Tools section in README.md